From 4fd80ff6bc7bb2f31671164aaa86b701f39023db Mon Sep 17 00:00:00 2001 From: amantley Date: Tue, 8 Jan 2019 15:26:46 -0800 Subject: [PATCH 001/474] implementing the spline ik for the spline as an anim node --- libraries/animation/src/AnimContext.h | 1 + libraries/animation/src/AnimNodeLoader.cpp | 22 +++++++ libraries/animation/src/AnimSplineIK.cpp | 77 ++++++++++++++++++++++ libraries/animation/src/AnimSplineIK.h | 76 +++++++++++++++++++++ 4 files changed, 176 insertions(+) create mode 100644 libraries/animation/src/AnimSplineIK.cpp create mode 100644 libraries/animation/src/AnimSplineIK.h diff --git a/libraries/animation/src/AnimContext.h b/libraries/animation/src/AnimContext.h index c455dd9c8f..e3ab5d9788 100644 --- a/libraries/animation/src/AnimContext.h +++ b/libraries/animation/src/AnimContext.h @@ -28,6 +28,7 @@ enum class AnimNodeType { InverseKinematics, DefaultPose, TwoBoneIK, + SplineIK, PoleVectorConstraint, NumTypes }; diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index dfa61e9fea..562a614b4a 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -26,6 +26,7 @@ #include "AnimInverseKinematics.h" #include "AnimDefaultPose.h" #include "AnimTwoBoneIK.h" +#include "AnimSplineIK.h" #include "AnimPoleVectorConstraint.h" using NodeLoaderFunc = AnimNode::Pointer (*)(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); @@ -41,6 +42,7 @@ static AnimNode::Pointer loadManipulatorNode(const QJsonObject& jsonObj, const Q static AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer loadDefaultPoseNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer loadTwoBoneIKNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); +static AnimNode::Pointer loadSplineIKNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer loadPoleVectorConstraintNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static const float ANIM_GRAPH_LOAD_PRIORITY = 10.0f; @@ -123,6 +125,7 @@ static NodeLoaderFunc animNodeTypeToLoaderFunc(AnimNode::Type type) { case AnimNode::Type::InverseKinematics: return loadInverseKinematicsNode; case AnimNode::Type::DefaultPose: return loadDefaultPoseNode; case AnimNode::Type::TwoBoneIK: return loadTwoBoneIKNode; + case AnimNode::Type::SplineIK: return loadSplineIKNode; case AnimNode::Type::PoleVectorConstraint: return loadPoleVectorConstraintNode; case AnimNode::Type::NumTypes: return nullptr; }; @@ -140,6 +143,7 @@ static NodeProcessFunc animNodeTypeToProcessFunc(AnimNode::Type type) { case AnimNode::Type::InverseKinematics: return processDoNothing; case AnimNode::Type::DefaultPose: return processDoNothing; case AnimNode::Type::TwoBoneIK: return processDoNothing; + case AnimNode::Type::SplineIK: return processDoNothing; case AnimNode::Type::PoleVectorConstraint: return processDoNothing; case AnimNode::Type::NumTypes: return nullptr; }; @@ -574,6 +578,24 @@ static AnimNode::Pointer loadDefaultPoseNode(const QJsonObject& jsonObj, const Q return node; } +static AnimNode::Pointer loadSplineIKNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { + READ_FLOAT(alpha, jsonObj, id, jsonUrl, nullptr); + READ_BOOL(enabled, jsonObj, id, jsonUrl, nullptr); + READ_FLOAT(interpDuration, jsonObj, id, jsonUrl, nullptr); + READ_STRING(baseJointName, jsonObj, id, jsonUrl, nullptr); + READ_STRING(tipJointName, jsonObj, id, jsonUrl, nullptr); + READ_STRING(alphaVar, jsonObj, id, jsonUrl, nullptr); + READ_STRING(enabledVar, jsonObj, id, jsonUrl, nullptr); + READ_STRING(endEffectorRotationVarVar, jsonObj, id, jsonUrl, nullptr); + READ_STRING(endEffectorPositionVarVar, jsonObj, id, jsonUrl, nullptr); + + auto node = std::make_shared(id, alpha, enabled, interpDuration, + baseJointName, tipJointName, + alphaVar, enabledVar, + endEffectorRotationVarVar, endEffectorPositionVarVar); + return node; +} + static AnimNode::Pointer loadTwoBoneIKNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { READ_FLOAT(alpha, jsonObj, id, jsonUrl, nullptr); READ_BOOL(enabled, jsonObj, id, jsonUrl, nullptr); diff --git a/libraries/animation/src/AnimSplineIK.cpp b/libraries/animation/src/AnimSplineIK.cpp new file mode 100644 index 0000000000..db6b6864b1 --- /dev/null +++ b/libraries/animation/src/AnimSplineIK.cpp @@ -0,0 +1,77 @@ +// +// AnimSplineIK.cpp +// +// Created by Angus Antley on 1/7/19. +// Copyright (c) 2019 High Fidelity, Inc. All rights reserved. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "AnimSplineIK.h" +#include "AnimationLogging.h" +#include "CubicHermiteSpline.h" +#include +#include "AnimUtil.h" + +AnimSplineIK::AnimSplineIK(const QString& id, float alpha, bool enabled, float interpDuration, + const QString& baseJointName, + const QString& tipJointName, + const QString& alphaVar, const QString& enabledVar, + const QString& endEffectorRotationVarVar, const QString& endEffectorPositionVarVar) : + AnimNode(AnimNode::Type::SplineIK, id), + _alpha(alpha), + _enabled(enabled), + _interpDuration(interpDuration), + _baseJointName(baseJointName), + _tipJointName(tipJointName), + _alphaVar(alphaVar), + _enabledVar(enabledVar), + _endEffectorRotationVarVar(endEffectorRotationVarVar), + _endEffectorPositionVarVar(endEffectorPositionVarVar), + _prevEndEffectorRotationVar(), + _prevEndEffectorPositionVar() { + +} + +AnimSplineIK::~AnimSplineIK() { + +} + +const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { + qCDebug(animation) << "evaluating the spline function"; + assert(_children.size() == 1); + if (_children.size() != 1) { + return _poses; + } + + // evalute underPoses + AnimPoseVec underPoses = _children[0]->evaluate(animVars, context, dt, triggersOut); + _poses = underPoses; + return _poses; +} + +void AnimSplineIK::lookUpIndices() { + assert(_skeleton); + + // look up bone indices by name + std::vector indices = _skeleton->lookUpJointIndices({ _baseJointName, _tipJointName }); + + // cache the results + _baseJointIndex = indices[0]; + _tipJointIndex = indices[1]; + + if (_baseJointIndex != -1) { + _baseParentJointIndex = _skeleton->getParentIndex(_baseJointIndex); + } +} + +// for AnimDebugDraw rendering +const AnimPoseVec& AnimSplineIK::getPosesInternal() const { + return _poses; +} + +void AnimSplineIK::setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) { + AnimNode::setSkeletonInternal(skeleton); + lookUpIndices(); +} \ No newline at end of file diff --git a/libraries/animation/src/AnimSplineIK.h b/libraries/animation/src/AnimSplineIK.h new file mode 100644 index 0000000000..36a29971a0 --- /dev/null +++ b/libraries/animation/src/AnimSplineIK.h @@ -0,0 +1,76 @@ +// +// AnimSplineIK.h +// +// Created by Angus Antley on 1/7/19. +// Copyright (c) 2019 High Fidelity, Inc. All rights reserved. +// +// 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_AnimSplineIK_h +#define hifi_AnimSplineIK_h + +#include "AnimNode.h" +#include "AnimChain.h" + +// Spline IK for the spine +class AnimSplineIK : public AnimNode { +public: + AnimSplineIK(const QString& id, float alpha, bool enabled, float interpDuration, + const QString& baseJointName, const QString& tipJointName, + const QString& alphaVar, const QString& enabledVar, + const QString& endEffectorRotationVarVar, const QString& endEffectorPositionVarVar); + + virtual ~AnimSplineIK() override; + virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override; + +protected: + + enum class InterpType { + None = 0, + SnapshotToUnderPoses, + SnapshotToSolve, + NumTypes + }; + + // for AnimDebugDraw rendering + virtual const AnimPoseVec& getPosesInternal() const override; + virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) override; + + void lookUpIndices(); + + AnimPoseVec _poses; + + float _alpha; + bool _enabled; + float _interpDuration; + QString _baseJointName; + QString _tipJointName; + + int _baseParentJointIndex{ -1 }; + int _baseJointIndex{ -1 }; + int _tipJointIndex{ -1 }; + + QString _alphaVar; // float - (0, 1) 0 means underPoses only, 1 means IK only. + QString _enabledVar; // bool + QString _endEffectorRotationVarVar; // string + QString _endEffectorPositionVarVar; // string + + QString _prevEndEffectorRotationVar; + QString _prevEndEffectorPositionVar; + + InterpType _interpType{ InterpType::None }; + float _interpAlphaVel{ 0.0f }; + float _interpAlpha{ 0.0f }; + + AnimChain _snapshotChain; + + bool _lastEnableDebugDrawIKTargets{ false }; + + // no copies + AnimSplineIK(const AnimSplineIK&) = delete; + AnimSplineIK& operator=(const AnimSplineIK&) = delete; + +}; +#endif // hifi_AnimSplineIK_h From 3b5d8db650287d18985a00ad4e9c11bd6880b505 Mon Sep 17 00:00:00 2001 From: amantley Date: Tue, 8 Jan 2019 17:33:14 -0800 Subject: [PATCH 002/474] updated the json to have a spline node at the root --- .../resources/avatar/avatar-animation.json | 3072 ++++++++++------- libraries/animation/src/AnimNodeLoader.cpp | 1 + libraries/animation/src/AnimStateMachine.cpp | 2 +- libraries/animation/src/AnimTwoBoneIK.cpp | 3 + 4 files changed, 1847 insertions(+), 1231 deletions(-) diff --git a/interface/resources/avatar/avatar-animation.json b/interface/resources/avatar/avatar-animation.json index 50fe5019f9..88ee1ff06b 100644 --- a/interface/resources/avatar/avatar-animation.json +++ b/interface/resources/avatar/avatar-animation.json @@ -38,419 +38,274 @@ "children": [ { "id": "userAnimNone", - "type": "poleVectorConstraint", + "type": "splineIK", "data": { + "alpha": 1.0, "enabled": false, - "referenceVector": [0, 0, 1], - "baseJointName": "RightUpLeg", - "midJointName": "RightLeg", - "tipJointName": "RightFoot", - "enabledVar": "rightFootPoleVectorEnabled", - "poleVectorVar": "rightFootPoleVector" + "interpDuration": 15, + "baseJointName": "Hips", + "tipJointName": "Head", + "alphaVar": "splineIKAlpha", + "enabledVar": "splineIKEnabled", + "endEffectorRotationVarVar": "splineIKRotationVar", + "endEffectorPositionVarVar": "splineIKPositionVar" }, "children": [ { - "id": "rightFootIK", - "type": "twoBoneIK", + "id": "rightFootPoleVector", + "type": "poleVectorConstraint", "data": { - "alpha": 1.0, "enabled": false, - "interpDuration": 15, + "referenceVector": [ 0, 0, 1 ], "baseJointName": "RightUpLeg", "midJointName": "RightLeg", "tipJointName": "RightFoot", - "midHingeAxis": [-1, 0, 0], - "alphaVar": "rightFootIKAlpha", - "enabledVar": "rightFootIKEnabled", - "endEffectorRotationVarVar": "rightFootIKRotationVar", - "endEffectorPositionVarVar": "rightFootIKPositionVar" + "enabledVar": "rightFootPoleVectorEnabled", + "poleVectorVar": "rightFootPoleVector" }, "children": [ { - "id": "leftFootPoleVector", - "type": "poleVectorConstraint", + "id": "rightFootIK", + "type": "twoBoneIK", "data": { + "alpha": 1.0, "enabled": false, - "referenceVector": [0, 0, 1], - "baseJointName": "LeftUpLeg", - "midJointName": "LeftLeg", - "tipJointName": "LeftFoot", - "enabledVar": "leftFootPoleVectorEnabled", - "poleVectorVar": "leftFootPoleVector" + "interpDuration": 15, + "baseJointName": "RightUpLeg", + "midJointName": "RightLeg", + "tipJointName": "RightFoot", + "midHingeAxis": [ -1, 0, 0 ], + "alphaVar": "rightFootIKAlpha", + "enabledVar": "rightFootIKEnabled", + "endEffectorRotationVarVar": "rightFootIKRotationVar", + "endEffectorPositionVarVar": "rightFootIKPositionVar" }, "children": [ { - "id": "leftFootIK", - "type": "twoBoneIK", + "id": "leftFootPoleVector", + "type": "poleVectorConstraint", "data": { - "alpha": 1.0, "enabled": false, - "interpDuration": 15, + "referenceVector": [ 0, 0, 1 ], "baseJointName": "LeftUpLeg", "midJointName": "LeftLeg", "tipJointName": "LeftFoot", - "midHingeAxis": [-1, 0, 0], - "alphaVar": "leftFootIKAlpha", - "enabledVar": "leftFootIKEnabled", - "endEffectorRotationVarVar": "leftFootIKRotationVar", - "endEffectorPositionVarVar": "leftFootIKPositionVar" + "enabledVar": "leftFootPoleVectorEnabled", + "poleVectorVar": "leftFootPoleVector" }, "children": [ { - "id": "ikOverlay", - "type": "overlay", + "id": "leftFootIK", + "type": "twoBoneIK", "data": { "alpha": 1.0, - "alphaVar": "ikOverlayAlpha", - "boneSet": "fullBody" + "enabled": false, + "interpDuration": 15, + "baseJointName": "LeftUpLeg", + "midJointName": "LeftLeg", + "tipJointName": "LeftFoot", + "midHingeAxis": [ -1, 0, 0 ], + "alphaVar": "leftFootIKAlpha", + "enabledVar": "leftFootIKEnabled", + "endEffectorRotationVarVar": "leftFootIKRotationVar", + "endEffectorPositionVarVar": "leftFootIKPositionVar" }, "children": [ { - "id": "ik", - "type": "inverseKinematics", - "data": { - "solutionSource": "relaxToUnderPoses", - "solutionSourceVar": "solutionSource", - "targets": [ - { - "jointName": "Hips", - "positionVar": "hipsPosition", - "rotationVar": "hipsRotation", - "typeVar": "hipsType", - "weightVar": "hipsWeight", - "weight": 1.0, - "flexCoefficients": [1] - }, - { - "jointName": "RightHand", - "positionVar": "rightHandPosition", - "rotationVar": "rightHandRotation", - "typeVar": "rightHandType", - "weightVar": "rightHandWeight", - "weight": 1.0, - "flexCoefficients": [1, 0.5, 0.5, 0.2, 0.01, 0.005, 0.001, 0.0, 0.0], - "poleVectorEnabledVar": "rightHandPoleVectorEnabled", - "poleReferenceVectorVar": "rightHandPoleReferenceVector", - "poleVectorVar": "rightHandPoleVector" - }, - { - "jointName": "LeftHand", - "positionVar": "leftHandPosition", - "rotationVar": "leftHandRotation", - "typeVar": "leftHandType", - "weightVar": "leftHandWeight", - "weight": 1.0, - "flexCoefficients": [1, 0.5, 0.5, 0.2, 0.01, 0.005, 0.001, 0.0, 0.0], - "poleVectorEnabledVar": "leftHandPoleVectorEnabled", - "poleReferenceVectorVar": "leftHandPoleReferenceVector", - "poleVectorVar": "leftHandPoleVector" - }, - { - "jointName": "Spine2", - "positionVar": "spine2Position", - "rotationVar": "spine2Rotation", - "typeVar": "spine2Type", - "weightVar": "spine2Weight", - "weight": 2.0, - "flexCoefficients": [1.0, 0.5, 0.25] - }, - { - "jointName": "Head", - "positionVar": "headPosition", - "rotationVar": "headRotation", - "typeVar": "headType", - "weightVar": "headWeight", - "weight": 4.0, - "flexCoefficients": [1, 0.5, 0.25, 0.2, 0.1] - } - ] - }, - "children": [] - }, - { - "id": "defaultPoseOverlay", + "id": "ikOverlay", "type": "overlay", "data": { - "alpha": 0.0, - "alphaVar": "defaultPoseOverlayAlpha", - "boneSet": "fullBody", - "boneSetVar": "defaultPoseOverlayBoneSet" + "alpha": 1.0, + "alphaVar": "ikOverlayAlpha", + "boneSet": "fullBody" }, "children": [ { - "id": "defaultPose", - "type": "defaultPose", + "id": "ik", + "type": "inverseKinematics", "data": { + "solutionSource": "relaxToUnderPoses", + "solutionSourceVar": "solutionSource", + "targets": [ + { + "jointName": "Hips", + "positionVar": "hipsPosition", + "rotationVar": "hipsRotation", + "typeVar": "hipsType", + "weightVar": "hipsWeight", + "weight": 1.0, + "flexCoefficients": [ 1 ] + }, + { + "jointName": "RightHand", + "positionVar": "rightHandPosition", + "rotationVar": "rightHandRotation", + "typeVar": "rightHandType", + "weightVar": "rightHandWeight", + "weight": 1.0, + "flexCoefficients": [ 1, 0.5, 0.5, 0.2, 0.01, 0.005, 0.001, 0.0, 0.0 ], + "poleVectorEnabledVar": "rightHandPoleVectorEnabled", + "poleReferenceVectorVar": "rightHandPoleReferenceVector", + "poleVectorVar": "rightHandPoleVector" + }, + { + "jointName": "LeftHand", + "positionVar": "leftHandPosition", + "rotationVar": "leftHandRotation", + "typeVar": "leftHandType", + "weightVar": "leftHandWeight", + "weight": 1.0, + "flexCoefficients": [ 1, 0.5, 0.5, 0.2, 0.01, 0.005, 0.001, 0.0, 0.0 ], + "poleVectorEnabledVar": "leftHandPoleVectorEnabled", + "poleReferenceVectorVar": "leftHandPoleReferenceVector", + "poleVectorVar": "leftHandPoleVector" + }, + { + "jointName": "Spine2", + "positionVar": "spine2Position", + "rotationVar": "spine2Rotation", + "typeVar": "spine2Type", + "weightVar": "spine2Weight", + "weight": 2.0, + "flexCoefficients": [ 1.0, 0.5, 0.25 ] + }, + { + "jointName": "Head", + "positionVar": "headPosition", + "rotationVar": "headRotation", + "typeVar": "headType", + "weightVar": "headWeight", + "weight": 4.0, + "flexCoefficients": [ 1, 0.5, 0.25, 0.2, 0.1 ] + } + ] }, "children": [] }, { - "id": "rightHandOverlay", + "id": "defaultPoseOverlay", "type": "overlay", "data": { "alpha": 0.0, - "boneSet": "rightHand", - "alphaVar": "rightHandOverlayAlpha" + "alphaVar": "defaultPoseOverlayAlpha", + "boneSet": "fullBody", + "boneSetVar": "defaultPoseOverlayBoneSet" }, "children": [ { - "id": "rightHandStateMachine", - "type": "stateMachine", + "id": "defaultPose", + "type": "defaultPose", "data": { - "currentState": "rightHandGrasp", - "states": [ - { - "id": "rightHandGrasp", - "interpTarget": 3, - "interpDuration": 3, - "transitions": [ - { "var": "isRightIndexPoint", "state": "rightIndexPoint" }, - { "var": "isRightThumbRaise", "state": "rightThumbRaise" }, - { "var": "isRightIndexPointAndThumbRaise", "state": "rightIndexPointAndThumbRaise" } - ] - }, - { - "id": "rightIndexPoint", - "interpTarget": 15, - "interpDuration": 3, - "transitions": [ - { "var": "isRightHandGrasp", "state": "rightHandGrasp" }, - { "var": "isRightThumbRaise", "state": "rightThumbRaise" }, - { "var": "isRightIndexPointAndThumbRaise", "state": "rightIndexPointAndThumbRaise" } - ] - }, - { - "id": "rightThumbRaise", - "interpTarget": 15, - "interpDuration": 3, - "transitions": [ - { "var": "isRightHandGrasp", "state": "rightHandGrasp" }, - { "var": "isRightIndexPoint", "state": "rightIndexPoint" }, - { "var": "isRightIndexPointAndThumbRaise", "state": "rightIndexPointAndThumbRaise" } - ] - }, - { - "id": "rightIndexPointAndThumbRaise", - "interpTarget": 15, - "interpDuration": 3, - "transitions": [ - { "var": "isRightHandGrasp", "state": "rightHandGrasp" }, - { "var": "isRightIndexPoint", "state": "rightIndexPoint" }, - { "var": "isRightThumbRaise", "state": "rightThumbRaise" } - ] - } - ] }, - "children": [ - { - "id": "rightHandGrasp", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "rightHandGraspAlpha" - }, - "children": [ - { - "id": "rightHandGraspOpen", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/hydra_pose_open_right.fbx", - "startFrame": 0.0, - "endFrame": 0.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "rightHandGraspClosed", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/hydra_pose_closed_right.fbx", - "startFrame": 0.0, - "endFrame": 0.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "rightIndexPoint", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "rightHandGraspAlpha" - }, - "children": [ - { - "id": "rightIndexPointOpen", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_point_open_right.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "rightIndexPointClosed", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_point_closed_right.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "rightThumbRaise", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "rightHandGraspAlpha" - }, - "children": [ - { - "id": "rightThumbRaiseOpen", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_thumb_open_right.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "rightThumbRaiseClosed", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_thumb_closed_right.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "rightIndexPointAndThumbRaise", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "rightHandGraspAlpha" - }, - "children": [ - { - "id": "rightIndexPointAndThumbRaiseOpen", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_thumb_point_open_right.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "rightIndexPointAndThumbRaiseClosed", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_thumb_point_closed_right.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - } - ] + "children": [] }, { - "id": "leftHandOverlay", + "id": "rightHandOverlay", "type": "overlay", "data": { "alpha": 0.0, - "boneSet": "leftHand", - "alphaVar": "leftHandOverlayAlpha" + "boneSet": "rightHand", + "alphaVar": "rightHandOverlayAlpha" }, "children": [ { - "id": "leftHandStateMachine", + "id": "rightHandStateMachine", "type": "stateMachine", "data": { - "currentState": "leftHandGrasp", + "currentState": "rightHandGrasp", "states": [ { - "id": "leftHandGrasp", + "id": "rightHandGrasp", "interpTarget": 3, "interpDuration": 3, "transitions": [ - { "var": "isLeftIndexPoint", "state": "leftIndexPoint" }, - { "var": "isLeftThumbRaise", "state": "leftThumbRaise" }, - { "var": "isLeftIndexPointAndThumbRaise", "state": "leftIndexPointAndThumbRaise" } + { + "var": "isRightIndexPoint", + "state": "rightIndexPoint" + }, + { + "var": "isRightThumbRaise", + "state": "rightThumbRaise" + }, + { + "var": "isRightIndexPointAndThumbRaise", + "state": "rightIndexPointAndThumbRaise" + } ] }, { - "id": "leftIndexPoint", + "id": "rightIndexPoint", "interpTarget": 15, "interpDuration": 3, "transitions": [ - { "var": "isLeftHandGrasp", "state": "leftHandGrasp" }, - { "var": "isLeftThumbRaise", "state": "leftThumbRaise" }, - { "var": "isLeftIndexPointAndThumbRaise", "state": "leftIndexPointAndThumbRaise" } + { + "var": "isRightHandGrasp", + "state": "rightHandGrasp" + }, + { + "var": "isRightThumbRaise", + "state": "rightThumbRaise" + }, + { + "var": "isRightIndexPointAndThumbRaise", + "state": "rightIndexPointAndThumbRaise" + } ] }, { - "id": "leftThumbRaise", + "id": "rightThumbRaise", "interpTarget": 15, "interpDuration": 3, "transitions": [ - { "var": "isLeftHandGrasp", "state": "leftHandGrasp" }, - { "var": "isLeftIndexPoint", "state": "leftIndexPoint" }, - { "var": "isLeftIndexPointAndThumbRaise", "state": "leftIndexPointAndThumbRaise" } + { + "var": "isRightHandGrasp", + "state": "rightHandGrasp" + }, + { + "var": "isRightIndexPoint", + "state": "rightIndexPoint" + }, + { + "var": "isRightIndexPointAndThumbRaise", + "state": "rightIndexPointAndThumbRaise" + } ] }, { - "id": "leftIndexPointAndThumbRaise", + "id": "rightIndexPointAndThumbRaise", "interpTarget": 15, "interpDuration": 3, "transitions": [ - { "var": "isLeftHandGrasp", "state": "leftHandGrasp" }, - { "var": "isLeftIndexPoint", "state": "leftIndexPoint" }, - { "var": "isLeftThumbRaise", "state": "leftThumbRaise" } + { + "var": "isRightHandGrasp", + "state": "rightHandGrasp" + }, + { + "var": "isRightIndexPoint", + "state": "rightIndexPoint" + }, + { + "var": "isRightThumbRaise", + "state": "rightThumbRaise" + } ] } ] }, "children": [ { - "id": "leftHandGrasp", + "id": "rightHandGrasp", "type": "blendLinear", "data": { "alpha": 0.0, - "alphaVar": "leftHandGraspAlpha" + "alphaVar": "rightHandGraspAlpha" }, "children": [ { - "id": "leftHandGraspOpen", + "id": "rightHandGraspOpen", "type": "clip", "data": { - "url": "qrc:///avatar/animations/hydra_pose_open_left.fbx", + "url": "qrc:///avatar/animations/hydra_pose_open_right.fbx", "startFrame": 0.0, "endFrame": 0.0, "timeScale": 1.0, @@ -459,12 +314,12 @@ "children": [] }, { - "id": "leftHandGraspClosed", + "id": "rightHandGraspClosed", "type": "clip", "data": { - "url": "qrc:///avatar/animations/hydra_pose_closed_left.fbx", - "startFrame": 10.0, - "endFrame": 10.0, + "url": "qrc:///avatar/animations/hydra_pose_closed_right.fbx", + "startFrame": 0.0, + "endFrame": 0.0, "timeScale": 1.0, "loopFlag": true }, @@ -473,18 +328,18 @@ ] }, { - "id": "leftIndexPoint", + "id": "rightIndexPoint", "type": "blendLinear", - "data": { + "data": { "alpha": 0.0, - "alphaVar": "leftHandGraspAlpha" + "alphaVar": "rightHandGraspAlpha" }, "children": [ { - "id": "leftIndexPointOpen", + "id": "rightIndexPointOpen", "type": "clip", "data": { - "url": "qrc:///avatar/animations/touch_point_open_left.fbx", + "url": "qrc:///avatar/animations/touch_point_open_right.fbx", "startFrame": 15.0, "endFrame": 15.0, "timeScale": 1.0, @@ -493,10 +348,10 @@ "children": [] }, { - "id": "leftIndexPointClosed", + "id": "rightIndexPointClosed", "type": "clip", "data": { - "url": "qrc:///avatar/animations/touch_point_closed_left.fbx", + "url": "qrc:///avatar/animations/touch_point_closed_right.fbx", "startFrame": 15.0, "endFrame": 15.0, "timeScale": 1.0, @@ -507,18 +362,18 @@ ] }, { - "id": "leftThumbRaise", + "id": "rightThumbRaise", "type": "blendLinear", - "data": { + "data": { "alpha": 0.0, - "alphaVar": "leftHandGraspAlpha" + "alphaVar": "rightHandGraspAlpha" }, "children": [ { - "id": "leftThumbRaiseOpen", + "id": "rightThumbRaiseOpen", "type": "clip", "data": { - "url": "qrc:///avatar/animations/touch_thumb_open_left.fbx", + "url": "qrc:///avatar/animations/touch_thumb_open_right.fbx", "startFrame": 15.0, "endFrame": 15.0, "timeScale": 1.0, @@ -527,10 +382,10 @@ "children": [] }, { - "id": "leftThumbRaiseClosed", + "id": "rightThumbRaiseClosed", "type": "clip", "data": { - "url": "qrc:///avatar/animations/touch_thumb_closed_left.fbx", + "url": "qrc:///avatar/animations/touch_thumb_closed_right.fbx", "startFrame": 15.0, "endFrame": 15.0, "timeScale": 1.0, @@ -541,18 +396,18 @@ ] }, { - "id": "leftIndexPointAndThumbRaise", + "id": "rightIndexPointAndThumbRaise", "type": "blendLinear", - "data": { + "data": { "alpha": 0.0, - "alphaVar": "leftHandGraspAlpha" + "alphaVar": "rightHandGraspAlpha" }, "children": [ { - "id": "leftIndexPointAndThumbRaiseOpen", + "id": "rightIndexPointAndThumbRaiseOpen", "type": "clip", "data": { - "url": "qrc:///avatar/animations/touch_thumb_point_open_left.fbx", + "url": "qrc:///avatar/animations/touch_thumb_point_open_right.fbx", "startFrame": 15.0, "endFrame": 15.0, "timeScale": 1.0, @@ -561,10 +416,10 @@ "children": [] }, { - "id": "leftIndexPointAndThumbRaiseClosed", + "id": "rightIndexPointAndThumbRaiseClosed", "type": "clip", "data": { - "url": "qrc:///avatar/animations/touch_thumb_point_closed_left.fbx", + "url": "qrc:///avatar/animations/touch_thumb_point_closed_right.fbx", "startFrame": 15.0, "endFrame": 15.0, "timeScale": 1.0, @@ -577,911 +432,1588 @@ ] }, { - "id": "mainStateMachine", - "type": "stateMachine", + "id": "leftHandOverlay", + "type": "overlay", "data": { - "outputJoints": ["LeftFoot", "RightFoot"], - "currentState": "idle", - "states": [ - { - "id": "idle", - "interpTarget": 20, - "interpDuration": 8, - "interpType": "snapshotPrev", - "transitions": [ - { "var": "isMovingForward", "state": "WALKFWD" }, - { "var": "isMovingBackward", "state": "WALKBWD" }, - { "var": "isMovingRight", "state": "STRAFERIGHT" }, - { "var": "isMovingLeft", "state": "STRAFELEFT" }, - { "var": "isTurningRight", "state": "turnRight" }, - { "var": "isTurningLeft", "state": "turnLeft" }, - { "var": "isFlying", "state": "fly" }, - { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, - { "var": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "INAIRRUN" }, - { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, - { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } - ] - }, - { - "id": "idleToWalkFwd", - "interpTarget": 12, - "interpDuration": 8, - "transitions": [ - { "var": "idleToWalkFwdOnDone", "state": "WALKFWD" }, - { "var": "isNotMoving", "state": "idle" }, - { "var": "isMovingBackward", "state": "WALKBWD" }, - { "var": "isMovingRight", "state": "STRAFERIGHT" }, - { "var": "isMovingLeft", "state": "STRAFELEFT" }, - { "var": "isTurningRight", "state": "turnRight" }, - { "var": "isTurningLeft", "state": "turnLeft" }, - { "var": "isFlying", "state": "fly" }, - { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, - { "var": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "INAIRRUN" }, - { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, - { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } - ] - }, - { - "id": "idleSettle", - "interpTarget": 15, - "interpDuration": 8, - "interpType": "snapshotPrev", - "transitions": [ - {"var": "idleSettleOnDone", "state": "idle" }, - {"var": "isMovingForward", "state": "WALKFWD" }, - { "var": "isMovingBackward", "state": "WALKBWD" }, - { "var": "isMovingRight", "state": "STRAFERIGHT" }, - { "var": "isMovingLeft", "state": "STRAFELEFT" }, - { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, - { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" }, - { "var": "isTurningRight", "state": "turnRight" }, - { "var": "isTurningLeft", "state": "turnLeft" }, - { "var": "isFlying", "state": "fly" }, - { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, - { "var": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "INAIRRUN" } - ] - }, - { - "id": "WALKFWD", - "interpTarget": 35, - "interpDuration": 10, - "interpType": "snapshotPrev", - "transitions": [ - { "var": "isNotMoving", "state": "idleSettle" }, - { "var": "isMovingBackward", "state": "WALKBWD" }, - { "var": "isMovingRight", "state": "STRAFERIGHT" }, - { "var": "isMovingLeft", "state": "STRAFELEFT" }, - { "var": "isTurningRight", "state": "turnRight" }, - { "var": "isTurningLeft", "state": "turnLeft" }, - { "var": "isFlying", "state": "fly" }, - { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, - { "var": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "INAIRRUN" }, - { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, - { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } - ] - }, - { - "id": "WALKBWD", - "interpTarget": 35, - "interpDuration": 10, - "interpType": "snapshotPrev", - "transitions": [ - { "var": "isNotMoving", "state": "idleSettle" }, - { "var": "isMovingForward", "state": "WALKFWD" }, - { "var": "isMovingRight", "state": "STRAFERIGHT" }, - { "var": "isMovingLeft", "state": "STRAFELEFT" }, - { "var": "isTurningRight", "state": "turnRight" }, - { "var": "isTurningLeft", "state": "turnLeft" }, - { "var": "isFlying", "state": "fly" }, - { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, - { "var": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "INAIRRUN" }, - { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, - { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } - ] - }, - { - "id": "STRAFERIGHT", - "interpTarget": 25, - "interpDuration": 8, - "interpType": "snapshotPrev", - "transitions": [ - { "var": "isNotMoving", "state": "idleSettle" }, - { "var": "isMovingForward", "state": "WALKFWD" }, - { "var": "isMovingBackward", "state": "WALKBWD" }, - { "var": "isMovingLeft", "state": "STRAFELEFT" }, - { "var": "isTurningRight", "state": "turnRight" }, - { "var": "isTurningLeft", "state": "turnLeft" }, - { "var": "isFlying", "state": "fly" }, - { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, - { "var": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "INAIRRUN" }, - { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, - { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } - ] - }, - { - "id": "STRAFELEFT", - "interpTarget": 25, - "interpDuration": 8, - "interpType": "snapshotPrev", - "transitions": [ - { "var": "isNotMoving", "state": "idleSettle" }, - { "var": "isMovingForward", "state": "WALKFWD" }, - { "var": "isMovingBackward", "state": "WALKBWD" }, - { "var": "isMovingRight", "state": "STRAFERIGHT" }, - { "var": "isTurningRight", "state": "turnRight" }, - { "var": "isTurningLeft", "state": "turnLeft" }, - { "var": "isFlying", "state": "fly" }, - { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, - { "var": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "INAIRRUN" }, - { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, - { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } - ] - }, - { - "id": "turnRight", - "interpTarget": 6, - "interpDuration": 8, - "transitions": [ - { "var": "isNotTurning", "state": "idle" }, - { "var": "isMovingForward", "state": "WALKFWD" }, - { "var": "isMovingBackward", "state": "WALKBWD" }, - { "var": "isMovingRight", "state": "STRAFERIGHT" }, - { "var": "isMovingLeft", "state": "STRAFELEFT" }, - { "var": "isTurningLeft", "state": "turnLeft" }, - { "var": "isFlying", "state": "fly" }, - { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, - { "var": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "INAIRRUN" }, - { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, - { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } - ] - }, - { - "id": "turnLeft", - "interpTarget": 6, - "interpDuration": 8, - "transitions": [ - { "var": "isNotTurning", "state": "idle" }, - { "var": "isMovingForward", "state": "WALKFWD" }, - { "var": "isMovingBackward", "state": "WALKBWD" }, - { "var": "isMovingRight", "state": "STRAFERIGHT" }, - { "var": "isMovingLeft", "state": "STRAFELEFT" }, - { "var": "isTurningRight", "state": "turnRight" }, - { "var": "isFlying", "state": "fly" }, - { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, - { "var": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "INAIRRUN" }, - { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, - { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } - ] - }, - { - "id": "strafeRightHmd", - "interpTarget": 5, - "interpDuration": 8, - "interpType": "snapshotPrev", - "transitions": [ - { "var": "isNotMoving", "state": "idleSettle" }, - { "var": "isMovingForward", "state": "WALKFWD" }, - { "var": "isMovingBackward", "state": "WALKBWD" }, - { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" }, - { "var": "isMovingRight", "state": "STRAFERIGHT" }, - { "var": "isMovingLeft", "state": "STRAFELEFT" }, - { "var": "isTurningRight", "state": "turnRight" }, - { "var": "isTurningLeft", "state": "turnLeft" }, - { "var": "isFlying", "state": "fly" }, - { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, - { "var": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "INAIRRUN" } - ] - }, - { - "id": "strafeLeftHmd", - "interpTarget": 5, - "interpDuration": 8, - "interpType": "snapshotPrev", - "transitions": [ - { "var": "isNotMoving", "state": "idleSettle" }, - { "var": "isMovingForward", "state": "WALKFWD" }, - { "var": "isMovingBackward", "state": "WALKBWD" }, - { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, - { "var": "isMovingRight", "state": "STRAFERIGHT" }, - { "var": "isMovingLeft", "state": "STRAFELEFT" }, - { "var": "isTurningRight", "state": "turnRight" }, - { "var": "isTurningLeft", "state": "turnLeft" }, - { "var": "isFlying", "state": "fly" }, - { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, - { "var": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "INAIRRUN" } - ] - }, - { - "id": "fly", - "interpTarget": 6, - "interpDuration": 6, - "transitions": [ - { "var": "isNotFlying", "state": "idleSettle" } - ] - }, - { - "id": "takeoffStand", - "interpTarget": 2, - "interpDuration": 2, - "transitions": [ - { "var": "isNotTakeoff", "state": "inAirStand" } - ] - }, - { - "id": "TAKEOFFRUN", - "interpTarget": 2, - "interpDuration": 2, - "transitions": [ - { "var": "isNotTakeoff", "state": "INAIRRUN" } - ] - }, - { - "id": "inAirStand", - "interpTarget": 3, - "interpDuration": 3, - "interpType": "snapshotPrev", - "transitions": [ - { "var": "isNotInAir", "state": "landStandImpact" } - ] - }, - { - "id": "INAIRRUN", - "interpTarget": 3, - "interpDuration": 3, - "interpType": "snapshotPrev", - "transitions": [ - { "var": "isNotInAir", "state": "WALKFWD" } - ] - }, - { - "id": "landStandImpact", - "interpTarget": 1, - "interpDuration": 1, - "transitions": [ - { "var": "isFlying", "state": "fly" }, - { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, - { "var": "landStandImpactOnDone", "state": "landStand" } - ] - }, - { - "id": "landStand", - "interpTarget": 1, - "interpDuration": 1, - "transitions": [ - { "var": "isMovingForward", "state": "WALKFWD" }, - { "var": "isMovingBackward", "state": "WALKBWD" }, - { "var": "isMovingRight", "state": "STRAFERIGHT" }, - { "var": "isMovingLeft", "state": "STRAFELEFT" }, - { "var": "isTurningRight", "state": "turnRight" }, - { "var": "isTurningLeft", "state": "turnLeft" }, - { "var": "isFlying", "state": "fly" }, - { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, - { "var": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "INAIRRUN" }, - { "var": "landStandOnDone", "state": "idle" }, - { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, - { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } - ] - }, - { - "id": "LANDRUN", - "interpTarget": 2, - "interpDuration": 2, - "transitions": [ - { "var": "isFlying", "state": "fly" }, - { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, - { "var": "landRunOnDone", "state": "WALKFWD" } - ] - } - ] + "alpha": 0.0, + "boneSet": "leftHand", + "alphaVar": "leftHandOverlayAlpha" }, "children": [ { - "id": "idle", + "id": "leftHandStateMachine", "type": "stateMachine", "data": { - "currentState": "idleStand", + "currentState": "leftHandGrasp", "states": [ { - "id": "idleStand", - "interpTarget": 6, - "interpDuration": 10, + "id": "leftHandGrasp", + "interpTarget": 3, + "interpDuration": 3, "transitions": [ - { "var": "isTalking", "state": "idleTalk" } + { + "var": "isLeftIndexPoint", + "state": "leftIndexPoint" + }, + { + "var": "isLeftThumbRaise", + "state": "leftThumbRaise" + }, + { + "var": "isLeftIndexPointAndThumbRaise", + "state": "leftIndexPointAndThumbRaise" + } ] }, { - "id": "idleTalk", - "interpTarget": 6, - "interpDuration": 10, + "id": "leftIndexPoint", + "interpTarget": 15, + "interpDuration": 3, "transitions": [ - { "var": "notIsTalking", "state": "idleStand" } + { + "var": "isLeftHandGrasp", + "state": "leftHandGrasp" + }, + { + "var": "isLeftThumbRaise", + "state": "leftThumbRaise" + }, + { + "var": "isLeftIndexPointAndThumbRaise", + "state": "leftIndexPointAndThumbRaise" + } + ] + }, + { + "id": "leftThumbRaise", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { + "var": "isLeftHandGrasp", + "state": "leftHandGrasp" + }, + { + "var": "isLeftIndexPoint", + "state": "leftIndexPoint" + }, + { + "var": "isLeftIndexPointAndThumbRaise", + "state": "leftIndexPointAndThumbRaise" + } + ] + }, + { + "id": "leftIndexPointAndThumbRaise", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { + "var": "isLeftHandGrasp", + "state": "leftHandGrasp" + }, + { + "var": "isLeftIndexPoint", + "state": "leftIndexPoint" + }, + { + "var": "isLeftThumbRaise", + "state": "leftThumbRaise" + } ] } ] }, "children": [ { - "id": "idleStand", - "type": "clip", + "id": "leftHandGrasp", + "type": "blendLinear", "data": { - "url": "qrc:///avatar/animations/idle.fbx", - "startFrame": 0.0, - "endFrame": 300.0, - "timeScale": 1.0, - "loopFlag": true + "alpha": 0.0, + "alphaVar": "leftHandGraspAlpha" }, - "children": [] + "children": [ + { + "id": "leftHandGraspOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/hydra_pose_open_left.fbx", + "startFrame": 0.0, + "endFrame": 0.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "leftHandGraspClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/hydra_pose_closed_left.fbx", + "startFrame": 10.0, + "endFrame": 10.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] }, { - "id": "idleTalk", - "type": "clip", + "id": "leftIndexPoint", + "type": "blendLinear", "data": { - "url": "qrc:///avatar/animations/talk.fbx", - "startFrame": 0.0, - "endFrame": 800.0, - "timeScale": 1.0, - "loopFlag": true + "alpha": 0.0, + "alphaVar": "leftHandGraspAlpha" }, - "children": [] + "children": [ + { + "id": "leftIndexPointOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_point_open_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "leftIndexPointClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_point_closed_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "leftThumbRaise", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "leftHandGraspAlpha" + }, + "children": [ + { + "id": "leftThumbRaiseOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_open_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "leftThumbRaiseClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_closed_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "leftIndexPointAndThumbRaise", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "leftHandGraspAlpha" + }, + "children": [ + { + "id": "leftIndexPointAndThumbRaiseOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_point_open_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "leftIndexPointAndThumbRaiseClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_point_closed_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] } ] }, { - "id": "WALKFWD", - "type": "blendLinearMove", + "id": "mainStateMachine", + "type": "stateMachine", "data": { - "alpha": 0.0, - "desiredSpeed": 1.4, - "characteristicSpeeds": [0.5, 1.8, 2.3, 3.2, 4.5], - "alphaVar": "moveForwardAlpha", - "desiredSpeedVar": "moveForwardSpeed" + "outputJoints": [ "LeftFoot", "RightFoot" ], + "currentState": "idle", + "states": [ + { + "id": "idle", + "interpTarget": 20, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "idleToWalkFwd", + "interpTarget": 12, + "interpDuration": 8, + "transitions": [ + { + "var": "idleToWalkFwdOnDone", + "state": "WALKFWD" + }, + { + "var": "isNotMoving", + "state": "idle" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "idleSettle", + "interpTarget": 15, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "idleSettleOnDone", + "state": "idle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + } + ] + }, + { + "id": "WALKFWD", + "interpTarget": 35, + "interpDuration": 10, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotMoving", + "state": "idleSettle" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "WALKBWD", + "interpTarget": 35, + "interpDuration": 10, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotMoving", + "state": "idleSettle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "STRAFERIGHT", + "interpTarget": 25, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotMoving", + "state": "idleSettle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "STRAFELEFT", + "interpTarget": 25, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotMoving", + "state": "idleSettle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "turnRight", + "interpTarget": 6, + "interpDuration": 8, + "transitions": [ + { + "var": "isNotTurning", + "state": "idle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "turnLeft", + "interpTarget": 6, + "interpDuration": 8, + "transitions": [ + { + "var": "isNotTurning", + "state": "idle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "strafeRightHmd", + "interpTarget": 5, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotMoving", + "state": "idleSettle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + } + ] + }, + { + "id": "strafeLeftHmd", + "interpTarget": 5, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotMoving", + "state": "idleSettle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + } + ] + }, + { + "id": "fly", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { + "var": "isNotFlying", + "state": "idleSettle" + } + ] + }, + { + "id": "takeoffStand", + "interpTarget": 2, + "interpDuration": 2, + "transitions": [ + { + "var": "isNotTakeoff", + "state": "inAirStand" + } + ] + }, + { + "id": "TAKEOFFRUN", + "interpTarget": 2, + "interpDuration": 2, + "transitions": [ + { + "var": "isNotTakeoff", + "state": "INAIRRUN" + } + ] + }, + { + "id": "inAirStand", + "interpTarget": 3, + "interpDuration": 3, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotInAir", + "state": "landStandImpact" + } + ] + }, + { + "id": "INAIRRUN", + "interpTarget": 3, + "interpDuration": 3, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotInAir", + "state": "WALKFWD" + } + ] + }, + { + "id": "landStandImpact", + "interpTarget": 1, + "interpDuration": 1, + "transitions": [ + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "landStandImpactOnDone", + "state": "landStand" + } + ] + }, + { + "id": "landStand", + "interpTarget": 1, + "interpDuration": 1, + "transitions": [ + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "landStandOnDone", + "state": "idle" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "LANDRUN", + "interpTarget": 2, + "interpDuration": 2, + "transitions": [ + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "landRunOnDone", + "state": "WALKFWD" + } + ] + } + ] }, "children": [ { - "id": "walkFwdShort_c", - "type": "clip", + "id": "idle", + "type": "stateMachine", "data": { - "url": "qrc:///avatar/animations/walk_short_fwd.fbx", - "startFrame": 0.0, - "endFrame": 39.0, - "timeScale": 1.0, - "loopFlag": true + "currentState": "idleStand", + "states": [ + { + "id": "idleStand", + "interpTarget": 6, + "interpDuration": 10, + "transitions": [ + { + "var": "isTalking", + "state": "idleTalk" + } + ] + }, + { + "id": "idleTalk", + "interpTarget": 6, + "interpDuration": 10, + "transitions": [ + { + "var": "notIsTalking", + "state": "idleStand" + } + ] + } + ] }, - "children": [] + "children": [ + { + "id": "idleStand", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle.fbx", + "startFrame": 0.0, + "endFrame": 300.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "idleTalk", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/talk.fbx", + "startFrame": 0.0, + "endFrame": 800.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] }, { - "id": "walkFwdNormal_c", - "type": "clip", + "id": "WALKFWD", + "type": "blendLinearMove", "data": { - "url": "qrc:///avatar/animations/walk_fwd.fbx", - "startFrame": 0.0, - "endFrame": 30.0, - "timeScale": 1.0, - "loopFlag": true + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [ 0.5, 1.8, 2.3, 3.2, 4.5 ], + "alphaVar": "moveForwardAlpha", + "desiredSpeedVar": "moveForwardSpeed" }, - "children": [] + "children": [ + { + "id": "walkFwdShort_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_short_fwd.fbx", + "startFrame": 0.0, + "endFrame": 39.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkFwdNormal_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_fwd.fbx", + "startFrame": 0.0, + "endFrame": 30.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkFwdFast_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_fwd_fast.fbx", + "startFrame": 0.0, + "endFrame": 25.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkFwdJog_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jog_fwd.fbx", + "startFrame": 0.0, + "endFrame": 25.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkFwdRun_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/run_fwd.fbx", + "startFrame": 0.0, + "endFrame": 21.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] }, { - "id": "walkFwdFast_c", + "id": "idleToWalkFwd", "type": "clip", "data": { - "url": "qrc:///avatar/animations/walk_fwd_fast.fbx", - "startFrame": 0.0, - "endFrame": 25.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "walkFwdJog_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jog_fwd.fbx", - "startFrame": 0.0, - "endFrame": 25.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "walkFwdRun_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/run_fwd.fbx", - "startFrame": 0.0, - "endFrame": 21.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "idleToWalkFwd", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/idle_to_walk.fbx", - "startFrame": 1.0, - "endFrame": 13.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "idleSettle", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/settle_to_idle.fbx", - "startFrame": 1.0, - "endFrame": 59.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "WALKBWD", - "type": "blendLinearMove", - "data": { - "alpha": 0.0, - "desiredSpeed": 1.4, - "characteristicSpeeds": [0.6, 1.6, 2.3, 3.1], - "alphaVar": "moveBackwardAlpha", - "desiredSpeedVar": "moveBackwardSpeed" - }, - "children": [ - { - "id": "walkBwdShort_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_short_bwd.fbx", - "startFrame": 0.0, - "endFrame": 38.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "walkBwdFast_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_bwd_fast.fbx", - "startFrame": 0.0, - "endFrame": 27.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "jogBwd_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jog_bwd.fbx", - "startFrame": 0.0, - "endFrame": 24.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "runBwd_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/run_bwd.fbx", - "startFrame": 0.0, - "endFrame": 16.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "turnLeft", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/turn_left.fbx", - "startFrame": 0.0, - "endFrame": 32.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "turnRight", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/turn_left.fbx", - "startFrame": 0.0, - "endFrame": 32.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "STRAFELEFT", - "type": "blendLinearMove", - "data": { - "alpha": 0.0, - "desiredSpeed": 1.4, - "characteristicSpeeds": [0.1, 0.5, 1.0, 2.6, 3.0], - "alphaVar": "moveLateralAlpha", - "desiredSpeedVar": "moveLateralSpeed" - }, - "children": [ - { - "id": "strafeLeftShortStep_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_short_left.fbx", - "startFrame": 0.0, - "endFrame": 29.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "strafeLeftStep_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_left.fbx", - "startFrame": 0.0, - "endFrame": 20.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "strafeLeftWalk_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_left.fbx", - "startFrame": 0.0, - "endFrame": 35.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "strafeLeftWalkFast_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_left_fast.fbx", - "startFrame": 0.0, - "endFrame": 21.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "strafeLeftJog_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jog_left.fbx", - "startFrame": 0.0, - "endFrame": 24.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "STRAFERIGHT", - "type": "blendLinearMove", - "data": { - "alpha": 0.0, - "desiredSpeed": 1.4, - "characteristicSpeeds": [0.1, 0.5, 1.0, 2.6, 3.0], - "alphaVar": "moveLateralAlpha", - "desiredSpeedVar": "moveLateralSpeed" - }, - "children": [ { - "id": "strafeRightShortStep_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_short_left.fbx", - "startFrame": 0.0, - "endFrame": 29.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "strafeRightStep_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_left.fbx", - "startFrame": 0.0, - "endFrame": 20.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "strafeRightWalk_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_left.fbx", - "startFrame": 0.0, - "endFrame": 35.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "strafeRightFast_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_left_fast.fbx", - "startFrame": 0.0, - "endFrame": 21.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "strafeRightJog_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jog_left.fbx", - "startFrame": 0.0, - "endFrame": 24.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - } - ] - }, - { - "id": "strafeLeftHmd", - "type": "blendLinearMove", - "data": { - "alpha": 0.0, - "desiredSpeed": 1.4, - "characteristicSpeeds": [0, 0.5, 2.5], - "alphaVar": "moveLateralAlpha", - "desiredSpeedVar": "moveLateralSpeed" - }, - "children": [ - { - "id": "stepLeftShort_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_short_left.fbx", - "startFrame": 0.0, - "endFrame": 29.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "stepLeft_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_left.fbx", - "startFrame": 0.0, - "endFrame": 20.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "strafeLeftAnim_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_left_fast.fbx", - "startFrame": 0.0, - "endFrame": 16.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "strafeRightHmd", - "type": "blendLinearMove", - "data": { - "alpha": 0.0, - "desiredSpeed": 1.4, - "characteristicSpeeds": [0, 0.5, 2.5], - "alphaVar": "moveLateralAlpha", - "desiredSpeedVar": "moveLateralSpeed" - }, - "children": [ - { - "id": "stepRightShort_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_short_left.fbx", - "startFrame": 0.0, - "endFrame": 29.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "stepRight_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_left.fbx", - "startFrame": 0.0, - "endFrame": 20.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "strafeRightAnim_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_left_fast.fbx", - "startFrame": 0.0, - "endFrame": 16.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - } - ] - }, - { - "id": "fly", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/fly.fbx", - "startFrame": 1.0, - "endFrame": 80.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "takeoffStand", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_standing_launch.fbx", - "startFrame": 2.0, - "endFrame": 16.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "TAKEOFFRUN", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", - "startFrame": 4.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "inAirStand", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "inAirAlpha" - }, - "children": [ - { - "id": "inAirStandPreApex", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_standing_apex.fbx", - "startFrame": 0.0, - "endFrame": 0.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "inAirStandApex", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_standing_apex.fbx", + "url": "qrc:///avatar/animations/idle_to_walk.fbx", "startFrame": 1.0, - "endFrame": 1.0, + "endFrame": 13.0, "timeScale": 1.0, "loopFlag": false }, "children": [] }, { - "id": "inAirStandPostApex", + "id": "idleSettle", "type": "clip", "data": { - "url": "qrc:///avatar/animations/jump_standing_apex.fbx", - "startFrame": 2.0, - "endFrame": 2.0, + "url": "qrc:///avatar/animations/settle_to_idle.fbx", + "startFrame": 1.0, + "endFrame": 59.0, "timeScale": 1.0, "loopFlag": false }, "children": [] - } - ] - }, - { - "id": "INAIRRUN", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "inAirAlpha" - }, - "children": [ + }, { - "id": "inAirRunPreApex", + "id": "WALKBWD", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [ 0.6, 1.6, 2.3, 3.1 ], + "alphaVar": "moveBackwardAlpha", + "desiredSpeedVar": "moveBackwardSpeed" + }, + "children": [ + { + "id": "walkBwdShort_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_short_bwd.fbx", + "startFrame": 0.0, + "endFrame": 38.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkBwdFast_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_bwd_fast.fbx", + "startFrame": 0.0, + "endFrame": 27.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "jogBwd_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jog_bwd.fbx", + "startFrame": 0.0, + "endFrame": 24.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "runBwd_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/run_bwd.fbx", + "startFrame": 0.0, + "endFrame": 16.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "turnLeft", "type": "clip", "data": { - "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", - "startFrame": 16.0, + "url": "qrc:///avatar/animations/turn_left.fbx", + "startFrame": 0.0, + "endFrame": 32.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "turnRight", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/turn_left.fbx", + "startFrame": 0.0, + "endFrame": 32.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "STRAFELEFT", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [ 0.1, 0.5, 1.0, 2.6, 3.0 ], + "alphaVar": "moveLateralAlpha", + "desiredSpeedVar": "moveLateralSpeed" + }, + "children": [ + { + "id": "strafeLeftShortStep_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_short_left.fbx", + "startFrame": 0.0, + "endFrame": 29.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftStep_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left.fbx", + "startFrame": 0.0, + "endFrame": 20.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftWalk_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_left.fbx", + "startFrame": 0.0, + "endFrame": 35.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftWalkFast_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_left_fast.fbx", + "startFrame": 0.0, + "endFrame": 21.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftJog_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jog_left.fbx", + "startFrame": 0.0, + "endFrame": 24.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "STRAFERIGHT", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [ 0.1, 0.5, 1.0, 2.6, 3.0 ], + "alphaVar": "moveLateralAlpha", + "desiredSpeedVar": "moveLateralSpeed" + }, + "children": [ + { + "id": "strafeRightShortStep_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_short_left.fbx", + "startFrame": 0.0, + "endFrame": 29.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "strafeRightStep_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left.fbx", + "startFrame": 0.0, + "endFrame": 20.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "strafeRightWalk_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_left.fbx", + "startFrame": 0.0, + "endFrame": 35.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "strafeRightFast_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_left_fast.fbx", + "startFrame": 0.0, + "endFrame": 21.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "strafeRightJog_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jog_left.fbx", + "startFrame": 0.0, + "endFrame": 24.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + } + ] + }, + { + "id": "strafeLeftHmd", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [ 0, 0.5, 2.5 ], + "alphaVar": "moveLateralAlpha", + "desiredSpeedVar": "moveLateralSpeed" + }, + "children": [ + { + "id": "stepLeftShort_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_short_left.fbx", + "startFrame": 0.0, + "endFrame": 29.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "stepLeft_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left.fbx", + "startFrame": 0.0, + "endFrame": 20.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftAnim_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left_fast.fbx", + "startFrame": 0.0, + "endFrame": 16.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "strafeRightHmd", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [ 0, 0.5, 2.5 ], + "alphaVar": "moveLateralAlpha", + "desiredSpeedVar": "moveLateralSpeed" + }, + "children": [ + { + "id": "stepRightShort_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_short_left.fbx", + "startFrame": 0.0, + "endFrame": 29.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "stepRight_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left.fbx", + "startFrame": 0.0, + "endFrame": 20.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "strafeRightAnim_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left_fast.fbx", + "startFrame": 0.0, + "endFrame": 16.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + } + ] + }, + { + "id": "fly", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/fly.fbx", + "startFrame": 1.0, + "endFrame": 80.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "takeoffStand", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_launch.fbx", + "startFrame": 2.0, "endFrame": 16.0, "timeScale": 1.0, "loopFlag": false @@ -1489,66 +2021,146 @@ "children": [] }, { - "id": "inAirRunApex", + "id": "TAKEOFFRUN", "type": "clip", "data": { "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", - "startFrame": 22.0, - "endFrame": 22.0, + "startFrame": 4.0, + "endFrame": 15.0, "timeScale": 1.0, "loopFlag": false }, "children": [] }, { - "id": "inAirRunPostApex", + "id": "inAirStand", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "inAirAlpha" + }, + "children": [ + { + "id": "inAirStandPreApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_apex.fbx", + "startFrame": 0.0, + "endFrame": 0.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirStandApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_apex.fbx", + "startFrame": 1.0, + "endFrame": 1.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirStandPostApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_apex.fbx", + "startFrame": 2.0, + "endFrame": 2.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + } + ] + }, + { + "id": "INAIRRUN", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "inAirAlpha" + }, + "children": [ + { + "id": "inAirRunPreApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "startFrame": 16.0, + "endFrame": 16.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirRunApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "startFrame": 22.0, + "endFrame": 22.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirRunPostApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "startFrame": 33.0, + "endFrame": 33.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + } + ] + }, + { + "id": "landStandImpact", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_land_settle.fbx", + "startFrame": 1.0, + "endFrame": 6.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "landStand", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_land_settle.fbx", + "startFrame": 6.0, + "endFrame": 68.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "LANDRUN", "type": "clip", "data": { "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", - "startFrame": 33.0, - "endFrame": 33.0, + "startFrame": 29.0, + "endFrame": 40.0, "timeScale": 1.0, "loopFlag": false }, "children": [] } ] - }, - { - "id": "landStandImpact", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_standing_land_settle.fbx", - "startFrame": 1.0, - "endFrame": 6.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "landStand", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_standing_land_settle.fbx", - "startFrame": 6.0, - "endFrame": 68.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "LANDRUN", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", - "startFrame": 29.0, - "endFrame": 40.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] } ] } diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 562a614b4a..ec9099df8b 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -63,6 +63,7 @@ static const char* animNodeTypeToString(AnimNode::Type type) { case AnimNode::Type::InverseKinematics: return "inverseKinematics"; case AnimNode::Type::DefaultPose: return "defaultPose"; case AnimNode::Type::TwoBoneIK: return "twoBoneIK"; + case AnimNode::Type::SplineIK: return "splineIK"; case AnimNode::Type::PoleVectorConstraint: return "poleVectorConstraint"; case AnimNode::Type::NumTypes: return nullptr; }; diff --git a/libraries/animation/src/AnimStateMachine.cpp b/libraries/animation/src/AnimStateMachine.cpp index fb13b8e71c..3042ad20f0 100644 --- a/libraries/animation/src/AnimStateMachine.cpp +++ b/libraries/animation/src/AnimStateMachine.cpp @@ -22,7 +22,7 @@ AnimStateMachine::~AnimStateMachine() { } const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { - + qCDebug(animation) << "evaluating state machine"; float parentDebugAlpha = context.getDebugAlpha(_id); QString desiredStateID = animVars.lookup(_currentStateVar, _currentState->getID()); diff --git a/libraries/animation/src/AnimTwoBoneIK.cpp b/libraries/animation/src/AnimTwoBoneIK.cpp index 8960b15940..d970e6b862 100644 --- a/libraries/animation/src/AnimTwoBoneIK.cpp +++ b/libraries/animation/src/AnimTwoBoneIK.cpp @@ -46,10 +46,13 @@ AnimTwoBoneIK::~AnimTwoBoneIK() { const AnimPoseVec& AnimTwoBoneIK::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { + qCDebug(animation) << "evaluating the 2 bone IK"; + assert(_children.size() == 1); if (_children.size() != 1) { return _poses; } + // evalute underPoses AnimPoseVec underPoses = _children[0]->evaluate(animVars, context, dt, triggersOut); From bd9405aef82575972fa3340c2907c50ac45f9601 Mon Sep 17 00:00:00 2001 From: amantley Date: Thu, 10 Jan 2019 10:27:15 -0800 Subject: [PATCH 003/474] add arm ik class for cleanup of inverse kinematics --- libraries/animation/src/AnimArmIK.cpp | 40 +++++++++++++++++++ libraries/animation/src/AnimArmIK.h | 34 ++++++++++++++++ .../animation/src/AnimInverseKinematics.cpp | 4 +- 3 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 libraries/animation/src/AnimArmIK.cpp create mode 100644 libraries/animation/src/AnimArmIK.h diff --git a/libraries/animation/src/AnimArmIK.cpp b/libraries/animation/src/AnimArmIK.cpp new file mode 100644 index 0000000000..202e8c8420 --- /dev/null +++ b/libraries/animation/src/AnimArmIK.cpp @@ -0,0 +1,40 @@ +// +// AnimArmIK.cpp +// +// Created by Angus Antley on 1/9/19. +// Copyright (c) 2019 High Fidelity, Inc. All rights reserved. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "AnimArmIK.h" + +#include + +#include "AnimationLogging.h" +#include "AnimUtil.h" + +AnimArmIK::AnimArmIK(const QString& id, float alpha, bool enabled, float interpDuration, + const QString& baseJointName, const QString& midJointName, + const QString& tipJointName, const glm::vec3& midHingeAxis, + const QString& alphaVar, const QString& enabledVar, + const QString& endEffectorRotationVarVar, const QString& endEffectorPositionVarVar) : + AnimTwoBoneIK(id, alpha, enabled, interpDuration, baseJointName, midJointName, tipJointName, midHingeAxis, alphaVar, enabledVar, endEffectorRotationVarVar, endEffectorPositionVarVar) { + +} + +AnimArmIK::~AnimArmIK() { + +} + +const AnimPoseVec& AnimArmIK::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { + qCDebug(animation) << "evaluating the arm IK"; + + assert(_children.size() == 1); + if (_children.size() != 1) { + return _poses; + } else { + return _poses; + } +} \ No newline at end of file diff --git a/libraries/animation/src/AnimArmIK.h b/libraries/animation/src/AnimArmIK.h new file mode 100644 index 0000000000..26f79a1b9c --- /dev/null +++ b/libraries/animation/src/AnimArmIK.h @@ -0,0 +1,34 @@ +// +// AnimArmIK.h +// +// Created by Angus Antley on 1/9/19. +// Copyright (c) 2019 High Fidelity, Inc. All rights reserved. +// +// 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_AnimArmIK_h +#define hifi_AnimArmIK_h + +//#include "AnimNode.h" +#include "AnimTwoBoneIK.h" +//#include "AnimChain.h" + +// Simple two bone IK chain +class AnimArmIK : public AnimTwoBoneIK { +public: + AnimArmIK(const QString& id, float alpha, bool enabled, float interpDuration, + const QString& baseJointName, const QString& midJointName, + const QString& tipJointName, const glm::vec3& midHingeAxis, + const QString& alphaVar, const QString& enabledVar, + const QString& endEffectorRotationVarVar, const QString& endEffectorPositionVarVar); + virtual ~AnimArmIK(); + virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override; + +protected: + +}; + +#endif // hifi_AnimArmIK_h + diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index a1809f3438..a6dcf5b2d9 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -237,6 +237,7 @@ void AnimInverseKinematics::solve(const AnimContext& context, const std::vector< // solve all targets for (size_t i = 0; i < targets.size(); i++) { + qCDebug(animation) << "target id: " << targets[i].getIndex() << " and type " << (int)targets[i].getType(); switch (targets[i].getType()) { case IKTarget::Type::Unknown: break; @@ -859,6 +860,7 @@ void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, co //virtual const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { // don't call this function, call overlay() instead + qCDebug(animation) << "called evaluate for inverse kinematics"; assert(false); return _relativePoses; } @@ -869,7 +871,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars // disable IK on android return underPoses; #endif - + qCDebug(animation) << "called overlay for inverse kinematics"; // allows solutionSource to be overridden by an animVar auto solutionSource = animVars.lookup(_solutionSourceVar, (int)_solutionSource); From 33ff5188c11b949fff7371c9ec155edffae0d52e Mon Sep 17 00:00:00 2001 From: amantley Date: Tue, 15 Jan 2019 18:28:29 -0800 Subject: [PATCH 004/474] adding the spline code to the splineik class --- libraries/animation/src/AnimSplineIK.cpp | 230 ++++++++++++++++++++++- libraries/animation/src/AnimSplineIK.h | 17 ++ 2 files changed, 246 insertions(+), 1 deletion(-) diff --git a/libraries/animation/src/AnimSplineIK.cpp b/libraries/animation/src/AnimSplineIK.cpp index db6b6864b1..65d7bfc821 100644 --- a/libraries/animation/src/AnimSplineIK.cpp +++ b/libraries/animation/src/AnimSplineIK.cpp @@ -44,10 +44,46 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const if (_children.size() != 1) { return _poses; } - // evalute underPoses AnimPoseVec underPoses = _children[0]->evaluate(animVars, context, dt, triggersOut); _poses = underPoses; + + // check to see if we actually need absolute poses. + AnimPoseVec absolutePoses; + absolutePoses.resize(_poses.size()); + computeAbsolutePoses(absolutePoses); + + IKTarget target; + int jointIndex = _skeleton->nameToJointIndex("Head"); + if (jointIndex != -1) { + target.setType(animVars.lookup("HeadType", (int)IKTarget::Type::RotationAndPosition)); + target.setIndex(jointIndex); + AnimPose absPose = _skeleton->getAbsolutePose(jointIndex, _poses); + glm::quat rotation = animVars.lookupRigToGeometry("headRotation", absPose.rot()); + glm::vec3 translation = animVars.lookupRigToGeometry("headPosition", absPose.trans()); + float weight = animVars.lookup("headWeight", "4.0"); + + target.setPose(rotation, translation); + target.setWeight(weight); + const float* flexCoefficients = new float[5]{ 1.0f, 0.5f, 0.25f, 0.2f, 0.1f }; + target.setFlexCoefficients(4, flexCoefficients); + + // record the index of the hips ik target. + if (target.getIndex() == _hipsIndex) { + _hipsTargetIndex = 1; + } + } + if (_poses.size() > 0) { + AnimChain jointChain; + jointChain.buildFromRelativePoses(_skeleton, _poses, target.getIndex()); + + // for each target solve target with spline + + solveTargetWithSpline(context, target, absolutePoses, false, jointChain); + qCDebug(animation) << "made it past the spline solve code"; + } + // we need to blend the old joint chain with the current joint chain, otherwise known as: _snapShotChain + /**/ return _poses; } @@ -66,6 +102,20 @@ void AnimSplineIK::lookUpIndices() { } } +void AnimSplineIK::computeAbsolutePoses(AnimPoseVec& absolutePoses) const { + int numJoints = (int)_poses.size(); + assert(numJoints <= _skeleton->getNumJoints()); + assert(numJoints == (int)absolutePoses.size()); + for (int i = 0; i < numJoints; ++i) { + int parentIndex = _skeleton->getParentIndex(i); + if (parentIndex < 0) { + absolutePoses[i] = _poses[i]; + } else { + absolutePoses[i] = absolutePoses[parentIndex] * _poses[i]; + } + } +} + // for AnimDebugDraw rendering const AnimPoseVec& AnimSplineIK::getPosesInternal() const { return _poses; @@ -73,5 +123,183 @@ const AnimPoseVec& AnimSplineIK::getPosesInternal() const { void AnimSplineIK::setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) { AnimNode::setSkeletonInternal(skeleton); + _headIndex = _skeleton->nameToJointIndex("Head"); + _hipsIndex = _skeleton->nameToJointIndex("Hips"); lookUpIndices(); +} + +static CubicHermiteSplineFunctorWithArcLength computeSplineFromTipAndBase(const AnimPose& tipPose, const AnimPose& basePose, float baseGain = 1.0f, float tipGain = 1.0f) { + float linearDistance = glm::length(basePose.trans() - tipPose.trans()); + glm::vec3 p0 = basePose.trans(); + glm::vec3 m0 = baseGain * linearDistance * (basePose.rot() * Vectors::UNIT_Y); + glm::vec3 p1 = tipPose.trans(); + glm::vec3 m1 = tipGain * linearDistance * (tipPose.rot() * Vectors::UNIT_Y); + + return CubicHermiteSplineFunctorWithArcLength(p0, m0, p1, m1); +} + +void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug, AnimChain& chainInfoOut) const { + + const int baseIndex = _hipsIndex; + + // build spline from tip to base + AnimPose tipPose = AnimPose(glm::vec3(1.0f), target.getRotation(), target.getTranslation()); + AnimPose basePose = absolutePoses[baseIndex]; + CubicHermiteSplineFunctorWithArcLength spline; + if (target.getIndex() == _headIndex) { + // set gain factors so that more curvature occurs near the tip of the spline. + const float HIPS_GAIN = 0.5f; + const float HEAD_GAIN = 1.0f; + spline = computeSplineFromTipAndBase(tipPose, basePose, HIPS_GAIN, HEAD_GAIN); + } else { + spline = computeSplineFromTipAndBase(tipPose, basePose); + } + float totalArcLength = spline.arcLength(1.0f); + + // This prevents the rotation interpolation from rotating the wrong physical way (but correct mathematical way) + // when the head is arched backwards very far. + glm::quat halfRot = safeLerp(basePose.rot(), tipPose.rot(), 0.5f); + if (glm::dot(halfRot * Vectors::UNIT_Z, basePose.rot() * Vectors::UNIT_Z) < 0.0f) { + tipPose.rot() = -tipPose.rot(); + } + qCDebug(animation) << "spot 1"; + // find or create splineJointInfo for this target + const std::vector* splineJointInfoVec = findOrCreateSplineJointInfo(context, target); + + if (splineJointInfoVec && splineJointInfoVec->size() > 0) { + const int baseParentIndex = _skeleton->getParentIndex(baseIndex); + AnimPose parentAbsPose = (baseParentIndex >= 0) ? absolutePoses[baseParentIndex] : AnimPose(); + qCDebug(animation) << "spot 2"; + // go thru splineJointInfoVec backwards (base to tip) + for (int i = (int)splineJointInfoVec->size() - 1; i >= 0; i--) { + const SplineJointInfo& splineJointInfo = (*splineJointInfoVec)[i]; + float t = spline.arcLengthInverse(splineJointInfo.ratio * totalArcLength); + glm::vec3 trans = spline(t); + + // for head splines, preform most twist toward the tip by using ease in function. t^2 + float rotT = t; + if (target.getIndex() == _headIndex) { + rotT = t * t; + } + glm::quat twistRot = safeLerp(basePose.rot(), tipPose.rot(), rotT); + + // compute the rotation by using the derivative of the spline as the y-axis, and the twistRot x-axis + glm::vec3 y = glm::normalize(spline.d(t)); + glm::vec3 x = twistRot * Vectors::UNIT_X; + glm::vec3 u, v, w; + generateBasisVectors(y, x, v, u, w); + glm::mat3 m(u, v, glm::cross(u, v)); + glm::quat rot = glm::normalize(glm::quat_cast(m)); + + AnimPose desiredAbsPose = AnimPose(glm::vec3(1.0f), rot, trans) * splineJointInfo.offsetPose; + + // apply flex coefficent + AnimPose flexedAbsPose; + ::blend(1, &absolutePoses[splineJointInfo.jointIndex], &desiredAbsPose, target.getFlexCoefficient(i), &flexedAbsPose); + + AnimPose relPose = parentAbsPose.inverse() * flexedAbsPose; + + bool constrained = false; + if (splineJointInfo.jointIndex != _hipsIndex) { + // constrain the amount the spine can stretch or compress + float length = glm::length(relPose.trans()); + const float EPSILON = 0.0001f; + if (length > EPSILON) { + float defaultLength = glm::length(_skeleton->getRelativeDefaultPose(splineJointInfo.jointIndex).trans()); + const float STRETCH_COMPRESS_PERCENTAGE = 0.15f; + const float MAX_LENGTH = defaultLength * (1.0f + STRETCH_COMPRESS_PERCENTAGE); + const float MIN_LENGTH = defaultLength * (1.0f - STRETCH_COMPRESS_PERCENTAGE); + if (length > MAX_LENGTH) { + relPose.trans() = (relPose.trans() / length) * MAX_LENGTH; + constrained = true; + } else if (length < MIN_LENGTH) { + relPose.trans() = (relPose.trans() / length) * MIN_LENGTH; + constrained = true; + } + } else { + relPose.trans() = glm::vec3(0.0f); + } + } + // note we are ignoring the constrained info for now. + if (!chainInfoOut.setRelativePoseAtJointIndex(splineJointInfo.jointIndex, relPose)) { + qCDebug(animation) << "we didn't find the joint index for the spline!!!!"; + } + + parentAbsPose = flexedAbsPose; + } + } + + if (debug) { + //debugDrawIKChain(jointChainInfoOut, context); + } +} + +const std::vector* AnimSplineIK::findOrCreateSplineJointInfo(const AnimContext& context, const IKTarget& target) const { + // find or create splineJointInfo for this target + auto iter = _splineJointInfoMap.find(target.getIndex()); + if (iter != _splineJointInfoMap.end()) { + return &(iter->second); + } else { + computeAndCacheSplineJointInfosForIKTarget(context, target); + auto iter = _splineJointInfoMap.find(target.getIndex()); + if (iter != _splineJointInfoMap.end()) { + return &(iter->second); + } + } + + return nullptr; +} + +// pre-compute information about each joint influenced by this spline IK target. +void AnimSplineIK::computeAndCacheSplineJointInfosForIKTarget(const AnimContext& context, const IKTarget& target) const { + std::vector splineJointInfoVec; + + // build spline between the default poses. + AnimPose tipPose = _skeleton->getAbsoluteDefaultPose(target.getIndex()); + AnimPose basePose = _skeleton->getAbsoluteDefaultPose(_hipsIndex); + + CubicHermiteSplineFunctorWithArcLength spline; + if (target.getIndex() == _headIndex) { + // set gain factors so that more curvature occurs near the tip of the spline. + const float HIPS_GAIN = 0.5f; + const float HEAD_GAIN = 1.0f; + spline = computeSplineFromTipAndBase(tipPose, basePose, HIPS_GAIN, HEAD_GAIN); + } else { + spline = computeSplineFromTipAndBase(tipPose, basePose); + } + + // measure the total arc length along the spline + float totalArcLength = spline.arcLength(1.0f); + + glm::vec3 baseToTip = tipPose.trans() - basePose.trans(); + float baseToTipLength = glm::length(baseToTip); + glm::vec3 baseToTipNormal = baseToTip / baseToTipLength; + + int index = target.getIndex(); + int endIndex = _skeleton->getParentIndex(_hipsIndex); + while (index != endIndex) { + AnimPose defaultPose = _skeleton->getAbsoluteDefaultPose(index); + + float ratio = glm::dot(defaultPose.trans() - basePose.trans(), baseToTipNormal) / baseToTipLength; + + // compute offset from spline to the default pose. + float t = spline.arcLengthInverse(ratio * totalArcLength); + + // compute the rotation by using the derivative of the spline as the y-axis, and the defaultPose x-axis + glm::vec3 y = glm::normalize(spline.d(t)); + glm::vec3 x = defaultPose.rot() * Vectors::UNIT_X; + glm::vec3 u, v, w; + generateBasisVectors(y, x, v, u, w); + glm::mat3 m(u, v, glm::cross(u, v)); + glm::quat rot = glm::normalize(glm::quat_cast(m)); + + AnimPose pose(glm::vec3(1.0f), rot, spline(t)); + AnimPose offsetPose = pose.inverse() * defaultPose; + + SplineJointInfo splineJointInfo = { index, ratio, offsetPose }; + splineJointInfoVec.push_back(splineJointInfo); + index = _skeleton->getParentIndex(index); + } + + _splineJointInfoMap[target.getIndex()] = splineJointInfoVec; } \ No newline at end of file diff --git a/libraries/animation/src/AnimSplineIK.h b/libraries/animation/src/AnimSplineIK.h index 36a29971a0..79f012365a 100644 --- a/libraries/animation/src/AnimSplineIK.h +++ b/libraries/animation/src/AnimSplineIK.h @@ -12,6 +12,7 @@ #define hifi_AnimSplineIK_h #include "AnimNode.h" +#include "IKTarget.h" #include "AnimChain.h" // Spline IK for the spine @@ -34,6 +35,8 @@ protected: NumTypes }; + void computeAbsolutePoses(AnimPoseVec& absolutePoses) const; + // for AnimDebugDraw rendering virtual const AnimPoseVec& getPosesInternal() const override; virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) override; @@ -51,6 +54,9 @@ protected: int _baseParentJointIndex{ -1 }; int _baseJointIndex{ -1 }; int _tipJointIndex{ -1 }; + int _headIndex{ -1 }; + int _hipsIndex{ -1 }; + int _hipsTargetIndex{ -1 }; QString _alphaVar; // float - (0, 1) 0 means underPoses only, 1 means IK only. QString _enabledVar; // bool @@ -66,7 +72,18 @@ protected: AnimChain _snapshotChain; + // used to pre-compute information about each joint influeced by a spline IK target. + struct SplineJointInfo { + int jointIndex; // joint in the skeleton that this information pertains to. + float ratio; // percentage (0..1) along the spline for this joint. + AnimPose offsetPose; // local offset from the spline to the joint. + }; + bool _lastEnableDebugDrawIKTargets{ false }; + void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug, AnimChain& chainInfoOut) const; + void computeAndCacheSplineJointInfosForIKTarget(const AnimContext& context, const IKTarget& target) const; + const std::vector* findOrCreateSplineJointInfo(const AnimContext& context, const IKTarget& target) const; + mutable std::map> _splineJointInfoMap; // no copies AnimSplineIK(const AnimSplineIK&) = delete; From e5b16ef17466d2dae88a31704f5994bf3dc45192 Mon Sep 17 00:00:00 2001 From: amantley Date: Wed, 16 Jan 2019 18:31:52 -0800 Subject: [PATCH 005/474] implemented the splineIK in animSplineIK.cpp, todo: disable animinversekinematic.cpp --- .../resources/avatar/avatar-animation.json | 2 +- .../animation/src/AnimInverseKinematics.cpp | 13 ++++++-- libraries/animation/src/AnimSplineIK.cpp | 32 +++++++++++++------ libraries/animation/src/AnimStateMachine.cpp | 1 - libraries/animation/src/AnimTwoBoneIK.cpp | 2 -- libraries/fbx/src/FBXSerializer.cpp | 1 + 6 files changed, 35 insertions(+), 16 deletions(-) diff --git a/interface/resources/avatar/avatar-animation.json b/interface/resources/avatar/avatar-animation.json index 88ee1ff06b..16523afba3 100644 --- a/interface/resources/avatar/avatar-animation.json +++ b/interface/resources/avatar/avatar-animation.json @@ -173,7 +173,7 @@ "jointName": "Head", "positionVar": "headPosition", "rotationVar": "headRotation", - "typeVar": "headType", + "typeVar": "spine2Type", "weightVar": "headWeight", "weight": 4.0, "flexCoefficients": [ 1, 0.5, 0.25, 0.2, 0.1 ] diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index a6dcf5b2d9..e5509dc43d 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -237,12 +237,19 @@ void AnimInverseKinematics::solve(const AnimContext& context, const std::vector< // solve all targets for (size_t i = 0; i < targets.size(); i++) { - qCDebug(animation) << "target id: " << targets[i].getIndex() << " and type " << (int)targets[i].getType(); + // qCDebug(animation) << "target id: " << targets[i].getIndex() << " and type " << (int)targets[i].getType(); switch (targets[i].getType()) { case IKTarget::Type::Unknown: break; case IKTarget::Type::Spline: solveTargetWithSpline(context, targets[i], absolutePoses, debug, jointChainInfoVec[i]); + //if (jointChainInfoVec[i].target.getIndex() == _skeleton->nameToJointIndex("Head")) { + // qCDebug(animation) << "AnimIK spline index is " << targets[i].getIndex() << " and chain info size is " << jointChainInfoVec[i].jointInfoVec.size(); + for (int w = 0; w < jointChainInfoVec[i].jointInfoVec.size(); w++) { + // qCDebug(animation) << "joint " << jointChainInfoVec[i].jointInfoVec[w].jointIndex << " rotation is " << jointChainInfoVec[i].jointInfoVec[w].rot; + // qCDebug(animation) << "joint " << jointChainInfoVec[i].jointInfoVec[w].jointIndex << " translation is " << jointChainInfoVec[i].jointInfoVec[w].trans; + } + //} break; default: solveTargetWithCCD(context, targets[i], absolutePoses, debug, jointChainInfoVec[i]); @@ -259,6 +266,7 @@ void AnimInverseKinematics::solve(const AnimContext& context, const std::vector< // ease in expo alpha = 1.0f - powf(2.0f, -10.0f * alpha); + qCDebug(animation) << "the alpha for joint chains is " << alpha; size_t chainSize = std::min(_prevJointChainInfoVec[i].jointInfoVec.size(), jointChainInfoVec[i].jointInfoVec.size()); @@ -361,6 +369,7 @@ void AnimInverseKinematics::solve(const AnimContext& context, const std::vector< // copy jointChainInfoVec into _prevJointChainInfoVec, and update timers for (size_t i = 0; i < jointChainInfoVec.size(); i++) { _prevJointChainInfoVec[i].timer = _prevJointChainInfoVec[i].timer - dt; + //qCDebug(animation) << "the alpha for joint chains is " << _prevJointChainInfoVec[i].timer; if (_prevJointChainInfoVec[i].timer <= 0.0f) { _prevJointChainInfoVec[i] = jointChainInfoVec[i]; _prevJointChainInfoVec[i].target = targets[i]; @@ -860,7 +869,6 @@ void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, co //virtual const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { // don't call this function, call overlay() instead - qCDebug(animation) << "called evaluate for inverse kinematics"; assert(false); return _relativePoses; } @@ -871,7 +879,6 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars // disable IK on android return underPoses; #endif - qCDebug(animation) << "called overlay for inverse kinematics"; // allows solutionSource to be overridden by an animVar auto solutionSource = animVars.lookup(_solutionSourceVar, (int)_solutionSource); diff --git a/libraries/animation/src/AnimSplineIK.cpp b/libraries/animation/src/AnimSplineIK.cpp index 65d7bfc821..442112e8f0 100644 --- a/libraries/animation/src/AnimSplineIK.cpp +++ b/libraries/animation/src/AnimSplineIK.cpp @@ -14,6 +14,8 @@ #include #include "AnimUtil.h" +static const float JOINT_CHAIN_INTERP_TIME = 0.5f; + AnimSplineIK::AnimSplineIK(const QString& id, float alpha, bool enabled, float interpDuration, const QString& baseJointName, const QString& tipJointName, @@ -39,7 +41,6 @@ AnimSplineIK::~AnimSplineIK() { } const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { - qCDebug(animation) << "evaluating the spline function"; assert(_children.size() == 1); if (_children.size() != 1) { return _poses; @@ -56,7 +57,7 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const IKTarget target; int jointIndex = _skeleton->nameToJointIndex("Head"); if (jointIndex != -1) { - target.setType(animVars.lookup("HeadType", (int)IKTarget::Type::RotationAndPosition)); + target.setType(animVars.lookup("headType", (int)IKTarget::Type::RotationAndPosition)); target.setIndex(jointIndex); AnimPose absPose = _skeleton->getAbsolutePose(jointIndex, _poses); glm::quat rotation = animVars.lookupRigToGeometry("headRotation", absPose.rot()); @@ -74,15 +75,30 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const } } if (_poses.size() > 0) { - AnimChain jointChain; - jointChain.buildFromRelativePoses(_skeleton, _poses, target.getIndex()); + AnimChain jointChain; + _snapshotChain.buildFromRelativePoses(_skeleton, _poses, target.getIndex()); + jointChain.buildFromRelativePoses(_skeleton, _poses, target.getIndex()); - // for each target solve target with spline - + // for each target solve target with spline solveTargetWithSpline(context, target, absolutePoses, false, jointChain); - qCDebug(animation) << "made it past the spline solve code"; + + // qCDebug(animation) << "AnimSpline Joint chain length " << jointChain.size(); + // jointChain.dump(); + jointChain.outputRelativePoses(_poses); + } + + const float FRAMES_PER_SECOND = 30.0f; + _interpAlphaVel = FRAMES_PER_SECOND / _interpDuration; + _alpha = _interpAlphaVel * dt; // we need to blend the old joint chain with the current joint chain, otherwise known as: _snapShotChain + // we blend the chain info then we accumulate it then we assign to relative poses then we return the value. + // make the alpha + // make the prevchain + // interp from the previous chain to the new chain/or underposes if the ik is disabled. + // update the relative poses and then we are done + + /**/ return _poses; } @@ -162,14 +178,12 @@ void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, const IKTar if (glm::dot(halfRot * Vectors::UNIT_Z, basePose.rot() * Vectors::UNIT_Z) < 0.0f) { tipPose.rot() = -tipPose.rot(); } - qCDebug(animation) << "spot 1"; // find or create splineJointInfo for this target const std::vector* splineJointInfoVec = findOrCreateSplineJointInfo(context, target); if (splineJointInfoVec && splineJointInfoVec->size() > 0) { const int baseParentIndex = _skeleton->getParentIndex(baseIndex); AnimPose parentAbsPose = (baseParentIndex >= 0) ? absolutePoses[baseParentIndex] : AnimPose(); - qCDebug(animation) << "spot 2"; // go thru splineJointInfoVec backwards (base to tip) for (int i = (int)splineJointInfoVec->size() - 1; i >= 0; i--) { const SplineJointInfo& splineJointInfo = (*splineJointInfoVec)[i]; diff --git a/libraries/animation/src/AnimStateMachine.cpp b/libraries/animation/src/AnimStateMachine.cpp index 3042ad20f0..2c5d4ad0f3 100644 --- a/libraries/animation/src/AnimStateMachine.cpp +++ b/libraries/animation/src/AnimStateMachine.cpp @@ -22,7 +22,6 @@ AnimStateMachine::~AnimStateMachine() { } const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { - qCDebug(animation) << "evaluating state machine"; float parentDebugAlpha = context.getDebugAlpha(_id); QString desiredStateID = animVars.lookup(_currentStateVar, _currentState->getID()); diff --git a/libraries/animation/src/AnimTwoBoneIK.cpp b/libraries/animation/src/AnimTwoBoneIK.cpp index d970e6b862..6334f6c4fd 100644 --- a/libraries/animation/src/AnimTwoBoneIK.cpp +++ b/libraries/animation/src/AnimTwoBoneIK.cpp @@ -46,8 +46,6 @@ AnimTwoBoneIK::~AnimTwoBoneIK() { const AnimPoseVec& AnimTwoBoneIK::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { - qCDebug(animation) << "evaluating the 2 bone IK"; - assert(_children.size() == 1); if (_children.size() != 1) { return _poses; diff --git a/libraries/fbx/src/FBXSerializer.cpp b/libraries/fbx/src/FBXSerializer.cpp index 68019c2f4b..5e7258f069 100644 --- a/libraries/fbx/src/FBXSerializer.cpp +++ b/libraries/fbx/src/FBXSerializer.cpp @@ -1308,6 +1308,7 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr joint.inverseBindRotation = joint.inverseDefaultRotation; joint.name = fbxModel.name; if (hfmModel.hfmToHifiJointNameMapping.contains(hfmModel.hfmToHifiJointNameMapping.key(joint.name))) { + // qCDebug(modelformat) << "joint name " << joint.name << " hifi name " << hfmModel.hfmToHifiJointNameMapping.key(fbxModel.name); joint.name = hfmModel.hfmToHifiJointNameMapping.key(fbxModel.name); } From 777716235106b6d8c5efa34cec4ad8a841309b47 Mon Sep 17 00:00:00 2001 From: amantley Date: Fri, 18 Jan 2019 15:28:41 -0800 Subject: [PATCH 006/474] got the spine2 rotationa and the head spline working --- .../resources/avatar/avatar-animation.json | 18 ---- interface/src/Application.cpp | 2 +- interface/src/avatar/MySkeletonModel.cpp | 5 +- libraries/animation/src/AnimSplineIK.cpp | 86 +++++++++++++++++-- 4 files changed, 86 insertions(+), 25 deletions(-) diff --git a/interface/resources/avatar/avatar-animation.json b/interface/resources/avatar/avatar-animation.json index 16523afba3..f68d7e61d4 100644 --- a/interface/resources/avatar/avatar-animation.json +++ b/interface/resources/avatar/avatar-animation.json @@ -159,24 +159,6 @@ "poleVectorEnabledVar": "leftHandPoleVectorEnabled", "poleReferenceVectorVar": "leftHandPoleReferenceVector", "poleVectorVar": "leftHandPoleVector" - }, - { - "jointName": "Spine2", - "positionVar": "spine2Position", - "rotationVar": "spine2Rotation", - "typeVar": "spine2Type", - "weightVar": "spine2Weight", - "weight": 2.0, - "flexCoefficients": [ 1.0, 0.5, 0.25 ] - }, - { - "jointName": "Head", - "positionVar": "headPosition", - "rotationVar": "headRotation", - "typeVar": "spine2Type", - "weightVar": "headWeight", - "weight": 4.0, - "flexCoefficients": [ 1, 0.5, 0.25, 0.2, 0.1 ] } ] }, diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 2816dbcb04..2401bf5315 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4679,7 +4679,7 @@ void setupCpuMonitorThread() { void Application::idle() { PerformanceTimer perfTimer("idle"); - + qCDebug(interfaceapp) << "idle called"; // Update the deadlock watchdog updateHeartbeat(); diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 356b365f93..e0362ef548 100644 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -199,7 +199,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { // if hips are not under direct control, estimate the hips position. if (avatarHeadPose.isValid() && !(params.primaryControllerFlags[Rig::PrimaryControllerType_Hips] & (uint8_t)Rig::ControllerFlags::Enabled)) { bool isFlying = (myAvatar->getCharacterController()->getState() == CharacterController::State::Hover || myAvatar->getCharacterController()->computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS); - + // timescale in seconds const float TRANS_HORIZ_TIMESCALE = 0.15f; const float TRANS_VERT_TIMESCALE = 0.01f; // We want the vertical component of the hips to follow quickly to prevent spine squash/stretch. @@ -255,6 +255,9 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { AnimPose newSpinePose(glm::mat4(glm::vec4(w, 0.0f), glm::vec4(u, 0.0f), glm::vec4(v, 0.0f), glm::vec4(glm::vec3(0.0f, 0.0f, 0.0f), 1.0f))); currentSpine2Pose.rot() = safeLerp(currentSpine2Pose.rot(), newSpinePose.rot(), SPINE2_ROTATION_FILTER); params.primaryControllerPoses[Rig::PrimaryControllerType_Spine2] = currentSpine2Pose; + qCDebug(interfaceapp) << "finding the spine 2 azimuth"; + qCDebug(interfaceapp) << currentSpine2Pose; + params.primaryControllerFlags[Rig::PrimaryControllerType_Spine2] = (uint8_t)Rig::ControllerFlags::Enabled | (uint8_t)Rig::ControllerFlags::Estimated; } } diff --git a/libraries/animation/src/AnimSplineIK.cpp b/libraries/animation/src/AnimSplineIK.cpp index 442112e8f0..6f1c15d0f9 100644 --- a/libraries/animation/src/AnimSplineIK.cpp +++ b/libraries/animation/src/AnimSplineIK.cpp @@ -53,7 +53,21 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const AnimPoseVec absolutePoses; absolutePoses.resize(_poses.size()); computeAbsolutePoses(absolutePoses); + AnimPoseVec absolutePoses2; + absolutePoses2.resize(_poses.size()); + // do this later + computeAbsolutePoses(absolutePoses2); + int jointIndex2 = _skeleton->nameToJointIndex("Spine2"); + AnimPose origSpine2PoseAbs = _skeleton->getAbsolutePose(jointIndex2, _poses); + if ((jointIndex2 != -1) && (_poses.size() > 0)) { + AnimPose origSpine2 = _skeleton->getAbsolutePose(jointIndex2, _poses); + AnimPose origSpine1 = _skeleton->getAbsolutePose(_skeleton->nameToJointIndex("Spine1"),_poses); + //origSpine2PoseRel = origSpine1.inverse() * origSpine2; + //qCDebug(animation) << "origSpine2Pose: " << origSpine2Pose.rot(); + qCDebug(animation) << "original relative spine2 " << origSpine2PoseAbs; + } + IKTarget target; int jointIndex = _skeleton->nameToJointIndex("Head"); if (jointIndex != -1) { @@ -64,30 +78,92 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const glm::vec3 translation = animVars.lookupRigToGeometry("headPosition", absPose.trans()); float weight = animVars.lookup("headWeight", "4.0"); + //qCDebug(animation) << "target 1 rotation absolute" << rotation; target.setPose(rotation, translation); target.setWeight(weight); const float* flexCoefficients = new float[5]{ 1.0f, 0.5f, 0.25f, 0.2f, 0.1f }; - target.setFlexCoefficients(4, flexCoefficients); + target.setFlexCoefficients(5, flexCoefficients); // record the index of the hips ik target. if (target.getIndex() == _hipsIndex) { _hipsTargetIndex = 1; } } + + AnimPose origAbsAfterHeadSpline; + AnimPose finalSpine2; + AnimChain jointChain; if (_poses.size() > 0) { - AnimChain jointChain; + _snapshotChain.buildFromRelativePoses(_skeleton, _poses, target.getIndex()); jointChain.buildFromRelativePoses(_skeleton, _poses, target.getIndex()); + // jointChain2.buildFromRelativePoses(_skeleton, _poses, target2.getIndex()); // for each target solve target with spline + //qCDebug(animation) << "before head spline"; + //jointChain.dump(); solveTargetWithSpline(context, target, absolutePoses, false, jointChain); + AnimPose afterSolveSpine2 = jointChain.getAbsolutePoseFromJointIndex(jointIndex2); + AnimPose afterSolveSpine1 = jointChain.getAbsolutePoseFromJointIndex(_skeleton->nameToJointIndex("Spine1")); + AnimPose afterSolveSpine2Rel = afterSolveSpine1.inverse() * afterSolveSpine2; + AnimPose originalSpine2Relative = afterSolveSpine1.inverse() * origSpine2PoseAbs; + glm::quat rotation3 = animVars.lookupRigToGeometry("spine2Rotation", afterSolveSpine2.rot()); + glm::vec3 translation3 = animVars.lookupRigToGeometry("spine2Position", afterSolveSpine2.trans()); + AnimPose targetSpine2(rotation3, afterSolveSpine2.trans()); + finalSpine2 = afterSolveSpine1.inverse() * targetSpine2; - // qCDebug(animation) << "AnimSpline Joint chain length " << jointChain.size(); - // jointChain.dump(); + qCDebug(animation) << "relative spine2 after solve" << afterSolveSpine2Rel; + qCDebug(animation) << "relative spine2 orig" << originalSpine2Relative; + AnimPose latestSpine2Relative(originalSpine2Relative.rot(), afterSolveSpine2Rel.trans()); + //jointChain.setRelativePoseAtJointIndex(jointIndex2, finalSpine2); jointChain.outputRelativePoses(_poses); + //qCDebug(animation) << "after head spline"; + //jointChain.dump(); + + //computeAbsolutePoses(absolutePoses2); + origAbsAfterHeadSpline = _skeleton->getAbsolutePose(jointIndex2, _poses); + // qCDebug(animation) << "Spine2 trans after head spline: " << origAbsAfterHeadSpline.trans(); + + } + + IKTarget target2; + //int jointIndex2 = _skeleton->nameToJointIndex("Spine2"); + if (jointIndex2 != -1) { + target2.setType(animVars.lookup("spine2Type", (int)IKTarget::Type::RotationAndPosition)); + target2.setIndex(jointIndex2); + AnimPose absPose2 = _skeleton->getAbsolutePose(jointIndex2, _poses); + glm::quat rotation2 = animVars.lookupRigToGeometry("spine2Rotation", absPose2.rot()); + glm::vec3 translation2 = animVars.lookupRigToGeometry("spine2Position", absPose2.trans()); + float weight2 = animVars.lookup("spine2Weight", "2.0"); + qCDebug(animation) << "rig to geometry" << rotation2; + + target2.setPose(rotation2, translation2); + target2.setPose(finalSpine2.rot(), finalSpine2.trans()); + target2.setWeight(weight2); + const float* flexCoefficients2 = new float[3]{ 1.0f, 0.5f, 0.25f }; + target2.setFlexCoefficients(3, flexCoefficients2); + + + } + AnimChain jointChain2; + if (_poses.size() > 0) { + + // _snapshotChain.buildFromRelativePoses(_skeleton, _poses, target.getIndex()); + // jointChain.buildFromRelativePoses(_skeleton, _poses, target.getIndex()); + // jointChain2.buildFromRelativePoses(_skeleton, underPoses, target2.getIndex()); + jointChain2.buildFromRelativePoses(_skeleton, _poses, target2.getIndex()); + + // for each target solve target with spline + solveTargetWithSpline(context, target2, absolutePoses2, false, jointChain2); + + //jointChain.outputRelativePoses(_poses); + jointChain2.outputRelativePoses(_poses); + //qCDebug(animation) << "Spine2 spline"; + //jointChain2.dump(); + //qCDebug(animation) << "Spine2Pose trans after spine2 spline: " << _skeleton->getAbsolutePose(jointIndex2, _poses).trans(); } - + const float FRAMES_PER_SECOND = 30.0f; _interpAlphaVel = FRAMES_PER_SECOND / _interpDuration; _alpha = _interpAlphaVel * dt; From 4a6d5e418730f54da54ede95a0adef94ca3dade4 Mon Sep 17 00:00:00 2001 From: amantley Date: Fri, 18 Jan 2019 15:50:29 -0800 Subject: [PATCH 007/474] both head and spine2 spline to working in consecutive order --- libraries/animation/src/AnimSplineIK.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/libraries/animation/src/AnimSplineIK.cpp b/libraries/animation/src/AnimSplineIK.cpp index 6f1c15d0f9..79a1a431c8 100644 --- a/libraries/animation/src/AnimSplineIK.cpp +++ b/libraries/animation/src/AnimSplineIK.cpp @@ -56,7 +56,7 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const AnimPoseVec absolutePoses2; absolutePoses2.resize(_poses.size()); // do this later - computeAbsolutePoses(absolutePoses2); + // computeAbsolutePoses(absolutePoses2); int jointIndex2 = _skeleton->nameToJointIndex("Spine2"); AnimPose origSpine2PoseAbs = _skeleton->getAbsolutePose(jointIndex2, _poses); @@ -93,6 +93,7 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const AnimPose origAbsAfterHeadSpline; AnimPose finalSpine2; AnimChain jointChain; + AnimPose targetSpine2; if (_poses.size() > 0) { _snapshotChain.buildFromRelativePoses(_skeleton, _poses, target.getIndex()); @@ -109,7 +110,7 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const AnimPose originalSpine2Relative = afterSolveSpine1.inverse() * origSpine2PoseAbs; glm::quat rotation3 = animVars.lookupRigToGeometry("spine2Rotation", afterSolveSpine2.rot()); glm::vec3 translation3 = animVars.lookupRigToGeometry("spine2Position", afterSolveSpine2.trans()); - AnimPose targetSpine2(rotation3, afterSolveSpine2.trans()); + targetSpine2 = AnimPose(rotation3, afterSolveSpine2.trans()); finalSpine2 = afterSolveSpine1.inverse() * targetSpine2; qCDebug(animation) << "relative spine2 after solve" << afterSolveSpine2Rel; @@ -120,7 +121,7 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const //qCDebug(animation) << "after head spline"; //jointChain.dump(); - //computeAbsolutePoses(absolutePoses2); + computeAbsolutePoses(absolutePoses2); origAbsAfterHeadSpline = _skeleton->getAbsolutePose(jointIndex2, _poses); // qCDebug(animation) << "Spine2 trans after head spline: " << origAbsAfterHeadSpline.trans(); @@ -137,8 +138,8 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const float weight2 = animVars.lookup("spine2Weight", "2.0"); qCDebug(animation) << "rig to geometry" << rotation2; - target2.setPose(rotation2, translation2); - target2.setPose(finalSpine2.rot(), finalSpine2.trans()); + //target2.setPose(rotation2, translation2); + target2.setPose(targetSpine2.rot(), targetSpine2.trans()); target2.setWeight(weight2); const float* flexCoefficients2 = new float[3]{ 1.0f, 0.5f, 0.25f }; target2.setFlexCoefficients(3, flexCoefficients2); From 7115796fc42db231fb6445a473663800bfab3bae Mon Sep 17 00:00:00 2001 From: amantley Date: Fri, 18 Jan 2019 16:47:55 -0800 Subject: [PATCH 008/474] version with spline from Spine2 to head --- libraries/animation/src/AnimChain.h | 10 ++ libraries/animation/src/AnimSplineIK.cpp | 127 ++++++++++++++--------- libraries/animation/src/AnimSplineIK.h | 13 +-- 3 files changed, 96 insertions(+), 54 deletions(-) diff --git a/libraries/animation/src/AnimChain.h b/libraries/animation/src/AnimChain.h index 2385e0c16a..37d175a334 100644 --- a/libraries/animation/src/AnimChain.h +++ b/libraries/animation/src/AnimChain.h @@ -82,6 +82,16 @@ public: return foundIndex; } + const AnimPose& getRelativePoseAtJointIndex(int jointIndex) const { + for (int i = 0; i < _top; i++) { + if (_chain[i].jointIndex == jointIndex) { + return _chain[i].relativePose; + } + } + return AnimPose::identity; + } + + void buildDirtyAbsolutePoses() { // the relative and absolute pose is the same for the base of the chain. _chain[_top - 1].absolutePose = _chain[_top - 1].relativePose; diff --git a/libraries/animation/src/AnimSplineIK.cpp b/libraries/animation/src/AnimSplineIK.cpp index 79a1a431c8..d55484f7c7 100644 --- a/libraries/animation/src/AnimSplineIK.cpp +++ b/libraries/animation/src/AnimSplineIK.cpp @@ -52,11 +52,11 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const // check to see if we actually need absolute poses. AnimPoseVec absolutePoses; absolutePoses.resize(_poses.size()); - computeAbsolutePoses(absolutePoses); + //computeAbsolutePoses(absolutePoses); AnimPoseVec absolutePoses2; absolutePoses2.resize(_poses.size()); // do this later - // computeAbsolutePoses(absolutePoses2); + computeAbsolutePoses(absolutePoses2); int jointIndex2 = _skeleton->nameToJointIndex("Spine2"); AnimPose origSpine2PoseAbs = _skeleton->getAbsolutePose(jointIndex2, _poses); @@ -67,6 +67,45 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const //qCDebug(animation) << "origSpine2Pose: " << origSpine2Pose.rot(); qCDebug(animation) << "original relative spine2 " << origSpine2PoseAbs; } + + IKTarget target2; + //int jointIndex2 = _skeleton->nameToJointIndex("Spine2"); + if (jointIndex2 != -1) { + target2.setType(animVars.lookup("spine2Type", (int)IKTarget::Type::RotationAndPosition)); + target2.setIndex(jointIndex2); + AnimPose absPose2 = _skeleton->getAbsolutePose(jointIndex2, _poses); + glm::quat rotation2 = animVars.lookupRigToGeometry("spine2Rotation", absPose2.rot()); + glm::vec3 translation2 = animVars.lookupRigToGeometry("spine2Position", absPose2.trans()); + float weight2 = animVars.lookup("spine2Weight", "2.0"); + qCDebug(animation) << "rig to geometry" << rotation2; + + target2.setPose(rotation2, translation2); + //target2.setPose(targetSpine2.rot(), targetSpine2.trans()); + target2.setWeight(weight2); + const float* flexCoefficients2 = new float[3]{ 1.0f, 0.5f, 0.25f }; + target2.setFlexCoefficients(3, flexCoefficients2); + + + } + AnimChain jointChain2; + if (_poses.size() > 0) { + + // _snapshotChain.buildFromRelativePoses(_skeleton, _poses, target.getIndex()); + // jointChain.buildFromRelativePoses(_skeleton, _poses, target.getIndex()); + // jointChain2.buildFromRelativePoses(_skeleton, underPoses, target2.getIndex()); + jointChain2.buildFromRelativePoses(_skeleton, _poses, target2.getIndex()); + + // for each target solve target with spline + solveTargetWithSpline(context, target2, absolutePoses2, false, jointChain2); + + //jointChain.outputRelativePoses(_poses); + jointChain2.outputRelativePoses(_poses); + computeAbsolutePoses(absolutePoses); + //qCDebug(animation) << "Spine2 spline"; + //jointChain2.dump(); + //qCDebug(animation) << "Spine2Pose trans after spine2 spline: " << _skeleton->getAbsolutePose(jointIndex2, _poses).trans(); + + } IKTarget target; int jointIndex = _skeleton->nameToJointIndex("Head"); @@ -81,8 +120,9 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const //qCDebug(animation) << "target 1 rotation absolute" << rotation; target.setPose(rotation, translation); target.setWeight(weight); - const float* flexCoefficients = new float[5]{ 1.0f, 0.5f, 0.25f, 0.2f, 0.1f }; - target.setFlexCoefficients(5, flexCoefficients); + //const float* flexCoefficients = new float[5]{ 1.0f, 0.5f, 0.25f, 0.2f, 0.1f }; + const float* flexCoefficients = new float[2]{ 1.0f, 0.5f }; + target.setFlexCoefficients(2, flexCoefficients); // record the index of the hips ik target. if (target.getIndex() == _hipsIndex) { @@ -118,52 +158,23 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const AnimPose latestSpine2Relative(originalSpine2Relative.rot(), afterSolveSpine2Rel.trans()); //jointChain.setRelativePoseAtJointIndex(jointIndex2, finalSpine2); jointChain.outputRelativePoses(_poses); + _poses[_headIndex] = jointChain.getRelativePoseAtJointIndex(_headIndex); + _poses[_skeleton->nameToJointIndex("Neck")] = jointChain.getRelativePoseAtJointIndex(_skeleton->nameToJointIndex("Neck")); + _poses[_spine2Index] = jointChain.getRelativePoseAtJointIndex(_spine2Index); + + //custom output code for the head. just do the head neck and spine2 + + //qCDebug(animation) << "after head spline"; //jointChain.dump(); - computeAbsolutePoses(absolutePoses2); - origAbsAfterHeadSpline = _skeleton->getAbsolutePose(jointIndex2, _poses); + //computeAbsolutePoses(absolutePoses2); + //origAbsAfterHeadSpline = _skeleton->getAbsolutePose(jointIndex2, _poses); // qCDebug(animation) << "Spine2 trans after head spline: " << origAbsAfterHeadSpline.trans(); } - IKTarget target2; - //int jointIndex2 = _skeleton->nameToJointIndex("Spine2"); - if (jointIndex2 != -1) { - target2.setType(animVars.lookup("spine2Type", (int)IKTarget::Type::RotationAndPosition)); - target2.setIndex(jointIndex2); - AnimPose absPose2 = _skeleton->getAbsolutePose(jointIndex2, _poses); - glm::quat rotation2 = animVars.lookupRigToGeometry("spine2Rotation", absPose2.rot()); - glm::vec3 translation2 = animVars.lookupRigToGeometry("spine2Position", absPose2.trans()); - float weight2 = animVars.lookup("spine2Weight", "2.0"); - qCDebug(animation) << "rig to geometry" << rotation2; - - //target2.setPose(rotation2, translation2); - target2.setPose(targetSpine2.rot(), targetSpine2.trans()); - target2.setWeight(weight2); - const float* flexCoefficients2 = new float[3]{ 1.0f, 0.5f, 0.25f }; - target2.setFlexCoefficients(3, flexCoefficients2); - - - } - AnimChain jointChain2; - if (_poses.size() > 0) { - - // _snapshotChain.buildFromRelativePoses(_skeleton, _poses, target.getIndex()); - // jointChain.buildFromRelativePoses(_skeleton, _poses, target.getIndex()); - // jointChain2.buildFromRelativePoses(_skeleton, underPoses, target2.getIndex()); - jointChain2.buildFromRelativePoses(_skeleton, _poses, target2.getIndex()); - - // for each target solve target with spline - solveTargetWithSpline(context, target2, absolutePoses2, false, jointChain2); - - //jointChain.outputRelativePoses(_poses); - jointChain2.outputRelativePoses(_poses); - //qCDebug(animation) << "Spine2 spline"; - //jointChain2.dump(); - //qCDebug(animation) << "Spine2Pose trans after spine2 spline: " << _skeleton->getAbsolutePose(jointIndex2, _poses).trans(); - - } + const float FRAMES_PER_SECOND = 30.0f; _interpAlphaVel = FRAMES_PER_SECOND / _interpDuration; @@ -218,6 +229,7 @@ void AnimSplineIK::setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) { AnimNode::setSkeletonInternal(skeleton); _headIndex = _skeleton->nameToJointIndex("Head"); _hipsIndex = _skeleton->nameToJointIndex("Hips"); + _spine2Index = _skeleton->nameToJointIndex("Spine2"); lookUpIndices(); } @@ -234,16 +246,24 @@ static CubicHermiteSplineFunctorWithArcLength computeSplineFromTipAndBase(const void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug, AnimChain& chainInfoOut) const { const int baseIndex = _hipsIndex; + const int headBaseIndex = _spine2Index; // build spline from tip to base AnimPose tipPose = AnimPose(glm::vec3(1.0f), target.getRotation(), target.getTranslation()); - AnimPose basePose = absolutePoses[baseIndex]; + AnimPose basePose; + if (target.getIndex() == _headIndex) { + basePose = absolutePoses[headBaseIndex]; + } else { + basePose = absolutePoses[baseIndex]; + } + CubicHermiteSplineFunctorWithArcLength spline; if (target.getIndex() == _headIndex) { // set gain factors so that more curvature occurs near the tip of the spline. const float HIPS_GAIN = 0.5f; const float HEAD_GAIN = 1.0f; - spline = computeSplineFromTipAndBase(tipPose, basePose, HIPS_GAIN, HEAD_GAIN); + //spline = computeSplineFromTipAndBase(tipPose, basePose, HIPS_GAIN, HEAD_GAIN); + spline = computeSplineFromTipAndBase(tipPose, basePose); } else { spline = computeSplineFromTipAndBase(tipPose, basePose); } @@ -347,14 +367,20 @@ void AnimSplineIK::computeAndCacheSplineJointInfosForIKTarget(const AnimContext& // build spline between the default poses. AnimPose tipPose = _skeleton->getAbsoluteDefaultPose(target.getIndex()); - AnimPose basePose = _skeleton->getAbsoluteDefaultPose(_hipsIndex); + AnimPose basePose; + if (target.getIndex() == _headIndex) { + basePose = _skeleton->getAbsoluteDefaultPose(_spine2Index); + } else { + basePose = _skeleton->getAbsoluteDefaultPose(_hipsIndex); + } CubicHermiteSplineFunctorWithArcLength spline; if (target.getIndex() == _headIndex) { // set gain factors so that more curvature occurs near the tip of the spline. const float HIPS_GAIN = 0.5f; const float HEAD_GAIN = 1.0f; - spline = computeSplineFromTipAndBase(tipPose, basePose, HIPS_GAIN, HEAD_GAIN); + // spline = computeSplineFromTipAndBase(tipPose, basePose, HIPS_GAIN, HEAD_GAIN); + spline = computeSplineFromTipAndBase(tipPose, basePose); } else { spline = computeSplineFromTipAndBase(tipPose, basePose); } @@ -367,7 +393,12 @@ void AnimSplineIK::computeAndCacheSplineJointInfosForIKTarget(const AnimContext& glm::vec3 baseToTipNormal = baseToTip / baseToTipLength; int index = target.getIndex(); - int endIndex = _skeleton->getParentIndex(_hipsIndex); + int endIndex; + if (target.getIndex() == _headIndex) { + endIndex = _skeleton->getParentIndex(_spine2Index); + } else { + endIndex = _skeleton->getParentIndex(_hipsIndex); + } while (index != endIndex) { AnimPose defaultPose = _skeleton->getAbsoluteDefaultPose(index); diff --git a/libraries/animation/src/AnimSplineIK.h b/libraries/animation/src/AnimSplineIK.h index 79f012365a..d303f81053 100644 --- a/libraries/animation/src/AnimSplineIK.h +++ b/libraries/animation/src/AnimSplineIK.h @@ -51,12 +51,13 @@ protected: QString _baseJointName; QString _tipJointName; - int _baseParentJointIndex{ -1 }; - int _baseJointIndex{ -1 }; - int _tipJointIndex{ -1 }; - int _headIndex{ -1 }; - int _hipsIndex{ -1 }; - int _hipsTargetIndex{ -1 }; + int _baseParentJointIndex { -1 }; + int _baseJointIndex { -1 }; + int _tipJointIndex { -1 }; + int _headIndex { -1 }; + int _hipsIndex { -1 }; + int _spine2Index { -1 }; + int _hipsTargetIndex { -1 }; QString _alphaVar; // float - (0, 1) 0 means underPoses only, 1 means IK only. QString _enabledVar; // bool From 3a2697fa8c3a69f80416988e88f818989adadaab Mon Sep 17 00:00:00 2001 From: amantley Date: Fri, 18 Jan 2019 17:55:19 -0800 Subject: [PATCH 009/474] working on the two bone ik for the hands --- .../avatar/avatar-animation_twoboneIK.json | 2213 +++++++++++++++++ libraries/animation/src/AnimSplineIK.cpp | 94 +- 2 files changed, 2262 insertions(+), 45 deletions(-) create mode 100644 interface/resources/avatar/avatar-animation_twoboneIK.json diff --git a/interface/resources/avatar/avatar-animation_twoboneIK.json b/interface/resources/avatar/avatar-animation_twoboneIK.json new file mode 100644 index 0000000000..ba84a5d527 --- /dev/null +++ b/interface/resources/avatar/avatar-animation_twoboneIK.json @@ -0,0 +1,2213 @@ +{ + "version": "1.1", + "root": { + "id": "userAnimStateMachine", + "type": "stateMachine", + "data": { + "currentState": "userAnimNone", + "states": [ + { + "id": "userAnimNone", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "userAnimA", "state": "userAnimA" }, + { "var": "userAnimB", "state": "userAnimB" } + ] + }, + { + "id": "userAnimA", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "userAnimNone", "state": "userAnimNone" }, + { "var": "userAnimB", "state": "userAnimB" } + ] + }, + { + "id": "userAnimB", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "userAnimNone", "state": "userAnimNone" }, + { "var": "userAnimA", "state": "userAnimA" } + ] + } + ] + }, + "children": [ + { + "id": "userAnimNone", + "type": "splineIK", + "data": { + "alpha": 1.0, + "enabled": false, + "interpDuration": 15, + "baseJointName": "Hips", + "tipJointName": "Head", + "alphaVar": "splineIKAlpha", + "enabledVar": "splineIKEnabled", + "endEffectorRotationVarVar": "splineIKRotationVar", + "endEffectorPositionVarVar": "splineIKPositionVar" + }, + "children": [ + { + "id": "rightFootPoleVector", + "type": "poleVectorConstraint", + "data": { + "enabled": false, + "referenceVector": [ 0, 0, 1 ], + "baseJointName": "RightUpLeg", + "midJointName": "RightLeg", + "tipJointName": "RightFoot", + "enabledVar": "rightFootPoleVectorEnabled", + "poleVectorVar": "rightFootPoleVector" + }, + "children": [ + { + "id": "rightFootIK", + "type": "twoBoneIK", + "data": { + "alpha": 1.0, + "enabled": false, + "interpDuration": 15, + "baseJointName": "RightUpLeg", + "midJointName": "RightLeg", + "tipJointName": "RightFoot", + "midHingeAxis": [ -1, 0, 0 ], + "alphaVar": "rightFootIKAlpha", + "enabledVar": "rightFootIKEnabled", + "endEffectorRotationVarVar": "rightFootIKRotationVar", + "endEffectorPositionVarVar": "rightFootIKPositionVar" + }, + "children": [ + { + "id": "leftFootPoleVector", + "type": "poleVectorConstraint", + "data": { + "enabled": false, + "referenceVector": [ 0, 0, 1 ], + "baseJointName": "LeftUpLeg", + "midJointName": "LeftLeg", + "tipJointName": "LeftFoot", + "enabledVar": "leftFootPoleVectorEnabled", + "poleVectorVar": "leftFootPoleVector" + }, + "children": [ + { + "id": "leftFootIK", + "type": "twoBoneIK", + "data": { + "alpha": 1.0, + "enabled": false, + "interpDuration": 15, + "baseJointName": "LeftUpLeg", + "midJointName": "LeftLeg", + "tipJointName": "LeftFoot", + "midHingeAxis": [ -1, 0, 0 ], + "alphaVar": "leftFootIKAlpha", + "enabledVar": "leftFootIKEnabled", + "endEffectorRotationVarVar": "leftFootIKRotationVar", + "endEffectorPositionVarVar": "leftFootIKPositionVar" + }, + "children": [ + { + "id": "rightArmPoleVector", + "type": "poleVectorConstraint", + "data": { + "enabled": false, + "referenceVector": [ 0, 0, 1 ], + "baseJointName": "RightArm", + "midJointName": "RightForeArm", + "tipJointName": "RightHand", + "enabledVar": "rightHandPoleVectorEnabled", + "poleVectorVar": "rightHandPoleVector" + }, + "children": [ + { + "id": "rightArmIK", + "type": "twoBoneIK", + "data": { + "alpha": 1.0, + "enabled": false, + "interpDuration": 15, + "baseJointName": "RightArm", + "midJointName": "RightForeArm", + "tipJointName": "RightHand", + "midHingeAxis": [ 1, 0, 0 ], + "alphaVar": "rightArmIKAlpha", + "enabledVar": "rightArmIKEnabled", + "endEffectorRotationVarVar": "rightArmIKRotationVar", + "endEffectorPositionVarVar": "rightArmIKPositionVar" + }, + "children": [ + { + "id": "ikOverlay", + "type": "overlay", + "data": { + "alpha": 1.0, + "alphaVar": "ikOverlayAlpha", + "boneSet": "fullBody" + }, + "children": [ + { + "id": "ik", + "type": "inverseKinematics", + "data": { + "solutionSource": "relaxToUnderPoses", + "solutionSourceVar": "solutionSource", + "targets": [ + { + "jointName": "Hips", + "positionVar": "hipsPosition", + "rotationVar": "hipsRotation", + "typeVar": "hipsType", + "weightVar": "hipsWeight", + "weight": 1.0, + "flexCoefficients": [ 1 ] + }, + { + "jointName": "LeftHand", + "positionVar": "leftHandPosition", + "rotationVar": "leftHandRotation", + "typeVar": "leftHandType", + "weightVar": "leftHandWeight", + "weight": 1.0, + "flexCoefficients": [ 1, 0.5, 0.5, 0.2, 0.01, 0.005, 0.001, 0.0, 0.0 ], + "poleVectorEnabledVar": "leftHandPoleVectorEnabled", + "poleReferenceVectorVar": "leftHandPoleReferenceVector", + "poleVectorVar": "leftHandPoleVector" + } + ] + }, + "children": [] + }, + { + "id": "defaultPoseOverlay", + "type": "overlay", + "data": { + "alpha": 0.0, + "alphaVar": "defaultPoseOverlayAlpha", + "boneSet": "fullBody", + "boneSetVar": "defaultPoseOverlayBoneSet" + }, + "children": [ + { + "id": "defaultPose", + "type": "defaultPose", + "data": { + }, + "children": [] + }, + { + "id": "rightHandOverlay", + "type": "overlay", + "data": { + "alpha": 0.0, + "boneSet": "rightHand", + "alphaVar": "rightHandOverlayAlpha" + }, + "children": [ + { + "id": "rightHandStateMachine", + "type": "stateMachine", + "data": { + "currentState": "rightHandGrasp", + "states": [ + { + "id": "rightHandGrasp", + "interpTarget": 3, + "interpDuration": 3, + "transitions": [ + { + "var": "isRightIndexPoint", + "state": "rightIndexPoint" + }, + { + "var": "isRightThumbRaise", + "state": "rightThumbRaise" + }, + { + "var": "isRightIndexPointAndThumbRaise", + "state": "rightIndexPointAndThumbRaise" + } + ] + }, + { + "id": "rightIndexPoint", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { + "var": "isRightHandGrasp", + "state": "rightHandGrasp" + }, + { + "var": "isRightThumbRaise", + "state": "rightThumbRaise" + }, + { + "var": "isRightIndexPointAndThumbRaise", + "state": "rightIndexPointAndThumbRaise" + } + ] + }, + { + "id": "rightThumbRaise", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { + "var": "isRightHandGrasp", + "state": "rightHandGrasp" + }, + { + "var": "isRightIndexPoint", + "state": "rightIndexPoint" + }, + { + "var": "isRightIndexPointAndThumbRaise", + "state": "rightIndexPointAndThumbRaise" + } + ] + }, + { + "id": "rightIndexPointAndThumbRaise", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { + "var": "isRightHandGrasp", + "state": "rightHandGrasp" + }, + { + "var": "isRightIndexPoint", + "state": "rightIndexPoint" + }, + { + "var": "isRightThumbRaise", + "state": "rightThumbRaise" + } + ] + } + ] + }, + "children": [ + { + "id": "rightHandGrasp", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "rightHandGraspAlpha" + }, + "children": [ + { + "id": "rightHandGraspOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/hydra_pose_open_right.fbx", + "startFrame": 0.0, + "endFrame": 0.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "rightHandGraspClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/hydra_pose_closed_right.fbx", + "startFrame": 0.0, + "endFrame": 0.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "rightIndexPoint", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "rightHandGraspAlpha" + }, + "children": [ + { + "id": "rightIndexPointOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_point_open_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "rightIndexPointClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_point_closed_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "rightThumbRaise", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "rightHandGraspAlpha" + }, + "children": [ + { + "id": "rightThumbRaiseOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_open_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "rightThumbRaiseClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_closed_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "rightIndexPointAndThumbRaise", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "rightHandGraspAlpha" + }, + "children": [ + { + "id": "rightIndexPointAndThumbRaiseOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_point_open_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "rightIndexPointAndThumbRaiseClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_point_closed_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + } + ] + }, + { + "id": "leftHandOverlay", + "type": "overlay", + "data": { + "alpha": 0.0, + "boneSet": "leftHand", + "alphaVar": "leftHandOverlayAlpha" + }, + "children": [ + { + "id": "leftHandStateMachine", + "type": "stateMachine", + "data": { + "currentState": "leftHandGrasp", + "states": [ + { + "id": "leftHandGrasp", + "interpTarget": 3, + "interpDuration": 3, + "transitions": [ + { + "var": "isLeftIndexPoint", + "state": "leftIndexPoint" + }, + { + "var": "isLeftThumbRaise", + "state": "leftThumbRaise" + }, + { + "var": "isLeftIndexPointAndThumbRaise", + "state": "leftIndexPointAndThumbRaise" + } + ] + }, + { + "id": "leftIndexPoint", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { + "var": "isLeftHandGrasp", + "state": "leftHandGrasp" + }, + { + "var": "isLeftThumbRaise", + "state": "leftThumbRaise" + }, + { + "var": "isLeftIndexPointAndThumbRaise", + "state": "leftIndexPointAndThumbRaise" + } + ] + }, + { + "id": "leftThumbRaise", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { + "var": "isLeftHandGrasp", + "state": "leftHandGrasp" + }, + { + "var": "isLeftIndexPoint", + "state": "leftIndexPoint" + }, + { + "var": "isLeftIndexPointAndThumbRaise", + "state": "leftIndexPointAndThumbRaise" + } + ] + }, + { + "id": "leftIndexPointAndThumbRaise", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { + "var": "isLeftHandGrasp", + "state": "leftHandGrasp" + }, + { + "var": "isLeftIndexPoint", + "state": "leftIndexPoint" + }, + { + "var": "isLeftThumbRaise", + "state": "leftThumbRaise" + } + ] + } + ] + }, + "children": [ + { + "id": "leftHandGrasp", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "leftHandGraspAlpha" + }, + "children": [ + { + "id": "leftHandGraspOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/hydra_pose_open_left.fbx", + "startFrame": 0.0, + "endFrame": 0.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "leftHandGraspClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/hydra_pose_closed_left.fbx", + "startFrame": 10.0, + "endFrame": 10.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "leftIndexPoint", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "leftHandGraspAlpha" + }, + "children": [ + { + "id": "leftIndexPointOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_point_open_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "leftIndexPointClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_point_closed_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "leftThumbRaise", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "leftHandGraspAlpha" + }, + "children": [ + { + "id": "leftThumbRaiseOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_open_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "leftThumbRaiseClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_closed_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "leftIndexPointAndThumbRaise", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "leftHandGraspAlpha" + }, + "children": [ + { + "id": "leftIndexPointAndThumbRaiseOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_point_open_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "leftIndexPointAndThumbRaiseClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_point_closed_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + } + ] + }, + { + "id": "mainStateMachine", + "type": "stateMachine", + "data": { + "outputJoints": [ "LeftFoot", "RightFoot" ], + "currentState": "idle", + "states": [ + { + "id": "idle", + "interpTarget": 20, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "idleToWalkFwd", + "interpTarget": 12, + "interpDuration": 8, + "transitions": [ + { + "var": "idleToWalkFwdOnDone", + "state": "WALKFWD" + }, + { + "var": "isNotMoving", + "state": "idle" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "idleSettle", + "interpTarget": 15, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "idleSettleOnDone", + "state": "idle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + } + ] + }, + { + "id": "WALKFWD", + "interpTarget": 35, + "interpDuration": 10, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotMoving", + "state": "idleSettle" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "WALKBWD", + "interpTarget": 35, + "interpDuration": 10, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotMoving", + "state": "idleSettle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "STRAFERIGHT", + "interpTarget": 25, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotMoving", + "state": "idleSettle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "STRAFELEFT", + "interpTarget": 25, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotMoving", + "state": "idleSettle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "turnRight", + "interpTarget": 6, + "interpDuration": 8, + "transitions": [ + { + "var": "isNotTurning", + "state": "idle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "turnLeft", + "interpTarget": 6, + "interpDuration": 8, + "transitions": [ + { + "var": "isNotTurning", + "state": "idle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "strafeRightHmd", + "interpTarget": 5, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotMoving", + "state": "idleSettle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + } + ] + }, + { + "id": "strafeLeftHmd", + "interpTarget": 5, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotMoving", + "state": "idleSettle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + } + ] + }, + { + "id": "fly", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { + "var": "isNotFlying", + "state": "idleSettle" + } + ] + }, + { + "id": "takeoffStand", + "interpTarget": 2, + "interpDuration": 2, + "transitions": [ + { + "var": "isNotTakeoff", + "state": "inAirStand" + } + ] + }, + { + "id": "TAKEOFFRUN", + "interpTarget": 2, + "interpDuration": 2, + "transitions": [ + { + "var": "isNotTakeoff", + "state": "INAIRRUN" + } + ] + }, + { + "id": "inAirStand", + "interpTarget": 3, + "interpDuration": 3, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotInAir", + "state": "landStandImpact" + } + ] + }, + { + "id": "INAIRRUN", + "interpTarget": 3, + "interpDuration": 3, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotInAir", + "state": "WALKFWD" + } + ] + }, + { + "id": "landStandImpact", + "interpTarget": 1, + "interpDuration": 1, + "transitions": [ + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "landStandImpactOnDone", + "state": "landStand" + } + ] + }, + { + "id": "landStand", + "interpTarget": 1, + "interpDuration": 1, + "transitions": [ + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "landStandOnDone", + "state": "idle" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "LANDRUN", + "interpTarget": 2, + "interpDuration": 2, + "transitions": [ + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "landRunOnDone", + "state": "WALKFWD" + } + ] + } + ] + }, + "children": [ + { + "id": "idle", + "type": "stateMachine", + "data": { + "currentState": "idleStand", + "states": [ + { + "id": "idleStand", + "interpTarget": 6, + "interpDuration": 10, + "transitions": [ + { + "var": "isTalking", + "state": "idleTalk" + } + ] + }, + { + "id": "idleTalk", + "interpTarget": 6, + "interpDuration": 10, + "transitions": [ + { + "var": "notIsTalking", + "state": "idleStand" + } + ] + } + ] + }, + "children": [ + { + "id": "idleStand", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle.fbx", + "startFrame": 0.0, + "endFrame": 300.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "idleTalk", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/talk.fbx", + "startFrame": 0.0, + "endFrame": 800.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "WALKFWD", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [ 0.5, 1.8, 2.3, 3.2, 4.5 ], + "alphaVar": "moveForwardAlpha", + "desiredSpeedVar": "moveForwardSpeed" + }, + "children": [ + { + "id": "walkFwdShort_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_short_fwd.fbx", + "startFrame": 0.0, + "endFrame": 39.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkFwdNormal_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_fwd.fbx", + "startFrame": 0.0, + "endFrame": 30.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkFwdFast_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_fwd_fast.fbx", + "startFrame": 0.0, + "endFrame": 25.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkFwdJog_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jog_fwd.fbx", + "startFrame": 0.0, + "endFrame": 25.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkFwdRun_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/run_fwd.fbx", + "startFrame": 0.0, + "endFrame": 21.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "idleToWalkFwd", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle_to_walk.fbx", + "startFrame": 1.0, + "endFrame": 13.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "idleSettle", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/settle_to_idle.fbx", + "startFrame": 1.0, + "endFrame": 59.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "WALKBWD", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [ 0.6, 1.6, 2.3, 3.1 ], + "alphaVar": "moveBackwardAlpha", + "desiredSpeedVar": "moveBackwardSpeed" + }, + "children": [ + { + "id": "walkBwdShort_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_short_bwd.fbx", + "startFrame": 0.0, + "endFrame": 38.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkBwdFast_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_bwd_fast.fbx", + "startFrame": 0.0, + "endFrame": 27.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "jogBwd_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jog_bwd.fbx", + "startFrame": 0.0, + "endFrame": 24.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "runBwd_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/run_bwd.fbx", + "startFrame": 0.0, + "endFrame": 16.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "turnLeft", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/turn_left.fbx", + "startFrame": 0.0, + "endFrame": 32.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "turnRight", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/turn_left.fbx", + "startFrame": 0.0, + "endFrame": 32.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "STRAFELEFT", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [ 0.1, 0.5, 1.0, 2.6, 3.0 ], + "alphaVar": "moveLateralAlpha", + "desiredSpeedVar": "moveLateralSpeed" + }, + "children": [ + { + "id": "strafeLeftShortStep_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_short_left.fbx", + "startFrame": 0.0, + "endFrame": 29.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftStep_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left.fbx", + "startFrame": 0.0, + "endFrame": 20.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftWalk_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_left.fbx", + "startFrame": 0.0, + "endFrame": 35.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftWalkFast_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_left_fast.fbx", + "startFrame": 0.0, + "endFrame": 21.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftJog_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jog_left.fbx", + "startFrame": 0.0, + "endFrame": 24.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "STRAFERIGHT", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [ 0.1, 0.5, 1.0, 2.6, 3.0 ], + "alphaVar": "moveLateralAlpha", + "desiredSpeedVar": "moveLateralSpeed" + }, + "children": [ + { + "id": "strafeRightShortStep_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_short_left.fbx", + "startFrame": 0.0, + "endFrame": 29.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "strafeRightStep_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left.fbx", + "startFrame": 0.0, + "endFrame": 20.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "strafeRightWalk_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_left.fbx", + "startFrame": 0.0, + "endFrame": 35.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "strafeRightFast_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_left_fast.fbx", + "startFrame": 0.0, + "endFrame": 21.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "strafeRightJog_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jog_left.fbx", + "startFrame": 0.0, + "endFrame": 24.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + } + ] + }, + { + "id": "strafeLeftHmd", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [ 0, 0.5, 2.5 ], + "alphaVar": "moveLateralAlpha", + "desiredSpeedVar": "moveLateralSpeed" + }, + "children": [ + { + "id": "stepLeftShort_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_short_left.fbx", + "startFrame": 0.0, + "endFrame": 29.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "stepLeft_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left.fbx", + "startFrame": 0.0, + "endFrame": 20.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftAnim_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left_fast.fbx", + "startFrame": 0.0, + "endFrame": 16.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "strafeRightHmd", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [ 0, 0.5, 2.5 ], + "alphaVar": "moveLateralAlpha", + "desiredSpeedVar": "moveLateralSpeed" + }, + "children": [ + { + "id": "stepRightShort_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_short_left.fbx", + "startFrame": 0.0, + "endFrame": 29.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "stepRight_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left.fbx", + "startFrame": 0.0, + "endFrame": 20.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "strafeRightAnim_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left_fast.fbx", + "startFrame": 0.0, + "endFrame": 16.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + } + ] + }, + { + "id": "fly", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/fly.fbx", + "startFrame": 1.0, + "endFrame": 80.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "takeoffStand", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_launch.fbx", + "startFrame": 2.0, + "endFrame": 16.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "TAKEOFFRUN", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "startFrame": 4.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirStand", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "inAirAlpha" + }, + "children": [ + { + "id": "inAirStandPreApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_apex.fbx", + "startFrame": 0.0, + "endFrame": 0.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirStandApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_apex.fbx", + "startFrame": 1.0, + "endFrame": 1.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirStandPostApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_apex.fbx", + "startFrame": 2.0, + "endFrame": 2.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + } + ] + }, + { + "id": "INAIRRUN", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "inAirAlpha" + }, + "children": [ + { + "id": "inAirRunPreApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "startFrame": 16.0, + "endFrame": 16.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirRunApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "startFrame": 22.0, + "endFrame": 22.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirRunPostApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "startFrame": 33.0, + "endFrame": 33.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + } + ] + }, + { + "id": "landStandImpact", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_land_settle.fbx", + "startFrame": 1.0, + "endFrame": 6.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "landStand", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_land_settle.fbx", + "startFrame": 6.0, + "endFrame": 68.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "LANDRUN", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "startFrame": 29.0, + "endFrame": 40.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "id": "userAnimA", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle.fbx", + "startFrame": 0.0, + "endFrame": 90.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "userAnimB", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle.fbx", + "startFrame": 0.0, + "endFrame": 90.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + } +} diff --git a/libraries/animation/src/AnimSplineIK.cpp b/libraries/animation/src/AnimSplineIK.cpp index d55484f7c7..fe54aa5fe7 100644 --- a/libraries/animation/src/AnimSplineIK.cpp +++ b/libraries/animation/src/AnimSplineIK.cpp @@ -52,11 +52,11 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const // check to see if we actually need absolute poses. AnimPoseVec absolutePoses; absolutePoses.resize(_poses.size()); - //computeAbsolutePoses(absolutePoses); + computeAbsolutePoses(absolutePoses); AnimPoseVec absolutePoses2; absolutePoses2.resize(_poses.size()); // do this later - computeAbsolutePoses(absolutePoses2); + //computeAbsolutePoses(absolutePoses2); int jointIndex2 = _skeleton->nameToJointIndex("Spine2"); AnimPose origSpine2PoseAbs = _skeleton->getAbsolutePose(jointIndex2, _poses); @@ -68,44 +68,7 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const qCDebug(animation) << "original relative spine2 " << origSpine2PoseAbs; } - IKTarget target2; - //int jointIndex2 = _skeleton->nameToJointIndex("Spine2"); - if (jointIndex2 != -1) { - target2.setType(animVars.lookup("spine2Type", (int)IKTarget::Type::RotationAndPosition)); - target2.setIndex(jointIndex2); - AnimPose absPose2 = _skeleton->getAbsolutePose(jointIndex2, _poses); - glm::quat rotation2 = animVars.lookupRigToGeometry("spine2Rotation", absPose2.rot()); - glm::vec3 translation2 = animVars.lookupRigToGeometry("spine2Position", absPose2.trans()); - float weight2 = animVars.lookup("spine2Weight", "2.0"); - qCDebug(animation) << "rig to geometry" << rotation2; - - target2.setPose(rotation2, translation2); - //target2.setPose(targetSpine2.rot(), targetSpine2.trans()); - target2.setWeight(weight2); - const float* flexCoefficients2 = new float[3]{ 1.0f, 0.5f, 0.25f }; - target2.setFlexCoefficients(3, flexCoefficients2); - - - } - AnimChain jointChain2; - if (_poses.size() > 0) { - - // _snapshotChain.buildFromRelativePoses(_skeleton, _poses, target.getIndex()); - // jointChain.buildFromRelativePoses(_skeleton, _poses, target.getIndex()); - // jointChain2.buildFromRelativePoses(_skeleton, underPoses, target2.getIndex()); - jointChain2.buildFromRelativePoses(_skeleton, _poses, target2.getIndex()); - - // for each target solve target with spline - solveTargetWithSpline(context, target2, absolutePoses2, false, jointChain2); - - //jointChain.outputRelativePoses(_poses); - jointChain2.outputRelativePoses(_poses); - computeAbsolutePoses(absolutePoses); - //qCDebug(animation) << "Spine2 spline"; - //jointChain2.dump(); - //qCDebug(animation) << "Spine2Pose trans after spine2 spline: " << _skeleton->getAbsolutePose(jointIndex2, _poses).trans(); - - } + IKTarget target; int jointIndex = _skeleton->nameToJointIndex("Head"); @@ -158,9 +121,9 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const AnimPose latestSpine2Relative(originalSpine2Relative.rot(), afterSolveSpine2Rel.trans()); //jointChain.setRelativePoseAtJointIndex(jointIndex2, finalSpine2); jointChain.outputRelativePoses(_poses); - _poses[_headIndex] = jointChain.getRelativePoseAtJointIndex(_headIndex); - _poses[_skeleton->nameToJointIndex("Neck")] = jointChain.getRelativePoseAtJointIndex(_skeleton->nameToJointIndex("Neck")); - _poses[_spine2Index] = jointChain.getRelativePoseAtJointIndex(_spine2Index); + //_poses[_headIndex] = jointChain.getRelativePoseAtJointIndex(_headIndex); + //_poses[_skeleton->nameToJointIndex("Neck")] = jointChain.getRelativePoseAtJointIndex(_skeleton->nameToJointIndex("Neck")); + //_poses[_spine2Index] = jointChain.getRelativePoseAtJointIndex(_spine2Index); //custom output code for the head. just do the head neck and spine2 @@ -168,11 +131,50 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const //qCDebug(animation) << "after head spline"; //jointChain.dump(); - //computeAbsolutePoses(absolutePoses2); + computeAbsolutePoses(absolutePoses2); //origAbsAfterHeadSpline = _skeleton->getAbsolutePose(jointIndex2, _poses); // qCDebug(animation) << "Spine2 trans after head spline: " << origAbsAfterHeadSpline.trans(); } + + IKTarget target2; + //int jointIndex2 = _skeleton->nameToJointIndex("Spine2"); + if (jointIndex2 != -1) { + target2.setType(animVars.lookup("spine2Type", (int)IKTarget::Type::RotationAndPosition)); + target2.setIndex(jointIndex2); + AnimPose absPose2 = _skeleton->getAbsolutePose(jointIndex2, _poses); + glm::quat rotation2 = animVars.lookupRigToGeometry("spine2Rotation", absPose2.rot()); + glm::vec3 translation2 = animVars.lookupRigToGeometry("spine2Position", absPose2.trans()); + float weight2 = animVars.lookup("spine2Weight", "2.0"); + qCDebug(animation) << "rig to geometry" << rotation2; + + //target2.setPose(rotation2, translation2); + target2.setPose(targetSpine2.rot(), targetSpine2.trans()); + target2.setWeight(weight2); + const float* flexCoefficients2 = new float[3]{ 1.0f, 0.5f, 0.25f }; + target2.setFlexCoefficients(3, flexCoefficients2); + + + } + AnimChain jointChain2; + if (_poses.size() > 0) { + + // _snapshotChain.buildFromRelativePoses(_skeleton, _poses, target.getIndex()); + // jointChain.buildFromRelativePoses(_skeleton, _poses, target.getIndex()); + // jointChain2.buildFromRelativePoses(_skeleton, underPoses, target2.getIndex()); + jointChain2.buildFromRelativePoses(_skeleton, _poses, target2.getIndex()); + + // for each target solve target with spline + solveTargetWithSpline(context, target2, absolutePoses2, false, jointChain2); + + //jointChain.outputRelativePoses(_poses); + jointChain2.outputRelativePoses(_poses); + //computeAbsolutePoses(absolutePoses); + //qCDebug(animation) << "Spine2 spline"; + //jointChain2.dump(); + //qCDebug(animation) << "Spine2Pose trans after spine2 spline: " << _skeleton->getAbsolutePose(jointIndex2, _poses).trans(); + + } @@ -246,7 +248,7 @@ static CubicHermiteSplineFunctorWithArcLength computeSplineFromTipAndBase(const void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug, AnimChain& chainInfoOut) const { const int baseIndex = _hipsIndex; - const int headBaseIndex = _spine2Index; + const int headBaseIndex = _spine2Index; // build spline from tip to base AnimPose tipPose = AnimPose(glm::vec3(1.0f), target.getRotation(), target.getTranslation()); @@ -369,6 +371,7 @@ void AnimSplineIK::computeAndCacheSplineJointInfosForIKTarget(const AnimContext& AnimPose tipPose = _skeleton->getAbsoluteDefaultPose(target.getIndex()); AnimPose basePose; if (target.getIndex() == _headIndex) { + //basePose = _skeleton->getAbsoluteDefaultPose(_hipsIndex); basePose = _skeleton->getAbsoluteDefaultPose(_spine2Index); } else { basePose = _skeleton->getAbsoluteDefaultPose(_hipsIndex); @@ -396,6 +399,7 @@ void AnimSplineIK::computeAndCacheSplineJointInfosForIKTarget(const AnimContext& int endIndex; if (target.getIndex() == _headIndex) { endIndex = _skeleton->getParentIndex(_spine2Index); + // endIndex = _skeleton->getParentIndex(_hipsIndex); } else { endIndex = _skeleton->getParentIndex(_hipsIndex); } From 6680ca53ad165e9ecdabd236ace78e66f2da73bb Mon Sep 17 00:00:00 2001 From: amantley Date: Sun, 20 Jan 2019 08:56:39 -0800 Subject: [PATCH 010/474] added updateHands2 which deals with two bone hands animvars --- .../avatar/avatar-animation_twoboneIK.json | 2 +- libraries/animation/src/AnimTwoBoneIK.cpp | 4 +- libraries/animation/src/Rig.cpp | 115 ++++++++++++++++++ libraries/animation/src/Rig.h | 9 ++ 4 files changed, 127 insertions(+), 3 deletions(-) diff --git a/interface/resources/avatar/avatar-animation_twoboneIK.json b/interface/resources/avatar/avatar-animation_twoboneIK.json index ba84a5d527..8a18b4859a 100644 --- a/interface/resources/avatar/avatar-animation_twoboneIK.json +++ b/interface/resources/avatar/avatar-animation_twoboneIK.json @@ -134,7 +134,7 @@ "baseJointName": "RightArm", "midJointName": "RightForeArm", "tipJointName": "RightHand", - "midHingeAxis": [ 1, 0, 0 ], + "midHingeAxis": [ 0, 0, -1 ], "alphaVar": "rightArmIKAlpha", "enabledVar": "rightArmIKEnabled", "endEffectorRotationVarVar": "rightArmIKRotationVar", diff --git a/libraries/animation/src/AnimTwoBoneIK.cpp b/libraries/animation/src/AnimTwoBoneIK.cpp index 6334f6c4fd..5df98df969 100644 --- a/libraries/animation/src/AnimTwoBoneIK.cpp +++ b/libraries/animation/src/AnimTwoBoneIK.cpp @@ -90,8 +90,8 @@ const AnimPoseVec& AnimTwoBoneIK::evaluate(const AnimVariantMap& animVars, const } _enabled = enabled; - // don't build chains or do IK if we are disbled & not interping. - if (_interpType == InterpType::None && !enabled) { + // don't build chains or do IK if we are disabled & not interping. + if (_interpType == InterpType::None){// && !enabled) { _poses = underPoses; return _poses; } diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 44fdd8797f..e1db2fef8f 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -75,6 +75,20 @@ static const QString RIGHT_FOOT_IK_ROTATION_VAR("rightFootIKRotationVar"); static const QString MAIN_STATE_MACHINE_RIGHT_FOOT_ROTATION("mainStateMachineRightFootRotation"); static const QString MAIN_STATE_MACHINE_RIGHT_FOOT_POSITION("mainStateMachineRightFootPosition"); +static const QString LEFT_HAND_POSITION("leftHandPosition"); +static const QString LEFT_HAND_ROTATION("leftHandRotation"); +static const QString LEFT_HAND_IK_POSITION_VAR("leftHandIKPositionVar"); +static const QString LEFT_HAND_IK_ROTATION_VAR("leftHandIKRotationVar"); +static const QString MAIN_STATE_MACHINE_LEFT_HAND_POSITION("mainStateMachineLeftHandPosition"); +static const QString MAIN_STATE_MACHINE_LEFT_HAND_ROTATION("mainStateMachineLeftHandRotation"); + +static const QString RIGHT_HAND_POSITION("rightHandPosition"); +static const QString RIGHT_HAND_ROTATION("rightHandRotation"); +static const QString RIGHT_HAND_IK_POSITION_VAR("rightHandIKPositionVar"); +static const QString RIGHT_HAND_IK_ROTATION_VAR("rightHandIKRotationVar"); +static const QString MAIN_STATE_MACHINE_RIGHT_HAND_ROTATION("mainStateMachineRightHandRotation"); +static const QString MAIN_STATE_MACHINE_RIGHT_HAND_POSITION("mainStateMachineRightHandPosition"); + Rig::Rig() { // Ensure thread-safe access to the rigRegistry. @@ -1498,6 +1512,103 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab } } +void Rig::updateHands2(bool leftHandEnabled, bool rightHandEnabled, bool headEnabled, + const AnimPose& leftHandPose, const AnimPose& rightHandPose, + const glm::mat4& rigToSensorMatrix, const glm::mat4& sensorToRigMatrix) { + + int hipsIndex = indexOfJoint("Hips"); + const float ELBOW_POLE_VECTOR_BLEND_FACTOR = 0.85f; + + if (headEnabled) { + // always do IK if head is enabled + _animVars.set("leftHandIKEnabled", true); + _animVars.set("rightHandIKEnabled", true); + } else { + // only do IK if we have a valid foot. + _animVars.set("leftHandIKEnabled", leftHandEnabled); + _animVars.set("rightHandIKEnabled", rightHandEnabled); + } + + if (leftHandEnabled) { + + _animVars.set(LEFT_HAND_POSITION, leftHandPose.trans()); + _animVars.set(LEFT_HAND_ROTATION, leftHandPose.rot()); + + // We want to drive the IK directly from the trackers. + _animVars.set(LEFT_HAND_IK_POSITION_VAR, LEFT_HAND_POSITION); + _animVars.set(LEFT_HAND_IK_ROTATION_VAR, LEFT_HAND_ROTATION); + + int handJointIndex = _animSkeleton->nameToJointIndex("LeftHand"); + int elbowJointIndex = _animSkeleton->nameToJointIndex("LeftForeArm"); + int shoulderJointIndex = _animSkeleton->nameToJointIndex("LeftArm"); + int oppositeArmJointIndex = _animSkeleton->nameToJointIndex("LeftArm"); + glm::vec3 poleVector; + bool usePoleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, shoulderJointIndex, oppositeArmJointIndex, poleVector); + glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector); + + // smooth toward desired pole vector from previous pole vector... to reduce jitter, but in sensor space. + if (!_prevLeftHandPoleVectorValid) { + _prevLeftHandPoleVectorValid = true; + _prevLeftHandPoleVector = sensorPoleVector; + } + glm::quat deltaRot = rotationBetween(_prevLeftHandPoleVector, sensorPoleVector); + glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, ELBOW_POLE_VECTOR_BLEND_FACTOR); + _prevLeftHandPoleVector = smoothDeltaRot * _prevLeftHandPoleVector; + + _animVars.set("leftHandPoleVectorEnabled", true); + _animVars.set("leftHandPoleVector", transformVectorFast(sensorToRigMatrix, _prevLeftFootPoleVector)); + } else { + // We want to drive the IK from the underlying animation. + // This gives us the ability to squat while in the HMD, without the feet from dipping under the floor. + _animVars.set(LEFT_HAND_IK_POSITION_VAR, MAIN_STATE_MACHINE_LEFT_HAND_POSITION); + _animVars.set(LEFT_HAND_IK_ROTATION_VAR, MAIN_STATE_MACHINE_LEFT_HAND_ROTATION); + + // We want to match the animated knee pose as close as possible, so don't use poleVectors + _animVars.set("leftHandPoleVectorEnabled", false); + _prevLeftHandPoleVectorValid = false; + } + + if (rightHandEnabled) { + + _animVars.set(RIGHT_HAND_POSITION, rightHandPose.trans()); + _animVars.set(RIGHT_HAND_ROTATION, rightHandPose.rot()); + + // We want to drive the IK directly from the trackers. + _animVars.set(RIGHT_HAND_IK_POSITION_VAR, RIGHT_HAND_POSITION); + _animVars.set(RIGHT_HAND_IK_ROTATION_VAR, RIGHT_HAND_ROTATION); + + int handJointIndex = _animSkeleton->nameToJointIndex("RightHand"); + int elbowJointIndex = _animSkeleton->nameToJointIndex("RightForeArm"); + int shoulderJointIndex = _animSkeleton->nameToJointIndex("RightArm"); + int oppositeArmJointIndex = _animSkeleton->nameToJointIndex("RightArm"); + glm::vec3 poleVector; + bool usePoleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, shoulderJointIndex, oppositeArmJointIndex, poleVector); + glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector); + + // smooth toward desired pole vector from previous pole vector... to reduce jitter, but in sensor space. + if (!_prevRightHandPoleVectorValid) { + _prevRightHandPoleVectorValid = true; + _prevRightHandPoleVector = sensorPoleVector; + } + glm::quat deltaRot = rotationBetween(_prevRightHandPoleVector, sensorPoleVector); + glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, ELBOW_POLE_VECTOR_BLEND_FACTOR); + _prevRightHandPoleVector = smoothDeltaRot * _prevRightHandPoleVector; + + _animVars.set("rightHandPoleVectorEnabled", true); + _animVars.set("rightHandPoleVector", transformVectorFast(sensorToRigMatrix, _prevRightFootPoleVector)); + } else { + // We want to drive the IK from the underlying animation. + // This gives us the ability to squat while in the HMD, without the feet from dipping under the floor. + _animVars.set(RIGHT_HAND_IK_POSITION_VAR, MAIN_STATE_MACHINE_RIGHT_HAND_POSITION); + _animVars.set(RIGHT_HAND_IK_ROTATION_VAR, MAIN_STATE_MACHINE_RIGHT_HAND_ROTATION); + + // We want to match the animated knee pose as close as possible, so don't use poleVectors + _animVars.set("rightHandPoleVectorEnabled", false); + _prevRightHandPoleVectorValid = false; + } + +} + void Rig::updateFeet(bool leftFootEnabled, bool rightFootEnabled, bool headEnabled, const AnimPose& leftFootPose, const AnimPose& rightFootPose, const glm::mat4& rigToSensorMatrix, const glm::mat4& sensorToRigMatrix) { @@ -1750,6 +1861,10 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo params.hipsShapeInfo, params.spineShapeInfo, params.spine1ShapeInfo, params.spine2ShapeInfo, params.rigToSensorMatrix, sensorToRigMatrix); + updateHands2(leftHandEnabled, rightHandEnabled, _headEnabled, + params.primaryControllerPoses[PrimaryControllerType_LeftHand], params.primaryControllerPoses[PrimaryControllerType_RightHand], + params.rigToSensorMatrix, sensorToRigMatrix); + updateFeet(leftFootEnabled, rightFootEnabled, _headEnabled, params.primaryControllerPoses[PrimaryControllerType_LeftFoot], params.primaryControllerPoses[PrimaryControllerType_RightFoot], params.rigToSensorMatrix, sensorToRigMatrix); diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 41c25a3c3e..e6effe9e53 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -250,6 +250,9 @@ protected: const HFMJointShapeInfo& hipsShapeInfo, const HFMJointShapeInfo& spineShapeInfo, const HFMJointShapeInfo& spine1ShapeInfo, const HFMJointShapeInfo& spine2ShapeInfo, const glm::mat4& rigToSensorMatrix, const glm::mat4& sensorToRigMatrix); + void updateHands2(bool leftHandEnabled, bool rightHandEnabled, bool headEnabled, + const AnimPose& leftHandPose, const AnimPose& rightHandPose, + const glm::mat4& rigToSensorMatrix, const glm::mat4& sensorToRigMatrix); void updateFeet(bool leftFootEnabled, bool rightFootEnabled, bool headEnabled, const AnimPose& leftFootPose, const AnimPose& rightFootPose, const glm::mat4& rigToSensorMatrix, const glm::mat4& sensorToRigMatrix); @@ -414,6 +417,12 @@ protected: glm::vec3 _prevLeftFootPoleVector { Vectors::UNIT_Z }; // sensor space bool _prevLeftFootPoleVectorValid { false }; + glm::vec3 _prevRightHandPoleVector { Vectors::UNIT_Z }; // sensor space + bool _prevRightHandPoleVectorValid { false }; + + glm::vec3 _prevLeftHandPoleVector { Vectors::UNIT_Z }; // sensor space + bool _prevLeftHandPoleVectorValid { false }; + int _rigId; bool _headEnabled { false }; bool _computeNetworkAnimation { false }; From 8d88c627b01161ed50942e192981c063f1dbb9df Mon Sep 17 00:00:00 2001 From: amantley Date: Mon, 21 Jan 2019 17:41:08 -0800 Subject: [PATCH 011/474] nearly have the arms legs and back working with out the ik node --- .../avatar/avatar-animation_twoboneIK.json | 3479 ++++++++--------- interface/src/Application.cpp | 1 - .../animation/src/AnimInverseKinematics.cpp | 13 +- libraries/animation/src/AnimSplineIK.cpp | 24 +- libraries/animation/src/AnimTwoBoneIK.cpp | 4 +- libraries/animation/src/Rig.cpp | 14 +- 6 files changed, 1773 insertions(+), 1762 deletions(-) diff --git a/interface/resources/avatar/avatar-animation_twoboneIK.json b/interface/resources/avatar/avatar-animation_twoboneIK.json index 8a18b4859a..45cb7b570e 100644 --- a/interface/resources/avatar/avatar-animation_twoboneIK.json +++ b/interface/resources/avatar/avatar-animation_twoboneIK.json @@ -112,7 +112,7 @@ }, "children": [ { - "id": "rightArmPoleVector", + "id": "rightHandPoleVector", "type": "poleVectorConstraint", "data": { "enabled": false, @@ -125,7 +125,7 @@ }, "children": [ { - "id": "rightArmIK", + "id": "rightHandIK", "type": "twoBoneIK", "data": { "alpha": 1.0, @@ -135,390 +135,147 @@ "midJointName": "RightForeArm", "tipJointName": "RightHand", "midHingeAxis": [ 0, 0, -1 ], - "alphaVar": "rightArmIKAlpha", - "enabledVar": "rightArmIKEnabled", - "endEffectorRotationVarVar": "rightArmIKRotationVar", - "endEffectorPositionVarVar": "rightArmIKPositionVar" + "alphaVar": "rightHandIKAlpha", + "enabledVar": "rightHandIKEnabled", + "endEffectorRotationVarVar": "rightHandIKRotationVar", + "endEffectorPositionVarVar": "rightHandIKPositionVar" }, "children": [ { - "id": "ikOverlay", - "type": "overlay", + "id": "leftHandPoleVector", + "type": "poleVectorConstraint", "data": { - "alpha": 1.0, - "alphaVar": "ikOverlayAlpha", - "boneSet": "fullBody" + "enabled": false, + "referenceVector": [ 0, 0, 1 ], + "baseJointName": "LeftArm", + "midJointName": "LeftForeArm", + "tipJointName": "LeftHand", + "enabledVar": "leftHandPoleVectorEnabled", + "poleVectorVar": "leftHandPoleVector" }, "children": [ { - "id": "ik", - "type": "inverseKinematics", + "id": "leftHandIK", + "type": "twoBoneIK", "data": { - "solutionSource": "relaxToUnderPoses", - "solutionSourceVar": "solutionSource", - "targets": [ - { - "jointName": "Hips", - "positionVar": "hipsPosition", - "rotationVar": "hipsRotation", - "typeVar": "hipsType", - "weightVar": "hipsWeight", - "weight": 1.0, - "flexCoefficients": [ 1 ] - }, - { - "jointName": "LeftHand", - "positionVar": "leftHandPosition", - "rotationVar": "leftHandRotation", - "typeVar": "leftHandType", - "weightVar": "leftHandWeight", - "weight": 1.0, - "flexCoefficients": [ 1, 0.5, 0.5, 0.2, 0.01, 0.005, 0.001, 0.0, 0.0 ], - "poleVectorEnabledVar": "leftHandPoleVectorEnabled", - "poleReferenceVectorVar": "leftHandPoleReferenceVector", - "poleVectorVar": "leftHandPoleVector" - } - ] - }, - "children": [] - }, - { - "id": "defaultPoseOverlay", - "type": "overlay", - "data": { - "alpha": 0.0, - "alphaVar": "defaultPoseOverlayAlpha", - "boneSet": "fullBody", - "boneSetVar": "defaultPoseOverlayBoneSet" + "alpha": 1.0, + "enabled": false, + "interpDuration": 15, + "baseJointName": "LeftArm", + "midJointName": "LeftForeArm", + "tipJointName": "LeftHand", + "midHingeAxis": [ 0, 0, 1 ], + "alphaVar": "leftHandIKAlpha", + "enabledVar": "leftHandIKEnabled", + "endEffectorRotationVarVar": "leftHandIKRotationVar", + "endEffectorPositionVarVar": "leftHandIKPositionVar" }, "children": [ { - "id": "defaultPose", - "type": "defaultPose", - "data": { - }, - "children": [] - }, - { - "id": "rightHandOverlay", + "id": "defaultPoseOverlay", "type": "overlay", "data": { "alpha": 0.0, - "boneSet": "rightHand", - "alphaVar": "rightHandOverlayAlpha" + "alphaVar": "defaultPoseOverlayAlpha", + "boneSet": "fullBody", + "boneSetVar": "defaultPoseOverlayBoneSet" }, "children": [ { - "id": "rightHandStateMachine", - "type": "stateMachine", + "id": "defaultPose", + "type": "defaultPose", "data": { - "currentState": "rightHandGrasp", - "states": [ - { - "id": "rightHandGrasp", - "interpTarget": 3, - "interpDuration": 3, - "transitions": [ - { - "var": "isRightIndexPoint", - "state": "rightIndexPoint" - }, - { - "var": "isRightThumbRaise", - "state": "rightThumbRaise" - }, - { - "var": "isRightIndexPointAndThumbRaise", - "state": "rightIndexPointAndThumbRaise" - } - ] - }, - { - "id": "rightIndexPoint", - "interpTarget": 15, - "interpDuration": 3, - "transitions": [ - { - "var": "isRightHandGrasp", - "state": "rightHandGrasp" - }, - { - "var": "isRightThumbRaise", - "state": "rightThumbRaise" - }, - { - "var": "isRightIndexPointAndThumbRaise", - "state": "rightIndexPointAndThumbRaise" - } - ] - }, - { - "id": "rightThumbRaise", - "interpTarget": 15, - "interpDuration": 3, - "transitions": [ - { - "var": "isRightHandGrasp", - "state": "rightHandGrasp" - }, - { - "var": "isRightIndexPoint", - "state": "rightIndexPoint" - }, - { - "var": "isRightIndexPointAndThumbRaise", - "state": "rightIndexPointAndThumbRaise" - } - ] - }, - { - "id": "rightIndexPointAndThumbRaise", - "interpTarget": 15, - "interpDuration": 3, - "transitions": [ - { - "var": "isRightHandGrasp", - "state": "rightHandGrasp" - }, - { - "var": "isRightIndexPoint", - "state": "rightIndexPoint" - }, - { - "var": "isRightThumbRaise", - "state": "rightThumbRaise" - } - ] - } - ] }, - "children": [ - { - "id": "rightHandGrasp", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "rightHandGraspAlpha" - }, - "children": [ - { - "id": "rightHandGraspOpen", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/hydra_pose_open_right.fbx", - "startFrame": 0.0, - "endFrame": 0.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "rightHandGraspClosed", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/hydra_pose_closed_right.fbx", - "startFrame": 0.0, - "endFrame": 0.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "rightIndexPoint", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "rightHandGraspAlpha" - }, - "children": [ - { - "id": "rightIndexPointOpen", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_point_open_right.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "rightIndexPointClosed", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_point_closed_right.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "rightThumbRaise", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "rightHandGraspAlpha" - }, - "children": [ - { - "id": "rightThumbRaiseOpen", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_thumb_open_right.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "rightThumbRaiseClosed", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_thumb_closed_right.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "rightIndexPointAndThumbRaise", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "rightHandGraspAlpha" - }, - "children": [ - { - "id": "rightIndexPointAndThumbRaiseOpen", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_thumb_point_open_right.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "rightIndexPointAndThumbRaiseClosed", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_thumb_point_closed_right.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - } - ] + "children": [] }, { - "id": "leftHandOverlay", + "id": "rightHandOverlay", "type": "overlay", "data": { "alpha": 0.0, - "boneSet": "leftHand", - "alphaVar": "leftHandOverlayAlpha" + "boneSet": "rightHand", + "alphaVar": "rightHandOverlayAlpha" }, "children": [ { - "id": "leftHandStateMachine", + "id": "rightHandStateMachine", "type": "stateMachine", "data": { - "currentState": "leftHandGrasp", + "currentState": "rightHandGrasp", "states": [ { - "id": "leftHandGrasp", + "id": "rightHandGrasp", "interpTarget": 3, "interpDuration": 3, "transitions": [ { - "var": "isLeftIndexPoint", - "state": "leftIndexPoint" + "var": "isRightIndexPoint", + "state": "rightIndexPoint" }, { - "var": "isLeftThumbRaise", - "state": "leftThumbRaise" + "var": "isRightThumbRaise", + "state": "rightThumbRaise" }, { - "var": "isLeftIndexPointAndThumbRaise", - "state": "leftIndexPointAndThumbRaise" + "var": "isRightIndexPointAndThumbRaise", + "state": "rightIndexPointAndThumbRaise" } ] }, { - "id": "leftIndexPoint", + "id": "rightIndexPoint", "interpTarget": 15, "interpDuration": 3, "transitions": [ { - "var": "isLeftHandGrasp", - "state": "leftHandGrasp" + "var": "isRightHandGrasp", + "state": "rightHandGrasp" }, { - "var": "isLeftThumbRaise", - "state": "leftThumbRaise" + "var": "isRightThumbRaise", + "state": "rightThumbRaise" }, { - "var": "isLeftIndexPointAndThumbRaise", - "state": "leftIndexPointAndThumbRaise" + "var": "isRightIndexPointAndThumbRaise", + "state": "rightIndexPointAndThumbRaise" } ] }, { - "id": "leftThumbRaise", + "id": "rightThumbRaise", "interpTarget": 15, "interpDuration": 3, "transitions": [ { - "var": "isLeftHandGrasp", - "state": "leftHandGrasp" + "var": "isRightHandGrasp", + "state": "rightHandGrasp" }, { - "var": "isLeftIndexPoint", - "state": "leftIndexPoint" + "var": "isRightIndexPoint", + "state": "rightIndexPoint" }, { - "var": "isLeftIndexPointAndThumbRaise", - "state": "leftIndexPointAndThumbRaise" + "var": "isRightIndexPointAndThumbRaise", + "state": "rightIndexPointAndThumbRaise" } ] }, { - "id": "leftIndexPointAndThumbRaise", + "id": "rightIndexPointAndThumbRaise", "interpTarget": 15, "interpDuration": 3, "transitions": [ { - "var": "isLeftHandGrasp", - "state": "leftHandGrasp" + "var": "isRightHandGrasp", + "state": "rightHandGrasp" }, { - "var": "isLeftIndexPoint", - "state": "leftIndexPoint" + "var": "isRightIndexPoint", + "state": "rightIndexPoint" }, { - "var": "isLeftThumbRaise", - "state": "leftThumbRaise" + "var": "isRightThumbRaise", + "state": "rightThumbRaise" } ] } @@ -526,18 +283,18 @@ }, "children": [ { - "id": "leftHandGrasp", + "id": "rightHandGrasp", "type": "blendLinear", "data": { "alpha": 0.0, - "alphaVar": "leftHandGraspAlpha" + "alphaVar": "rightHandGraspAlpha" }, "children": [ { - "id": "leftHandGraspOpen", + "id": "rightHandGraspOpen", "type": "clip", "data": { - "url": "qrc:///avatar/animations/hydra_pose_open_left.fbx", + "url": "qrc:///avatar/animations/hydra_pose_open_right.fbx", "startFrame": 0.0, "endFrame": 0.0, "timeScale": 1.0, @@ -546,12 +303,12 @@ "children": [] }, { - "id": "leftHandGraspClosed", + "id": "rightHandGraspClosed", "type": "clip", "data": { - "url": "qrc:///avatar/animations/hydra_pose_closed_left.fbx", - "startFrame": 10.0, - "endFrame": 10.0, + "url": "qrc:///avatar/animations/hydra_pose_closed_right.fbx", + "startFrame": 0.0, + "endFrame": 0.0, "timeScale": 1.0, "loopFlag": true }, @@ -560,18 +317,18 @@ ] }, { - "id": "leftIndexPoint", + "id": "rightIndexPoint", "type": "blendLinear", "data": { "alpha": 0.0, - "alphaVar": "leftHandGraspAlpha" + "alphaVar": "rightHandGraspAlpha" }, "children": [ { - "id": "leftIndexPointOpen", + "id": "rightIndexPointOpen", "type": "clip", "data": { - "url": "qrc:///avatar/animations/touch_point_open_left.fbx", + "url": "qrc:///avatar/animations/touch_point_open_right.fbx", "startFrame": 15.0, "endFrame": 15.0, "timeScale": 1.0, @@ -580,10 +337,10 @@ "children": [] }, { - "id": "leftIndexPointClosed", + "id": "rightIndexPointClosed", "type": "clip", "data": { - "url": "qrc:///avatar/animations/touch_point_closed_left.fbx", + "url": "qrc:///avatar/animations/touch_point_closed_right.fbx", "startFrame": 15.0, "endFrame": 15.0, "timeScale": 1.0, @@ -594,18 +351,18 @@ ] }, { - "id": "leftThumbRaise", + "id": "rightThumbRaise", "type": "blendLinear", "data": { "alpha": 0.0, - "alphaVar": "leftHandGraspAlpha" + "alphaVar": "rightHandGraspAlpha" }, "children": [ { - "id": "leftThumbRaiseOpen", + "id": "rightThumbRaiseOpen", "type": "clip", "data": { - "url": "qrc:///avatar/animations/touch_thumb_open_left.fbx", + "url": "qrc:///avatar/animations/touch_thumb_open_right.fbx", "startFrame": 15.0, "endFrame": 15.0, "timeScale": 1.0, @@ -614,10 +371,10 @@ "children": [] }, { - "id": "leftThumbRaiseClosed", + "id": "rightThumbRaiseClosed", "type": "clip", "data": { - "url": "qrc:///avatar/animations/touch_thumb_closed_left.fbx", + "url": "qrc:///avatar/animations/touch_thumb_closed_right.fbx", "startFrame": 15.0, "endFrame": 15.0, "timeScale": 1.0, @@ -628,18 +385,18 @@ ] }, { - "id": "leftIndexPointAndThumbRaise", + "id": "rightIndexPointAndThumbRaise", "type": "blendLinear", "data": { "alpha": 0.0, - "alphaVar": "leftHandGraspAlpha" + "alphaVar": "rightHandGraspAlpha" }, "children": [ { - "id": "leftIndexPointAndThumbRaiseOpen", + "id": "rightIndexPointAndThumbRaiseOpen", "type": "clip", "data": { - "url": "qrc:///avatar/animations/touch_thumb_point_open_left.fbx", + "url": "qrc:///avatar/animations/touch_thumb_point_open_right.fbx", "startFrame": 15.0, "endFrame": 15.0, "timeScale": 1.0, @@ -648,10 +405,10 @@ "children": [] }, { - "id": "leftIndexPointAndThumbRaiseClosed", + "id": "rightIndexPointAndThumbRaiseClosed", "type": "clip", "data": { - "url": "qrc:///avatar/animations/touch_thumb_point_closed_left.fbx", + "url": "qrc:///avatar/animations/touch_thumb_point_closed_right.fbx", "startFrame": 15.0, "endFrame": 15.0, "timeScale": 1.0, @@ -664,871 +421,93 @@ ] }, { - "id": "mainStateMachine", - "type": "stateMachine", + "id": "leftHandOverlay", + "type": "overlay", "data": { - "outputJoints": [ "LeftFoot", "RightFoot" ], - "currentState": "idle", - "states": [ - { - "id": "idle", - "interpTarget": 20, - "interpDuration": 8, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "idleToWalkFwd", - "interpTarget": 12, - "interpDuration": 8, - "transitions": [ - { - "var": "idleToWalkFwdOnDone", - "state": "WALKFWD" - }, - { - "var": "isNotMoving", - "state": "idle" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "idleSettle", - "interpTarget": 15, - "interpDuration": 8, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "idleSettleOnDone", - "state": "idle" - }, - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - } - ] - }, - { - "id": "WALKFWD", - "interpTarget": 35, - "interpDuration": 10, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isNotMoving", - "state": "idleSettle" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "WALKBWD", - "interpTarget": 35, - "interpDuration": 10, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isNotMoving", - "state": "idleSettle" - }, - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "STRAFERIGHT", - "interpTarget": 25, - "interpDuration": 8, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isNotMoving", - "state": "idleSettle" - }, - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "STRAFELEFT", - "interpTarget": 25, - "interpDuration": 8, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isNotMoving", - "state": "idleSettle" - }, - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "turnRight", - "interpTarget": 6, - "interpDuration": 8, - "transitions": [ - { - "var": "isNotTurning", - "state": "idle" - }, - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "turnLeft", - "interpTarget": 6, - "interpDuration": 8, - "transitions": [ - { - "var": "isNotTurning", - "state": "idle" - }, - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "strafeRightHmd", - "interpTarget": 5, - "interpDuration": 8, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isNotMoving", - "state": "idleSettle" - }, - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - } - ] - }, - { - "id": "strafeLeftHmd", - "interpTarget": 5, - "interpDuration": 8, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isNotMoving", - "state": "idleSettle" - }, - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - } - ] - }, - { - "id": "fly", - "interpTarget": 6, - "interpDuration": 6, - "transitions": [ - { - "var": "isNotFlying", - "state": "idleSettle" - } - ] - }, - { - "id": "takeoffStand", - "interpTarget": 2, - "interpDuration": 2, - "transitions": [ - { - "var": "isNotTakeoff", - "state": "inAirStand" - } - ] - }, - { - "id": "TAKEOFFRUN", - "interpTarget": 2, - "interpDuration": 2, - "transitions": [ - { - "var": "isNotTakeoff", - "state": "INAIRRUN" - } - ] - }, - { - "id": "inAirStand", - "interpTarget": 3, - "interpDuration": 3, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isNotInAir", - "state": "landStandImpact" - } - ] - }, - { - "id": "INAIRRUN", - "interpTarget": 3, - "interpDuration": 3, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isNotInAir", - "state": "WALKFWD" - } - ] - }, - { - "id": "landStandImpact", - "interpTarget": 1, - "interpDuration": 1, - "transitions": [ - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "landStandImpactOnDone", - "state": "landStand" - } - ] - }, - { - "id": "landStand", - "interpTarget": 1, - "interpDuration": 1, - "transitions": [ - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "landStandOnDone", - "state": "idle" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "LANDRUN", - "interpTarget": 2, - "interpDuration": 2, - "transitions": [ - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "landRunOnDone", - "state": "WALKFWD" - } - ] - } - ] + "alpha": 0.0, + "boneSet": "leftHand", + "alphaVar": "leftHandOverlayAlpha" }, "children": [ { - "id": "idle", + "id": "leftHandStateMachine", "type": "stateMachine", "data": { - "currentState": "idleStand", + "currentState": "leftHandGrasp", "states": [ { - "id": "idleStand", - "interpTarget": 6, - "interpDuration": 10, + "id": "leftHandGrasp", + "interpTarget": 3, + "interpDuration": 3, "transitions": [ { - "var": "isTalking", - "state": "idleTalk" + "var": "isLeftIndexPoint", + "state": "leftIndexPoint" + }, + { + "var": "isLeftThumbRaise", + "state": "leftThumbRaise" + }, + { + "var": "isLeftIndexPointAndThumbRaise", + "state": "leftIndexPointAndThumbRaise" } ] }, { - "id": "idleTalk", - "interpTarget": 6, - "interpDuration": 10, + "id": "leftIndexPoint", + "interpTarget": 15, + "interpDuration": 3, "transitions": [ { - "var": "notIsTalking", - "state": "idleStand" + "var": "isLeftHandGrasp", + "state": "leftHandGrasp" + }, + { + "var": "isLeftThumbRaise", + "state": "leftThumbRaise" + }, + { + "var": "isLeftIndexPointAndThumbRaise", + "state": "leftIndexPointAndThumbRaise" + } + ] + }, + { + "id": "leftThumbRaise", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { + "var": "isLeftHandGrasp", + "state": "leftHandGrasp" + }, + { + "var": "isLeftIndexPoint", + "state": "leftIndexPoint" + }, + { + "var": "isLeftIndexPointAndThumbRaise", + "state": "leftIndexPointAndThumbRaise" + } + ] + }, + { + "id": "leftIndexPointAndThumbRaise", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { + "var": "isLeftHandGrasp", + "state": "leftHandGrasp" + }, + { + "var": "isLeftIndexPoint", + "state": "leftIndexPoint" + }, + { + "var": "isLeftThumbRaise", + "state": "leftThumbRaise" } ] } @@ -1536,562 +515,1494 @@ }, "children": [ { - "id": "idleStand", - "type": "clip", + "id": "leftHandGrasp", + "type": "blendLinear", "data": { - "url": "qrc:///avatar/animations/idle.fbx", - "startFrame": 0.0, - "endFrame": 300.0, - "timeScale": 1.0, - "loopFlag": true + "alpha": 0.0, + "alphaVar": "leftHandGraspAlpha" }, - "children": [] + "children": [ + { + "id": "leftHandGraspOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/hydra_pose_open_left.fbx", + "startFrame": 0.0, + "endFrame": 0.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "leftHandGraspClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/hydra_pose_closed_left.fbx", + "startFrame": 10.0, + "endFrame": 10.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] }, { - "id": "idleTalk", - "type": "clip", + "id": "leftIndexPoint", + "type": "blendLinear", "data": { - "url": "qrc:///avatar/animations/talk.fbx", - "startFrame": 0.0, - "endFrame": 800.0, - "timeScale": 1.0, - "loopFlag": true + "alpha": 0.0, + "alphaVar": "leftHandGraspAlpha" }, - "children": [] + "children": [ + { + "id": "leftIndexPointOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_point_open_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "leftIndexPointClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_point_closed_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "leftThumbRaise", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "leftHandGraspAlpha" + }, + "children": [ + { + "id": "leftThumbRaiseOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_open_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "leftThumbRaiseClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_closed_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "leftIndexPointAndThumbRaise", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "leftHandGraspAlpha" + }, + "children": [ + { + "id": "leftIndexPointAndThumbRaiseOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_point_open_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "leftIndexPointAndThumbRaiseClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_point_closed_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] } ] }, { - "id": "WALKFWD", - "type": "blendLinearMove", + "id": "mainStateMachine", + "type": "stateMachine", "data": { - "alpha": 0.0, - "desiredSpeed": 1.4, - "characteristicSpeeds": [ 0.5, 1.8, 2.3, 3.2, 4.5 ], - "alphaVar": "moveForwardAlpha", - "desiredSpeedVar": "moveForwardSpeed" + "outputJoints": [ "LeftFoot", "RightFoot" ], + "currentState": "idle", + "states": [ + { + "id": "idle", + "interpTarget": 20, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "idleToWalkFwd", + "interpTarget": 12, + "interpDuration": 8, + "transitions": [ + { + "var": "idleToWalkFwdOnDone", + "state": "WALKFWD" + }, + { + "var": "isNotMoving", + "state": "idle" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "idleSettle", + "interpTarget": 15, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "idleSettleOnDone", + "state": "idle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + } + ] + }, + { + "id": "WALKFWD", + "interpTarget": 35, + "interpDuration": 10, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotMoving", + "state": "idleSettle" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "WALKBWD", + "interpTarget": 35, + "interpDuration": 10, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotMoving", + "state": "idleSettle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "STRAFERIGHT", + "interpTarget": 25, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotMoving", + "state": "idleSettle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "STRAFELEFT", + "interpTarget": 25, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotMoving", + "state": "idleSettle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "turnRight", + "interpTarget": 6, + "interpDuration": 8, + "transitions": [ + { + "var": "isNotTurning", + "state": "idle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "turnLeft", + "interpTarget": 6, + "interpDuration": 8, + "transitions": [ + { + "var": "isNotTurning", + "state": "idle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "strafeRightHmd", + "interpTarget": 5, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotMoving", + "state": "idleSettle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + } + ] + }, + { + "id": "strafeLeftHmd", + "interpTarget": 5, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotMoving", + "state": "idleSettle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + } + ] + }, + { + "id": "fly", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { + "var": "isNotFlying", + "state": "idleSettle" + } + ] + }, + { + "id": "takeoffStand", + "interpTarget": 2, + "interpDuration": 2, + "transitions": [ + { + "var": "isNotTakeoff", + "state": "inAirStand" + } + ] + }, + { + "id": "TAKEOFFRUN", + "interpTarget": 2, + "interpDuration": 2, + "transitions": [ + { + "var": "isNotTakeoff", + "state": "INAIRRUN" + } + ] + }, + { + "id": "inAirStand", + "interpTarget": 3, + "interpDuration": 3, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotInAir", + "state": "landStandImpact" + } + ] + }, + { + "id": "INAIRRUN", + "interpTarget": 3, + "interpDuration": 3, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotInAir", + "state": "WALKFWD" + } + ] + }, + { + "id": "landStandImpact", + "interpTarget": 1, + "interpDuration": 1, + "transitions": [ + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "landStandImpactOnDone", + "state": "landStand" + } + ] + }, + { + "id": "landStand", + "interpTarget": 1, + "interpDuration": 1, + "transitions": [ + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "landStandOnDone", + "state": "idle" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "LANDRUN", + "interpTarget": 2, + "interpDuration": 2, + "transitions": [ + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "landRunOnDone", + "state": "WALKFWD" + } + ] + } + ] }, "children": [ { - "id": "walkFwdShort_c", - "type": "clip", + "id": "idle", + "type": "stateMachine", "data": { - "url": "qrc:///avatar/animations/walk_short_fwd.fbx", - "startFrame": 0.0, - "endFrame": 39.0, - "timeScale": 1.0, - "loopFlag": true + "currentState": "idleStand", + "states": [ + { + "id": "idleStand", + "interpTarget": 6, + "interpDuration": 10, + "transitions": [ + { + "var": "isTalking", + "state": "idleTalk" + } + ] + }, + { + "id": "idleTalk", + "interpTarget": 6, + "interpDuration": 10, + "transitions": [ + { + "var": "notIsTalking", + "state": "idleStand" + } + ] + } + ] }, - "children": [] + "children": [ + { + "id": "idleStand", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle.fbx", + "startFrame": 0.0, + "endFrame": 300.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "idleTalk", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/talk.fbx", + "startFrame": 0.0, + "endFrame": 800.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] }, { - "id": "walkFwdNormal_c", - "type": "clip", + "id": "WALKFWD", + "type": "blendLinearMove", "data": { - "url": "qrc:///avatar/animations/walk_fwd.fbx", - "startFrame": 0.0, - "endFrame": 30.0, - "timeScale": 1.0, - "loopFlag": true + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [ 0.5, 1.8, 2.3, 3.2, 4.5 ], + "alphaVar": "moveForwardAlpha", + "desiredSpeedVar": "moveForwardSpeed" }, - "children": [] + "children": [ + { + "id": "walkFwdShort_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_short_fwd.fbx", + "startFrame": 0.0, + "endFrame": 39.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkFwdNormal_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_fwd.fbx", + "startFrame": 0.0, + "endFrame": 30.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkFwdFast_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_fwd_fast.fbx", + "startFrame": 0.0, + "endFrame": 25.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkFwdJog_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jog_fwd.fbx", + "startFrame": 0.0, + "endFrame": 25.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkFwdRun_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/run_fwd.fbx", + "startFrame": 0.0, + "endFrame": 21.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] }, { - "id": "walkFwdFast_c", + "id": "idleToWalkFwd", "type": "clip", "data": { - "url": "qrc:///avatar/animations/walk_fwd_fast.fbx", - "startFrame": 0.0, - "endFrame": 25.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "walkFwdJog_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jog_fwd.fbx", - "startFrame": 0.0, - "endFrame": 25.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "walkFwdRun_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/run_fwd.fbx", - "startFrame": 0.0, - "endFrame": 21.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "idleToWalkFwd", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/idle_to_walk.fbx", - "startFrame": 1.0, - "endFrame": 13.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "idleSettle", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/settle_to_idle.fbx", - "startFrame": 1.0, - "endFrame": 59.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "WALKBWD", - "type": "blendLinearMove", - "data": { - "alpha": 0.0, - "desiredSpeed": 1.4, - "characteristicSpeeds": [ 0.6, 1.6, 2.3, 3.1 ], - "alphaVar": "moveBackwardAlpha", - "desiredSpeedVar": "moveBackwardSpeed" - }, - "children": [ - { - "id": "walkBwdShort_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_short_bwd.fbx", - "startFrame": 0.0, - "endFrame": 38.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "walkBwdFast_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_bwd_fast.fbx", - "startFrame": 0.0, - "endFrame": 27.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "jogBwd_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jog_bwd.fbx", - "startFrame": 0.0, - "endFrame": 24.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "runBwd_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/run_bwd.fbx", - "startFrame": 0.0, - "endFrame": 16.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "turnLeft", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/turn_left.fbx", - "startFrame": 0.0, - "endFrame": 32.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "turnRight", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/turn_left.fbx", - "startFrame": 0.0, - "endFrame": 32.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "STRAFELEFT", - "type": "blendLinearMove", - "data": { - "alpha": 0.0, - "desiredSpeed": 1.4, - "characteristicSpeeds": [ 0.1, 0.5, 1.0, 2.6, 3.0 ], - "alphaVar": "moveLateralAlpha", - "desiredSpeedVar": "moveLateralSpeed" - }, - "children": [ - { - "id": "strafeLeftShortStep_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_short_left.fbx", - "startFrame": 0.0, - "endFrame": 29.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "strafeLeftStep_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_left.fbx", - "startFrame": 0.0, - "endFrame": 20.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "strafeLeftWalk_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_left.fbx", - "startFrame": 0.0, - "endFrame": 35.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "strafeLeftWalkFast_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_left_fast.fbx", - "startFrame": 0.0, - "endFrame": 21.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "strafeLeftJog_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jog_left.fbx", - "startFrame": 0.0, - "endFrame": 24.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "STRAFERIGHT", - "type": "blendLinearMove", - "data": { - "alpha": 0.0, - "desiredSpeed": 1.4, - "characteristicSpeeds": [ 0.1, 0.5, 1.0, 2.6, 3.0 ], - "alphaVar": "moveLateralAlpha", - "desiredSpeedVar": "moveLateralSpeed" - }, - "children": [ - { - "id": "strafeRightShortStep_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_short_left.fbx", - "startFrame": 0.0, - "endFrame": 29.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "strafeRightStep_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_left.fbx", - "startFrame": 0.0, - "endFrame": 20.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "strafeRightWalk_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_left.fbx", - "startFrame": 0.0, - "endFrame": 35.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "strafeRightFast_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_left_fast.fbx", - "startFrame": 0.0, - "endFrame": 21.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "strafeRightJog_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jog_left.fbx", - "startFrame": 0.0, - "endFrame": 24.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - } - ] - }, - { - "id": "strafeLeftHmd", - "type": "blendLinearMove", - "data": { - "alpha": 0.0, - "desiredSpeed": 1.4, - "characteristicSpeeds": [ 0, 0.5, 2.5 ], - "alphaVar": "moveLateralAlpha", - "desiredSpeedVar": "moveLateralSpeed" - }, - "children": [ - { - "id": "stepLeftShort_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_short_left.fbx", - "startFrame": 0.0, - "endFrame": 29.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "stepLeft_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_left.fbx", - "startFrame": 0.0, - "endFrame": 20.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "strafeLeftAnim_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_left_fast.fbx", - "startFrame": 0.0, - "endFrame": 16.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "strafeRightHmd", - "type": "blendLinearMove", - "data": { - "alpha": 0.0, - "desiredSpeed": 1.4, - "characteristicSpeeds": [ 0, 0.5, 2.5 ], - "alphaVar": "moveLateralAlpha", - "desiredSpeedVar": "moveLateralSpeed" - }, - "children": [ - { - "id": "stepRightShort_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_short_left.fbx", - "startFrame": 0.0, - "endFrame": 29.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "stepRight_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_left.fbx", - "startFrame": 0.0, - "endFrame": 20.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "strafeRightAnim_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_left_fast.fbx", - "startFrame": 0.0, - "endFrame": 16.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - } - ] - }, - { - "id": "fly", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/fly.fbx", - "startFrame": 1.0, - "endFrame": 80.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "takeoffStand", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_standing_launch.fbx", - "startFrame": 2.0, - "endFrame": 16.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "TAKEOFFRUN", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", - "startFrame": 4.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "inAirStand", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "inAirAlpha" - }, - "children": [ - { - "id": "inAirStandPreApex", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_standing_apex.fbx", - "startFrame": 0.0, - "endFrame": 0.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "inAirStandApex", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_standing_apex.fbx", + "url": "qrc:///avatar/animations/idle_to_walk.fbx", "startFrame": 1.0, - "endFrame": 1.0, + "endFrame": 13.0, "timeScale": 1.0, "loopFlag": false }, "children": [] }, { - "id": "inAirStandPostApex", + "id": "idleSettle", "type": "clip", "data": { - "url": "qrc:///avatar/animations/jump_standing_apex.fbx", - "startFrame": 2.0, - "endFrame": 2.0, + "url": "qrc:///avatar/animations/settle_to_idle.fbx", + "startFrame": 1.0, + "endFrame": 59.0, "timeScale": 1.0, "loopFlag": false }, "children": [] - } - ] - }, - { - "id": "INAIRRUN", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "inAirAlpha" - }, - "children": [ + }, { - "id": "inAirRunPreApex", + "id": "WALKBWD", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [ 0.6, 1.6, 2.3, 3.1 ], + "alphaVar": "moveBackwardAlpha", + "desiredSpeedVar": "moveBackwardSpeed" + }, + "children": [ + { + "id": "walkBwdShort_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_short_bwd.fbx", + "startFrame": 0.0, + "endFrame": 38.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkBwdFast_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_bwd_fast.fbx", + "startFrame": 0.0, + "endFrame": 27.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "jogBwd_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jog_bwd.fbx", + "startFrame": 0.0, + "endFrame": 24.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "runBwd_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/run_bwd.fbx", + "startFrame": 0.0, + "endFrame": 16.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "turnLeft", "type": "clip", "data": { - "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", - "startFrame": 16.0, + "url": "qrc:///avatar/animations/turn_left.fbx", + "startFrame": 0.0, + "endFrame": 32.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "turnRight", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/turn_left.fbx", + "startFrame": 0.0, + "endFrame": 32.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "STRAFELEFT", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [ 0.1, 0.5, 1.0, 2.6, 3.0 ], + "alphaVar": "moveLateralAlpha", + "desiredSpeedVar": "moveLateralSpeed" + }, + "children": [ + { + "id": "strafeLeftShortStep_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_short_left.fbx", + "startFrame": 0.0, + "endFrame": 29.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftStep_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left.fbx", + "startFrame": 0.0, + "endFrame": 20.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftWalk_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_left.fbx", + "startFrame": 0.0, + "endFrame": 35.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftWalkFast_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_left_fast.fbx", + "startFrame": 0.0, + "endFrame": 21.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftJog_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jog_left.fbx", + "startFrame": 0.0, + "endFrame": 24.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "STRAFERIGHT", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [ 0.1, 0.5, 1.0, 2.6, 3.0 ], + "alphaVar": "moveLateralAlpha", + "desiredSpeedVar": "moveLateralSpeed" + }, + "children": [ + { + "id": "strafeRightShortStep_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_short_left.fbx", + "startFrame": 0.0, + "endFrame": 29.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "strafeRightStep_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left.fbx", + "startFrame": 0.0, + "endFrame": 20.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "strafeRightWalk_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_left.fbx", + "startFrame": 0.0, + "endFrame": 35.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "strafeRightFast_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_left_fast.fbx", + "startFrame": 0.0, + "endFrame": 21.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "strafeRightJog_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jog_left.fbx", + "startFrame": 0.0, + "endFrame": 24.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + } + ] + }, + { + "id": "strafeLeftHmd", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [ 0, 0.5, 2.5 ], + "alphaVar": "moveLateralAlpha", + "desiredSpeedVar": "moveLateralSpeed" + }, + "children": [ + { + "id": "stepLeftShort_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_short_left.fbx", + "startFrame": 0.0, + "endFrame": 29.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "stepLeft_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left.fbx", + "startFrame": 0.0, + "endFrame": 20.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftAnim_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left_fast.fbx", + "startFrame": 0.0, + "endFrame": 16.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "strafeRightHmd", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [ 0, 0.5, 2.5 ], + "alphaVar": "moveLateralAlpha", + "desiredSpeedVar": "moveLateralSpeed" + }, + "children": [ + { + "id": "stepRightShort_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_short_left.fbx", + "startFrame": 0.0, + "endFrame": 29.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "stepRight_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left.fbx", + "startFrame": 0.0, + "endFrame": 20.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "strafeRightAnim_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left_fast.fbx", + "startFrame": 0.0, + "endFrame": 16.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + } + ] + }, + { + "id": "fly", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/fly.fbx", + "startFrame": 1.0, + "endFrame": 80.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "takeoffStand", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_launch.fbx", + "startFrame": 2.0, "endFrame": 16.0, "timeScale": 1.0, "loopFlag": false @@ -2099,66 +2010,146 @@ "children": [] }, { - "id": "inAirRunApex", + "id": "TAKEOFFRUN", "type": "clip", "data": { "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", - "startFrame": 22.0, - "endFrame": 22.0, + "startFrame": 4.0, + "endFrame": 15.0, "timeScale": 1.0, "loopFlag": false }, "children": [] }, { - "id": "inAirRunPostApex", + "id": "inAirStand", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "inAirAlpha" + }, + "children": [ + { + "id": "inAirStandPreApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_apex.fbx", + "startFrame": 0.0, + "endFrame": 0.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirStandApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_apex.fbx", + "startFrame": 1.0, + "endFrame": 1.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirStandPostApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_apex.fbx", + "startFrame": 2.0, + "endFrame": 2.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + } + ] + }, + { + "id": "INAIRRUN", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "inAirAlpha" + }, + "children": [ + { + "id": "inAirRunPreApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "startFrame": 16.0, + "endFrame": 16.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirRunApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "startFrame": 22.0, + "endFrame": 22.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirRunPostApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "startFrame": 33.0, + "endFrame": 33.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + } + ] + }, + { + "id": "landStandImpact", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_land_settle.fbx", + "startFrame": 1.0, + "endFrame": 6.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "landStand", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_land_settle.fbx", + "startFrame": 6.0, + "endFrame": 68.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "LANDRUN", "type": "clip", "data": { "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", - "startFrame": 33.0, - "endFrame": 33.0, + "startFrame": 29.0, + "endFrame": 40.0, "timeScale": 1.0, "loopFlag": false }, "children": [] } ] - }, - { - "id": "landStandImpact", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_standing_land_settle.fbx", - "startFrame": 1.0, - "endFrame": 6.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "landStand", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_standing_land_settle.fbx", - "startFrame": 6.0, - "endFrame": 68.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "LANDRUN", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", - "startFrame": 29.0, - "endFrame": 40.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] } ] } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 2401bf5315..de773f2d20 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4679,7 +4679,6 @@ void setupCpuMonitorThread() { void Application::idle() { PerformanceTimer perfTimer("idle"); - qCDebug(interfaceapp) << "idle called"; // Update the deadlock watchdog updateHeartbeat(); diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index e5509dc43d..8d2ba0fd78 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -886,7 +886,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars if (dt > MAX_OVERLAY_DT) { dt = MAX_OVERLAY_DT; } - + if (_relativePoses.size() != underPoses.size()) { loadPoses(underPoses); } else { @@ -973,8 +973,8 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars ::blend(1, &prevHipsAbsPose, &absPose, alpha, &absPose); } - _relativePoses[_hipsIndex] = parentAbsPose.inverse() * absPose; - _relativePoses[_hipsIndex].scale() = glm::vec3(1.0f); + //_relativePoses[_hipsIndex] = parentAbsPose.inverse() * absPose; + //_relativePoses[_hipsIndex].scale() = glm::vec3(1.0f); } // if there is an active jointChainInfo for the hips store the post shifted hips into it. @@ -1041,10 +1041,13 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars preconditionRelativePosesToAvoidLimbLock(context, targets); - solve(context, targets, dt, jointChainInfoVec); + //qCDebug(animation) << "hips before ccd" << _relativePoses[_hipsIndex]; + //solve(context, targets, dt, jointChainInfoVec); + //qCDebug(animation) << "hips after ccd" << _relativePoses[_hipsIndex]; + } } - + if (context.getEnableDebugDrawIKConstraints()) { debugDrawConstraints(context); } diff --git a/libraries/animation/src/AnimSplineIK.cpp b/libraries/animation/src/AnimSplineIK.cpp index fe54aa5fe7..23bf8b2800 100644 --- a/libraries/animation/src/AnimSplineIK.cpp +++ b/libraries/animation/src/AnimSplineIK.cpp @@ -48,6 +48,22 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const // evalute underPoses AnimPoseVec underPoses = _children[0]->evaluate(animVars, context, dt, triggersOut); _poses = underPoses; + + // now we override the hips relative pose based on the hips target that has been set. + //////////////////////////////////////////////////// + if (_poses.size() > 0) { + AnimPose hipsUnderPose = _skeleton->getAbsolutePose(_hipsIndex, _poses); + glm::quat hipsTargetRotation = animVars.lookupRigToGeometry("hipsRotation", hipsUnderPose.rot()); + glm::vec3 hipsTargetTranslation = animVars.lookupRigToGeometry("hipsPosition", hipsUnderPose.trans()); + AnimPose absHipsTargetPose(hipsTargetRotation, hipsTargetTranslation); + + int hipsParentIndex = _skeleton->getParentIndex(_hipsIndex); + AnimPose hipsParentAbsPose = _skeleton->getAbsolutePose(hipsParentIndex, _poses); + + _poses[_hipsIndex] = hipsParentAbsPose.inverse() * absHipsTargetPose; + _poses[_hipsIndex].scale() = glm::vec3(1.0f); + } + ////////////////////////////////////////////////////////////////////////////////// // check to see if we actually need absolute poses. AnimPoseVec absolutePoses; @@ -65,7 +81,7 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const AnimPose origSpine1 = _skeleton->getAbsolutePose(_skeleton->nameToJointIndex("Spine1"),_poses); //origSpine2PoseRel = origSpine1.inverse() * origSpine2; //qCDebug(animation) << "origSpine2Pose: " << origSpine2Pose.rot(); - qCDebug(animation) << "original relative spine2 " << origSpine2PoseAbs; + //qCDebug(animation) << "original relative spine2 " << origSpine2PoseAbs; } @@ -116,8 +132,8 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const targetSpine2 = AnimPose(rotation3, afterSolveSpine2.trans()); finalSpine2 = afterSolveSpine1.inverse() * targetSpine2; - qCDebug(animation) << "relative spine2 after solve" << afterSolveSpine2Rel; - qCDebug(animation) << "relative spine2 orig" << originalSpine2Relative; + //qCDebug(animation) << "relative spine2 after solve" << afterSolveSpine2Rel; + //qCDebug(animation) << "relative spine2 orig" << originalSpine2Relative; AnimPose latestSpine2Relative(originalSpine2Relative.rot(), afterSolveSpine2Rel.trans()); //jointChain.setRelativePoseAtJointIndex(jointIndex2, finalSpine2); jointChain.outputRelativePoses(_poses); @@ -146,7 +162,7 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const glm::quat rotation2 = animVars.lookupRigToGeometry("spine2Rotation", absPose2.rot()); glm::vec3 translation2 = animVars.lookupRigToGeometry("spine2Position", absPose2.trans()); float weight2 = animVars.lookup("spine2Weight", "2.0"); - qCDebug(animation) << "rig to geometry" << rotation2; + // qCDebug(animation) << "rig to geometry" << rotation2; //target2.setPose(rotation2, translation2); target2.setPose(targetSpine2.rot(), targetSpine2.trans()); diff --git a/libraries/animation/src/AnimTwoBoneIK.cpp b/libraries/animation/src/AnimTwoBoneIK.cpp index 5df98df969..31397157ac 100644 --- a/libraries/animation/src/AnimTwoBoneIK.cpp +++ b/libraries/animation/src/AnimTwoBoneIK.cpp @@ -79,6 +79,7 @@ const AnimPoseVec& AnimTwoBoneIK::evaluate(const AnimVariantMap& animVars, const // determine if we should interpolate bool enabled = animVars.lookup(_enabledVar, _enabled); + // qCDebug(animation) << "two bone var " << _enabledVar; if (enabled != _enabled) { AnimChain poseChain; poseChain.buildFromRelativePoses(_skeleton, _poses, _tipJointIndex); @@ -91,7 +92,7 @@ const AnimPoseVec& AnimTwoBoneIK::evaluate(const AnimVariantMap& animVars, const _enabled = enabled; // don't build chains or do IK if we are disabled & not interping. - if (_interpType == InterpType::None){// && !enabled) { + if (_interpType == InterpType::None && !enabled) { _poses = underPoses; return _poses; } @@ -122,6 +123,7 @@ const AnimPoseVec& AnimTwoBoneIK::evaluate(const AnimVariantMap& animVars, const // First look in the triggers then look in the animVars, so we can follow output joints underneath us in the anim graph AnimPose targetPose(tipPose); if (triggersOut.hasKey(endEffectorRotationVar)) { + qCDebug(animation) << " end effector variable " << endEffectorRotationVar << " is " << triggersOut.lookupRigToGeometry(endEffectorRotationVar, tipPose.rot()); targetPose.rot() = triggersOut.lookupRigToGeometry(endEffectorRotationVar, tipPose.rot()); } else if (animVars.hasKey(endEffectorRotationVar)) { targetPose.rot() = animVars.lookupRigToGeometry(endEffectorRotationVar, tipPose.rot()); diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index e1db2fef8f..a34af09b27 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1541,7 +1541,7 @@ void Rig::updateHands2(bool leftHandEnabled, bool rightHandEnabled, bool headEna int handJointIndex = _animSkeleton->nameToJointIndex("LeftHand"); int elbowJointIndex = _animSkeleton->nameToJointIndex("LeftForeArm"); int shoulderJointIndex = _animSkeleton->nameToJointIndex("LeftArm"); - int oppositeArmJointIndex = _animSkeleton->nameToJointIndex("LeftArm"); + int oppositeArmJointIndex = _animSkeleton->nameToJointIndex("RightArm"); glm::vec3 poleVector; bool usePoleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, shoulderJointIndex, oppositeArmJointIndex, poleVector); glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector); @@ -1580,7 +1580,7 @@ void Rig::updateHands2(bool leftHandEnabled, bool rightHandEnabled, bool headEna int handJointIndex = _animSkeleton->nameToJointIndex("RightHand"); int elbowJointIndex = _animSkeleton->nameToJointIndex("RightForeArm"); int shoulderJointIndex = _animSkeleton->nameToJointIndex("RightArm"); - int oppositeArmJointIndex = _animSkeleton->nameToJointIndex("RightArm"); + int oppositeArmJointIndex = _animSkeleton->nameToJointIndex("LeftArm"); glm::vec3 poleVector; bool usePoleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, shoulderJointIndex, oppositeArmJointIndex, poleVector); glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector); @@ -1856,10 +1856,10 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo updateHead(_headEnabled, hipsEnabled, params.primaryControllerPoses[PrimaryControllerType_Head]); - updateHands(leftHandEnabled, rightHandEnabled, hipsEnabled, hipsEstimated, leftArmEnabled, rightArmEnabled, _headEnabled, dt, - params.primaryControllerPoses[PrimaryControllerType_LeftHand], params.primaryControllerPoses[PrimaryControllerType_RightHand], - params.hipsShapeInfo, params.spineShapeInfo, params.spine1ShapeInfo, params.spine2ShapeInfo, - params.rigToSensorMatrix, sensorToRigMatrix); + //updateHands(leftHandEnabled, rightHandEnabled, hipsEnabled, hipsEstimated, leftArmEnabled, rightArmEnabled, _headEnabled, dt, + // params.primaryControllerPoses[PrimaryControllerType_LeftHand], params.primaryControllerPoses[PrimaryControllerType_RightHand], + // params.hipsShapeInfo, params.spineShapeInfo, params.spine1ShapeInfo, params.spine2ShapeInfo, + // params.rigToSensorMatrix, sensorToRigMatrix); updateHands2(leftHandEnabled, rightHandEnabled, _headEnabled, params.primaryControllerPoses[PrimaryControllerType_LeftHand], params.primaryControllerPoses[PrimaryControllerType_RightHand], @@ -1940,7 +1940,7 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo std::shared_ptr ikNode = getAnimInverseKinematicsNode(); for (int i = 0; i < (int)NumSecondaryControllerTypes; i++) { int index = indexOfJoint(secondaryControllerJointNames[i]); - if (index >= 0) { + if ((index >= 0) && (ikNode)) { if (params.secondaryControllerFlags[i] & (uint8_t)ControllerFlags::Enabled) { ikNode->setSecondaryTargetInRigFrame(index, params.secondaryControllerPoses[i]); } else { From dbd03e2eb411b297587fde8ea9d44ee5cc81aff3 Mon Sep 17 00:00:00 2001 From: amantley Date: Tue, 22 Jan 2019 11:53:18 -0800 Subject: [PATCH 012/474] got the spline arms and legs working without any inverse kinematic node in the json, using _withIKnode.json --- .../avatar/avatar-animation_withIKNode.json | 2240 +++++++++++++++++ .../animation/src/AnimInverseKinematics.cpp | 5 +- libraries/animation/src/AnimSplineIK.cpp | 15 + libraries/animation/src/AnimSplineIK.h | 2 + 4 files changed, 2260 insertions(+), 2 deletions(-) create mode 100644 interface/resources/avatar/avatar-animation_withIKNode.json diff --git a/interface/resources/avatar/avatar-animation_withIKNode.json b/interface/resources/avatar/avatar-animation_withIKNode.json new file mode 100644 index 0000000000..8a66ed21b3 --- /dev/null +++ b/interface/resources/avatar/avatar-animation_withIKNode.json @@ -0,0 +1,2240 @@ +{ + "version": "1.1", + "root": { + "id": "userAnimStateMachine", + "type": "stateMachine", + "data": { + "currentState": "userAnimNone", + "states": [ + { + "id": "userAnimNone", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { + "var": "userAnimA", + "state": "userAnimA" + }, + { + "var": "userAnimB", + "state": "userAnimB" + } + ] + }, + { + "id": "userAnimA", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { + "var": "userAnimNone", + "state": "userAnimNone" + }, + { + "var": "userAnimB", + "state": "userAnimB" + } + ] + }, + { + "id": "userAnimB", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { + "var": "userAnimNone", + "state": "userAnimNone" + }, + { + "var": "userAnimA", + "state": "userAnimA" + } + ] + } + ] + }, + "children": [ + { + "id": "userAnimNone", + "type": "poleVectorConstraint", + "data": { + "enabled": false, + "referenceVector": [ 0, 0, 1 ], + "baseJointName": "RightUpLeg", + "midJointName": "RightLeg", + "tipJointName": "RightFoot", + "enabledVar": "rightFootPoleVectorEnabled", + "poleVectorVar": "rightFootPoleVector" + }, + "children": [ + { + "id": "rightFootIK", + "type": "twoBoneIK", + "data": { + "alpha": 1.0, + "enabled": false, + "interpDuration": 15, + "baseJointName": "RightUpLeg", + "midJointName": "RightLeg", + "tipJointName": "RightFoot", + "midHingeAxis": [ -1, 0, 0 ], + "alphaVar": "rightFootIKAlpha", + "enabledVar": "rightFootIKEnabled", + "endEffectorRotationVarVar": "rightFootIKRotationVar", + "endEffectorPositionVarVar": "rightFootIKPositionVar" + }, + "children": [ + { + "id": "leftFootPoleVector", + "type": "poleVectorConstraint", + "data": { + "enabled": false, + "referenceVector": [ 0, 0, 1 ], + "baseJointName": "LeftUpLeg", + "midJointName": "LeftLeg", + "tipJointName": "LeftFoot", + "enabledVar": "leftFootPoleVectorEnabled", + "poleVectorVar": "leftFootPoleVector" + }, + "children": [ + { + "id": "leftFootIK", + "type": "twoBoneIK", + "data": { + "alpha": 1.0, + "enabled": false, + "interpDuration": 15, + "baseJointName": "LeftUpLeg", + "midJointName": "LeftLeg", + "tipJointName": "LeftFoot", + "midHingeAxis": [ -1, 0, 0 ], + "alphaVar": "leftFootIKAlpha", + "enabledVar": "leftFootIKEnabled", + "endEffectorRotationVarVar": "leftFootIKRotationVar", + "endEffectorPositionVarVar": "leftFootIKPositionVar" + }, + "children": [ + { + "id": "rightHandPoleVector", + "type": "poleVectorConstraint", + "data": { + "enabled": false, + "referenceVector": [ 0, 0, 1 ], + "baseJointName": "RightArm", + "midJointName": "RightForeArm", + "tipJointName": "RightHand", + "enabledVar": "rightHandPoleVectorEnabled", + "poleVectorVar": "rightHandPoleVector" + }, + "children": [ + { + "id": "rightHandIK", + "type": "twoBoneIK", + "data": { + "alpha": 1.0, + "enabled": false, + "interpDuration": 15, + "baseJointName": "RightArm", + "midJointName": "RightForeArm", + "tipJointName": "RightHand", + "midHingeAxis": [ 0, 0, -1 ], + "alphaVar": "rightHandIKAlpha", + "enabledVar": "rightHandIKEnabled", + "endEffectorRotationVarVar": "rightHandIKRotationVar", + "endEffectorPositionVarVar": "rightHandIKPositionVar" + }, + "children": [ + { + "id": "leftHandPoleVector", + "type": "poleVectorConstraint", + "data": { + "enabled": false, + "referenceVector": [ 0, 0, 1 ], + "baseJointName": "LeftArm", + "midJointName": "LeftForeArm", + "tipJointName": "LeftHand", + "enabledVar": "leftHandPoleVectorEnabled", + "poleVectorVar": "leftHandPoleVector" + }, + "children": [ + { + "id": "leftHandIK", + "type": "twoBoneIK", + "data": { + "alpha": 1.0, + "enabled": false, + "interpDuration": 15, + "baseJointName": "LeftArm", + "midJointName": "LeftForeArm", + "tipJointName": "LeftHand", + "midHingeAxis": [ 0, 0, 1 ], + "alphaVar": "leftHandIKAlpha", + "enabledVar": "leftHandIKEnabled", + "endEffectorRotationVarVar": "leftHandIKRotationVar", + "endEffectorPositionVarVar": "leftHandIKPositionVar" + }, + "children": [ + { + "id": "userSplineIK", + "type": "splineIK", + "data": { + "alpha": 1.0, + "enabled": false, + "interpDuration": 15, + "baseJointName": "Hips", + "tipJointName": "Head", + "alphaVar": "splineIKAlpha", + "enabledVar": "splineIKEnabled", + "endEffectorRotationVarVar": "splineIKRotationVar", + "endEffectorPositionVarVar": "splineIKPositionVar" + }, + "children": [ + { + "id": "ikOverlay", + "type": "overlay", + "data": { + "alpha": 0.0, + "alphaVar": "ikOverlayAlpha", + "boneSet": "fullBody" + }, + "children": [ + { + "id": "defaultPose", + "type": "defaultPose", + "data": { + }, + "children": [] + }, + { + "id": "defaultPoseOverlay", + "type": "overlay", + "data": { + "alpha": 0.0, + "alphaVar": "defaultPoseOverlayAlpha", + "boneSet": "fullBody", + "boneSetVar": "defaultPoseOverlayBoneSet" + }, + "children": [ + { + "id": "defaultPose", + "type": "defaultPose", + "data": { + }, + "children": [] + }, + { + "id": "rightHandOverlay", + "type": "overlay", + "data": { + "alpha": 0.0, + "boneSet": "rightHand", + "alphaVar": "rightHandOverlayAlpha" + }, + "children": [ + { + "id": "rightHandStateMachine", + "type": "stateMachine", + "data": { + "currentState": "rightHandGrasp", + "states": [ + { + "id": "rightHandGrasp", + "interpTarget": 3, + "interpDuration": 3, + "transitions": [ + { + "var": "isRightIndexPoint", + "state": "rightIndexPoint" + }, + { + "var": "isRightThumbRaise", + "state": "rightThumbRaise" + }, + { + "var": "isRightIndexPointAndThumbRaise", + "state": "rightIndexPointAndThumbRaise" + } + ] + }, + { + "id": "rightIndexPoint", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { + "var": "isRightHandGrasp", + "state": "rightHandGrasp" + }, + { + "var": "isRightThumbRaise", + "state": "rightThumbRaise" + }, + { + "var": "isRightIndexPointAndThumbRaise", + "state": "rightIndexPointAndThumbRaise" + } + ] + }, + { + "id": "rightThumbRaise", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { + "var": "isRightHandGrasp", + "state": "rightHandGrasp" + }, + { + "var": "isRightIndexPoint", + "state": "rightIndexPoint" + }, + { + "var": "isRightIndexPointAndThumbRaise", + "state": "rightIndexPointAndThumbRaise" + } + ] + }, + { + "id": "rightIndexPointAndThumbRaise", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { + "var": "isRightHandGrasp", + "state": "rightHandGrasp" + }, + { + "var": "isRightIndexPoint", + "state": "rightIndexPoint" + }, + { + "var": "isRightThumbRaise", + "state": "rightThumbRaise" + } + ] + } + ] + }, + "children": [ + { + "id": "rightHandGrasp", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "rightHandGraspAlpha" + }, + "children": [ + { + "id": "rightHandGraspOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/hydra_pose_open_right.fbx", + "startFrame": 0.0, + "endFrame": 0.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "rightHandGraspClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/hydra_pose_closed_right.fbx", + "startFrame": 0.0, + "endFrame": 0.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "rightIndexPoint", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "rightHandGraspAlpha" + }, + "children": [ + { + "id": "rightIndexPointOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_point_open_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "rightIndexPointClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_point_closed_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "rightThumbRaise", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "rightHandGraspAlpha" + }, + "children": [ + { + "id": "rightThumbRaiseOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_open_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "rightThumbRaiseClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_closed_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "rightIndexPointAndThumbRaise", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "rightHandGraspAlpha" + }, + "children": [ + { + "id": "rightIndexPointAndThumbRaiseOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_point_open_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "rightIndexPointAndThumbRaiseClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_point_closed_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + } + ] + }, + { + "id": "leftHandOverlay", + "type": "overlay", + "data": { + "alpha": 0.0, + "boneSet": "leftHand", + "alphaVar": "leftHandOverlayAlpha" + }, + "children": [ + { + "id": "leftHandStateMachine", + "type": "stateMachine", + "data": { + "currentState": "leftHandGrasp", + "states": [ + { + "id": "leftHandGrasp", + "interpTarget": 3, + "interpDuration": 3, + "transitions": [ + { + "var": "isLeftIndexPoint", + "state": "leftIndexPoint" + }, + { + "var": "isLeftThumbRaise", + "state": "leftThumbRaise" + }, + { + "var": "isLeftIndexPointAndThumbRaise", + "state": "leftIndexPointAndThumbRaise" + } + ] + }, + { + "id": "leftIndexPoint", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { + "var": "isLeftHandGrasp", + "state": "leftHandGrasp" + }, + { + "var": "isLeftThumbRaise", + "state": "leftThumbRaise" + }, + { + "var": "isLeftIndexPointAndThumbRaise", + "state": "leftIndexPointAndThumbRaise" + } + ] + }, + { + "id": "leftThumbRaise", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { + "var": "isLeftHandGrasp", + "state": "leftHandGrasp" + }, + { + "var": "isLeftIndexPoint", + "state": "leftIndexPoint" + }, + { + "var": "isLeftIndexPointAndThumbRaise", + "state": "leftIndexPointAndThumbRaise" + } + ] + }, + { + "id": "leftIndexPointAndThumbRaise", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { + "var": "isLeftHandGrasp", + "state": "leftHandGrasp" + }, + { + "var": "isLeftIndexPoint", + "state": "leftIndexPoint" + }, + { + "var": "isLeftThumbRaise", + "state": "leftThumbRaise" + } + ] + } + ] + }, + "children": [ + { + "id": "leftHandGrasp", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "leftHandGraspAlpha" + }, + "children": [ + { + "id": "leftHandGraspOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/hydra_pose_open_left.fbx", + "startFrame": 0.0, + "endFrame": 0.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "leftHandGraspClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/hydra_pose_closed_left.fbx", + "startFrame": 10.0, + "endFrame": 10.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "leftIndexPoint", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "leftHandGraspAlpha" + }, + "children": [ + { + "id": "leftIndexPointOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_point_open_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "leftIndexPointClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_point_closed_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "leftThumbRaise", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "leftHandGraspAlpha" + }, + "children": [ + { + "id": "leftThumbRaiseOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_open_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "leftThumbRaiseClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_closed_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "leftIndexPointAndThumbRaise", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "leftHandGraspAlpha" + }, + "children": [ + { + "id": "leftIndexPointAndThumbRaiseOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_point_open_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "leftIndexPointAndThumbRaiseClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_point_closed_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + } + ] + }, + { + "id": "mainStateMachine", + "type": "stateMachine", + "data": { + "outputJoints": [ "LeftFoot", "RightFoot" ], + "currentState": "idle", + "states": [ + { + "id": "idle", + "interpTarget": 20, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "idleToWalkFwd", + "interpTarget": 12, + "interpDuration": 8, + "transitions": [ + { + "var": "idleToWalkFwdOnDone", + "state": "WALKFWD" + }, + { + "var": "isNotMoving", + "state": "idle" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "idleSettle", + "interpTarget": 15, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "idleSettleOnDone", + "state": "idle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + } + ] + }, + { + "id": "WALKFWD", + "interpTarget": 35, + "interpDuration": 10, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotMoving", + "state": "idleSettle" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "WALKBWD", + "interpTarget": 35, + "interpDuration": 10, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotMoving", + "state": "idleSettle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "STRAFERIGHT", + "interpTarget": 25, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotMoving", + "state": "idleSettle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "STRAFELEFT", + "interpTarget": 25, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotMoving", + "state": "idleSettle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "turnRight", + "interpTarget": 6, + "interpDuration": 8, + "transitions": [ + { + "var": "isNotTurning", + "state": "idle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "turnLeft", + "interpTarget": 6, + "interpDuration": 8, + "transitions": [ + { + "var": "isNotTurning", + "state": "idle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "strafeRightHmd", + "interpTarget": 5, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotMoving", + "state": "idleSettle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + } + ] + }, + { + "id": "strafeLeftHmd", + "interpTarget": 5, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotMoving", + "state": "idleSettle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + } + ] + }, + { + "id": "fly", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { + "var": "isNotFlying", + "state": "idleSettle" + } + ] + }, + { + "id": "takeoffStand", + "interpTarget": 2, + "interpDuration": 2, + "transitions": [ + { + "var": "isNotTakeoff", + "state": "inAirStand" + } + ] + }, + { + "id": "TAKEOFFRUN", + "interpTarget": 2, + "interpDuration": 2, + "transitions": [ + { + "var": "isNotTakeoff", + "state": "INAIRRUN" + } + ] + }, + { + "id": "inAirStand", + "interpTarget": 3, + "interpDuration": 3, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotInAir", + "state": "landStandImpact" + } + ] + }, + { + "id": "INAIRRUN", + "interpTarget": 3, + "interpDuration": 3, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotInAir", + "state": "WALKFWD" + } + ] + }, + { + "id": "landStandImpact", + "interpTarget": 1, + "interpDuration": 1, + "transitions": [ + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "landStandImpactOnDone", + "state": "landStand" + } + ] + }, + { + "id": "landStand", + "interpTarget": 1, + "interpDuration": 1, + "transitions": [ + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "landStandOnDone", + "state": "idle" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "LANDRUN", + "interpTarget": 2, + "interpDuration": 2, + "transitions": [ + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "landRunOnDone", + "state": "WALKFWD" + } + ] + } + ] + }, + "children": [ + { + "id": "idle", + "type": "stateMachine", + "data": { + "currentState": "idleStand", + "states": [ + { + "id": "idleStand", + "interpTarget": 6, + "interpDuration": 10, + "transitions": [ + { + "var": "isTalking", + "state": "idleTalk" + } + ] + }, + { + "id": "idleTalk", + "interpTarget": 6, + "interpDuration": 10, + "transitions": [ + { + "var": "notIsTalking", + "state": "idleStand" + } + ] + } + ] + }, + "children": [ + { + "id": "idleStand", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle.fbx", + "startFrame": 0.0, + "endFrame": 300.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "idleTalk", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/talk.fbx", + "startFrame": 0.0, + "endFrame": 800.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "WALKFWD", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [ 0.5, 1.8, 2.3, 3.2, 4.5 ], + "alphaVar": "moveForwardAlpha", + "desiredSpeedVar": "moveForwardSpeed" + }, + "children": [ + { + "id": "walkFwdShort_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_short_fwd.fbx", + "startFrame": 0.0, + "endFrame": 39.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkFwdNormal_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_fwd.fbx", + "startFrame": 0.0, + "endFrame": 30.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkFwdFast_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_fwd_fast.fbx", + "startFrame": 0.0, + "endFrame": 25.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkFwdJog_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jog_fwd.fbx", + "startFrame": 0.0, + "endFrame": 25.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkFwdRun_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/run_fwd.fbx", + "startFrame": 0.0, + "endFrame": 21.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "idleToWalkFwd", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle_to_walk.fbx", + "startFrame": 1.0, + "endFrame": 13.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "idleSettle", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/settle_to_idle.fbx", + "startFrame": 1.0, + "endFrame": 59.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "WALKBWD", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [ 0.6, 1.6, 2.3, 3.1 ], + "alphaVar": "moveBackwardAlpha", + "desiredSpeedVar": "moveBackwardSpeed" + }, + "children": [ + { + "id": "walkBwdShort_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_short_bwd.fbx", + "startFrame": 0.0, + "endFrame": 38.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkBwdFast_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_bwd_fast.fbx", + "startFrame": 0.0, + "endFrame": 27.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "jogBwd_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jog_bwd.fbx", + "startFrame": 0.0, + "endFrame": 24.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "runBwd_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/run_bwd.fbx", + "startFrame": 0.0, + "endFrame": 16.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "turnLeft", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/turn_left.fbx", + "startFrame": 0.0, + "endFrame": 32.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "turnRight", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/turn_left.fbx", + "startFrame": 0.0, + "endFrame": 32.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "STRAFELEFT", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [ 0.1, 0.5, 1.0, 2.6, 3.0 ], + "alphaVar": "moveLateralAlpha", + "desiredSpeedVar": "moveLateralSpeed" + }, + "children": [ + { + "id": "strafeLeftShortStep_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_short_left.fbx", + "startFrame": 0.0, + "endFrame": 29.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftStep_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left.fbx", + "startFrame": 0.0, + "endFrame": 20.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftWalk_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_left.fbx", + "startFrame": 0.0, + "endFrame": 35.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftWalkFast_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_left_fast.fbx", + "startFrame": 0.0, + "endFrame": 21.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftJog_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jog_left.fbx", + "startFrame": 0.0, + "endFrame": 24.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "STRAFERIGHT", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [ 0.1, 0.5, 1.0, 2.6, 3.0 ], + "alphaVar": "moveLateralAlpha", + "desiredSpeedVar": "moveLateralSpeed" + }, + "children": [ + { + "id": "strafeRightShortStep_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_short_left.fbx", + "startFrame": 0.0, + "endFrame": 29.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "strafeRightStep_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left.fbx", + "startFrame": 0.0, + "endFrame": 20.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "strafeRightWalk_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_left.fbx", + "startFrame": 0.0, + "endFrame": 35.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "strafeRightFast_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_left_fast.fbx", + "startFrame": 0.0, + "endFrame": 21.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "strafeRightJog_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jog_left.fbx", + "startFrame": 0.0, + "endFrame": 24.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + } + ] + }, + { + "id": "strafeLeftHmd", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [ 0, 0.5, 2.5 ], + "alphaVar": "moveLateralAlpha", + "desiredSpeedVar": "moveLateralSpeed" + }, + "children": [ + { + "id": "stepLeftShort_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_short_left.fbx", + "startFrame": 0.0, + "endFrame": 29.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "stepLeft_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left.fbx", + "startFrame": 0.0, + "endFrame": 20.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftAnim_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left_fast.fbx", + "startFrame": 0.0, + "endFrame": 16.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "strafeRightHmd", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [ 0, 0.5, 2.5 ], + "alphaVar": "moveLateralAlpha", + "desiredSpeedVar": "moveLateralSpeed" + }, + "children": [ + { + "id": "stepRightShort_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_short_left.fbx", + "startFrame": 0.0, + "endFrame": 29.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "stepRight_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left.fbx", + "startFrame": 0.0, + "endFrame": 20.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "strafeRightAnim_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left_fast.fbx", + "startFrame": 0.0, + "endFrame": 16.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + } + ] + }, + { + "id": "fly", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/fly.fbx", + "startFrame": 1.0, + "endFrame": 80.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "takeoffStand", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_launch.fbx", + "startFrame": 2.0, + "endFrame": 16.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "TAKEOFFRUN", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "startFrame": 4.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirStand", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "inAirAlpha" + }, + "children": [ + { + "id": "inAirStandPreApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_apex.fbx", + "startFrame": 0.0, + "endFrame": 0.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirStandApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_apex.fbx", + "startFrame": 1.0, + "endFrame": 1.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirStandPostApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_apex.fbx", + "startFrame": 2.0, + "endFrame": 2.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + } + ] + }, + { + "id": "INAIRRUN", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "inAirAlpha" + }, + "children": [ + { + "id": "inAirRunPreApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "startFrame": 16.0, + "endFrame": 16.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirRunApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "startFrame": 22.0, + "endFrame": 22.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirRunPostApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "startFrame": 33.0, + "endFrame": 33.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + } + ] + }, + { + "id": "landStandImpact", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_land_settle.fbx", + "startFrame": 1.0, + "endFrame": 6.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "landStand", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_land_settle.fbx", + "startFrame": 6.0, + "endFrame": 68.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "LANDRUN", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "startFrame": 29.0, + "endFrame": 40.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "id": "userAnimA", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle.fbx", + "startFrame": 0.0, + "endFrame": 90.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "userAnimB", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle.fbx", + "startFrame": 0.0, + "endFrame": 90.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + } +} diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 8d2ba0fd78..7a29ff4001 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -886,7 +886,8 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars if (dt > MAX_OVERLAY_DT) { dt = MAX_OVERLAY_DT; } - + loadPoses(underPoses); + /* if (_relativePoses.size() != underPoses.size()) { loadPoses(underPoses); } else { @@ -1054,7 +1055,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars } processOutputJoints(triggersOut); - + */ return _relativePoses; } diff --git a/libraries/animation/src/AnimSplineIK.cpp b/libraries/animation/src/AnimSplineIK.cpp index 23bf8b2800..847d512a51 100644 --- a/libraries/animation/src/AnimSplineIK.cpp +++ b/libraries/animation/src/AnimSplineIK.cpp @@ -40,6 +40,12 @@ AnimSplineIK::~AnimSplineIK() { } +//virtual +const AnimPoseVec& AnimSplineIK::overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut, const AnimPoseVec& underPoses) { + loadPoses(underPoses); + return _poses; +} + const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { assert(_children.size() == 1); if (_children.size() != 1) { @@ -444,4 +450,13 @@ void AnimSplineIK::computeAndCacheSplineJointInfosForIKTarget(const AnimContext& } _splineJointInfoMap[target.getIndex()] = splineJointInfoVec; +} + +void AnimSplineIK ::loadPoses(const AnimPoseVec& poses) { + assert(_skeleton && ((poses.size() == 0) || (_skeleton->getNumJoints() == (int)poses.size()))); + if (_skeleton->getNumJoints() == (int)poses.size()) { + _poses = poses; + } else { + _poses.clear(); + } } \ No newline at end of file diff --git a/libraries/animation/src/AnimSplineIK.h b/libraries/animation/src/AnimSplineIK.h index d303f81053..12f43fa680 100644 --- a/libraries/animation/src/AnimSplineIK.h +++ b/libraries/animation/src/AnimSplineIK.h @@ -25,6 +25,7 @@ public: virtual ~AnimSplineIK() override; virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override; + virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut, const AnimPoseVec& underPoses) override; protected: @@ -36,6 +37,7 @@ protected: }; void computeAbsolutePoses(AnimPoseVec& absolutePoses) const; + void loadPoses(const AnimPoseVec& poses); // for AnimDebugDraw rendering virtual const AnimPoseVec& getPosesInternal() const override; From cdd03646c2ab95b5de2af30720a3735ca5ba93c1 Mon Sep 17 00:00:00 2001 From: amantley Date: Wed, 23 Jan 2019 17:22:13 -0800 Subject: [PATCH 013/474] latest spline code with ik node removed started cleanup --- .../resources/avatar/avatar-animation.json | 3086 +++++++---------- .../avatar/avatar-animation_withIKNode.json | 4 +- .../animation/src/AnimInverseKinematics.cpp | 11 +- libraries/animation/src/AnimSplineIK.cpp | 158 +- libraries/animation/src/Rig.cpp | 19 +- 5 files changed, 1321 insertions(+), 1957 deletions(-) diff --git a/interface/resources/avatar/avatar-animation.json b/interface/resources/avatar/avatar-animation.json index f68d7e61d4..50fe5019f9 100644 --- a/interface/resources/avatar/avatar-animation.json +++ b/interface/resources/avatar/avatar-animation.json @@ -38,256 +38,419 @@ "children": [ { "id": "userAnimNone", - "type": "splineIK", + "type": "poleVectorConstraint", "data": { - "alpha": 1.0, "enabled": false, - "interpDuration": 15, - "baseJointName": "Hips", - "tipJointName": "Head", - "alphaVar": "splineIKAlpha", - "enabledVar": "splineIKEnabled", - "endEffectorRotationVarVar": "splineIKRotationVar", - "endEffectorPositionVarVar": "splineIKPositionVar" + "referenceVector": [0, 0, 1], + "baseJointName": "RightUpLeg", + "midJointName": "RightLeg", + "tipJointName": "RightFoot", + "enabledVar": "rightFootPoleVectorEnabled", + "poleVectorVar": "rightFootPoleVector" }, "children": [ { - "id": "rightFootPoleVector", - "type": "poleVectorConstraint", + "id": "rightFootIK", + "type": "twoBoneIK", "data": { + "alpha": 1.0, "enabled": false, - "referenceVector": [ 0, 0, 1 ], + "interpDuration": 15, "baseJointName": "RightUpLeg", "midJointName": "RightLeg", "tipJointName": "RightFoot", - "enabledVar": "rightFootPoleVectorEnabled", - "poleVectorVar": "rightFootPoleVector" + "midHingeAxis": [-1, 0, 0], + "alphaVar": "rightFootIKAlpha", + "enabledVar": "rightFootIKEnabled", + "endEffectorRotationVarVar": "rightFootIKRotationVar", + "endEffectorPositionVarVar": "rightFootIKPositionVar" }, "children": [ { - "id": "rightFootIK", - "type": "twoBoneIK", + "id": "leftFootPoleVector", + "type": "poleVectorConstraint", "data": { - "alpha": 1.0, "enabled": false, - "interpDuration": 15, - "baseJointName": "RightUpLeg", - "midJointName": "RightLeg", - "tipJointName": "RightFoot", - "midHingeAxis": [ -1, 0, 0 ], - "alphaVar": "rightFootIKAlpha", - "enabledVar": "rightFootIKEnabled", - "endEffectorRotationVarVar": "rightFootIKRotationVar", - "endEffectorPositionVarVar": "rightFootIKPositionVar" + "referenceVector": [0, 0, 1], + "baseJointName": "LeftUpLeg", + "midJointName": "LeftLeg", + "tipJointName": "LeftFoot", + "enabledVar": "leftFootPoleVectorEnabled", + "poleVectorVar": "leftFootPoleVector" }, "children": [ { - "id": "leftFootPoleVector", - "type": "poleVectorConstraint", + "id": "leftFootIK", + "type": "twoBoneIK", "data": { + "alpha": 1.0, "enabled": false, - "referenceVector": [ 0, 0, 1 ], + "interpDuration": 15, "baseJointName": "LeftUpLeg", "midJointName": "LeftLeg", "tipJointName": "LeftFoot", - "enabledVar": "leftFootPoleVectorEnabled", - "poleVectorVar": "leftFootPoleVector" + "midHingeAxis": [-1, 0, 0], + "alphaVar": "leftFootIKAlpha", + "enabledVar": "leftFootIKEnabled", + "endEffectorRotationVarVar": "leftFootIKRotationVar", + "endEffectorPositionVarVar": "leftFootIKPositionVar" }, "children": [ { - "id": "leftFootIK", - "type": "twoBoneIK", + "id": "ikOverlay", + "type": "overlay", "data": { "alpha": 1.0, - "enabled": false, - "interpDuration": 15, - "baseJointName": "LeftUpLeg", - "midJointName": "LeftLeg", - "tipJointName": "LeftFoot", - "midHingeAxis": [ -1, 0, 0 ], - "alphaVar": "leftFootIKAlpha", - "enabledVar": "leftFootIKEnabled", - "endEffectorRotationVarVar": "leftFootIKRotationVar", - "endEffectorPositionVarVar": "leftFootIKPositionVar" + "alphaVar": "ikOverlayAlpha", + "boneSet": "fullBody" }, "children": [ { - "id": "ikOverlay", + "id": "ik", + "type": "inverseKinematics", + "data": { + "solutionSource": "relaxToUnderPoses", + "solutionSourceVar": "solutionSource", + "targets": [ + { + "jointName": "Hips", + "positionVar": "hipsPosition", + "rotationVar": "hipsRotation", + "typeVar": "hipsType", + "weightVar": "hipsWeight", + "weight": 1.0, + "flexCoefficients": [1] + }, + { + "jointName": "RightHand", + "positionVar": "rightHandPosition", + "rotationVar": "rightHandRotation", + "typeVar": "rightHandType", + "weightVar": "rightHandWeight", + "weight": 1.0, + "flexCoefficients": [1, 0.5, 0.5, 0.2, 0.01, 0.005, 0.001, 0.0, 0.0], + "poleVectorEnabledVar": "rightHandPoleVectorEnabled", + "poleReferenceVectorVar": "rightHandPoleReferenceVector", + "poleVectorVar": "rightHandPoleVector" + }, + { + "jointName": "LeftHand", + "positionVar": "leftHandPosition", + "rotationVar": "leftHandRotation", + "typeVar": "leftHandType", + "weightVar": "leftHandWeight", + "weight": 1.0, + "flexCoefficients": [1, 0.5, 0.5, 0.2, 0.01, 0.005, 0.001, 0.0, 0.0], + "poleVectorEnabledVar": "leftHandPoleVectorEnabled", + "poleReferenceVectorVar": "leftHandPoleReferenceVector", + "poleVectorVar": "leftHandPoleVector" + }, + { + "jointName": "Spine2", + "positionVar": "spine2Position", + "rotationVar": "spine2Rotation", + "typeVar": "spine2Type", + "weightVar": "spine2Weight", + "weight": 2.0, + "flexCoefficients": [1.0, 0.5, 0.25] + }, + { + "jointName": "Head", + "positionVar": "headPosition", + "rotationVar": "headRotation", + "typeVar": "headType", + "weightVar": "headWeight", + "weight": 4.0, + "flexCoefficients": [1, 0.5, 0.25, 0.2, 0.1] + } + ] + }, + "children": [] + }, + { + "id": "defaultPoseOverlay", "type": "overlay", "data": { - "alpha": 1.0, - "alphaVar": "ikOverlayAlpha", - "boneSet": "fullBody" + "alpha": 0.0, + "alphaVar": "defaultPoseOverlayAlpha", + "boneSet": "fullBody", + "boneSetVar": "defaultPoseOverlayBoneSet" }, "children": [ { - "id": "ik", - "type": "inverseKinematics", + "id": "defaultPose", + "type": "defaultPose", "data": { - "solutionSource": "relaxToUnderPoses", - "solutionSourceVar": "solutionSource", - "targets": [ - { - "jointName": "Hips", - "positionVar": "hipsPosition", - "rotationVar": "hipsRotation", - "typeVar": "hipsType", - "weightVar": "hipsWeight", - "weight": 1.0, - "flexCoefficients": [ 1 ] - }, - { - "jointName": "RightHand", - "positionVar": "rightHandPosition", - "rotationVar": "rightHandRotation", - "typeVar": "rightHandType", - "weightVar": "rightHandWeight", - "weight": 1.0, - "flexCoefficients": [ 1, 0.5, 0.5, 0.2, 0.01, 0.005, 0.001, 0.0, 0.0 ], - "poleVectorEnabledVar": "rightHandPoleVectorEnabled", - "poleReferenceVectorVar": "rightHandPoleReferenceVector", - "poleVectorVar": "rightHandPoleVector" - }, - { - "jointName": "LeftHand", - "positionVar": "leftHandPosition", - "rotationVar": "leftHandRotation", - "typeVar": "leftHandType", - "weightVar": "leftHandWeight", - "weight": 1.0, - "flexCoefficients": [ 1, 0.5, 0.5, 0.2, 0.01, 0.005, 0.001, 0.0, 0.0 ], - "poleVectorEnabledVar": "leftHandPoleVectorEnabled", - "poleReferenceVectorVar": "leftHandPoleReferenceVector", - "poleVectorVar": "leftHandPoleVector" - } - ] }, "children": [] }, { - "id": "defaultPoseOverlay", + "id": "rightHandOverlay", "type": "overlay", "data": { "alpha": 0.0, - "alphaVar": "defaultPoseOverlayAlpha", - "boneSet": "fullBody", - "boneSetVar": "defaultPoseOverlayBoneSet" + "boneSet": "rightHand", + "alphaVar": "rightHandOverlayAlpha" }, "children": [ { - "id": "defaultPose", - "type": "defaultPose", + "id": "rightHandStateMachine", + "type": "stateMachine", "data": { - }, - "children": [] - }, - { - "id": "rightHandOverlay", - "type": "overlay", - "data": { - "alpha": 0.0, - "boneSet": "rightHand", - "alphaVar": "rightHandOverlayAlpha" + "currentState": "rightHandGrasp", + "states": [ + { + "id": "rightHandGrasp", + "interpTarget": 3, + "interpDuration": 3, + "transitions": [ + { "var": "isRightIndexPoint", "state": "rightIndexPoint" }, + { "var": "isRightThumbRaise", "state": "rightThumbRaise" }, + { "var": "isRightIndexPointAndThumbRaise", "state": "rightIndexPointAndThumbRaise" } + ] + }, + { + "id": "rightIndexPoint", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { "var": "isRightHandGrasp", "state": "rightHandGrasp" }, + { "var": "isRightThumbRaise", "state": "rightThumbRaise" }, + { "var": "isRightIndexPointAndThumbRaise", "state": "rightIndexPointAndThumbRaise" } + ] + }, + { + "id": "rightThumbRaise", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { "var": "isRightHandGrasp", "state": "rightHandGrasp" }, + { "var": "isRightIndexPoint", "state": "rightIndexPoint" }, + { "var": "isRightIndexPointAndThumbRaise", "state": "rightIndexPointAndThumbRaise" } + ] + }, + { + "id": "rightIndexPointAndThumbRaise", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { "var": "isRightHandGrasp", "state": "rightHandGrasp" }, + { "var": "isRightIndexPoint", "state": "rightIndexPoint" }, + { "var": "isRightThumbRaise", "state": "rightThumbRaise" } + ] + } + ] }, "children": [ { - "id": "rightHandStateMachine", + "id": "rightHandGrasp", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "rightHandGraspAlpha" + }, + "children": [ + { + "id": "rightHandGraspOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/hydra_pose_open_right.fbx", + "startFrame": 0.0, + "endFrame": 0.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "rightHandGraspClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/hydra_pose_closed_right.fbx", + "startFrame": 0.0, + "endFrame": 0.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "rightIndexPoint", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "rightHandGraspAlpha" + }, + "children": [ + { + "id": "rightIndexPointOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_point_open_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "rightIndexPointClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_point_closed_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "rightThumbRaise", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "rightHandGraspAlpha" + }, + "children": [ + { + "id": "rightThumbRaiseOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_open_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "rightThumbRaiseClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_closed_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "rightIndexPointAndThumbRaise", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "rightHandGraspAlpha" + }, + "children": [ + { + "id": "rightIndexPointAndThumbRaiseOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_point_open_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "rightIndexPointAndThumbRaiseClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_point_closed_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + } + ] + }, + { + "id": "leftHandOverlay", + "type": "overlay", + "data": { + "alpha": 0.0, + "boneSet": "leftHand", + "alphaVar": "leftHandOverlayAlpha" + }, + "children": [ + { + "id": "leftHandStateMachine", "type": "stateMachine", "data": { - "currentState": "rightHandGrasp", + "currentState": "leftHandGrasp", "states": [ { - "id": "rightHandGrasp", + "id": "leftHandGrasp", "interpTarget": 3, "interpDuration": 3, "transitions": [ - { - "var": "isRightIndexPoint", - "state": "rightIndexPoint" - }, - { - "var": "isRightThumbRaise", - "state": "rightThumbRaise" - }, - { - "var": "isRightIndexPointAndThumbRaise", - "state": "rightIndexPointAndThumbRaise" - } + { "var": "isLeftIndexPoint", "state": "leftIndexPoint" }, + { "var": "isLeftThumbRaise", "state": "leftThumbRaise" }, + { "var": "isLeftIndexPointAndThumbRaise", "state": "leftIndexPointAndThumbRaise" } ] }, { - "id": "rightIndexPoint", + "id": "leftIndexPoint", "interpTarget": 15, "interpDuration": 3, "transitions": [ - { - "var": "isRightHandGrasp", - "state": "rightHandGrasp" - }, - { - "var": "isRightThumbRaise", - "state": "rightThumbRaise" - }, - { - "var": "isRightIndexPointAndThumbRaise", - "state": "rightIndexPointAndThumbRaise" - } + { "var": "isLeftHandGrasp", "state": "leftHandGrasp" }, + { "var": "isLeftThumbRaise", "state": "leftThumbRaise" }, + { "var": "isLeftIndexPointAndThumbRaise", "state": "leftIndexPointAndThumbRaise" } ] }, { - "id": "rightThumbRaise", + "id": "leftThumbRaise", "interpTarget": 15, "interpDuration": 3, "transitions": [ - { - "var": "isRightHandGrasp", - "state": "rightHandGrasp" - }, - { - "var": "isRightIndexPoint", - "state": "rightIndexPoint" - }, - { - "var": "isRightIndexPointAndThumbRaise", - "state": "rightIndexPointAndThumbRaise" - } + { "var": "isLeftHandGrasp", "state": "leftHandGrasp" }, + { "var": "isLeftIndexPoint", "state": "leftIndexPoint" }, + { "var": "isLeftIndexPointAndThumbRaise", "state": "leftIndexPointAndThumbRaise" } ] }, { - "id": "rightIndexPointAndThumbRaise", + "id": "leftIndexPointAndThumbRaise", "interpTarget": 15, "interpDuration": 3, "transitions": [ - { - "var": "isRightHandGrasp", - "state": "rightHandGrasp" - }, - { - "var": "isRightIndexPoint", - "state": "rightIndexPoint" - }, - { - "var": "isRightThumbRaise", - "state": "rightThumbRaise" - } + { "var": "isLeftHandGrasp", "state": "leftHandGrasp" }, + { "var": "isLeftIndexPoint", "state": "leftIndexPoint" }, + { "var": "isLeftThumbRaise", "state": "leftThumbRaise" } ] } ] }, "children": [ { - "id": "rightHandGrasp", + "id": "leftHandGrasp", "type": "blendLinear", "data": { "alpha": 0.0, - "alphaVar": "rightHandGraspAlpha" + "alphaVar": "leftHandGraspAlpha" }, "children": [ { - "id": "rightHandGraspOpen", + "id": "leftHandGraspOpen", "type": "clip", "data": { - "url": "qrc:///avatar/animations/hydra_pose_open_right.fbx", + "url": "qrc:///avatar/animations/hydra_pose_open_left.fbx", "startFrame": 0.0, "endFrame": 0.0, "timeScale": 1.0, @@ -296,12 +459,12 @@ "children": [] }, { - "id": "rightHandGraspClosed", + "id": "leftHandGraspClosed", "type": "clip", "data": { - "url": "qrc:///avatar/animations/hydra_pose_closed_right.fbx", - "startFrame": 0.0, - "endFrame": 0.0, + "url": "qrc:///avatar/animations/hydra_pose_closed_left.fbx", + "startFrame": 10.0, + "endFrame": 10.0, "timeScale": 1.0, "loopFlag": true }, @@ -310,18 +473,18 @@ ] }, { - "id": "rightIndexPoint", + "id": "leftIndexPoint", "type": "blendLinear", - "data": { + "data": { "alpha": 0.0, - "alphaVar": "rightHandGraspAlpha" + "alphaVar": "leftHandGraspAlpha" }, "children": [ { - "id": "rightIndexPointOpen", + "id": "leftIndexPointOpen", "type": "clip", "data": { - "url": "qrc:///avatar/animations/touch_point_open_right.fbx", + "url": "qrc:///avatar/animations/touch_point_open_left.fbx", "startFrame": 15.0, "endFrame": 15.0, "timeScale": 1.0, @@ -330,10 +493,10 @@ "children": [] }, { - "id": "rightIndexPointClosed", + "id": "leftIndexPointClosed", "type": "clip", "data": { - "url": "qrc:///avatar/animations/touch_point_closed_right.fbx", + "url": "qrc:///avatar/animations/touch_point_closed_left.fbx", "startFrame": 15.0, "endFrame": 15.0, "timeScale": 1.0, @@ -344,18 +507,18 @@ ] }, { - "id": "rightThumbRaise", + "id": "leftThumbRaise", "type": "blendLinear", - "data": { + "data": { "alpha": 0.0, - "alphaVar": "rightHandGraspAlpha" + "alphaVar": "leftHandGraspAlpha" }, "children": [ { - "id": "rightThumbRaiseOpen", + "id": "leftThumbRaiseOpen", "type": "clip", "data": { - "url": "qrc:///avatar/animations/touch_thumb_open_right.fbx", + "url": "qrc:///avatar/animations/touch_thumb_open_left.fbx", "startFrame": 15.0, "endFrame": 15.0, "timeScale": 1.0, @@ -364,10 +527,10 @@ "children": [] }, { - "id": "rightThumbRaiseClosed", + "id": "leftThumbRaiseClosed", "type": "clip", "data": { - "url": "qrc:///avatar/animations/touch_thumb_closed_right.fbx", + "url": "qrc:///avatar/animations/touch_thumb_closed_left.fbx", "startFrame": 15.0, "endFrame": 15.0, "timeScale": 1.0, @@ -378,18 +541,18 @@ ] }, { - "id": "rightIndexPointAndThumbRaise", + "id": "leftIndexPointAndThumbRaise", "type": "blendLinear", - "data": { + "data": { "alpha": 0.0, - "alphaVar": "rightHandGraspAlpha" + "alphaVar": "leftHandGraspAlpha" }, "children": [ { - "id": "rightIndexPointAndThumbRaiseOpen", + "id": "leftIndexPointAndThumbRaiseOpen", "type": "clip", "data": { - "url": "qrc:///avatar/animations/touch_thumb_point_open_right.fbx", + "url": "qrc:///avatar/animations/touch_thumb_point_open_left.fbx", "startFrame": 15.0, "endFrame": 15.0, "timeScale": 1.0, @@ -398,10 +561,10 @@ "children": [] }, { - "id": "rightIndexPointAndThumbRaiseClosed", + "id": "leftIndexPointAndThumbRaiseClosed", "type": "clip", "data": { - "url": "qrc:///avatar/animations/touch_thumb_point_closed_right.fbx", + "url": "qrc:///avatar/animations/touch_thumb_point_closed_left.fbx", "startFrame": 15.0, "endFrame": 15.0, "timeScale": 1.0, @@ -414,1312 +577,654 @@ ] }, { - "id": "leftHandOverlay", - "type": "overlay", + "id": "mainStateMachine", + "type": "stateMachine", "data": { - "alpha": 0.0, - "boneSet": "leftHand", - "alphaVar": "leftHandOverlayAlpha" + "outputJoints": ["LeftFoot", "RightFoot"], + "currentState": "idle", + "states": [ + { + "id": "idle", + "interpTarget": 20, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { "var": "isMovingForward", "state": "WALKFWD" }, + { "var": "isMovingBackward", "state": "WALKBWD" }, + { "var": "isMovingRight", "state": "STRAFERIGHT" }, + { "var": "isMovingLeft", "state": "STRAFELEFT" }, + { "var": "isTurningRight", "state": "turnRight" }, + { "var": "isTurningLeft", "state": "turnLeft" }, + { "var": "isFlying", "state": "fly" }, + { "var": "isTakeoffStand", "state": "takeoffStand" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, + { "var": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "INAIRRUN" }, + { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, + { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } + ] + }, + { + "id": "idleToWalkFwd", + "interpTarget": 12, + "interpDuration": 8, + "transitions": [ + { "var": "idleToWalkFwdOnDone", "state": "WALKFWD" }, + { "var": "isNotMoving", "state": "idle" }, + { "var": "isMovingBackward", "state": "WALKBWD" }, + { "var": "isMovingRight", "state": "STRAFERIGHT" }, + { "var": "isMovingLeft", "state": "STRAFELEFT" }, + { "var": "isTurningRight", "state": "turnRight" }, + { "var": "isTurningLeft", "state": "turnLeft" }, + { "var": "isFlying", "state": "fly" }, + { "var": "isTakeoffStand", "state": "takeoffStand" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, + { "var": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "INAIRRUN" }, + { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, + { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } + ] + }, + { + "id": "idleSettle", + "interpTarget": 15, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + {"var": "idleSettleOnDone", "state": "idle" }, + {"var": "isMovingForward", "state": "WALKFWD" }, + { "var": "isMovingBackward", "state": "WALKBWD" }, + { "var": "isMovingRight", "state": "STRAFERIGHT" }, + { "var": "isMovingLeft", "state": "STRAFELEFT" }, + { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, + { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" }, + { "var": "isTurningRight", "state": "turnRight" }, + { "var": "isTurningLeft", "state": "turnLeft" }, + { "var": "isFlying", "state": "fly" }, + { "var": "isTakeoffStand", "state": "takeoffStand" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, + { "var": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "INAIRRUN" } + ] + }, + { + "id": "WALKFWD", + "interpTarget": 35, + "interpDuration": 10, + "interpType": "snapshotPrev", + "transitions": [ + { "var": "isNotMoving", "state": "idleSettle" }, + { "var": "isMovingBackward", "state": "WALKBWD" }, + { "var": "isMovingRight", "state": "STRAFERIGHT" }, + { "var": "isMovingLeft", "state": "STRAFELEFT" }, + { "var": "isTurningRight", "state": "turnRight" }, + { "var": "isTurningLeft", "state": "turnLeft" }, + { "var": "isFlying", "state": "fly" }, + { "var": "isTakeoffStand", "state": "takeoffStand" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, + { "var": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "INAIRRUN" }, + { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, + { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } + ] + }, + { + "id": "WALKBWD", + "interpTarget": 35, + "interpDuration": 10, + "interpType": "snapshotPrev", + "transitions": [ + { "var": "isNotMoving", "state": "idleSettle" }, + { "var": "isMovingForward", "state": "WALKFWD" }, + { "var": "isMovingRight", "state": "STRAFERIGHT" }, + { "var": "isMovingLeft", "state": "STRAFELEFT" }, + { "var": "isTurningRight", "state": "turnRight" }, + { "var": "isTurningLeft", "state": "turnLeft" }, + { "var": "isFlying", "state": "fly" }, + { "var": "isTakeoffStand", "state": "takeoffStand" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, + { "var": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "INAIRRUN" }, + { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, + { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } + ] + }, + { + "id": "STRAFERIGHT", + "interpTarget": 25, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { "var": "isNotMoving", "state": "idleSettle" }, + { "var": "isMovingForward", "state": "WALKFWD" }, + { "var": "isMovingBackward", "state": "WALKBWD" }, + { "var": "isMovingLeft", "state": "STRAFELEFT" }, + { "var": "isTurningRight", "state": "turnRight" }, + { "var": "isTurningLeft", "state": "turnLeft" }, + { "var": "isFlying", "state": "fly" }, + { "var": "isTakeoffStand", "state": "takeoffStand" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, + { "var": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "INAIRRUN" }, + { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, + { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } + ] + }, + { + "id": "STRAFELEFT", + "interpTarget": 25, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { "var": "isNotMoving", "state": "idleSettle" }, + { "var": "isMovingForward", "state": "WALKFWD" }, + { "var": "isMovingBackward", "state": "WALKBWD" }, + { "var": "isMovingRight", "state": "STRAFERIGHT" }, + { "var": "isTurningRight", "state": "turnRight" }, + { "var": "isTurningLeft", "state": "turnLeft" }, + { "var": "isFlying", "state": "fly" }, + { "var": "isTakeoffStand", "state": "takeoffStand" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, + { "var": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "INAIRRUN" }, + { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, + { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } + ] + }, + { + "id": "turnRight", + "interpTarget": 6, + "interpDuration": 8, + "transitions": [ + { "var": "isNotTurning", "state": "idle" }, + { "var": "isMovingForward", "state": "WALKFWD" }, + { "var": "isMovingBackward", "state": "WALKBWD" }, + { "var": "isMovingRight", "state": "STRAFERIGHT" }, + { "var": "isMovingLeft", "state": "STRAFELEFT" }, + { "var": "isTurningLeft", "state": "turnLeft" }, + { "var": "isFlying", "state": "fly" }, + { "var": "isTakeoffStand", "state": "takeoffStand" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, + { "var": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "INAIRRUN" }, + { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, + { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } + ] + }, + { + "id": "turnLeft", + "interpTarget": 6, + "interpDuration": 8, + "transitions": [ + { "var": "isNotTurning", "state": "idle" }, + { "var": "isMovingForward", "state": "WALKFWD" }, + { "var": "isMovingBackward", "state": "WALKBWD" }, + { "var": "isMovingRight", "state": "STRAFERIGHT" }, + { "var": "isMovingLeft", "state": "STRAFELEFT" }, + { "var": "isTurningRight", "state": "turnRight" }, + { "var": "isFlying", "state": "fly" }, + { "var": "isTakeoffStand", "state": "takeoffStand" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, + { "var": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "INAIRRUN" }, + { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, + { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } + ] + }, + { + "id": "strafeRightHmd", + "interpTarget": 5, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { "var": "isNotMoving", "state": "idleSettle" }, + { "var": "isMovingForward", "state": "WALKFWD" }, + { "var": "isMovingBackward", "state": "WALKBWD" }, + { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" }, + { "var": "isMovingRight", "state": "STRAFERIGHT" }, + { "var": "isMovingLeft", "state": "STRAFELEFT" }, + { "var": "isTurningRight", "state": "turnRight" }, + { "var": "isTurningLeft", "state": "turnLeft" }, + { "var": "isFlying", "state": "fly" }, + { "var": "isTakeoffStand", "state": "takeoffStand" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, + { "var": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "INAIRRUN" } + ] + }, + { + "id": "strafeLeftHmd", + "interpTarget": 5, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { "var": "isNotMoving", "state": "idleSettle" }, + { "var": "isMovingForward", "state": "WALKFWD" }, + { "var": "isMovingBackward", "state": "WALKBWD" }, + { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, + { "var": "isMovingRight", "state": "STRAFERIGHT" }, + { "var": "isMovingLeft", "state": "STRAFELEFT" }, + { "var": "isTurningRight", "state": "turnRight" }, + { "var": "isTurningLeft", "state": "turnLeft" }, + { "var": "isFlying", "state": "fly" }, + { "var": "isTakeoffStand", "state": "takeoffStand" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, + { "var": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "INAIRRUN" } + ] + }, + { + "id": "fly", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "isNotFlying", "state": "idleSettle" } + ] + }, + { + "id": "takeoffStand", + "interpTarget": 2, + "interpDuration": 2, + "transitions": [ + { "var": "isNotTakeoff", "state": "inAirStand" } + ] + }, + { + "id": "TAKEOFFRUN", + "interpTarget": 2, + "interpDuration": 2, + "transitions": [ + { "var": "isNotTakeoff", "state": "INAIRRUN" } + ] + }, + { + "id": "inAirStand", + "interpTarget": 3, + "interpDuration": 3, + "interpType": "snapshotPrev", + "transitions": [ + { "var": "isNotInAir", "state": "landStandImpact" } + ] + }, + { + "id": "INAIRRUN", + "interpTarget": 3, + "interpDuration": 3, + "interpType": "snapshotPrev", + "transitions": [ + { "var": "isNotInAir", "state": "WALKFWD" } + ] + }, + { + "id": "landStandImpact", + "interpTarget": 1, + "interpDuration": 1, + "transitions": [ + { "var": "isFlying", "state": "fly" }, + { "var": "isTakeoffStand", "state": "takeoffStand" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, + { "var": "landStandImpactOnDone", "state": "landStand" } + ] + }, + { + "id": "landStand", + "interpTarget": 1, + "interpDuration": 1, + "transitions": [ + { "var": "isMovingForward", "state": "WALKFWD" }, + { "var": "isMovingBackward", "state": "WALKBWD" }, + { "var": "isMovingRight", "state": "STRAFERIGHT" }, + { "var": "isMovingLeft", "state": "STRAFELEFT" }, + { "var": "isTurningRight", "state": "turnRight" }, + { "var": "isTurningLeft", "state": "turnLeft" }, + { "var": "isFlying", "state": "fly" }, + { "var": "isTakeoffStand", "state": "takeoffStand" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, + { "var": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "INAIRRUN" }, + { "var": "landStandOnDone", "state": "idle" }, + { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, + { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } + ] + }, + { + "id": "LANDRUN", + "interpTarget": 2, + "interpDuration": 2, + "transitions": [ + { "var": "isFlying", "state": "fly" }, + { "var": "isTakeoffStand", "state": "takeoffStand" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, + { "var": "landRunOnDone", "state": "WALKFWD" } + ] + } + ] }, "children": [ { - "id": "leftHandStateMachine", + "id": "idle", "type": "stateMachine", "data": { - "currentState": "leftHandGrasp", + "currentState": "idleStand", "states": [ { - "id": "leftHandGrasp", - "interpTarget": 3, - "interpDuration": 3, + "id": "idleStand", + "interpTarget": 6, + "interpDuration": 10, "transitions": [ - { - "var": "isLeftIndexPoint", - "state": "leftIndexPoint" - }, - { - "var": "isLeftThumbRaise", - "state": "leftThumbRaise" - }, - { - "var": "isLeftIndexPointAndThumbRaise", - "state": "leftIndexPointAndThumbRaise" - } + { "var": "isTalking", "state": "idleTalk" } ] }, { - "id": "leftIndexPoint", - "interpTarget": 15, - "interpDuration": 3, + "id": "idleTalk", + "interpTarget": 6, + "interpDuration": 10, "transitions": [ - { - "var": "isLeftHandGrasp", - "state": "leftHandGrasp" - }, - { - "var": "isLeftThumbRaise", - "state": "leftThumbRaise" - }, - { - "var": "isLeftIndexPointAndThumbRaise", - "state": "leftIndexPointAndThumbRaise" - } - ] - }, - { - "id": "leftThumbRaise", - "interpTarget": 15, - "interpDuration": 3, - "transitions": [ - { - "var": "isLeftHandGrasp", - "state": "leftHandGrasp" - }, - { - "var": "isLeftIndexPoint", - "state": "leftIndexPoint" - }, - { - "var": "isLeftIndexPointAndThumbRaise", - "state": "leftIndexPointAndThumbRaise" - } - ] - }, - { - "id": "leftIndexPointAndThumbRaise", - "interpTarget": 15, - "interpDuration": 3, - "transitions": [ - { - "var": "isLeftHandGrasp", - "state": "leftHandGrasp" - }, - { - "var": "isLeftIndexPoint", - "state": "leftIndexPoint" - }, - { - "var": "isLeftThumbRaise", - "state": "leftThumbRaise" - } + { "var": "notIsTalking", "state": "idleStand" } ] } ] }, "children": [ { - "id": "leftHandGrasp", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "leftHandGraspAlpha" - }, - "children": [ - { - "id": "leftHandGraspOpen", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/hydra_pose_open_left.fbx", - "startFrame": 0.0, - "endFrame": 0.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "leftHandGraspClosed", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/hydra_pose_closed_left.fbx", - "startFrame": 10.0, - "endFrame": 10.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "leftIndexPoint", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "leftHandGraspAlpha" - }, - "children": [ - { - "id": "leftIndexPointOpen", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_point_open_left.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "leftIndexPointClosed", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_point_closed_left.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "leftThumbRaise", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "leftHandGraspAlpha" - }, - "children": [ - { - "id": "leftThumbRaiseOpen", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_thumb_open_left.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "leftThumbRaiseClosed", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_thumb_closed_left.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "leftIndexPointAndThumbRaise", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "leftHandGraspAlpha" - }, - "children": [ - { - "id": "leftIndexPointAndThumbRaiseOpen", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_thumb_point_open_left.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "leftIndexPointAndThumbRaiseClosed", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_thumb_point_closed_left.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - } - ] - }, - { - "id": "mainStateMachine", - "type": "stateMachine", - "data": { - "outputJoints": [ "LeftFoot", "RightFoot" ], - "currentState": "idle", - "states": [ - { - "id": "idle", - "interpTarget": 20, - "interpDuration": 8, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "idleToWalkFwd", - "interpTarget": 12, - "interpDuration": 8, - "transitions": [ - { - "var": "idleToWalkFwdOnDone", - "state": "WALKFWD" - }, - { - "var": "isNotMoving", - "state": "idle" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "idleSettle", - "interpTarget": 15, - "interpDuration": 8, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "idleSettleOnDone", - "state": "idle" - }, - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - } - ] - }, - { - "id": "WALKFWD", - "interpTarget": 35, - "interpDuration": 10, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isNotMoving", - "state": "idleSettle" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "WALKBWD", - "interpTarget": 35, - "interpDuration": 10, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isNotMoving", - "state": "idleSettle" - }, - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "STRAFERIGHT", - "interpTarget": 25, - "interpDuration": 8, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isNotMoving", - "state": "idleSettle" - }, - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "STRAFELEFT", - "interpTarget": 25, - "interpDuration": 8, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isNotMoving", - "state": "idleSettle" - }, - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "turnRight", - "interpTarget": 6, - "interpDuration": 8, - "transitions": [ - { - "var": "isNotTurning", - "state": "idle" - }, - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "turnLeft", - "interpTarget": 6, - "interpDuration": 8, - "transitions": [ - { - "var": "isNotTurning", - "state": "idle" - }, - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "strafeRightHmd", - "interpTarget": 5, - "interpDuration": 8, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isNotMoving", - "state": "idleSettle" - }, - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - } - ] - }, - { - "id": "strafeLeftHmd", - "interpTarget": 5, - "interpDuration": 8, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isNotMoving", - "state": "idleSettle" - }, - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - } - ] - }, - { - "id": "fly", - "interpTarget": 6, - "interpDuration": 6, - "transitions": [ - { - "var": "isNotFlying", - "state": "idleSettle" - } - ] - }, - { - "id": "takeoffStand", - "interpTarget": 2, - "interpDuration": 2, - "transitions": [ - { - "var": "isNotTakeoff", - "state": "inAirStand" - } - ] - }, - { - "id": "TAKEOFFRUN", - "interpTarget": 2, - "interpDuration": 2, - "transitions": [ - { - "var": "isNotTakeoff", - "state": "INAIRRUN" - } - ] - }, - { - "id": "inAirStand", - "interpTarget": 3, - "interpDuration": 3, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isNotInAir", - "state": "landStandImpact" - } - ] - }, - { - "id": "INAIRRUN", - "interpTarget": 3, - "interpDuration": 3, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isNotInAir", - "state": "WALKFWD" - } - ] - }, - { - "id": "landStandImpact", - "interpTarget": 1, - "interpDuration": 1, - "transitions": [ - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "landStandImpactOnDone", - "state": "landStand" - } - ] - }, - { - "id": "landStand", - "interpTarget": 1, - "interpDuration": 1, - "transitions": [ - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "landStandOnDone", - "state": "idle" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "LANDRUN", - "interpTarget": 2, - "interpDuration": 2, - "transitions": [ - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "landRunOnDone", - "state": "WALKFWD" - } - ] - } - ] - }, - "children": [ - { - "id": "idle", - "type": "stateMachine", - "data": { - "currentState": "idleStand", - "states": [ - { - "id": "idleStand", - "interpTarget": 6, - "interpDuration": 10, - "transitions": [ - { - "var": "isTalking", - "state": "idleTalk" - } - ] - }, - { - "id": "idleTalk", - "interpTarget": 6, - "interpDuration": 10, - "transitions": [ - { - "var": "notIsTalking", - "state": "idleStand" - } - ] - } - ] - }, - "children": [ - { - "id": "idleStand", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/idle.fbx", - "startFrame": 0.0, - "endFrame": 300.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "idleTalk", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/talk.fbx", - "startFrame": 0.0, - "endFrame": 800.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "WALKFWD", - "type": "blendLinearMove", - "data": { - "alpha": 0.0, - "desiredSpeed": 1.4, - "characteristicSpeeds": [ 0.5, 1.8, 2.3, 3.2, 4.5 ], - "alphaVar": "moveForwardAlpha", - "desiredSpeedVar": "moveForwardSpeed" - }, - "children": [ - { - "id": "walkFwdShort_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_short_fwd.fbx", - "startFrame": 0.0, - "endFrame": 39.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "walkFwdNormal_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_fwd.fbx", - "startFrame": 0.0, - "endFrame": 30.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "walkFwdFast_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_fwd_fast.fbx", - "startFrame": 0.0, - "endFrame": 25.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "walkFwdJog_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jog_fwd.fbx", - "startFrame": 0.0, - "endFrame": 25.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "walkFwdRun_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/run_fwd.fbx", - "startFrame": 0.0, - "endFrame": 21.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "idleToWalkFwd", + "id": "idleStand", "type": "clip", "data": { - "url": "qrc:///avatar/animations/idle_to_walk.fbx", - "startFrame": 1.0, - "endFrame": 13.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "idleSettle", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/settle_to_idle.fbx", - "startFrame": 1.0, - "endFrame": 59.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "WALKBWD", - "type": "blendLinearMove", - "data": { - "alpha": 0.0, - "desiredSpeed": 1.4, - "characteristicSpeeds": [ 0.6, 1.6, 2.3, 3.1 ], - "alphaVar": "moveBackwardAlpha", - "desiredSpeedVar": "moveBackwardSpeed" - }, - "children": [ - { - "id": "walkBwdShort_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_short_bwd.fbx", - "startFrame": 0.0, - "endFrame": 38.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "walkBwdFast_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_bwd_fast.fbx", - "startFrame": 0.0, - "endFrame": 27.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "jogBwd_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jog_bwd.fbx", - "startFrame": 0.0, - "endFrame": 24.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "runBwd_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/run_bwd.fbx", - "startFrame": 0.0, - "endFrame": 16.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "turnLeft", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/turn_left.fbx", + "url": "qrc:///avatar/animations/idle.fbx", "startFrame": 0.0, - "endFrame": 32.0, + "endFrame": 300.0, "timeScale": 1.0, "loopFlag": true }, "children": [] }, { - "id": "turnRight", + "id": "idleTalk", "type": "clip", "data": { - "url": "qrc:///avatar/animations/turn_left.fbx", + "url": "qrc:///avatar/animations/talk.fbx", "startFrame": 0.0, - "endFrame": 32.0, + "endFrame": 800.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "WALKFWD", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [0.5, 1.8, 2.3, 3.2, 4.5], + "alphaVar": "moveForwardAlpha", + "desiredSpeedVar": "moveForwardSpeed" + }, + "children": [ + { + "id": "walkFwdShort_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_short_fwd.fbx", + "startFrame": 0.0, + "endFrame": 39.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkFwdNormal_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_fwd.fbx", + "startFrame": 0.0, + "endFrame": 30.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkFwdFast_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_fwd_fast.fbx", + "startFrame": 0.0, + "endFrame": 25.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkFwdJog_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jog_fwd.fbx", + "startFrame": 0.0, + "endFrame": 25.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkFwdRun_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/run_fwd.fbx", + "startFrame": 0.0, + "endFrame": 21.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "idleToWalkFwd", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle_to_walk.fbx", + "startFrame": 1.0, + "endFrame": 13.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "idleSettle", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/settle_to_idle.fbx", + "startFrame": 1.0, + "endFrame": 59.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "WALKBWD", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [0.6, 1.6, 2.3, 3.1], + "alphaVar": "moveBackwardAlpha", + "desiredSpeedVar": "moveBackwardSpeed" + }, + "children": [ + { + "id": "walkBwdShort_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_short_bwd.fbx", + "startFrame": 0.0, + "endFrame": 38.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkBwdFast_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_bwd_fast.fbx", + "startFrame": 0.0, + "endFrame": 27.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "jogBwd_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jog_bwd.fbx", + "startFrame": 0.0, + "endFrame": 24.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "runBwd_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/run_bwd.fbx", + "startFrame": 0.0, + "endFrame": 16.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "turnLeft", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/turn_left.fbx", + "startFrame": 0.0, + "endFrame": 32.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "turnRight", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/turn_left.fbx", + "startFrame": 0.0, + "endFrame": 32.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "STRAFELEFT", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [0.1, 0.5, 1.0, 2.6, 3.0], + "alphaVar": "moveLateralAlpha", + "desiredSpeedVar": "moveLateralSpeed" + }, + "children": [ + { + "id": "strafeLeftShortStep_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_short_left.fbx", + "startFrame": 0.0, + "endFrame": 29.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftStep_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left.fbx", + "startFrame": 0.0, + "endFrame": 20.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftWalk_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_left.fbx", + "startFrame": 0.0, + "endFrame": 35.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftWalkFast_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_left_fast.fbx", + "startFrame": 0.0, + "endFrame": 21.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftJog_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jog_left.fbx", + "startFrame": 0.0, + "endFrame": 24.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "STRAFERIGHT", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [0.1, 0.5, 1.0, 2.6, 3.0], + "alphaVar": "moveLateralAlpha", + "desiredSpeedVar": "moveLateralSpeed" + }, + "children": [ { + "id": "strafeRightShortStep_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_short_left.fbx", + "startFrame": 0.0, + "endFrame": 29.0, "timeScale": 1.0, "loopFlag": true, "mirrorFlag": true @@ -1727,275 +1232,256 @@ "children": [] }, { - "id": "STRAFELEFT", - "type": "blendLinearMove", - "data": { - "alpha": 0.0, - "desiredSpeed": 1.4, - "characteristicSpeeds": [ 0.1, 0.5, 1.0, 2.6, 3.0 ], - "alphaVar": "moveLateralAlpha", - "desiredSpeedVar": "moveLateralSpeed" - }, - "children": [ - { - "id": "strafeLeftShortStep_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_short_left.fbx", - "startFrame": 0.0, - "endFrame": 29.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "strafeLeftStep_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_left.fbx", - "startFrame": 0.0, - "endFrame": 20.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "strafeLeftWalk_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_left.fbx", - "startFrame": 0.0, - "endFrame": 35.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "strafeLeftWalkFast_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_left_fast.fbx", - "startFrame": 0.0, - "endFrame": 21.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "strafeLeftJog_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jog_left.fbx", - "startFrame": 0.0, - "endFrame": 24.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "STRAFERIGHT", - "type": "blendLinearMove", - "data": { - "alpha": 0.0, - "desiredSpeed": 1.4, - "characteristicSpeeds": [ 0.1, 0.5, 1.0, 2.6, 3.0 ], - "alphaVar": "moveLateralAlpha", - "desiredSpeedVar": "moveLateralSpeed" - }, - "children": [ - { - "id": "strafeRightShortStep_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_short_left.fbx", - "startFrame": 0.0, - "endFrame": 29.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "strafeRightStep_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_left.fbx", - "startFrame": 0.0, - "endFrame": 20.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "strafeRightWalk_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_left.fbx", - "startFrame": 0.0, - "endFrame": 35.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "strafeRightFast_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_left_fast.fbx", - "startFrame": 0.0, - "endFrame": 21.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "strafeRightJog_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jog_left.fbx", - "startFrame": 0.0, - "endFrame": 24.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - } - ] - }, - { - "id": "strafeLeftHmd", - "type": "blendLinearMove", - "data": { - "alpha": 0.0, - "desiredSpeed": 1.4, - "characteristicSpeeds": [ 0, 0.5, 2.5 ], - "alphaVar": "moveLateralAlpha", - "desiredSpeedVar": "moveLateralSpeed" - }, - "children": [ - { - "id": "stepLeftShort_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_short_left.fbx", - "startFrame": 0.0, - "endFrame": 29.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "stepLeft_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_left.fbx", - "startFrame": 0.0, - "endFrame": 20.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "strafeLeftAnim_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_left_fast.fbx", - "startFrame": 0.0, - "endFrame": 16.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "strafeRightHmd", - "type": "blendLinearMove", - "data": { - "alpha": 0.0, - "desiredSpeed": 1.4, - "characteristicSpeeds": [ 0, 0.5, 2.5 ], - "alphaVar": "moveLateralAlpha", - "desiredSpeedVar": "moveLateralSpeed" - }, - "children": [ - { - "id": "stepRightShort_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_short_left.fbx", - "startFrame": 0.0, - "endFrame": 29.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "stepRight_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_left.fbx", - "startFrame": 0.0, - "endFrame": 20.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "strafeRightAnim_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_left_fast.fbx", - "startFrame": 0.0, - "endFrame": 16.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - } - ] - }, - { - "id": "fly", + "id": "strafeRightStep_c", "type": "clip", "data": { - "url": "qrc:///avatar/animations/fly.fbx", - "startFrame": 1.0, - "endFrame": 80.0, + "url": "qrc:///avatar/animations/side_step_left.fbx", + "startFrame": 0.0, + "endFrame": 20.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "strafeRightWalk_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_left.fbx", + "startFrame": 0.0, + "endFrame": 35.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "strafeRightFast_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_left_fast.fbx", + "startFrame": 0.0, + "endFrame": 21.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "strafeRightJog_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jog_left.fbx", + "startFrame": 0.0, + "endFrame": 24.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + } + ] + }, + { + "id": "strafeLeftHmd", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [0, 0.5, 2.5], + "alphaVar": "moveLateralAlpha", + "desiredSpeedVar": "moveLateralSpeed" + }, + "children": [ + { + "id": "stepLeftShort_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_short_left.fbx", + "startFrame": 0.0, + "endFrame": 29.0, "timeScale": 1.0, "loopFlag": true }, "children": [] }, { - "id": "takeoffStand", + "id": "stepLeft_c", "type": "clip", "data": { - "url": "qrc:///avatar/animations/jump_standing_launch.fbx", + "url": "qrc:///avatar/animations/side_step_left.fbx", + "startFrame": 0.0, + "endFrame": 20.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftAnim_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left_fast.fbx", + "startFrame": 0.0, + "endFrame": 16.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "strafeRightHmd", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [0, 0.5, 2.5], + "alphaVar": "moveLateralAlpha", + "desiredSpeedVar": "moveLateralSpeed" + }, + "children": [ + { + "id": "stepRightShort_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_short_left.fbx", + "startFrame": 0.0, + "endFrame": 29.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "stepRight_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left.fbx", + "startFrame": 0.0, + "endFrame": 20.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "strafeRightAnim_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left_fast.fbx", + "startFrame": 0.0, + "endFrame": 16.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + } + ] + }, + { + "id": "fly", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/fly.fbx", + "startFrame": 1.0, + "endFrame": 80.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "takeoffStand", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_launch.fbx", + "startFrame": 2.0, + "endFrame": 16.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "TAKEOFFRUN", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "startFrame": 4.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirStand", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "inAirAlpha" + }, + "children": [ + { + "id": "inAirStandPreApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_apex.fbx", + "startFrame": 0.0, + "endFrame": 0.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirStandApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_apex.fbx", + "startFrame": 1.0, + "endFrame": 1.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirStandPostApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_apex.fbx", "startFrame": 2.0, + "endFrame": 2.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + } + ] + }, + { + "id": "INAIRRUN", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "inAirAlpha" + }, + "children": [ + { + "id": "inAirRunPreApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "startFrame": 16.0, "endFrame": 16.0, "timeScale": 1.0, "loopFlag": false @@ -2003,146 +1489,66 @@ "children": [] }, { - "id": "TAKEOFFRUN", + "id": "inAirRunApex", "type": "clip", "data": { "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", - "startFrame": 4.0, - "endFrame": 15.0, + "startFrame": 22.0, + "endFrame": 22.0, "timeScale": 1.0, "loopFlag": false }, "children": [] }, { - "id": "inAirStand", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "inAirAlpha" - }, - "children": [ - { - "id": "inAirStandPreApex", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_standing_apex.fbx", - "startFrame": 0.0, - "endFrame": 0.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "inAirStandApex", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_standing_apex.fbx", - "startFrame": 1.0, - "endFrame": 1.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "inAirStandPostApex", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_standing_apex.fbx", - "startFrame": 2.0, - "endFrame": 2.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - } - ] - }, - { - "id": "INAIRRUN", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "inAirAlpha" - }, - "children": [ - { - "id": "inAirRunPreApex", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", - "startFrame": 16.0, - "endFrame": 16.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "inAirRunApex", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", - "startFrame": 22.0, - "endFrame": 22.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "inAirRunPostApex", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", - "startFrame": 33.0, - "endFrame": 33.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - } - ] - }, - { - "id": "landStandImpact", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_standing_land_settle.fbx", - "startFrame": 1.0, - "endFrame": 6.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "landStand", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_standing_land_settle.fbx", - "startFrame": 6.0, - "endFrame": 68.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "LANDRUN", + "id": "inAirRunPostApex", "type": "clip", "data": { "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", - "startFrame": 29.0, - "endFrame": 40.0, + "startFrame": 33.0, + "endFrame": 33.0, "timeScale": 1.0, "loopFlag": false }, "children": [] } ] + }, + { + "id": "landStandImpact", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_land_settle.fbx", + "startFrame": 1.0, + "endFrame": 6.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "landStand", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_land_settle.fbx", + "startFrame": 6.0, + "endFrame": 68.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "LANDRUN", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "startFrame": 29.0, + "endFrame": 40.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] } ] } diff --git a/interface/resources/avatar/avatar-animation_withIKNode.json b/interface/resources/avatar/avatar-animation_withIKNode.json index 8a66ed21b3..44b2b9c25f 100644 --- a/interface/resources/avatar/avatar-animation_withIKNode.json +++ b/interface/resources/avatar/avatar-animation_withIKNode.json @@ -119,7 +119,7 @@ "type": "poleVectorConstraint", "data": { "enabled": false, - "referenceVector": [ 0, 0, 1 ], + "referenceVector": [ -1, 0, 0 ], "baseJointName": "RightArm", "midJointName": "RightForeArm", "tipJointName": "RightHand", @@ -149,7 +149,7 @@ "type": "poleVectorConstraint", "data": { "enabled": false, - "referenceVector": [ 0, 0, 1 ], + "referenceVector": [ 1, 0, 0 ], "baseJointName": "LeftArm", "midJointName": "LeftForeArm", "tipJointName": "LeftHand", diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 7a29ff4001..8c365d2561 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -886,8 +886,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars if (dt > MAX_OVERLAY_DT) { dt = MAX_OVERLAY_DT; } - loadPoses(underPoses); - /* + if (_relativePoses.size() != underPoses.size()) { loadPoses(underPoses); } else { @@ -974,8 +973,8 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars ::blend(1, &prevHipsAbsPose, &absPose, alpha, &absPose); } - //_relativePoses[_hipsIndex] = parentAbsPose.inverse() * absPose; - //_relativePoses[_hipsIndex].scale() = glm::vec3(1.0f); + _relativePoses[_hipsIndex] = parentAbsPose.inverse() * absPose; + _relativePoses[_hipsIndex].scale() = glm::vec3(1.0f); } // if there is an active jointChainInfo for the hips store the post shifted hips into it. @@ -1043,7 +1042,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars preconditionRelativePosesToAvoidLimbLock(context, targets); //qCDebug(animation) << "hips before ccd" << _relativePoses[_hipsIndex]; - //solve(context, targets, dt, jointChainInfoVec); + solve(context, targets, dt, jointChainInfoVec); //qCDebug(animation) << "hips after ccd" << _relativePoses[_hipsIndex]; } @@ -1055,7 +1054,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars } processOutputJoints(triggersOut); - */ + return _relativePoses; } diff --git a/libraries/animation/src/AnimSplineIK.cpp b/libraries/animation/src/AnimSplineIK.cpp index 847d512a51..39f8767465 100644 --- a/libraries/animation/src/AnimSplineIK.cpp +++ b/libraries/animation/src/AnimSplineIK.cpp @@ -71,133 +71,71 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const } ////////////////////////////////////////////////////////////////////////////////// - // check to see if we actually need absolute poses. AnimPoseVec absolutePoses; absolutePoses.resize(_poses.size()); computeAbsolutePoses(absolutePoses); - AnimPoseVec absolutePoses2; - absolutePoses2.resize(_poses.size()); - // do this later - //computeAbsolutePoses(absolutePoses2); - - int jointIndex2 = _skeleton->nameToJointIndex("Spine2"); - AnimPose origSpine2PoseAbs = _skeleton->getAbsolutePose(jointIndex2, _poses); - if ((jointIndex2 != -1) && (_poses.size() > 0)) { - AnimPose origSpine2 = _skeleton->getAbsolutePose(jointIndex2, _poses); - AnimPose origSpine1 = _skeleton->getAbsolutePose(_skeleton->nameToJointIndex("Spine1"),_poses); - //origSpine2PoseRel = origSpine1.inverse() * origSpine2; - //qCDebug(animation) << "origSpine2Pose: " << origSpine2Pose.rot(); - //qCDebug(animation) << "original relative spine2 " << origSpine2PoseAbs; - } - - IKTarget target; - int jointIndex = _skeleton->nameToJointIndex("Head"); - if (jointIndex != -1) { + if (_headIndex != -1) { target.setType(animVars.lookup("headType", (int)IKTarget::Type::RotationAndPosition)); - target.setIndex(jointIndex); - AnimPose absPose = _skeleton->getAbsolutePose(jointIndex, _poses); + target.setIndex(_headIndex); + AnimPose absPose = _skeleton->getAbsolutePose(_headIndex, _poses); glm::quat rotation = animVars.lookupRigToGeometry("headRotation", absPose.rot()); glm::vec3 translation = animVars.lookupRigToGeometry("headPosition", absPose.trans()); float weight = animVars.lookup("headWeight", "4.0"); - //qCDebug(animation) << "target 1 rotation absolute" << rotation; target.setPose(rotation, translation); target.setWeight(weight); - //const float* flexCoefficients = new float[5]{ 1.0f, 0.5f, 0.25f, 0.2f, 0.1f }; - const float* flexCoefficients = new float[2]{ 1.0f, 0.5f }; - target.setFlexCoefficients(2, flexCoefficients); - - // record the index of the hips ik target. - if (target.getIndex() == _hipsIndex) { - _hipsTargetIndex = 1; - } + const float* flexCoefficients = new float[5]{ 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }; + //const float* flexCoefficients = new float[2]{ 0.0f, 1.0f }; + target.setFlexCoefficients(5, flexCoefficients); } - AnimPose origAbsAfterHeadSpline; - AnimPose finalSpine2; AnimChain jointChain; AnimPose targetSpine2; + AnimPoseVec absolutePoses2; + absolutePoses2.resize(_poses.size()); if (_poses.size() > 0) { - _snapshotChain.buildFromRelativePoses(_skeleton, _poses, target.getIndex()); + //_snapshotChain.buildFromRelativePoses(_skeleton, _poses, target.getIndex()); jointChain.buildFromRelativePoses(_skeleton, _poses, target.getIndex()); - // jointChain2.buildFromRelativePoses(_skeleton, _poses, target2.getIndex()); - - // for each target solve target with spline - //qCDebug(animation) << "before head spline"; - //jointChain.dump(); solveTargetWithSpline(context, target, absolutePoses, false, jointChain); - AnimPose afterSolveSpine2 = jointChain.getAbsolutePoseFromJointIndex(jointIndex2); - AnimPose afterSolveSpine1 = jointChain.getAbsolutePoseFromJointIndex(_skeleton->nameToJointIndex("Spine1")); - AnimPose afterSolveSpine2Rel = afterSolveSpine1.inverse() * afterSolveSpine2; - AnimPose originalSpine2Relative = afterSolveSpine1.inverse() * origSpine2PoseAbs; - glm::quat rotation3 = animVars.lookupRigToGeometry("spine2Rotation", afterSolveSpine2.rot()); - glm::vec3 translation3 = animVars.lookupRigToGeometry("spine2Position", afterSolveSpine2.trans()); - targetSpine2 = AnimPose(rotation3, afterSolveSpine2.trans()); - finalSpine2 = afterSolveSpine1.inverse() * targetSpine2; - - //qCDebug(animation) << "relative spine2 after solve" << afterSolveSpine2Rel; - //qCDebug(animation) << "relative spine2 orig" << originalSpine2Relative; - AnimPose latestSpine2Relative(originalSpine2Relative.rot(), afterSolveSpine2Rel.trans()); - //jointChain.setRelativePoseAtJointIndex(jointIndex2, finalSpine2); jointChain.outputRelativePoses(_poses); - //_poses[_headIndex] = jointChain.getRelativePoseAtJointIndex(_headIndex); - //_poses[_skeleton->nameToJointIndex("Neck")] = jointChain.getRelativePoseAtJointIndex(_skeleton->nameToJointIndex("Neck")); - //_poses[_spine2Index] = jointChain.getRelativePoseAtJointIndex(_spine2Index); - - //custom output code for the head. just do the head neck and spine2 - - //qCDebug(animation) << "after head spline"; - //jointChain.dump(); - - computeAbsolutePoses(absolutePoses2); - //origAbsAfterHeadSpline = _skeleton->getAbsolutePose(jointIndex2, _poses); - // qCDebug(animation) << "Spine2 trans after head spline: " << origAbsAfterHeadSpline.trans(); + AnimPose afterSolveSpine2 = _skeleton->getAbsolutePose(_spine2Index, _poses); + glm::quat spine2RotationTarget = animVars.lookupRigToGeometry("spine2Rotation", afterSolveSpine2.rot()); + targetSpine2 = AnimPose(spine2RotationTarget, afterSolveSpine2.trans()); } + /* IKTarget target2; - //int jointIndex2 = _skeleton->nameToJointIndex("Spine2"); - if (jointIndex2 != -1) { + computeAbsolutePoses(absolutePoses2); + if (_spine2Index != -1) { target2.setType(animVars.lookup("spine2Type", (int)IKTarget::Type::RotationAndPosition)); - target2.setIndex(jointIndex2); - AnimPose absPose2 = _skeleton->getAbsolutePose(jointIndex2, _poses); - glm::quat rotation2 = animVars.lookupRigToGeometry("spine2Rotation", absPose2.rot()); - glm::vec3 translation2 = animVars.lookupRigToGeometry("spine2Position", absPose2.trans()); - float weight2 = animVars.lookup("spine2Weight", "2.0"); - // qCDebug(animation) << "rig to geometry" << rotation2; + target2.setIndex(_spine2Index); - //target2.setPose(rotation2, translation2); + float weight2 = animVars.lookup("spine2Weight", "2.0"); + target2.setPose(targetSpine2.rot(), targetSpine2.trans()); target2.setWeight(weight2); - const float* flexCoefficients2 = new float[3]{ 1.0f, 0.5f, 0.25f }; + + const float* flexCoefficients2 = new float[3]{ 1.0f, 1.0f, 1.0f }; target2.setFlexCoefficients(3, flexCoefficients2); - - } + AnimChain jointChain2; + AnimPose beforeSolveChestNeck; if (_poses.size() > 0) { - // _snapshotChain.buildFromRelativePoses(_skeleton, _poses, target.getIndex()); - // jointChain.buildFromRelativePoses(_skeleton, _poses, target.getIndex()); - // jointChain2.buildFromRelativePoses(_skeleton, underPoses, target2.getIndex()); + + beforeSolveChestNeck = _skeleton->getAbsolutePose(_skeleton->nameToJointIndex("Neck"), _poses); + jointChain2.buildFromRelativePoses(_skeleton, _poses, target2.getIndex()); - - // for each target solve target with spline solveTargetWithSpline(context, target2, absolutePoses2, false, jointChain2); - - //jointChain.outputRelativePoses(_poses); jointChain2.outputRelativePoses(_poses); - //computeAbsolutePoses(absolutePoses); - //qCDebug(animation) << "Spine2 spline"; - //jointChain2.dump(); - //qCDebug(animation) << "Spine2Pose trans after spine2 spline: " << _skeleton->getAbsolutePose(jointIndex2, _poses).trans(); - } - + */ const float FRAMES_PER_SECOND = 30.0f; @@ -209,9 +147,26 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const // make the prevchain // interp from the previous chain to the new chain/or underposes if the ik is disabled. // update the relative poses and then we are done - - /**/ + // set the tip/head rotation to match the absolute rotation of the target. + int headParent = _skeleton->getParentIndex(_headIndex); + int spine2Parent = _skeleton->getParentIndex(_spine2Index); + if ((spine2Parent != -1) && (headParent != -1) && (_poses.size() > 0)) { + /* + AnimPose spine2Target(target2.getRotation(), target2.getTranslation()); + AnimPose finalSpine2RelativePose = _skeleton->getAbsolutePose(spine2Parent, _poses).inverse() * spine2Target; + _poses[_spine2Index] = finalSpine2RelativePose; + + + AnimPose neckAbsolute = _skeleton->getAbsolutePose(headParent, _poses); + AnimPose finalNeckAbsolute = AnimPose(safeLerp(target2.getRotation(), target.getRotation(), 1.0f),neckAbsolute.trans()); + _poses[headParent] = spine2Target.inverse() * beforeSolveChestNeck; + */ + AnimPose headTarget(target.getRotation(),target.getTranslation()); + AnimPose finalHeadRelativePose = _skeleton->getAbsolutePose(headParent,_poses).inverse() * headTarget; + _poses[_headIndex] = finalHeadRelativePose; + } + return _poses; } @@ -275,19 +230,19 @@ void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, const IKTar // build spline from tip to base AnimPose tipPose = AnimPose(glm::vec3(1.0f), target.getRotation(), target.getTranslation()); AnimPose basePose; - if (target.getIndex() == _headIndex) { - basePose = absolutePoses[headBaseIndex]; - } else { + //if (target.getIndex() == _headIndex) { + // basePose = absolutePoses[headBaseIndex]; + //} else { basePose = absolutePoses[baseIndex]; - } + //} CubicHermiteSplineFunctorWithArcLength spline; if (target.getIndex() == _headIndex) { // set gain factors so that more curvature occurs near the tip of the spline. const float HIPS_GAIN = 0.5f; const float HEAD_GAIN = 1.0f; - //spline = computeSplineFromTipAndBase(tipPose, basePose, HIPS_GAIN, HEAD_GAIN); - spline = computeSplineFromTipAndBase(tipPose, basePose); + spline = computeSplineFromTipAndBase(tipPose, basePose, HIPS_GAIN, HEAD_GAIN); + // spline = computeSplineFromTipAndBase(tipPose, basePose); } else { spline = computeSplineFromTipAndBase(tipPose, basePose); } @@ -359,6 +314,9 @@ void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, const IKTar if (!chainInfoOut.setRelativePoseAtJointIndex(splineJointInfo.jointIndex, relPose)) { qCDebug(animation) << "we didn't find the joint index for the spline!!!!"; } + if (splineJointInfo.jointIndex == _skeleton->nameToJointIndex("Neck")) { + qCDebug(animation) << "neck is " << relPose; + } parentAbsPose = flexedAbsPose; } @@ -393,8 +351,8 @@ void AnimSplineIK::computeAndCacheSplineJointInfosForIKTarget(const AnimContext& AnimPose tipPose = _skeleton->getAbsoluteDefaultPose(target.getIndex()); AnimPose basePose; if (target.getIndex() == _headIndex) { - //basePose = _skeleton->getAbsoluteDefaultPose(_hipsIndex); - basePose = _skeleton->getAbsoluteDefaultPose(_spine2Index); + basePose = _skeleton->getAbsoluteDefaultPose(_hipsIndex); + //basePose = _skeleton->getAbsoluteDefaultPose(_spine2Index); } else { basePose = _skeleton->getAbsoluteDefaultPose(_hipsIndex); } @@ -404,8 +362,8 @@ void AnimSplineIK::computeAndCacheSplineJointInfosForIKTarget(const AnimContext& // set gain factors so that more curvature occurs near the tip of the spline. const float HIPS_GAIN = 0.5f; const float HEAD_GAIN = 1.0f; - // spline = computeSplineFromTipAndBase(tipPose, basePose, HIPS_GAIN, HEAD_GAIN); - spline = computeSplineFromTipAndBase(tipPose, basePose); + spline = computeSplineFromTipAndBase(tipPose, basePose, HIPS_GAIN, HEAD_GAIN); + // spline = computeSplineFromTipAndBase(tipPose, basePose); } else { spline = computeSplineFromTipAndBase(tipPose, basePose); } diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index a34af09b27..70f1f9a1f7 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1544,6 +1544,7 @@ void Rig::updateHands2(bool leftHandEnabled, bool rightHandEnabled, bool headEna int oppositeArmJointIndex = _animSkeleton->nameToJointIndex("RightArm"); glm::vec3 poleVector; bool usePoleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, shoulderJointIndex, oppositeArmJointIndex, poleVector); + //glm::vec3 poleVector = calculateKneePoleVector(handJointIndex, elbowJointIndex, shoulderJointIndex, hipsIndex, rightHandPose); glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector); // smooth toward desired pole vector from previous pole vector... to reduce jitter, but in sensor space. @@ -1556,7 +1557,7 @@ void Rig::updateHands2(bool leftHandEnabled, bool rightHandEnabled, bool headEna _prevLeftHandPoleVector = smoothDeltaRot * _prevLeftHandPoleVector; _animVars.set("leftHandPoleVectorEnabled", true); - _animVars.set("leftHandPoleVector", transformVectorFast(sensorToRigMatrix, _prevLeftFootPoleVector)); + _animVars.set("leftHandPoleVector", transformVectorFast(sensorToRigMatrix, _prevLeftHandPoleVector)); } else { // We want to drive the IK from the underlying animation. // This gives us the ability to squat while in the HMD, without the feet from dipping under the floor. @@ -1595,7 +1596,7 @@ void Rig::updateHands2(bool leftHandEnabled, bool rightHandEnabled, bool headEna _prevRightHandPoleVector = smoothDeltaRot * _prevRightHandPoleVector; _animVars.set("rightHandPoleVectorEnabled", true); - _animVars.set("rightHandPoleVector", transformVectorFast(sensorToRigMatrix, _prevRightFootPoleVector)); + _animVars.set("rightHandPoleVector", transformVectorFast(sensorToRigMatrix, _prevRightHandPoleVector)); } else { // We want to drive the IK from the underlying animation. // This gives us the ability to squat while in the HMD, without the feet from dipping under the floor. @@ -1856,14 +1857,14 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo updateHead(_headEnabled, hipsEnabled, params.primaryControllerPoses[PrimaryControllerType_Head]); - //updateHands(leftHandEnabled, rightHandEnabled, hipsEnabled, hipsEstimated, leftArmEnabled, rightArmEnabled, _headEnabled, dt, - // params.primaryControllerPoses[PrimaryControllerType_LeftHand], params.primaryControllerPoses[PrimaryControllerType_RightHand], - // params.hipsShapeInfo, params.spineShapeInfo, params.spine1ShapeInfo, params.spine2ShapeInfo, - // params.rigToSensorMatrix, sensorToRigMatrix); + updateHands(leftHandEnabled, rightHandEnabled, hipsEnabled, hipsEstimated, leftArmEnabled, rightArmEnabled, _headEnabled, dt, + params.primaryControllerPoses[PrimaryControllerType_LeftHand], params.primaryControllerPoses[PrimaryControllerType_RightHand], + params.hipsShapeInfo, params.spineShapeInfo, params.spine1ShapeInfo, params.spine2ShapeInfo, + params.rigToSensorMatrix, sensorToRigMatrix); - updateHands2(leftHandEnabled, rightHandEnabled, _headEnabled, - params.primaryControllerPoses[PrimaryControllerType_LeftHand], params.primaryControllerPoses[PrimaryControllerType_RightHand], - params.rigToSensorMatrix, sensorToRigMatrix); + //updateHands2(leftHandEnabled, rightHandEnabled, _headEnabled, + // params.primaryControllerPoses[PrimaryControllerType_LeftHand], params.primaryControllerPoses[PrimaryControllerType_RightHand], + // params.rigToSensorMatrix, sensorToRigMatrix); updateFeet(leftFootEnabled, rightFootEnabled, _headEnabled, params.primaryControllerPoses[PrimaryControllerType_LeftFoot], params.primaryControllerPoses[PrimaryControllerType_RightFoot], From 37f92d2319b3a09148706ffb1987c3f4715a0c48 Mon Sep 17 00:00:00 2001 From: amantley Date: Thu, 24 Jan 2019 17:02:30 -0800 Subject: [PATCH 014/474] added code to read tip and base var info from the json --- .../avatar/avatar-animation_withIKNode.json | 7 + .../animation/src/AnimInverseKinematics.cpp | 15 +- libraries/animation/src/AnimNodeLoader.cpp | 12 +- libraries/animation/src/AnimSplineIK.cpp | 208 ++++++++++++------ libraries/animation/src/AnimSplineIK.h | 20 +- libraries/animation/src/Rig.cpp | 14 +- 6 files changed, 194 insertions(+), 82 deletions(-) diff --git a/interface/resources/avatar/avatar-animation_withIKNode.json b/interface/resources/avatar/avatar-animation_withIKNode.json index 44b2b9c25f..04ed593755 100644 --- a/interface/resources/avatar/avatar-animation_withIKNode.json +++ b/interface/resources/avatar/avatar-animation_withIKNode.json @@ -183,6 +183,13 @@ "interpDuration": 15, "baseJointName": "Hips", "tipJointName": "Head", + "secondaryTargetJointName": "Spine2", + "basePositionVar": "hipsPosition", + "baseRotationVar": "hipsRotation", + "tipPositionVar": "headPosition", + "tipRotationVar": "headRotation", + "secondaryTargetPositionVar": "spine2Position", + "secondaryTargetRotationVar": "spine2Rotation", "alphaVar": "splineIKAlpha", "enabledVar": "splineIKEnabled", "endEffectorRotationVarVar": "splineIKRotationVar", diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 8c365d2561..f95fe3333f 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -243,13 +243,6 @@ void AnimInverseKinematics::solve(const AnimContext& context, const std::vector< break; case IKTarget::Type::Spline: solveTargetWithSpline(context, targets[i], absolutePoses, debug, jointChainInfoVec[i]); - //if (jointChainInfoVec[i].target.getIndex() == _skeleton->nameToJointIndex("Head")) { - // qCDebug(animation) << "AnimIK spline index is " << targets[i].getIndex() << " and chain info size is " << jointChainInfoVec[i].jointInfoVec.size(); - for (int w = 0; w < jointChainInfoVec[i].jointInfoVec.size(); w++) { - // qCDebug(animation) << "joint " << jointChainInfoVec[i].jointInfoVec[w].jointIndex << " rotation is " << jointChainInfoVec[i].jointInfoVec[w].rot; - // qCDebug(animation) << "joint " << jointChainInfoVec[i].jointInfoVec[w].jointIndex << " translation is " << jointChainInfoVec[i].jointInfoVec[w].trans; - } - //} break; default: solveTargetWithCCD(context, targets[i], absolutePoses, debug, jointChainInfoVec[i]); @@ -329,6 +322,10 @@ void AnimInverseKinematics::solve(const AnimContext& context, const std::vector< } } + //qCDebug(animation) << "joint chain pose for head animIK " << jointChainInfoVec[4].jointInfoVec[w].trans << " " << jointChainInfoVec[4].jointInfoVec[w].rot; + qCDebug(animation) << "absolute pose for head animIK " << absolutePoses[_skeleton->nameToJointIndex("Head")]; + qCDebug(animation) << "target pose for head animIK " << targets[4].getTranslation() << " " << targets[4].getRotation(); + // compute maxError maxError = 0.0f; for (size_t i = 0; i < targets.size(); i++) { @@ -833,7 +830,9 @@ void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, co AnimPose relPose = parentAbsPose.inverse() * flexedAbsPose; + bool constrained = false; + /* if (splineJointInfo.jointIndex != _hipsIndex) { // constrain the amount the spine can stretch or compress float length = glm::length(relPose.trans()); @@ -854,7 +853,7 @@ void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, co relPose.trans() = glm::vec3(0.0f); } } - + */ jointChainInfoOut.jointInfoVec[i] = { relPose.rot(), relPose.trans(), splineJointInfo.jointIndex, constrained }; parentAbsPose = flexedAbsPose; diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index ec9099df8b..ebe9dbe3ba 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -585,6 +585,13 @@ static AnimNode::Pointer loadSplineIKNode(const QJsonObject& jsonObj, const QStr READ_FLOAT(interpDuration, jsonObj, id, jsonUrl, nullptr); READ_STRING(baseJointName, jsonObj, id, jsonUrl, nullptr); READ_STRING(tipJointName, jsonObj, id, jsonUrl, nullptr); + READ_STRING(secondaryTargetJointName, jsonObj, id, jsonUrl, nullptr); + READ_STRING(basePositionVar, jsonObj, id, jsonUrl, nullptr); + READ_STRING(baseRotationVar, jsonObj, id, jsonUrl, nullptr); + READ_STRING(tipPositionVar, jsonObj, id, jsonUrl, nullptr); + READ_STRING(tipRotationVar, jsonObj, id, jsonUrl, nullptr); + READ_STRING(secondaryTargetPositionVar, jsonObj, id, jsonUrl, nullptr); + READ_STRING(secondaryTargetRotationVar, jsonObj, id, jsonUrl, nullptr); READ_STRING(alphaVar, jsonObj, id, jsonUrl, nullptr); READ_STRING(enabledVar, jsonObj, id, jsonUrl, nullptr); READ_STRING(endEffectorRotationVarVar, jsonObj, id, jsonUrl, nullptr); @@ -592,8 +599,9 @@ static AnimNode::Pointer loadSplineIKNode(const QJsonObject& jsonObj, const QStr auto node = std::make_shared(id, alpha, enabled, interpDuration, baseJointName, tipJointName, - alphaVar, enabledVar, - endEffectorRotationVarVar, endEffectorPositionVarVar); + alphaVar, enabledVar, endEffectorRotationVarVar, endEffectorPositionVarVar, + basePositionVar, baseRotationVar, + tipPositionVar, tipRotationVar, secondaryTargetJointName, secondaryTargetPositionVar, secondaryTargetRotationVar); return node; } diff --git a/libraries/animation/src/AnimSplineIK.cpp b/libraries/animation/src/AnimSplineIK.cpp index 39f8767465..afe9f6dfcb 100644 --- a/libraries/animation/src/AnimSplineIK.cpp +++ b/libraries/animation/src/AnimSplineIK.cpp @@ -20,7 +20,14 @@ AnimSplineIK::AnimSplineIK(const QString& id, float alpha, bool enabled, float i const QString& baseJointName, const QString& tipJointName, const QString& alphaVar, const QString& enabledVar, - const QString& endEffectorRotationVarVar, const QString& endEffectorPositionVarVar) : + const QString& endEffectorRotationVarVar, const QString& endEffectorPositionVarVar, + const QString& basePositionVar, + const QString& baseRotationVar, + const QString& tipPositionVar, + const QString& tipRotationVar, + const QString& secondaryTargetJointName, + const QString& secondaryTargetPositionVar, + const QString& secondaryTargetRotationVar) : AnimNode(AnimNode::Type::SplineIK, id), _alpha(alpha), _enabled(enabled), @@ -32,7 +39,15 @@ AnimSplineIK::AnimSplineIK(const QString& id, float alpha, bool enabled, float i _endEffectorRotationVarVar(endEffectorRotationVarVar), _endEffectorPositionVarVar(endEffectorPositionVarVar), _prevEndEffectorRotationVar(), - _prevEndEffectorPositionVar() { + _prevEndEffectorPositionVar(), + _basePositionVar(basePositionVar), + _baseRotationVar(baseRotationVar), + _tipPositionVar(tipPositionVar), + _tipRotationVar(tipRotationVar), + _secondaryTargetJointName(secondaryTargetJointName), + _secondaryTargetPositionVar(secondaryTargetPositionVar), + _secondaryTargetRotationVar(secondaryTargetRotationVar) +{ } @@ -51,16 +66,19 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const if (_children.size() != 1) { return _poses; } - // evalute underPoses + // evaluate underPoses AnimPoseVec underPoses = _children[0]->evaluate(animVars, context, dt, triggersOut); _poses = underPoses; - // now we override the hips relative pose based on the hips target that has been set. + glm::quat hipsTargetRotation; + glm::vec3 hipsTargetTranslation; + + // now we override the hips with the hips target pose. //////////////////////////////////////////////////// if (_poses.size() > 0) { AnimPose hipsUnderPose = _skeleton->getAbsolutePose(_hipsIndex, _poses); - glm::quat hipsTargetRotation = animVars.lookupRigToGeometry("hipsRotation", hipsUnderPose.rot()); - glm::vec3 hipsTargetTranslation = animVars.lookupRigToGeometry("hipsPosition", hipsUnderPose.trans()); + hipsTargetRotation = animVars.lookupRigToGeometry("hipsRotation", hipsUnderPose.rot()); + hipsTargetTranslation = animVars.lookupRigToGeometry("hipsPosition", hipsUnderPose.trans()); AnimPose absHipsTargetPose(hipsTargetRotation, hipsTargetTranslation); int hipsParentIndex = _skeleton->getParentIndex(_hipsIndex); @@ -76,23 +94,22 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const computeAbsolutePoses(absolutePoses); IKTarget target; - if (_headIndex != -1) { - target.setType(animVars.lookup("headType", (int)IKTarget::Type::RotationAndPosition)); - target.setIndex(_headIndex); - AnimPose absPose = _skeleton->getAbsolutePose(_headIndex, _poses); - glm::quat rotation = animVars.lookupRigToGeometry("headRotation", absPose.rot()); - glm::vec3 translation = animVars.lookupRigToGeometry("headPosition", absPose.trans()); - float weight = animVars.lookup("headWeight", "4.0"); + if (_tipJointIndex != -1) { + target.setType((int)IKTarget::Type::Spline); + target.setIndex(_tipJointIndex); + AnimPose absPose = _skeleton->getAbsolutePose(_tipJointIndex, _poses); + glm::quat rotation = animVars.lookupRigToGeometry(_tipRotationVar, absPose.rot()); + glm::vec3 translation = animVars.lookupRigToGeometry(_tipPositionVar, absPose.trans()); + float weight = 1.0f; target.setPose(rotation, translation); target.setWeight(weight); - const float* flexCoefficients = new float[5]{ 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }; - //const float* flexCoefficients = new float[2]{ 0.0f, 1.0f }; + const float* flexCoefficients = new float[5]{ 1.0f, 0.5f, 0.25f, 0.2f, 0.1f }; target.setFlexCoefficients(5, flexCoefficients); } AnimChain jointChain; - AnimPose targetSpine2; + AnimPose updatedSecondaryTarget; AnimPoseVec absolutePoses2; absolutePoses2.resize(_poses.size()); if (_poses.size() > 0) { @@ -100,43 +117,47 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const //_snapshotChain.buildFromRelativePoses(_skeleton, _poses, target.getIndex()); jointChain.buildFromRelativePoses(_skeleton, _poses, target.getIndex()); solveTargetWithSpline(context, target, absolutePoses, false, jointChain); + jointChain.buildDirtyAbsolutePoses(); + qCDebug(animation) << "the jointChain Result for head " << jointChain.getAbsolutePoseFromJointIndex(_tipJointIndex); + qCDebug(animation) << "the orig target pose for head " << target.getPose(); jointChain.outputRelativePoses(_poses); - AnimPose afterSolveSpine2 = _skeleton->getAbsolutePose(_spine2Index, _poses); - glm::quat spine2RotationTarget = animVars.lookupRigToGeometry("spine2Rotation", afterSolveSpine2.rot()); + AnimPose afterSolveSecondaryTarget = _skeleton->getAbsolutePose(_secondaryTargetIndex, _poses); + glm::quat secondaryTargetRotation = animVars.lookupRigToGeometry(_secondaryTargetRotationVar, afterSolveSecondaryTarget.rot()); - targetSpine2 = AnimPose(spine2RotationTarget, afterSolveSpine2.trans()); + // updatedSecondaryTarget = AnimPose(secondaryTargetRotation, afterSolveSpine2.trans()); + updatedSecondaryTarget = AnimPose(afterSolveSecondaryTarget.rot(), afterSolveSecondaryTarget.trans()); } - /* - IKTarget target2; + + IKTarget secondaryTarget; computeAbsolutePoses(absolutePoses2); - if (_spine2Index != -1) { - target2.setType(animVars.lookup("spine2Type", (int)IKTarget::Type::RotationAndPosition)); - target2.setIndex(_spine2Index); + if (_secondaryTargetIndex != -1) { + secondaryTarget.setType((int)IKTarget::Type::Spline); + secondaryTarget.setIndex(_secondaryTargetIndex); - float weight2 = animVars.lookup("spine2Weight", "2.0"); + float weight2 = 1.0f; - target2.setPose(targetSpine2.rot(), targetSpine2.trans()); - target2.setWeight(weight2); + secondaryTarget.setPose(updatedSecondaryTarget.rot(), updatedSecondaryTarget.trans()); + secondaryTarget.setWeight(weight2); const float* flexCoefficients2 = new float[3]{ 1.0f, 1.0f, 1.0f }; - target2.setFlexCoefficients(3, flexCoefficients2); + secondaryTarget.setFlexCoefficients(3, flexCoefficients2); } - - AnimChain jointChain2; + /* + AnimChain secondaryJointChain; AnimPose beforeSolveChestNeck; if (_poses.size() > 0) { beforeSolveChestNeck = _skeleton->getAbsolutePose(_skeleton->nameToJointIndex("Neck"), _poses); - jointChain2.buildFromRelativePoses(_skeleton, _poses, target2.getIndex()); - solveTargetWithSpline(context, target2, absolutePoses2, false, jointChain2); - jointChain2.outputRelativePoses(_poses); + secondaryJointChain.buildFromRelativePoses(_skeleton, _poses, secondaryTarget.getIndex()); + solveTargetWithSpline(context, secondaryTarget, absolutePoses2, false, secondaryJointChain); + secondaryJointChain.outputRelativePoses(_poses); } - */ + */ const float FRAMES_PER_SECOND = 30.0f; _interpAlphaVel = FRAMES_PER_SECOND / _interpDuration; @@ -149,23 +170,59 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const // update the relative poses and then we are done // set the tip/head rotation to match the absolute rotation of the target. - int headParent = _skeleton->getParentIndex(_headIndex); - int spine2Parent = _skeleton->getParentIndex(_spine2Index); - if ((spine2Parent != -1) && (headParent != -1) && (_poses.size() > 0)) { + int tipParent = _skeleton->getParentIndex(_tipJointIndex); + int secondaryTargetParent = _skeleton->getParentIndex(_secondaryTargetIndex); + if ((secondaryTargetParent != -1) && (tipParent != -1) && (_poses.size() > 0)) { /* - AnimPose spine2Target(target2.getRotation(), target2.getTranslation()); - AnimPose finalSpine2RelativePose = _skeleton->getAbsolutePose(spine2Parent, _poses).inverse() * spine2Target; - _poses[_spine2Index] = finalSpine2RelativePose; + AnimPose secondaryTargetPose(target2.getRotation(), target2.getTranslation()); + AnimPose secondaryTargetRelativePose = _skeleton->getAbsolutePose(secondaryTargetParent, _poses).inverse() * secondaryTargetPose; + _poses[_secondaryTargetIndex] = secondaryTargetRelativePose; - AnimPose neckAbsolute = _skeleton->getAbsolutePose(headParent, _poses); + AnimPose neckAbsolute = _skeleton->getAbsolutePose(tipParent, _poses); AnimPose finalNeckAbsolute = AnimPose(safeLerp(target2.getRotation(), target.getRotation(), 1.0f),neckAbsolute.trans()); _poses[headParent] = spine2Target.inverse() * beforeSolveChestNeck; */ - AnimPose headTarget(target.getRotation(),target.getTranslation()); - AnimPose finalHeadRelativePose = _skeleton->getAbsolutePose(headParent,_poses).inverse() * headTarget; - _poses[_headIndex] = finalHeadRelativePose; + //AnimPose tipTarget(target.getRotation(),target.getTranslation()); + //AnimPose tipRelativePose = _skeleton->getAbsolutePose(tipParent,_poses).inverse() * headTarget; + //_poses[_tipJointIndex] = tipHeadRelativePose; } + + // debug render ik targets + if (context.getEnableDebugDrawIKTargets()) { + const vec4 WHITE(1.0f); + const vec4 GREEN(0.0f, 1.0f, 0.0f, 1.0f); + glm::mat4 rigToAvatarMat = createMatFromQuatAndPos(Quaternions::Y_180, glm::vec3()); + + glm::mat4 geomTargetMat = createMatFromQuatAndPos(target.getRotation(), target.getTranslation()); + glm::mat4 avatarTargetMat = rigToAvatarMat * context.getGeometryToRigMatrix() * geomTargetMat; + QString name = QString("ikTargetHead"); + DebugDraw::getInstance().addMyAvatarMarker(name, glmExtractRotation(avatarTargetMat), extractTranslation(avatarTargetMat), WHITE); + + glm::mat4 geomTargetMat2 = createMatFromQuatAndPos(secondaryTarget.getRotation(), secondaryTarget.getTranslation()); + glm::mat4 avatarTargetMat2 = rigToAvatarMat * context.getGeometryToRigMatrix() * geomTargetMat2; + QString name2 = QString("ikTargetSpine2"); + DebugDraw::getInstance().addMyAvatarMarker(name2, glmExtractRotation(avatarTargetMat2), extractTranslation(avatarTargetMat2), WHITE); + + + glm::mat4 geomTargetMat3 = createMatFromQuatAndPos(hipsTargetRotation, hipsTargetTranslation); + glm::mat4 avatarTargetMat3 = rigToAvatarMat * context.getGeometryToRigMatrix() * geomTargetMat3; + QString name3 = QString("ikTargetHips"); + DebugDraw::getInstance().addMyAvatarMarker(name3, glmExtractRotation(avatarTargetMat3), extractTranslation(avatarTargetMat3), WHITE); + + + } else if (context.getEnableDebugDrawIKTargets() != _previousEnableDebugIKTargets) { + // remove markers if they were added last frame. + + QString name = QString("ikTargetHead"); + DebugDraw::getInstance().removeMyAvatarMarker(name); + QString name2 = QString("ikTargetSpine2"); + DebugDraw::getInstance().removeMyAvatarMarker(name2); + QString name3 = QString("ikTargetHips"); + DebugDraw::getInstance().removeMyAvatarMarker(name3); + + } + _previousEnableDebugIKTargets = context.getEnableDebugDrawIKTargets(); return _poses; } @@ -174,11 +231,12 @@ void AnimSplineIK::lookUpIndices() { assert(_skeleton); // look up bone indices by name - std::vector indices = _skeleton->lookUpJointIndices({ _baseJointName, _tipJointName }); + std::vector indices = _skeleton->lookUpJointIndices({ _baseJointName, _tipJointName, _secondaryTargetJointName }); // cache the results _baseJointIndex = indices[0]; _tipJointIndex = indices[1]; + _secondaryTargetIndex = indices[2]; if (_baseJointIndex != -1) { _baseParentJointIndex = _skeleton->getParentIndex(_baseJointIndex); @@ -206,9 +264,8 @@ const AnimPoseVec& AnimSplineIK::getPosesInternal() const { void AnimSplineIK::setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) { AnimNode::setSkeletonInternal(skeleton); - _headIndex = _skeleton->nameToJointIndex("Head"); + //_headIndex = _skeleton->nameToJointIndex("Head"); _hipsIndex = _skeleton->nameToJointIndex("Hips"); - _spine2Index = _skeleton->nameToJointIndex("Spine2"); lookUpIndices(); } @@ -224,20 +281,20 @@ static CubicHermiteSplineFunctorWithArcLength computeSplineFromTipAndBase(const void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug, AnimChain& chainInfoOut) const { - const int baseIndex = _hipsIndex; - const int headBaseIndex = _spine2Index; + const int baseIndex = _baseJointIndex; + const int tipBaseIndex = _secondaryTargetIndex; // build spline from tip to base AnimPose tipPose = AnimPose(glm::vec3(1.0f), target.getRotation(), target.getTranslation()); AnimPose basePose; - //if (target.getIndex() == _headIndex) { - // basePose = absolutePoses[headBaseIndex]; + //if (target.getIndex() == _tipJointIndex) { + // basePose = absolutePoses[tipBaseIndex]; //} else { basePose = absolutePoses[baseIndex]; //} CubicHermiteSplineFunctorWithArcLength spline; - if (target.getIndex() == _headIndex) { + if (target.getIndex() == _tipJointIndex) { // set gain factors so that more curvature occurs near the tip of the spline. const float HIPS_GAIN = 0.5f; const float HEAD_GAIN = 1.0f; @@ -268,7 +325,7 @@ void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, const IKTar // for head splines, preform most twist toward the tip by using ease in function. t^2 float rotT = t; - if (target.getIndex() == _headIndex) { + if (target.getIndex() == _tipJointIndex) { rotT = t * t; } glm::quat twistRot = safeLerp(basePose.rot(), tipPose.rot(), rotT); @@ -290,7 +347,7 @@ void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, const IKTar AnimPose relPose = parentAbsPose.inverse() * flexedAbsPose; bool constrained = false; - if (splineJointInfo.jointIndex != _hipsIndex) { + if (splineJointInfo.jointIndex != _baseJointIndex) { // constrain the amount the spine can stretch or compress float length = glm::length(relPose.trans()); const float EPSILON = 0.0001f; @@ -350,15 +407,15 @@ void AnimSplineIK::computeAndCacheSplineJointInfosForIKTarget(const AnimContext& // build spline between the default poses. AnimPose tipPose = _skeleton->getAbsoluteDefaultPose(target.getIndex()); AnimPose basePose; - if (target.getIndex() == _headIndex) { - basePose = _skeleton->getAbsoluteDefaultPose(_hipsIndex); - //basePose = _skeleton->getAbsoluteDefaultPose(_spine2Index); + if (target.getIndex() == _tipJointIndex) { + basePose = _skeleton->getAbsoluteDefaultPose(_baseJointIndex); + //basePose = _skeleton->getAbsoluteDefaultPose(_secondaryTargetIndex); } else { - basePose = _skeleton->getAbsoluteDefaultPose(_hipsIndex); + basePose = _skeleton->getAbsoluteDefaultPose(_baseJointIndex); } CubicHermiteSplineFunctorWithArcLength spline; - if (target.getIndex() == _headIndex) { + if (target.getIndex() == _tipJointIndex) { // set gain factors so that more curvature occurs near the tip of the spline. const float HIPS_GAIN = 0.5f; const float HEAD_GAIN = 1.0f; @@ -377,11 +434,11 @@ void AnimSplineIK::computeAndCacheSplineJointInfosForIKTarget(const AnimContext& int index = target.getIndex(); int endIndex; - if (target.getIndex() == _headIndex) { - endIndex = _skeleton->getParentIndex(_spine2Index); - // endIndex = _skeleton->getParentIndex(_hipsIndex); + if (target.getIndex() == _tipJointIndex) { + //endIndex = _skeleton->getParentIndex(_secondaryTargetIndex); + endIndex = _skeleton->getParentIndex(_baseJointIndex); } else { - endIndex = _skeleton->getParentIndex(_hipsIndex); + endIndex = _skeleton->getParentIndex(_baseJointIndex); } while (index != endIndex) { AnimPose defaultPose = _skeleton->getAbsoluteDefaultPose(index); @@ -410,11 +467,34 @@ void AnimSplineIK::computeAndCacheSplineJointInfosForIKTarget(const AnimContext& _splineJointInfoMap[target.getIndex()] = splineJointInfoVec; } -void AnimSplineIK ::loadPoses(const AnimPoseVec& poses) { +void AnimSplineIK::loadPoses(const AnimPoseVec& poses) { assert(_skeleton && ((poses.size() == 0) || (_skeleton->getNumJoints() == (int)poses.size()))); if (_skeleton->getNumJoints() == (int)poses.size()) { _poses = poses; } else { _poses.clear(); } +} + + +void AnimSplineIK::setTargetVars(const QString& jointName, const QString& positionVar, const QString& rotationVar, + const QString& typeVar, const QString& weightVar, float weight, const std::vector& flexCoefficients, + const QString& poleVectorEnabledVar, const QString& poleReferenceVectorVar, const QString& poleVectorVar) { + /* + IKTargetVar targetVar(jointName, positionVar, rotationVar, typeVar, weightVar, weight, flexCoefficients, poleVectorEnabledVar, poleReferenceVectorVar, poleVectorVar); + + // if there are dups, last one wins. + bool found = false; + for (auto& targetVarIter : _targetVarVec) { + if (targetVarIter.jointName == jointName) { + targetVarIter = targetVar; + found = true; + break; + } + } + if (!found) { + // create a new entry + _targetVarVec.push_back(targetVar); + } + */ } \ No newline at end of file diff --git a/libraries/animation/src/AnimSplineIK.h b/libraries/animation/src/AnimSplineIK.h index 12f43fa680..4c51a0b9e1 100644 --- a/libraries/animation/src/AnimSplineIK.h +++ b/libraries/animation/src/AnimSplineIK.h @@ -21,7 +21,12 @@ public: AnimSplineIK(const QString& id, float alpha, bool enabled, float interpDuration, const QString& baseJointName, const QString& tipJointName, const QString& alphaVar, const QString& enabledVar, - const QString& endEffectorRotationVarVar, const QString& endEffectorPositionVarVar); + const QString& endEffectorRotationVarVar, const QString& endEffectorPositionVarVar, + const QString& basePositionVar, const QString& baseRotationVar, + const QString& tipPositionVar, const QString& tipRotationVar, + const QString& secondaryTargetJointName, + const QString& secondaryTargetPositionVar, + const QString& secondaryTargetRotationVar); virtual ~AnimSplineIK() override; virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override; @@ -52,9 +57,17 @@ protected: float _interpDuration; QString _baseJointName; QString _tipJointName; + QString _secondaryTargetJointName; + QString _basePositionVar; + QString _baseRotationVar; + QString _tipPositionVar; + QString _tipRotationVar; + QString _secondaryTargetPositionVar; + QString _secondaryTargetRotationVar; int _baseParentJointIndex { -1 }; int _baseJointIndex { -1 }; + int _secondaryTargetIndex { -1 }; int _tipJointIndex { -1 }; int _headIndex { -1 }; int _hipsIndex { -1 }; @@ -69,6 +82,8 @@ protected: QString _prevEndEffectorRotationVar; QString _prevEndEffectorPositionVar; + bool _previousEnableDebugIKTargets { false }; + InterpType _interpType{ InterpType::None }; float _interpAlphaVel{ 0.0f }; float _interpAlpha{ 0.0f }; @@ -84,6 +99,9 @@ protected: bool _lastEnableDebugDrawIKTargets{ false }; void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug, AnimChain& chainInfoOut) const; + void AnimSplineIK::setTargetVars(const QString& jointName, const QString& positionVar, const QString& rotationVar, + const QString& typeVar, const QString& weightVar, float weight, const std::vector& flexCoefficients, + const QString& poleVectorEnabledVar, const QString& poleReferenceVectorVar, const QString& poleVectorVar); void computeAndCacheSplineJointInfosForIKTarget(const AnimContext& context, const IKTarget& target) const; const std::vector* findOrCreateSplineJointInfo(const AnimContext& context, const IKTarget& target) const; mutable std::map> _splineJointInfoMap; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 70f1f9a1f7..b92e095b4f 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1857,14 +1857,14 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo updateHead(_headEnabled, hipsEnabled, params.primaryControllerPoses[PrimaryControllerType_Head]); - updateHands(leftHandEnabled, rightHandEnabled, hipsEnabled, hipsEstimated, leftArmEnabled, rightArmEnabled, _headEnabled, dt, - params.primaryControllerPoses[PrimaryControllerType_LeftHand], params.primaryControllerPoses[PrimaryControllerType_RightHand], - params.hipsShapeInfo, params.spineShapeInfo, params.spine1ShapeInfo, params.spine2ShapeInfo, - params.rigToSensorMatrix, sensorToRigMatrix); + //updateHands(leftHandEnabled, rightHandEnabled, hipsEnabled, hipsEstimated, leftArmEnabled, rightArmEnabled, _headEnabled, dt, + // params.primaryControllerPoses[PrimaryControllerType_LeftHand], params.primaryControllerPoses[PrimaryControllerType_RightHand], + // params.hipsShapeInfo, params.spineShapeInfo, params.spine1ShapeInfo, params.spine2ShapeInfo, + // params.rigToSensorMatrix, sensorToRigMatrix); - //updateHands2(leftHandEnabled, rightHandEnabled, _headEnabled, - // params.primaryControllerPoses[PrimaryControllerType_LeftHand], params.primaryControllerPoses[PrimaryControllerType_RightHand], - // params.rigToSensorMatrix, sensorToRigMatrix); + updateHands2(leftHandEnabled, rightHandEnabled, _headEnabled, + params.primaryControllerPoses[PrimaryControllerType_LeftHand], params.primaryControllerPoses[PrimaryControllerType_RightHand], + params.rigToSensorMatrix, sensorToRigMatrix); updateFeet(leftFootEnabled, rightFootEnabled, _headEnabled, params.primaryControllerPoses[PrimaryControllerType_LeftFoot], params.primaryControllerPoses[PrimaryControllerType_RightFoot], From e7941f78d67f99844697e8563957129e7e865c9a Mon Sep 17 00:00:00 2001 From: amantley Date: Thu, 24 Jan 2019 17:17:39 -0800 Subject: [PATCH 015/474] adding the shoulder rotation back in --- libraries/animation/src/AnimSplineIK.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/libraries/animation/src/AnimSplineIK.cpp b/libraries/animation/src/AnimSplineIK.cpp index afe9f6dfcb..1f135df120 100644 --- a/libraries/animation/src/AnimSplineIK.cpp +++ b/libraries/animation/src/AnimSplineIK.cpp @@ -125,8 +125,8 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const AnimPose afterSolveSecondaryTarget = _skeleton->getAbsolutePose(_secondaryTargetIndex, _poses); glm::quat secondaryTargetRotation = animVars.lookupRigToGeometry(_secondaryTargetRotationVar, afterSolveSecondaryTarget.rot()); - // updatedSecondaryTarget = AnimPose(secondaryTargetRotation, afterSolveSpine2.trans()); - updatedSecondaryTarget = AnimPose(afterSolveSecondaryTarget.rot(), afterSolveSecondaryTarget.trans()); + updatedSecondaryTarget = AnimPose(secondaryTargetRotation, afterSolveSecondaryTarget.trans()); + //updatedSecondaryTarget = AnimPose(afterSolveSecondaryTarget.rot(), afterSolveSecondaryTarget.trans()); } @@ -141,10 +141,10 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const secondaryTarget.setPose(updatedSecondaryTarget.rot(), updatedSecondaryTarget.trans()); secondaryTarget.setWeight(weight2); - const float* flexCoefficients2 = new float[3]{ 1.0f, 1.0f, 1.0f }; + const float* flexCoefficients2 = new float[3]{ 1.0f, 0.5f, 0.25f }; secondaryTarget.setFlexCoefficients(3, flexCoefficients2); } - /* + AnimChain secondaryJointChain; AnimPose beforeSolveChestNeck; if (_poses.size() > 0) { @@ -157,7 +157,7 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const secondaryJointChain.outputRelativePoses(_poses); } - */ + const float FRAMES_PER_SECOND = 30.0f; _interpAlphaVel = FRAMES_PER_SECOND / _interpDuration; @@ -183,9 +183,9 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const AnimPose finalNeckAbsolute = AnimPose(safeLerp(target2.getRotation(), target.getRotation(), 1.0f),neckAbsolute.trans()); _poses[headParent] = spine2Target.inverse() * beforeSolveChestNeck; */ - //AnimPose tipTarget(target.getRotation(),target.getTranslation()); - //AnimPose tipRelativePose = _skeleton->getAbsolutePose(tipParent,_poses).inverse() * headTarget; - //_poses[_tipJointIndex] = tipHeadRelativePose; + AnimPose tipTarget(target.getRotation(),target.getTranslation()); + AnimPose tipRelativePose = _skeleton->getAbsolutePose(tipParent,_poses).inverse() * tipTarget; + _poses[_tipJointIndex] = tipRelativePose; } // debug render ik targets From 2574e821840952b694c463ece9bfbb5fc27aa1b9 Mon Sep 17 00:00:00 2001 From: amantley Date: Thu, 24 Jan 2019 19:47:27 -0800 Subject: [PATCH 016/474] cleaned up print statements --- interface/src/avatar/MySkeletonModel.cpp | 5 +---- .../animation/src/AnimInverseKinematics.cpp | 21 +++++-------------- libraries/animation/src/AnimTwoBoneIK.cpp | 3 --- 3 files changed, 6 insertions(+), 23 deletions(-) diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index e0362ef548..356b365f93 100644 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -199,7 +199,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { // if hips are not under direct control, estimate the hips position. if (avatarHeadPose.isValid() && !(params.primaryControllerFlags[Rig::PrimaryControllerType_Hips] & (uint8_t)Rig::ControllerFlags::Enabled)) { bool isFlying = (myAvatar->getCharacterController()->getState() == CharacterController::State::Hover || myAvatar->getCharacterController()->computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS); - + // timescale in seconds const float TRANS_HORIZ_TIMESCALE = 0.15f; const float TRANS_VERT_TIMESCALE = 0.01f; // We want the vertical component of the hips to follow quickly to prevent spine squash/stretch. @@ -255,9 +255,6 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { AnimPose newSpinePose(glm::mat4(glm::vec4(w, 0.0f), glm::vec4(u, 0.0f), glm::vec4(v, 0.0f), glm::vec4(glm::vec3(0.0f, 0.0f, 0.0f), 1.0f))); currentSpine2Pose.rot() = safeLerp(currentSpine2Pose.rot(), newSpinePose.rot(), SPINE2_ROTATION_FILTER); params.primaryControllerPoses[Rig::PrimaryControllerType_Spine2] = currentSpine2Pose; - qCDebug(interfaceapp) << "finding the spine 2 azimuth"; - qCDebug(interfaceapp) << currentSpine2Pose; - params.primaryControllerFlags[Rig::PrimaryControllerType_Spine2] = (uint8_t)Rig::ControllerFlags::Enabled | (uint8_t)Rig::ControllerFlags::Estimated; } } diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index f95fe3333f..a1809f3438 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -237,7 +237,6 @@ void AnimInverseKinematics::solve(const AnimContext& context, const std::vector< // solve all targets for (size_t i = 0; i < targets.size(); i++) { - // qCDebug(animation) << "target id: " << targets[i].getIndex() << " and type " << (int)targets[i].getType(); switch (targets[i].getType()) { case IKTarget::Type::Unknown: break; @@ -259,7 +258,6 @@ void AnimInverseKinematics::solve(const AnimContext& context, const std::vector< // ease in expo alpha = 1.0f - powf(2.0f, -10.0f * alpha); - qCDebug(animation) << "the alpha for joint chains is " << alpha; size_t chainSize = std::min(_prevJointChainInfoVec[i].jointInfoVec.size(), jointChainInfoVec[i].jointInfoVec.size()); @@ -322,10 +320,6 @@ void AnimInverseKinematics::solve(const AnimContext& context, const std::vector< } } - //qCDebug(animation) << "joint chain pose for head animIK " << jointChainInfoVec[4].jointInfoVec[w].trans << " " << jointChainInfoVec[4].jointInfoVec[w].rot; - qCDebug(animation) << "absolute pose for head animIK " << absolutePoses[_skeleton->nameToJointIndex("Head")]; - qCDebug(animation) << "target pose for head animIK " << targets[4].getTranslation() << " " << targets[4].getRotation(); - // compute maxError maxError = 0.0f; for (size_t i = 0; i < targets.size(); i++) { @@ -366,7 +360,6 @@ void AnimInverseKinematics::solve(const AnimContext& context, const std::vector< // copy jointChainInfoVec into _prevJointChainInfoVec, and update timers for (size_t i = 0; i < jointChainInfoVec.size(); i++) { _prevJointChainInfoVec[i].timer = _prevJointChainInfoVec[i].timer - dt; - //qCDebug(animation) << "the alpha for joint chains is " << _prevJointChainInfoVec[i].timer; if (_prevJointChainInfoVec[i].timer <= 0.0f) { _prevJointChainInfoVec[i] = jointChainInfoVec[i]; _prevJointChainInfoVec[i].target = targets[i]; @@ -830,9 +823,7 @@ void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, co AnimPose relPose = parentAbsPose.inverse() * flexedAbsPose; - bool constrained = false; - /* if (splineJointInfo.jointIndex != _hipsIndex) { // constrain the amount the spine can stretch or compress float length = glm::length(relPose.trans()); @@ -853,7 +844,7 @@ void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, co relPose.trans() = glm::vec3(0.0f); } } - */ + jointChainInfoOut.jointInfoVec[i] = { relPose.rot(), relPose.trans(), splineJointInfo.jointIndex, constrained }; parentAbsPose = flexedAbsPose; @@ -878,6 +869,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars // disable IK on android return underPoses; #endif + // allows solutionSource to be overridden by an animVar auto solutionSource = animVars.lookup(_solutionSourceVar, (int)_solutionSource); @@ -885,7 +877,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars if (dt > MAX_OVERLAY_DT) { dt = MAX_OVERLAY_DT; } - + if (_relativePoses.size() != underPoses.size()) { loadPoses(underPoses); } else { @@ -1040,20 +1032,17 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars preconditionRelativePosesToAvoidLimbLock(context, targets); - //qCDebug(animation) << "hips before ccd" << _relativePoses[_hipsIndex]; solve(context, targets, dt, jointChainInfoVec); - //qCDebug(animation) << "hips after ccd" << _relativePoses[_hipsIndex]; - } } - + if (context.getEnableDebugDrawIKConstraints()) { debugDrawConstraints(context); } } processOutputJoints(triggersOut); - + return _relativePoses; } diff --git a/libraries/animation/src/AnimTwoBoneIK.cpp b/libraries/animation/src/AnimTwoBoneIK.cpp index 31397157ac..3f8de488ff 100644 --- a/libraries/animation/src/AnimTwoBoneIK.cpp +++ b/libraries/animation/src/AnimTwoBoneIK.cpp @@ -50,7 +50,6 @@ const AnimPoseVec& AnimTwoBoneIK::evaluate(const AnimVariantMap& animVars, const if (_children.size() != 1) { return _poses; } - // evalute underPoses AnimPoseVec underPoses = _children[0]->evaluate(animVars, context, dt, triggersOut); @@ -79,7 +78,6 @@ const AnimPoseVec& AnimTwoBoneIK::evaluate(const AnimVariantMap& animVars, const // determine if we should interpolate bool enabled = animVars.lookup(_enabledVar, _enabled); - // qCDebug(animation) << "two bone var " << _enabledVar; if (enabled != _enabled) { AnimChain poseChain; poseChain.buildFromRelativePoses(_skeleton, _poses, _tipJointIndex); @@ -123,7 +121,6 @@ const AnimPoseVec& AnimTwoBoneIK::evaluate(const AnimVariantMap& animVars, const // First look in the triggers then look in the animVars, so we can follow output joints underneath us in the anim graph AnimPose targetPose(tipPose); if (triggersOut.hasKey(endEffectorRotationVar)) { - qCDebug(animation) << " end effector variable " << endEffectorRotationVar << " is " << triggersOut.lookupRigToGeometry(endEffectorRotationVar, tipPose.rot()); targetPose.rot() = triggersOut.lookupRigToGeometry(endEffectorRotationVar, tipPose.rot()); } else if (animVars.hasKey(endEffectorRotationVar)) { targetPose.rot() = animVars.lookupRigToGeometry(endEffectorRotationVar, tipPose.rot()); From bc635306ead7c5c6640aa652f936ae0a3fcab98f Mon Sep 17 00:00:00 2001 From: amantley Date: Thu, 24 Jan 2019 22:28:40 -0800 Subject: [PATCH 017/474] added interp from enabled to disabled and vice versa --- libraries/animation/src/AnimSplineIK.cpp | 107 +++++++++++++++++++---- libraries/animation/src/AnimSplineIK.h | 1 + 2 files changed, 93 insertions(+), 15 deletions(-) diff --git a/libraries/animation/src/AnimSplineIK.cpp b/libraries/animation/src/AnimSplineIK.cpp index 1f135df120..a036bb47a5 100644 --- a/libraries/animation/src/AnimSplineIK.cpp +++ b/libraries/animation/src/AnimSplineIK.cpp @@ -15,6 +15,7 @@ #include "AnimUtil.h" static const float JOINT_CHAIN_INTERP_TIME = 0.5f; +static const float FRAMES_PER_SECOND = 30.0f; AnimSplineIK::AnimSplineIK(const QString& id, float alpha, bool enabled, float interpDuration, const QString& baseJointName, @@ -66,10 +67,50 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const if (_children.size() != 1) { return _poses; } + + const float MIN_ALPHA = 0.0f; + const float MAX_ALPHA = 1.0f; + float alpha = glm::clamp(animVars.lookup(_alphaVar, _alpha), MIN_ALPHA, MAX_ALPHA); + // evaluate underPoses AnimPoseVec underPoses = _children[0]->evaluate(animVars, context, dt, triggersOut); + + // if we don't have a skeleton, or jointName lookup failed. + if (!_skeleton || _baseJointIndex == -1 || _tipJointIndex == -1 || alpha == 0.0f || underPoses.size() == 0) { + // pass underPoses through unmodified. + _poses = underPoses; + return _poses; + } + + // guard against size changes + if (underPoses.size() != _poses.size()) { + _poses = underPoses; + } + + // determine if we should interpolate + bool enabled = animVars.lookup(_enabledVar, _enabled); + if (enabled != _enabled) { + AnimChain poseChain; + poseChain.buildFromRelativePoses(_skeleton, _poses, _tipJointIndex); + if (enabled) { + beginInterp(InterpType::SnapshotToSolve, poseChain); + } else { + beginInterp(InterpType::SnapshotToUnderPoses, poseChain); + } + } + _enabled = enabled; + _poses = underPoses; + // don't build chains or do IK if we are disabled & not interping. + if (_interpType == InterpType::None && !enabled) { + return _poses; + } + + // compute under chain for possible interpolation + AnimChain underChain; + underChain.buildFromRelativePoses(_skeleton, underPoses, _tipJointIndex); + glm::quat hipsTargetRotation; glm::vec3 hipsTargetTranslation; @@ -113,8 +154,7 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const AnimPoseVec absolutePoses2; absolutePoses2.resize(_poses.size()); if (_poses.size() > 0) { - - //_snapshotChain.buildFromRelativePoses(_skeleton, _poses, target.getIndex()); + jointChain.buildFromRelativePoses(_skeleton, _poses, target.getIndex()); solveTargetWithSpline(context, target, absolutePoses, false, jointChain); jointChain.buildDirtyAbsolutePoses(); @@ -149,25 +189,13 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const AnimPose beforeSolveChestNeck; if (_poses.size() > 0) { - + beforeSolveChestNeck = _skeleton->getAbsolutePose(_skeleton->nameToJointIndex("Neck"), _poses); secondaryJointChain.buildFromRelativePoses(_skeleton, _poses, secondaryTarget.getIndex()); solveTargetWithSpline(context, secondaryTarget, absolutePoses2, false, secondaryJointChain); secondaryJointChain.outputRelativePoses(_poses); } - - - - const float FRAMES_PER_SECOND = 30.0f; - _interpAlphaVel = FRAMES_PER_SECOND / _interpDuration; - _alpha = _interpAlphaVel * dt; - // we need to blend the old joint chain with the current joint chain, otherwise known as: _snapShotChain - // we blend the chain info then we accumulate it then we assign to relative poses then we return the value. - // make the alpha - // make the prevchain - // interp from the previous chain to the new chain/or underposes if the ik is disabled. - // update the relative poses and then we are done // set the tip/head rotation to match the absolute rotation of the target. int tipParent = _skeleton->getParentIndex(_tipJointIndex); @@ -188,6 +216,46 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const _poses[_tipJointIndex] = tipRelativePose; } + // compute chain + AnimChain ikChain; + ikChain.buildFromRelativePoses(_skeleton, _poses, _tipJointIndex); + // blend with the underChain + ikChain.blend(underChain, alpha); + + // apply smooth interpolation when turning ik on and off + if (_interpType != InterpType::None) { + _interpAlpha += _interpAlphaVel * dt; + + // ease in expo + float easeInAlpha = 1.0f - powf(2.0f, -10.0f * _interpAlpha); + + if (_interpAlpha < 1.0f) { + AnimChain interpChain; + if (_interpType == InterpType::SnapshotToUnderPoses) { + interpChain = underChain; + interpChain.blend(_snapshotChain, easeInAlpha); + } else if (_interpType == InterpType::SnapshotToSolve) { + interpChain = ikChain; + interpChain.blend(_snapshotChain, easeInAlpha); + } + // copy interpChain into _poses + interpChain.outputRelativePoses(_poses); + } else { + // interpolation complete + _interpType = InterpType::None; + } + } + + if (_interpType == InterpType::None) { + if (enabled) { + // copy chain into _poses + ikChain.outputRelativePoses(_poses); + } else { + // copy under chain into _poses + underChain.outputRelativePoses(_poses); + } + } + // debug render ik targets if (context.getEnableDebugDrawIKTargets()) { const vec4 WHITE(1.0f); @@ -497,4 +565,13 @@ void AnimSplineIK::setTargetVars(const QString& jointName, const QString& positi _targetVarVec.push_back(targetVar); } */ +} + +void AnimSplineIK::beginInterp(InterpType interpType, const AnimChain& chain) { + // capture the current poses in a snapshot. + _snapshotChain = chain; + + _interpType = interpType; + _interpAlphaVel = FRAMES_PER_SECOND / _interpDuration; + _interpAlpha = 0.0f; } \ No newline at end of file diff --git a/libraries/animation/src/AnimSplineIK.h b/libraries/animation/src/AnimSplineIK.h index 4c51a0b9e1..d4dbb85a9e 100644 --- a/libraries/animation/src/AnimSplineIK.h +++ b/libraries/animation/src/AnimSplineIK.h @@ -49,6 +49,7 @@ protected: virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) override; void lookUpIndices(); + void beginInterp(InterpType interpType, const AnimChain& chain); AnimPoseVec _poses; From 71df61498997afb1d0e64a9b0d690630ed92ab47 Mon Sep 17 00:00:00 2001 From: amantley Date: Fri, 25 Jan 2019 11:28:37 -0800 Subject: [PATCH 018/474] put all the hand update code in one function that works for two bone IK and legacy animInverseKinematics Ik --- libraries/animation/src/AnimSplineIK.cpp | 13 +++---- libraries/animation/src/Rig.cpp | 45 +++++++++++++++++++----- 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/libraries/animation/src/AnimSplineIK.cpp b/libraries/animation/src/AnimSplineIK.cpp index a036bb47a5..bb3ec654f5 100644 --- a/libraries/animation/src/AnimSplineIK.cpp +++ b/libraries/animation/src/AnimSplineIK.cpp @@ -189,7 +189,7 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const AnimPose beforeSolveChestNeck; if (_poses.size() > 0) { - + // fix this to deal with no neck AA beforeSolveChestNeck = _skeleton->getAbsolutePose(_skeleton->nameToJointIndex("Neck"), _poses); secondaryJointChain.buildFromRelativePoses(_skeleton, _poses, secondaryTarget.getIndex()); @@ -205,12 +205,13 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const AnimPose secondaryTargetPose(target2.getRotation(), target2.getTranslation()); AnimPose secondaryTargetRelativePose = _skeleton->getAbsolutePose(secondaryTargetParent, _poses).inverse() * secondaryTargetPose; _poses[_secondaryTargetIndex] = secondaryTargetRelativePose; - - - AnimPose neckAbsolute = _skeleton->getAbsolutePose(tipParent, _poses); - AnimPose finalNeckAbsolute = AnimPose(safeLerp(target2.getRotation(), target.getRotation(), 1.0f),neckAbsolute.trans()); - _poses[headParent] = spine2Target.inverse() * beforeSolveChestNeck; */ + + AnimPose secondaryTargetPose(secondaryTarget.getRotation(), secondaryTarget.getTranslation()); + AnimPose neckAbsolute = _skeleton->getAbsolutePose(tipParent, _poses); + //AnimPose finalNeckAbsolute = AnimPose(safeLerp(target2.getRotation(), target.getRotation(), 1.0f),neckAbsolute.trans()); + _poses[tipParent] = secondaryTargetPose.inverse() * beforeSolveChestNeck; + AnimPose tipTarget(target.getRotation(),target.getTranslation()); AnimPose tipRelativePose = _skeleton->getAbsolutePose(tipParent,_poses).inverse() * tipTarget; _poses[_tipJointIndex] = tipRelativePose; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index b92e095b4f..9e2e099cfe 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1271,6 +1271,7 @@ void Rig::computeHeadFromHMD(const AnimPose& hmdPose, glm::vec3& headPositionOut void Rig::updateHead(bool headEnabled, bool hipsEnabled, const AnimPose& headPose) { if (_animSkeleton) { if (headEnabled) { + _animVars.set("splineIKEnabled", true); _animVars.set("headPosition", headPose.trans()); _animVars.set("headRotation", headPose.rot()); if (hipsEnabled) { @@ -1285,6 +1286,7 @@ void Rig::updateHead(bool headEnabled, bool hipsEnabled, const AnimPose& headPos _animVars.set("headWeight", 8.0f); } } else { + _animVars.set("splineIKEnabled", false); _animVars.unset("headPosition"); _animVars.set("headRotation", headPose.rot()); _animVars.set("headType", (int)IKTarget::Type::RotationOnly); @@ -1416,8 +1418,22 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab const bool ENABLE_POLE_VECTORS = true; + if (headEnabled) { + // always do IK if head is enabled + _animVars.set("leftHandIKEnabled", true); + _animVars.set("rightHandIKEnabled", true); + } else { + // only do IK if we have a valid foot. + _animVars.set("leftHandIKEnabled", leftHandEnabled); + _animVars.set("rightHandIKEnabled", rightHandEnabled); + } + if (leftHandEnabled) { + // we need this for twoBoneIK version of hands. + _animVars.set(LEFT_HAND_IK_POSITION_VAR, LEFT_HAND_POSITION); + _animVars.set(LEFT_HAND_IK_ROTATION_VAR, LEFT_HAND_ROTATION); + glm::vec3 handPosition = leftHandPose.trans(); glm::quat handRotation = leftHandPose.rot(); @@ -1450,8 +1466,11 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab _animVars.set("leftHandPoleVectorEnabled", false); } } else { + // need this for two bone ik + _animVars.set(LEFT_HAND_IK_POSITION_VAR, MAIN_STATE_MACHINE_LEFT_HAND_POSITION); + _animVars.set(LEFT_HAND_IK_ROTATION_VAR, MAIN_STATE_MACHINE_LEFT_HAND_ROTATION); + _animVars.set("leftHandPoleVectorEnabled", false); - _animVars.unset("leftHandPosition"); _animVars.unset("leftHandRotation"); @@ -1465,6 +1484,10 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab if (rightHandEnabled) { + // need this for two bone IK + _animVars.set(RIGHT_HAND_IK_POSITION_VAR, RIGHT_HAND_POSITION); + _animVars.set(RIGHT_HAND_IK_ROTATION_VAR, RIGHT_HAND_ROTATION); + glm::vec3 handPosition = rightHandPose.trans(); glm::quat handRotation = rightHandPose.rot(); @@ -1498,8 +1521,12 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab _animVars.set("rightHandPoleVectorEnabled", false); } } else { - _animVars.set("rightHandPoleVectorEnabled", false); + // need this for two bone IK + _animVars.set(RIGHT_HAND_IK_POSITION_VAR, MAIN_STATE_MACHINE_RIGHT_HAND_POSITION); + _animVars.set(RIGHT_HAND_IK_ROTATION_VAR, MAIN_STATE_MACHINE_RIGHT_HAND_ROTATION); + + _animVars.set("rightHandPoleVectorEnabled", false); _animVars.unset("rightHandPosition"); _animVars.unset("rightHandRotation"); @@ -1857,14 +1884,14 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo updateHead(_headEnabled, hipsEnabled, params.primaryControllerPoses[PrimaryControllerType_Head]); - //updateHands(leftHandEnabled, rightHandEnabled, hipsEnabled, hipsEstimated, leftArmEnabled, rightArmEnabled, _headEnabled, dt, - // params.primaryControllerPoses[PrimaryControllerType_LeftHand], params.primaryControllerPoses[PrimaryControllerType_RightHand], - // params.hipsShapeInfo, params.spineShapeInfo, params.spine1ShapeInfo, params.spine2ShapeInfo, - // params.rigToSensorMatrix, sensorToRigMatrix); + updateHands(leftHandEnabled, rightHandEnabled, hipsEnabled, hipsEstimated, leftArmEnabled, rightArmEnabled, _headEnabled, dt, + params.primaryControllerPoses[PrimaryControllerType_LeftHand], params.primaryControllerPoses[PrimaryControllerType_RightHand], + params.hipsShapeInfo, params.spineShapeInfo, params.spine1ShapeInfo, params.spine2ShapeInfo, + params.rigToSensorMatrix, sensorToRigMatrix); - updateHands2(leftHandEnabled, rightHandEnabled, _headEnabled, - params.primaryControllerPoses[PrimaryControllerType_LeftHand], params.primaryControllerPoses[PrimaryControllerType_RightHand], - params.rigToSensorMatrix, sensorToRigMatrix); + //updateHands2(leftHandEnabled, rightHandEnabled, _headEnabled, + // params.primaryControllerPoses[PrimaryControllerType_LeftHand], params.primaryControllerPoses[PrimaryControllerType_RightHand], + // params.rigToSensorMatrix, sensorToRigMatrix); updateFeet(leftFootEnabled, rightFootEnabled, _headEnabled, params.primaryControllerPoses[PrimaryControllerType_LeftFoot], params.primaryControllerPoses[PrimaryControllerType_RightFoot], From 992820cd674dfc37fc31c8517b81bf1ecc4e5e3e Mon Sep 17 00:00:00 2001 From: amantley Date: Fri, 25 Jan 2019 11:34:18 -0800 Subject: [PATCH 019/474] removed unnecessary hand update function for two bone IK --- libraries/animation/src/Rig.cpp | 102 -------------------------------- libraries/animation/src/Rig.h | 3 - 2 files changed, 105 deletions(-) diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 9e2e099cfe..7a6a1530d7 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1539,104 +1539,6 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab } } -void Rig::updateHands2(bool leftHandEnabled, bool rightHandEnabled, bool headEnabled, - const AnimPose& leftHandPose, const AnimPose& rightHandPose, - const glm::mat4& rigToSensorMatrix, const glm::mat4& sensorToRigMatrix) { - - int hipsIndex = indexOfJoint("Hips"); - const float ELBOW_POLE_VECTOR_BLEND_FACTOR = 0.85f; - - if (headEnabled) { - // always do IK if head is enabled - _animVars.set("leftHandIKEnabled", true); - _animVars.set("rightHandIKEnabled", true); - } else { - // only do IK if we have a valid foot. - _animVars.set("leftHandIKEnabled", leftHandEnabled); - _animVars.set("rightHandIKEnabled", rightHandEnabled); - } - - if (leftHandEnabled) { - - _animVars.set(LEFT_HAND_POSITION, leftHandPose.trans()); - _animVars.set(LEFT_HAND_ROTATION, leftHandPose.rot()); - - // We want to drive the IK directly from the trackers. - _animVars.set(LEFT_HAND_IK_POSITION_VAR, LEFT_HAND_POSITION); - _animVars.set(LEFT_HAND_IK_ROTATION_VAR, LEFT_HAND_ROTATION); - - int handJointIndex = _animSkeleton->nameToJointIndex("LeftHand"); - int elbowJointIndex = _animSkeleton->nameToJointIndex("LeftForeArm"); - int shoulderJointIndex = _animSkeleton->nameToJointIndex("LeftArm"); - int oppositeArmJointIndex = _animSkeleton->nameToJointIndex("RightArm"); - glm::vec3 poleVector; - bool usePoleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, shoulderJointIndex, oppositeArmJointIndex, poleVector); - //glm::vec3 poleVector = calculateKneePoleVector(handJointIndex, elbowJointIndex, shoulderJointIndex, hipsIndex, rightHandPose); - glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector); - - // smooth toward desired pole vector from previous pole vector... to reduce jitter, but in sensor space. - if (!_prevLeftHandPoleVectorValid) { - _prevLeftHandPoleVectorValid = true; - _prevLeftHandPoleVector = sensorPoleVector; - } - glm::quat deltaRot = rotationBetween(_prevLeftHandPoleVector, sensorPoleVector); - glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, ELBOW_POLE_VECTOR_BLEND_FACTOR); - _prevLeftHandPoleVector = smoothDeltaRot * _prevLeftHandPoleVector; - - _animVars.set("leftHandPoleVectorEnabled", true); - _animVars.set("leftHandPoleVector", transformVectorFast(sensorToRigMatrix, _prevLeftHandPoleVector)); - } else { - // We want to drive the IK from the underlying animation. - // This gives us the ability to squat while in the HMD, without the feet from dipping under the floor. - _animVars.set(LEFT_HAND_IK_POSITION_VAR, MAIN_STATE_MACHINE_LEFT_HAND_POSITION); - _animVars.set(LEFT_HAND_IK_ROTATION_VAR, MAIN_STATE_MACHINE_LEFT_HAND_ROTATION); - - // We want to match the animated knee pose as close as possible, so don't use poleVectors - _animVars.set("leftHandPoleVectorEnabled", false); - _prevLeftHandPoleVectorValid = false; - } - - if (rightHandEnabled) { - - _animVars.set(RIGHT_HAND_POSITION, rightHandPose.trans()); - _animVars.set(RIGHT_HAND_ROTATION, rightHandPose.rot()); - - // We want to drive the IK directly from the trackers. - _animVars.set(RIGHT_HAND_IK_POSITION_VAR, RIGHT_HAND_POSITION); - _animVars.set(RIGHT_HAND_IK_ROTATION_VAR, RIGHT_HAND_ROTATION); - - int handJointIndex = _animSkeleton->nameToJointIndex("RightHand"); - int elbowJointIndex = _animSkeleton->nameToJointIndex("RightForeArm"); - int shoulderJointIndex = _animSkeleton->nameToJointIndex("RightArm"); - int oppositeArmJointIndex = _animSkeleton->nameToJointIndex("LeftArm"); - glm::vec3 poleVector; - bool usePoleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, shoulderJointIndex, oppositeArmJointIndex, poleVector); - glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector); - - // smooth toward desired pole vector from previous pole vector... to reduce jitter, but in sensor space. - if (!_prevRightHandPoleVectorValid) { - _prevRightHandPoleVectorValid = true; - _prevRightHandPoleVector = sensorPoleVector; - } - glm::quat deltaRot = rotationBetween(_prevRightHandPoleVector, sensorPoleVector); - glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, ELBOW_POLE_VECTOR_BLEND_FACTOR); - _prevRightHandPoleVector = smoothDeltaRot * _prevRightHandPoleVector; - - _animVars.set("rightHandPoleVectorEnabled", true); - _animVars.set("rightHandPoleVector", transformVectorFast(sensorToRigMatrix, _prevRightHandPoleVector)); - } else { - // We want to drive the IK from the underlying animation. - // This gives us the ability to squat while in the HMD, without the feet from dipping under the floor. - _animVars.set(RIGHT_HAND_IK_POSITION_VAR, MAIN_STATE_MACHINE_RIGHT_HAND_POSITION); - _animVars.set(RIGHT_HAND_IK_ROTATION_VAR, MAIN_STATE_MACHINE_RIGHT_HAND_ROTATION); - - // We want to match the animated knee pose as close as possible, so don't use poleVectors - _animVars.set("rightHandPoleVectorEnabled", false); - _prevRightHandPoleVectorValid = false; - } - -} - void Rig::updateFeet(bool leftFootEnabled, bool rightFootEnabled, bool headEnabled, const AnimPose& leftFootPose, const AnimPose& rightFootPose, const glm::mat4& rigToSensorMatrix, const glm::mat4& sensorToRigMatrix) { @@ -1889,10 +1791,6 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo params.hipsShapeInfo, params.spineShapeInfo, params.spine1ShapeInfo, params.spine2ShapeInfo, params.rigToSensorMatrix, sensorToRigMatrix); - //updateHands2(leftHandEnabled, rightHandEnabled, _headEnabled, - // params.primaryControllerPoses[PrimaryControllerType_LeftHand], params.primaryControllerPoses[PrimaryControllerType_RightHand], - // params.rigToSensorMatrix, sensorToRigMatrix); - updateFeet(leftFootEnabled, rightFootEnabled, _headEnabled, params.primaryControllerPoses[PrimaryControllerType_LeftFoot], params.primaryControllerPoses[PrimaryControllerType_RightFoot], params.rigToSensorMatrix, sensorToRigMatrix); diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index e6effe9e53..7468e9f6f9 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -250,9 +250,6 @@ protected: const HFMJointShapeInfo& hipsShapeInfo, const HFMJointShapeInfo& spineShapeInfo, const HFMJointShapeInfo& spine1ShapeInfo, const HFMJointShapeInfo& spine2ShapeInfo, const glm::mat4& rigToSensorMatrix, const glm::mat4& sensorToRigMatrix); - void updateHands2(bool leftHandEnabled, bool rightHandEnabled, bool headEnabled, - const AnimPose& leftHandPose, const AnimPose& rightHandPose, - const glm::mat4& rigToSensorMatrix, const glm::mat4& sensorToRigMatrix); void updateFeet(bool leftFootEnabled, bool rightFootEnabled, bool headEnabled, const AnimPose& leftFootPose, const AnimPose& rightFootPose, const glm::mat4& rigToSensorMatrix, const glm::mat4& sensorToRigMatrix); From 446d7b9514e6b1b87384b78b99e984d6f48134ee Mon Sep 17 00:00:00 2001 From: amantley Date: Fri, 25 Jan 2019 14:41:18 -0800 Subject: [PATCH 020/474] added the flex coeff for the primary and secondary spline targets to the json --- .../avatar/avatar-animation_withIKNode.json | 4 ++- libraries/animation/src/AnimNodeLoader.cpp | 5 +++- libraries/animation/src/AnimSplineIK.cpp | 30 ++++++++++++++++--- libraries/animation/src/AnimSplineIK.h | 12 +++++++- 4 files changed, 44 insertions(+), 7 deletions(-) diff --git a/interface/resources/avatar/avatar-animation_withIKNode.json b/interface/resources/avatar/avatar-animation_withIKNode.json index 04ed593755..13c102a5f1 100644 --- a/interface/resources/avatar/avatar-animation_withIKNode.json +++ b/interface/resources/avatar/avatar-animation_withIKNode.json @@ -193,7 +193,9 @@ "alphaVar": "splineIKAlpha", "enabledVar": "splineIKEnabled", "endEffectorRotationVarVar": "splineIKRotationVar", - "endEffectorPositionVarVar": "splineIKPositionVar" + "endEffectorPositionVarVar": "splineIKPositionVar", + "primaryFlexCoefficients": "1, 0.5, 0.25, 0.2, 0.1", + "secondaryFlexCoefficients": "1.0, 0.5, 0.25" }, "children": [ { diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index ebe9dbe3ba..c5d17e8884 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -596,12 +596,15 @@ static AnimNode::Pointer loadSplineIKNode(const QJsonObject& jsonObj, const QStr READ_STRING(enabledVar, jsonObj, id, jsonUrl, nullptr); READ_STRING(endEffectorRotationVarVar, jsonObj, id, jsonUrl, nullptr); READ_STRING(endEffectorPositionVarVar, jsonObj, id, jsonUrl, nullptr); + READ_STRING(primaryFlexCoefficients, jsonObj, id, jsonUrl, nullptr); + READ_STRING(secondaryFlexCoefficients, jsonObj, id, jsonUrl, nullptr); auto node = std::make_shared(id, alpha, enabled, interpDuration, baseJointName, tipJointName, alphaVar, enabledVar, endEffectorRotationVarVar, endEffectorPositionVarVar, basePositionVar, baseRotationVar, - tipPositionVar, tipRotationVar, secondaryTargetJointName, secondaryTargetPositionVar, secondaryTargetRotationVar); + tipPositionVar, tipRotationVar, secondaryTargetJointName, secondaryTargetPositionVar, + secondaryTargetRotationVar, primaryFlexCoefficients, secondaryFlexCoefficients); return node; } diff --git a/libraries/animation/src/AnimSplineIK.cpp b/libraries/animation/src/AnimSplineIK.cpp index bb3ec654f5..658fa2cf6a 100644 --- a/libraries/animation/src/AnimSplineIK.cpp +++ b/libraries/animation/src/AnimSplineIK.cpp @@ -28,7 +28,9 @@ AnimSplineIK::AnimSplineIK(const QString& id, float alpha, bool enabled, float i const QString& tipRotationVar, const QString& secondaryTargetJointName, const QString& secondaryTargetPositionVar, - const QString& secondaryTargetRotationVar) : + const QString& secondaryTargetRotationVar, + const QString& primaryFlexCoefficients, + const QString& secondaryFlexCoefficients) : AnimNode(AnimNode::Type::SplineIK, id), _alpha(alpha), _enabled(enabled), @@ -47,8 +49,25 @@ AnimSplineIK::AnimSplineIK(const QString& id, float alpha, bool enabled, float i _tipRotationVar(tipRotationVar), _secondaryTargetJointName(secondaryTargetJointName), _secondaryTargetPositionVar(secondaryTargetPositionVar), - _secondaryTargetRotationVar(secondaryTargetRotationVar) + _secondaryTargetRotationVar(secondaryTargetRotationVar) { + + QStringList flexCoefficientsValues = primaryFlexCoefficients.split(',', QString::SkipEmptyParts); + for (int i = 0; i < flexCoefficientsValues.size(); i++) { + if (i < MAX_NUMBER_FLEX_VARIABLES) { + qCDebug(animation) << "flex value " << flexCoefficientsValues[i].toDouble(); + _primaryFlexCoefficients[i] = (float)flexCoefficientsValues[i].toDouble(); + } + } + _numPrimaryFlexCoefficients = std::min(flexCoefficientsValues.size(), MAX_NUMBER_FLEX_VARIABLES); + QStringList secondaryFlexCoefficientsValues = secondaryFlexCoefficients.split(',', QString::SkipEmptyParts); + for (int i = 0; i < secondaryFlexCoefficientsValues.size(); i++) { + if (i < MAX_NUMBER_FLEX_VARIABLES) { + qCDebug(animation) << "secondaryflex value " << secondaryFlexCoefficientsValues[i].toDouble(); + _secondaryFlexCoefficients[i] = (float)secondaryFlexCoefficientsValues[i].toDouble(); + } + } + _numSecondaryFlexCoefficients = std::min(secondaryFlexCoefficientsValues.size(), MAX_NUMBER_FLEX_VARIABLES); } @@ -145,8 +164,11 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const target.setPose(rotation, translation); target.setWeight(weight); + + + const float* flexCoefficients = new float[5]{ 1.0f, 0.5f, 0.25f, 0.2f, 0.1f }; - target.setFlexCoefficients(5, flexCoefficients); + target.setFlexCoefficients(_numPrimaryFlexCoefficients, _primaryFlexCoefficients); } AnimChain jointChain; @@ -182,7 +204,7 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const secondaryTarget.setWeight(weight2); const float* flexCoefficients2 = new float[3]{ 1.0f, 0.5f, 0.25f }; - secondaryTarget.setFlexCoefficients(3, flexCoefficients2); + secondaryTarget.setFlexCoefficients(_numSecondaryFlexCoefficients, _secondaryFlexCoefficients); } AnimChain secondaryJointChain; diff --git a/libraries/animation/src/AnimSplineIK.h b/libraries/animation/src/AnimSplineIK.h index d4dbb85a9e..e00b5a5b04 100644 --- a/libraries/animation/src/AnimSplineIK.h +++ b/libraries/animation/src/AnimSplineIK.h @@ -26,7 +26,9 @@ public: const QString& tipPositionVar, const QString& tipRotationVar, const QString& secondaryTargetJointName, const QString& secondaryTargetPositionVar, - const QString& secondaryTargetRotationVar); + const QString& secondaryTargetRotationVar, + const QString& primaryFlexCoefficients, + const QString& secondaryFlexCoefficients); virtual ~AnimSplineIK() override; virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override; @@ -65,6 +67,14 @@ protected: QString _tipRotationVar; QString _secondaryTargetPositionVar; QString _secondaryTargetRotationVar; + //QString _primaryFlexCoefficients; + //QString _secondaryFlexCoefficients; + + static const int MAX_NUMBER_FLEX_VARIABLES = 10; + float _primaryFlexCoefficients[MAX_NUMBER_FLEX_VARIABLES]; + float _secondaryFlexCoefficients[MAX_NUMBER_FLEX_VARIABLES]; + int _numPrimaryFlexCoefficients { 0 }; + int _numSecondaryFlexCoefficients { 0 }; int _baseParentJointIndex { -1 }; int _baseJointIndex { -1 }; From f2a7f3795084b460a39ae1070cdecc3d89ad2309 Mon Sep 17 00:00:00 2001 From: amantley Date: Fri, 25 Jan 2019 17:11:07 -0800 Subject: [PATCH 021/474] got rid of the lag in the spline code by setting the flex coeffs to 1.0 --- .../avatar/avatar-animation_withIKNode.json | 4 +-- interface/src/Application.cpp | 1 + libraries/animation/src/AnimChain.h | 10 ------ libraries/animation/src/AnimNodeLoader.cpp | 6 ++-- libraries/animation/src/AnimSplineIK.cpp | 31 ++++++------------- libraries/animation/src/AnimSplineIK.h | 4 +-- libraries/animation/src/AnimTwoBoneIK.cpp | 2 +- libraries/animation/src/Rig.cpp | 2 +- libraries/fbx/src/FBXSerializer.cpp | 1 - 9 files changed, 20 insertions(+), 41 deletions(-) diff --git a/interface/resources/avatar/avatar-animation_withIKNode.json b/interface/resources/avatar/avatar-animation_withIKNode.json index 13c102a5f1..bd0baf473e 100644 --- a/interface/resources/avatar/avatar-animation_withIKNode.json +++ b/interface/resources/avatar/avatar-animation_withIKNode.json @@ -194,8 +194,8 @@ "enabledVar": "splineIKEnabled", "endEffectorRotationVarVar": "splineIKRotationVar", "endEffectorPositionVarVar": "splineIKPositionVar", - "primaryFlexCoefficients": "1, 0.5, 0.25, 0.2, 0.1", - "secondaryFlexCoefficients": "1.0, 0.5, 0.25" + "primaryFlexCoefficients": "1.0, 1.0, 1.0, 1.0, 1.0", + "secondaryFlexCoefficients": "1.0, 1.0, 1.0" }, "children": [ { diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ed1d1a4c99..68ac05ef18 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4814,6 +4814,7 @@ void setupCpuMonitorThread() { void Application::idle() { PerformanceTimer perfTimer("idle"); + // Update the deadlock watchdog updateHeartbeat(); diff --git a/libraries/animation/src/AnimChain.h b/libraries/animation/src/AnimChain.h index 37d175a334..2385e0c16a 100644 --- a/libraries/animation/src/AnimChain.h +++ b/libraries/animation/src/AnimChain.h @@ -82,16 +82,6 @@ public: return foundIndex; } - const AnimPose& getRelativePoseAtJointIndex(int jointIndex) const { - for (int i = 0; i < _top; i++) { - if (_chain[i].jointIndex == jointIndex) { - return _chain[i].relativePose; - } - } - return AnimPose::identity; - } - - void buildDirtyAbsolutePoses() { // the relative and absolute pose is the same for the base of the chain. _chain[_top - 1].absolutePose = _chain[_top - 1].relativePose; diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index c5d17e8884..bce242b50d 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -601,9 +601,9 @@ static AnimNode::Pointer loadSplineIKNode(const QJsonObject& jsonObj, const QStr auto node = std::make_shared(id, alpha, enabled, interpDuration, baseJointName, tipJointName, - alphaVar, enabledVar, endEffectorRotationVarVar, endEffectorPositionVarVar, - basePositionVar, baseRotationVar, - tipPositionVar, tipRotationVar, secondaryTargetJointName, secondaryTargetPositionVar, + alphaVar, enabledVar, endEffectorRotationVarVar, endEffectorPositionVarVar, + basePositionVar, baseRotationVar, + tipPositionVar, tipRotationVar, secondaryTargetJointName, secondaryTargetPositionVar, secondaryTargetRotationVar, primaryFlexCoefficients, secondaryFlexCoefficients); return node; } diff --git a/libraries/animation/src/AnimSplineIK.cpp b/libraries/animation/src/AnimSplineIK.cpp index 658fa2cf6a..b38850773d 100644 --- a/libraries/animation/src/AnimSplineIK.cpp +++ b/libraries/animation/src/AnimSplineIK.cpp @@ -51,7 +51,7 @@ AnimSplineIK::AnimSplineIK(const QString& id, float alpha, bool enabled, float i _secondaryTargetPositionVar(secondaryTargetPositionVar), _secondaryTargetRotationVar(secondaryTargetRotationVar) { - + QStringList flexCoefficientsValues = primaryFlexCoefficients.split(',', QString::SkipEmptyParts); for (int i = 0; i < flexCoefficientsValues.size(); i++) { if (i < MAX_NUMBER_FLEX_VARIABLES) { @@ -134,7 +134,6 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const glm::vec3 hipsTargetTranslation; // now we override the hips with the hips target pose. - //////////////////////////////////////////////////// if (_poses.size() > 0) { AnimPose hipsUnderPose = _skeleton->getAbsolutePose(_hipsIndex, _poses); hipsTargetRotation = animVars.lookupRigToGeometry("hipsRotation", hipsUnderPose.rot()); @@ -147,12 +146,11 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const _poses[_hipsIndex] = hipsParentAbsPose.inverse() * absHipsTargetPose; _poses[_hipsIndex].scale() = glm::vec3(1.0f); } - ////////////////////////////////////////////////////////////////////////////////// - + AnimPoseVec absolutePoses; absolutePoses.resize(_poses.size()); computeAbsolutePoses(absolutePoses); - + IKTarget target; if (_tipJointIndex != -1) { target.setType((int)IKTarget::Type::Spline); @@ -165,7 +163,7 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const target.setPose(rotation, translation); target.setWeight(weight); - + const float* flexCoefficients = new float[5]{ 1.0f, 0.5f, 0.25f, 0.2f, 0.1f }; target.setFlexCoefficients(_numPrimaryFlexCoefficients, _primaryFlexCoefficients); @@ -186,12 +184,10 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const AnimPose afterSolveSecondaryTarget = _skeleton->getAbsolutePose(_secondaryTargetIndex, _poses); glm::quat secondaryTargetRotation = animVars.lookupRigToGeometry(_secondaryTargetRotationVar, afterSolveSecondaryTarget.rot()); - - updatedSecondaryTarget = AnimPose(secondaryTargetRotation, afterSolveSecondaryTarget.trans()); + updatedSecondaryTarget = AnimPose(secondaryTargetRotation, afterSolveSecondaryTarget.trans()); //updatedSecondaryTarget = AnimPose(afterSolveSecondaryTarget.rot(), afterSolveSecondaryTarget.trans()); } - IKTarget secondaryTarget; computeAbsolutePoses(absolutePoses2); if (_secondaryTargetIndex != -1) { @@ -199,14 +195,13 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const secondaryTarget.setIndex(_secondaryTargetIndex); float weight2 = 1.0f; - secondaryTarget.setPose(updatedSecondaryTarget.rot(), updatedSecondaryTarget.trans()); secondaryTarget.setWeight(weight2); - + const float* flexCoefficients2 = new float[3]{ 1.0f, 0.5f, 0.25f }; secondaryTarget.setFlexCoefficients(_numSecondaryFlexCoefficients, _secondaryFlexCoefficients); } - + AnimChain secondaryJointChain; AnimPose beforeSolveChestNeck; if (_poses.size() > 0) { @@ -223,17 +218,11 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const int tipParent = _skeleton->getParentIndex(_tipJointIndex); int secondaryTargetParent = _skeleton->getParentIndex(_secondaryTargetIndex); if ((secondaryTargetParent != -1) && (tipParent != -1) && (_poses.size() > 0)) { - /* - AnimPose secondaryTargetPose(target2.getRotation(), target2.getTranslation()); - AnimPose secondaryTargetRelativePose = _skeleton->getAbsolutePose(secondaryTargetParent, _poses).inverse() * secondaryTargetPose; - _poses[_secondaryTargetIndex] = secondaryTargetRelativePose; - */ AnimPose secondaryTargetPose(secondaryTarget.getRotation(), secondaryTarget.getTranslation()); AnimPose neckAbsolute = _skeleton->getAbsolutePose(tipParent, _poses); - //AnimPose finalNeckAbsolute = AnimPose(safeLerp(target2.getRotation(), target.getRotation(), 1.0f),neckAbsolute.trans()); _poses[tipParent] = secondaryTargetPose.inverse() * beforeSolveChestNeck; - + AnimPose tipTarget(target.getRotation(),target.getTranslation()); AnimPose tipRelativePose = _skeleton->getAbsolutePose(tipParent,_poses).inverse() * tipTarget; _poses[_tipJointIndex] = tipRelativePose; @@ -314,7 +303,7 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const } _previousEnableDebugIKTargets = context.getEnableDebugDrawIKTargets(); - + return _poses; } @@ -458,7 +447,7 @@ void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, const IKTar relPose.trans() = glm::vec3(0.0f); } } - // note we are ignoring the constrained info for now. + // note we are ignoring the constrained info for now. if (!chainInfoOut.setRelativePoseAtJointIndex(splineJointInfo.jointIndex, relPose)) { qCDebug(animation) << "we didn't find the joint index for the spline!!!!"; } diff --git a/libraries/animation/src/AnimSplineIK.h b/libraries/animation/src/AnimSplineIK.h index e00b5a5b04..c6d435fd6b 100644 --- a/libraries/animation/src/AnimSplineIK.h +++ b/libraries/animation/src/AnimSplineIK.h @@ -21,8 +21,8 @@ public: AnimSplineIK(const QString& id, float alpha, bool enabled, float interpDuration, const QString& baseJointName, const QString& tipJointName, const QString& alphaVar, const QString& enabledVar, - const QString& endEffectorRotationVarVar, const QString& endEffectorPositionVarVar, - const QString& basePositionVar, const QString& baseRotationVar, + const QString& endEffectorRotationVarVar, const QString& endEffectorPositionVarVar, + const QString& basePositionVar, const QString& baseRotationVar, const QString& tipPositionVar, const QString& tipRotationVar, const QString& secondaryTargetJointName, const QString& secondaryTargetPositionVar, diff --git a/libraries/animation/src/AnimTwoBoneIK.cpp b/libraries/animation/src/AnimTwoBoneIK.cpp index 3f8de488ff..8960b15940 100644 --- a/libraries/animation/src/AnimTwoBoneIK.cpp +++ b/libraries/animation/src/AnimTwoBoneIK.cpp @@ -89,7 +89,7 @@ const AnimPoseVec& AnimTwoBoneIK::evaluate(const AnimVariantMap& animVars, const } _enabled = enabled; - // don't build chains or do IK if we are disabled & not interping. + // don't build chains or do IK if we are disbled & not interping. if (_interpType == InterpType::None && !enabled) { _poses = underPoses; return _poses; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 93daeda4d9..713f9bc385 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1468,7 +1468,7 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab // need this for two bone ik _animVars.set(LEFT_HAND_IK_POSITION_VAR, MAIN_STATE_MACHINE_LEFT_HAND_POSITION); _animVars.set(LEFT_HAND_IK_ROTATION_VAR, MAIN_STATE_MACHINE_LEFT_HAND_ROTATION); - + _animVars.set("leftHandPoleVectorEnabled", false); _animVars.unset("leftHandPosition"); _animVars.unset("leftHandRotation"); diff --git a/libraries/fbx/src/FBXSerializer.cpp b/libraries/fbx/src/FBXSerializer.cpp index 2c13be534a..4c82b4f5d7 100644 --- a/libraries/fbx/src/FBXSerializer.cpp +++ b/libraries/fbx/src/FBXSerializer.cpp @@ -1314,7 +1314,6 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr joint.inverseBindRotation = joint.inverseDefaultRotation; joint.name = fbxModel.name; if (hfmModel.hfmToHifiJointNameMapping.contains(hfmModel.hfmToHifiJointNameMapping.key(joint.name))) { - // qCDebug(modelformat) << "joint name " << joint.name << " hifi name " << hfmModel.hfmToHifiJointNameMapping.key(fbxModel.name); joint.name = hfmModel.hfmToHifiJointNameMapping.key(fbxModel.name); } From 0b6d0b4bafaab5ea6e61f5bbefba7440efb03c3f Mon Sep 17 00:00:00 2001 From: amantley Date: Fri, 25 Jan 2019 18:17:15 -0800 Subject: [PATCH 022/474] renamed json to reflect spline node --- .../avatar/avatar-animation_twoboneIK.json | 2204 ----------------- ...=> avatar-animation_withSplineIKNode.json} | 0 2 files changed, 2204 deletions(-) delete mode 100644 interface/resources/avatar/avatar-animation_twoboneIK.json rename interface/resources/avatar/{avatar-animation_withIKNode.json => avatar-animation_withSplineIKNode.json} (100%) diff --git a/interface/resources/avatar/avatar-animation_twoboneIK.json b/interface/resources/avatar/avatar-animation_twoboneIK.json deleted file mode 100644 index 45cb7b570e..0000000000 --- a/interface/resources/avatar/avatar-animation_twoboneIK.json +++ /dev/null @@ -1,2204 +0,0 @@ -{ - "version": "1.1", - "root": { - "id": "userAnimStateMachine", - "type": "stateMachine", - "data": { - "currentState": "userAnimNone", - "states": [ - { - "id": "userAnimNone", - "interpTarget": 6, - "interpDuration": 6, - "transitions": [ - { "var": "userAnimA", "state": "userAnimA" }, - { "var": "userAnimB", "state": "userAnimB" } - ] - }, - { - "id": "userAnimA", - "interpTarget": 6, - "interpDuration": 6, - "transitions": [ - { "var": "userAnimNone", "state": "userAnimNone" }, - { "var": "userAnimB", "state": "userAnimB" } - ] - }, - { - "id": "userAnimB", - "interpTarget": 6, - "interpDuration": 6, - "transitions": [ - { "var": "userAnimNone", "state": "userAnimNone" }, - { "var": "userAnimA", "state": "userAnimA" } - ] - } - ] - }, - "children": [ - { - "id": "userAnimNone", - "type": "splineIK", - "data": { - "alpha": 1.0, - "enabled": false, - "interpDuration": 15, - "baseJointName": "Hips", - "tipJointName": "Head", - "alphaVar": "splineIKAlpha", - "enabledVar": "splineIKEnabled", - "endEffectorRotationVarVar": "splineIKRotationVar", - "endEffectorPositionVarVar": "splineIKPositionVar" - }, - "children": [ - { - "id": "rightFootPoleVector", - "type": "poleVectorConstraint", - "data": { - "enabled": false, - "referenceVector": [ 0, 0, 1 ], - "baseJointName": "RightUpLeg", - "midJointName": "RightLeg", - "tipJointName": "RightFoot", - "enabledVar": "rightFootPoleVectorEnabled", - "poleVectorVar": "rightFootPoleVector" - }, - "children": [ - { - "id": "rightFootIK", - "type": "twoBoneIK", - "data": { - "alpha": 1.0, - "enabled": false, - "interpDuration": 15, - "baseJointName": "RightUpLeg", - "midJointName": "RightLeg", - "tipJointName": "RightFoot", - "midHingeAxis": [ -1, 0, 0 ], - "alphaVar": "rightFootIKAlpha", - "enabledVar": "rightFootIKEnabled", - "endEffectorRotationVarVar": "rightFootIKRotationVar", - "endEffectorPositionVarVar": "rightFootIKPositionVar" - }, - "children": [ - { - "id": "leftFootPoleVector", - "type": "poleVectorConstraint", - "data": { - "enabled": false, - "referenceVector": [ 0, 0, 1 ], - "baseJointName": "LeftUpLeg", - "midJointName": "LeftLeg", - "tipJointName": "LeftFoot", - "enabledVar": "leftFootPoleVectorEnabled", - "poleVectorVar": "leftFootPoleVector" - }, - "children": [ - { - "id": "leftFootIK", - "type": "twoBoneIK", - "data": { - "alpha": 1.0, - "enabled": false, - "interpDuration": 15, - "baseJointName": "LeftUpLeg", - "midJointName": "LeftLeg", - "tipJointName": "LeftFoot", - "midHingeAxis": [ -1, 0, 0 ], - "alphaVar": "leftFootIKAlpha", - "enabledVar": "leftFootIKEnabled", - "endEffectorRotationVarVar": "leftFootIKRotationVar", - "endEffectorPositionVarVar": "leftFootIKPositionVar" - }, - "children": [ - { - "id": "rightHandPoleVector", - "type": "poleVectorConstraint", - "data": { - "enabled": false, - "referenceVector": [ 0, 0, 1 ], - "baseJointName": "RightArm", - "midJointName": "RightForeArm", - "tipJointName": "RightHand", - "enabledVar": "rightHandPoleVectorEnabled", - "poleVectorVar": "rightHandPoleVector" - }, - "children": [ - { - "id": "rightHandIK", - "type": "twoBoneIK", - "data": { - "alpha": 1.0, - "enabled": false, - "interpDuration": 15, - "baseJointName": "RightArm", - "midJointName": "RightForeArm", - "tipJointName": "RightHand", - "midHingeAxis": [ 0, 0, -1 ], - "alphaVar": "rightHandIKAlpha", - "enabledVar": "rightHandIKEnabled", - "endEffectorRotationVarVar": "rightHandIKRotationVar", - "endEffectorPositionVarVar": "rightHandIKPositionVar" - }, - "children": [ - { - "id": "leftHandPoleVector", - "type": "poleVectorConstraint", - "data": { - "enabled": false, - "referenceVector": [ 0, 0, 1 ], - "baseJointName": "LeftArm", - "midJointName": "LeftForeArm", - "tipJointName": "LeftHand", - "enabledVar": "leftHandPoleVectorEnabled", - "poleVectorVar": "leftHandPoleVector" - }, - "children": [ - { - "id": "leftHandIK", - "type": "twoBoneIK", - "data": { - "alpha": 1.0, - "enabled": false, - "interpDuration": 15, - "baseJointName": "LeftArm", - "midJointName": "LeftForeArm", - "tipJointName": "LeftHand", - "midHingeAxis": [ 0, 0, 1 ], - "alphaVar": "leftHandIKAlpha", - "enabledVar": "leftHandIKEnabled", - "endEffectorRotationVarVar": "leftHandIKRotationVar", - "endEffectorPositionVarVar": "leftHandIKPositionVar" - }, - "children": [ - { - "id": "defaultPoseOverlay", - "type": "overlay", - "data": { - "alpha": 0.0, - "alphaVar": "defaultPoseOverlayAlpha", - "boneSet": "fullBody", - "boneSetVar": "defaultPoseOverlayBoneSet" - }, - "children": [ - { - "id": "defaultPose", - "type": "defaultPose", - "data": { - }, - "children": [] - }, - { - "id": "rightHandOverlay", - "type": "overlay", - "data": { - "alpha": 0.0, - "boneSet": "rightHand", - "alphaVar": "rightHandOverlayAlpha" - }, - "children": [ - { - "id": "rightHandStateMachine", - "type": "stateMachine", - "data": { - "currentState": "rightHandGrasp", - "states": [ - { - "id": "rightHandGrasp", - "interpTarget": 3, - "interpDuration": 3, - "transitions": [ - { - "var": "isRightIndexPoint", - "state": "rightIndexPoint" - }, - { - "var": "isRightThumbRaise", - "state": "rightThumbRaise" - }, - { - "var": "isRightIndexPointAndThumbRaise", - "state": "rightIndexPointAndThumbRaise" - } - ] - }, - { - "id": "rightIndexPoint", - "interpTarget": 15, - "interpDuration": 3, - "transitions": [ - { - "var": "isRightHandGrasp", - "state": "rightHandGrasp" - }, - { - "var": "isRightThumbRaise", - "state": "rightThumbRaise" - }, - { - "var": "isRightIndexPointAndThumbRaise", - "state": "rightIndexPointAndThumbRaise" - } - ] - }, - { - "id": "rightThumbRaise", - "interpTarget": 15, - "interpDuration": 3, - "transitions": [ - { - "var": "isRightHandGrasp", - "state": "rightHandGrasp" - }, - { - "var": "isRightIndexPoint", - "state": "rightIndexPoint" - }, - { - "var": "isRightIndexPointAndThumbRaise", - "state": "rightIndexPointAndThumbRaise" - } - ] - }, - { - "id": "rightIndexPointAndThumbRaise", - "interpTarget": 15, - "interpDuration": 3, - "transitions": [ - { - "var": "isRightHandGrasp", - "state": "rightHandGrasp" - }, - { - "var": "isRightIndexPoint", - "state": "rightIndexPoint" - }, - { - "var": "isRightThumbRaise", - "state": "rightThumbRaise" - } - ] - } - ] - }, - "children": [ - { - "id": "rightHandGrasp", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "rightHandGraspAlpha" - }, - "children": [ - { - "id": "rightHandGraspOpen", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/hydra_pose_open_right.fbx", - "startFrame": 0.0, - "endFrame": 0.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "rightHandGraspClosed", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/hydra_pose_closed_right.fbx", - "startFrame": 0.0, - "endFrame": 0.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "rightIndexPoint", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "rightHandGraspAlpha" - }, - "children": [ - { - "id": "rightIndexPointOpen", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_point_open_right.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "rightIndexPointClosed", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_point_closed_right.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "rightThumbRaise", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "rightHandGraspAlpha" - }, - "children": [ - { - "id": "rightThumbRaiseOpen", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_thumb_open_right.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "rightThumbRaiseClosed", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_thumb_closed_right.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "rightIndexPointAndThumbRaise", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "rightHandGraspAlpha" - }, - "children": [ - { - "id": "rightIndexPointAndThumbRaiseOpen", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_thumb_point_open_right.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "rightIndexPointAndThumbRaiseClosed", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_thumb_point_closed_right.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - } - ] - }, - { - "id": "leftHandOverlay", - "type": "overlay", - "data": { - "alpha": 0.0, - "boneSet": "leftHand", - "alphaVar": "leftHandOverlayAlpha" - }, - "children": [ - { - "id": "leftHandStateMachine", - "type": "stateMachine", - "data": { - "currentState": "leftHandGrasp", - "states": [ - { - "id": "leftHandGrasp", - "interpTarget": 3, - "interpDuration": 3, - "transitions": [ - { - "var": "isLeftIndexPoint", - "state": "leftIndexPoint" - }, - { - "var": "isLeftThumbRaise", - "state": "leftThumbRaise" - }, - { - "var": "isLeftIndexPointAndThumbRaise", - "state": "leftIndexPointAndThumbRaise" - } - ] - }, - { - "id": "leftIndexPoint", - "interpTarget": 15, - "interpDuration": 3, - "transitions": [ - { - "var": "isLeftHandGrasp", - "state": "leftHandGrasp" - }, - { - "var": "isLeftThumbRaise", - "state": "leftThumbRaise" - }, - { - "var": "isLeftIndexPointAndThumbRaise", - "state": "leftIndexPointAndThumbRaise" - } - ] - }, - { - "id": "leftThumbRaise", - "interpTarget": 15, - "interpDuration": 3, - "transitions": [ - { - "var": "isLeftHandGrasp", - "state": "leftHandGrasp" - }, - { - "var": "isLeftIndexPoint", - "state": "leftIndexPoint" - }, - { - "var": "isLeftIndexPointAndThumbRaise", - "state": "leftIndexPointAndThumbRaise" - } - ] - }, - { - "id": "leftIndexPointAndThumbRaise", - "interpTarget": 15, - "interpDuration": 3, - "transitions": [ - { - "var": "isLeftHandGrasp", - "state": "leftHandGrasp" - }, - { - "var": "isLeftIndexPoint", - "state": "leftIndexPoint" - }, - { - "var": "isLeftThumbRaise", - "state": "leftThumbRaise" - } - ] - } - ] - }, - "children": [ - { - "id": "leftHandGrasp", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "leftHandGraspAlpha" - }, - "children": [ - { - "id": "leftHandGraspOpen", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/hydra_pose_open_left.fbx", - "startFrame": 0.0, - "endFrame": 0.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "leftHandGraspClosed", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/hydra_pose_closed_left.fbx", - "startFrame": 10.0, - "endFrame": 10.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "leftIndexPoint", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "leftHandGraspAlpha" - }, - "children": [ - { - "id": "leftIndexPointOpen", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_point_open_left.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "leftIndexPointClosed", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_point_closed_left.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "leftThumbRaise", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "leftHandGraspAlpha" - }, - "children": [ - { - "id": "leftThumbRaiseOpen", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_thumb_open_left.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "leftThumbRaiseClosed", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_thumb_closed_left.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "leftIndexPointAndThumbRaise", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "leftHandGraspAlpha" - }, - "children": [ - { - "id": "leftIndexPointAndThumbRaiseOpen", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_thumb_point_open_left.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "leftIndexPointAndThumbRaiseClosed", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_thumb_point_closed_left.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - } - ] - }, - { - "id": "mainStateMachine", - "type": "stateMachine", - "data": { - "outputJoints": [ "LeftFoot", "RightFoot" ], - "currentState": "idle", - "states": [ - { - "id": "idle", - "interpTarget": 20, - "interpDuration": 8, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "idleToWalkFwd", - "interpTarget": 12, - "interpDuration": 8, - "transitions": [ - { - "var": "idleToWalkFwdOnDone", - "state": "WALKFWD" - }, - { - "var": "isNotMoving", - "state": "idle" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "idleSettle", - "interpTarget": 15, - "interpDuration": 8, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "idleSettleOnDone", - "state": "idle" - }, - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - } - ] - }, - { - "id": "WALKFWD", - "interpTarget": 35, - "interpDuration": 10, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isNotMoving", - "state": "idleSettle" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "WALKBWD", - "interpTarget": 35, - "interpDuration": 10, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isNotMoving", - "state": "idleSettle" - }, - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "STRAFERIGHT", - "interpTarget": 25, - "interpDuration": 8, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isNotMoving", - "state": "idleSettle" - }, - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "STRAFELEFT", - "interpTarget": 25, - "interpDuration": 8, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isNotMoving", - "state": "idleSettle" - }, - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "turnRight", - "interpTarget": 6, - "interpDuration": 8, - "transitions": [ - { - "var": "isNotTurning", - "state": "idle" - }, - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "turnLeft", - "interpTarget": 6, - "interpDuration": 8, - "transitions": [ - { - "var": "isNotTurning", - "state": "idle" - }, - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "strafeRightHmd", - "interpTarget": 5, - "interpDuration": 8, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isNotMoving", - "state": "idleSettle" - }, - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - } - ] - }, - { - "id": "strafeLeftHmd", - "interpTarget": 5, - "interpDuration": 8, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isNotMoving", - "state": "idleSettle" - }, - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - } - ] - }, - { - "id": "fly", - "interpTarget": 6, - "interpDuration": 6, - "transitions": [ - { - "var": "isNotFlying", - "state": "idleSettle" - } - ] - }, - { - "id": "takeoffStand", - "interpTarget": 2, - "interpDuration": 2, - "transitions": [ - { - "var": "isNotTakeoff", - "state": "inAirStand" - } - ] - }, - { - "id": "TAKEOFFRUN", - "interpTarget": 2, - "interpDuration": 2, - "transitions": [ - { - "var": "isNotTakeoff", - "state": "INAIRRUN" - } - ] - }, - { - "id": "inAirStand", - "interpTarget": 3, - "interpDuration": 3, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isNotInAir", - "state": "landStandImpact" - } - ] - }, - { - "id": "INAIRRUN", - "interpTarget": 3, - "interpDuration": 3, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isNotInAir", - "state": "WALKFWD" - } - ] - }, - { - "id": "landStandImpact", - "interpTarget": 1, - "interpDuration": 1, - "transitions": [ - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "landStandImpactOnDone", - "state": "landStand" - } - ] - }, - { - "id": "landStand", - "interpTarget": 1, - "interpDuration": 1, - "transitions": [ - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "landStandOnDone", - "state": "idle" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "LANDRUN", - "interpTarget": 2, - "interpDuration": 2, - "transitions": [ - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "landRunOnDone", - "state": "WALKFWD" - } - ] - } - ] - }, - "children": [ - { - "id": "idle", - "type": "stateMachine", - "data": { - "currentState": "idleStand", - "states": [ - { - "id": "idleStand", - "interpTarget": 6, - "interpDuration": 10, - "transitions": [ - { - "var": "isTalking", - "state": "idleTalk" - } - ] - }, - { - "id": "idleTalk", - "interpTarget": 6, - "interpDuration": 10, - "transitions": [ - { - "var": "notIsTalking", - "state": "idleStand" - } - ] - } - ] - }, - "children": [ - { - "id": "idleStand", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/idle.fbx", - "startFrame": 0.0, - "endFrame": 300.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "idleTalk", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/talk.fbx", - "startFrame": 0.0, - "endFrame": 800.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "WALKFWD", - "type": "blendLinearMove", - "data": { - "alpha": 0.0, - "desiredSpeed": 1.4, - "characteristicSpeeds": [ 0.5, 1.8, 2.3, 3.2, 4.5 ], - "alphaVar": "moveForwardAlpha", - "desiredSpeedVar": "moveForwardSpeed" - }, - "children": [ - { - "id": "walkFwdShort_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_short_fwd.fbx", - "startFrame": 0.0, - "endFrame": 39.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "walkFwdNormal_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_fwd.fbx", - "startFrame": 0.0, - "endFrame": 30.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "walkFwdFast_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_fwd_fast.fbx", - "startFrame": 0.0, - "endFrame": 25.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "walkFwdJog_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jog_fwd.fbx", - "startFrame": 0.0, - "endFrame": 25.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "walkFwdRun_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/run_fwd.fbx", - "startFrame": 0.0, - "endFrame": 21.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "idleToWalkFwd", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/idle_to_walk.fbx", - "startFrame": 1.0, - "endFrame": 13.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "idleSettle", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/settle_to_idle.fbx", - "startFrame": 1.0, - "endFrame": 59.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "WALKBWD", - "type": "blendLinearMove", - "data": { - "alpha": 0.0, - "desiredSpeed": 1.4, - "characteristicSpeeds": [ 0.6, 1.6, 2.3, 3.1 ], - "alphaVar": "moveBackwardAlpha", - "desiredSpeedVar": "moveBackwardSpeed" - }, - "children": [ - { - "id": "walkBwdShort_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_short_bwd.fbx", - "startFrame": 0.0, - "endFrame": 38.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "walkBwdFast_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_bwd_fast.fbx", - "startFrame": 0.0, - "endFrame": 27.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "jogBwd_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jog_bwd.fbx", - "startFrame": 0.0, - "endFrame": 24.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "runBwd_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/run_bwd.fbx", - "startFrame": 0.0, - "endFrame": 16.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "turnLeft", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/turn_left.fbx", - "startFrame": 0.0, - "endFrame": 32.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "turnRight", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/turn_left.fbx", - "startFrame": 0.0, - "endFrame": 32.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "STRAFELEFT", - "type": "blendLinearMove", - "data": { - "alpha": 0.0, - "desiredSpeed": 1.4, - "characteristicSpeeds": [ 0.1, 0.5, 1.0, 2.6, 3.0 ], - "alphaVar": "moveLateralAlpha", - "desiredSpeedVar": "moveLateralSpeed" - }, - "children": [ - { - "id": "strafeLeftShortStep_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_short_left.fbx", - "startFrame": 0.0, - "endFrame": 29.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "strafeLeftStep_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_left.fbx", - "startFrame": 0.0, - "endFrame": 20.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "strafeLeftWalk_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_left.fbx", - "startFrame": 0.0, - "endFrame": 35.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "strafeLeftWalkFast_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_left_fast.fbx", - "startFrame": 0.0, - "endFrame": 21.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "strafeLeftJog_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jog_left.fbx", - "startFrame": 0.0, - "endFrame": 24.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "STRAFERIGHT", - "type": "blendLinearMove", - "data": { - "alpha": 0.0, - "desiredSpeed": 1.4, - "characteristicSpeeds": [ 0.1, 0.5, 1.0, 2.6, 3.0 ], - "alphaVar": "moveLateralAlpha", - "desiredSpeedVar": "moveLateralSpeed" - }, - "children": [ - { - "id": "strafeRightShortStep_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_short_left.fbx", - "startFrame": 0.0, - "endFrame": 29.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "strafeRightStep_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_left.fbx", - "startFrame": 0.0, - "endFrame": 20.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "strafeRightWalk_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_left.fbx", - "startFrame": 0.0, - "endFrame": 35.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "strafeRightFast_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_left_fast.fbx", - "startFrame": 0.0, - "endFrame": 21.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "strafeRightJog_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jog_left.fbx", - "startFrame": 0.0, - "endFrame": 24.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - } - ] - }, - { - "id": "strafeLeftHmd", - "type": "blendLinearMove", - "data": { - "alpha": 0.0, - "desiredSpeed": 1.4, - "characteristicSpeeds": [ 0, 0.5, 2.5 ], - "alphaVar": "moveLateralAlpha", - "desiredSpeedVar": "moveLateralSpeed" - }, - "children": [ - { - "id": "stepLeftShort_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_short_left.fbx", - "startFrame": 0.0, - "endFrame": 29.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "stepLeft_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_left.fbx", - "startFrame": 0.0, - "endFrame": 20.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "strafeLeftAnim_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_left_fast.fbx", - "startFrame": 0.0, - "endFrame": 16.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "strafeRightHmd", - "type": "blendLinearMove", - "data": { - "alpha": 0.0, - "desiredSpeed": 1.4, - "characteristicSpeeds": [ 0, 0.5, 2.5 ], - "alphaVar": "moveLateralAlpha", - "desiredSpeedVar": "moveLateralSpeed" - }, - "children": [ - { - "id": "stepRightShort_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_short_left.fbx", - "startFrame": 0.0, - "endFrame": 29.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "stepRight_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_left.fbx", - "startFrame": 0.0, - "endFrame": 20.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "strafeRightAnim_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_left_fast.fbx", - "startFrame": 0.0, - "endFrame": 16.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - } - ] - }, - { - "id": "fly", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/fly.fbx", - "startFrame": 1.0, - "endFrame": 80.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "takeoffStand", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_standing_launch.fbx", - "startFrame": 2.0, - "endFrame": 16.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "TAKEOFFRUN", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", - "startFrame": 4.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "inAirStand", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "inAirAlpha" - }, - "children": [ - { - "id": "inAirStandPreApex", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_standing_apex.fbx", - "startFrame": 0.0, - "endFrame": 0.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "inAirStandApex", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_standing_apex.fbx", - "startFrame": 1.0, - "endFrame": 1.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "inAirStandPostApex", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_standing_apex.fbx", - "startFrame": 2.0, - "endFrame": 2.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - } - ] - }, - { - "id": "INAIRRUN", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "inAirAlpha" - }, - "children": [ - { - "id": "inAirRunPreApex", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", - "startFrame": 16.0, - "endFrame": 16.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "inAirRunApex", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", - "startFrame": 22.0, - "endFrame": 22.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "inAirRunPostApex", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", - "startFrame": 33.0, - "endFrame": 33.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - } - ] - }, - { - "id": "landStandImpact", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_standing_land_settle.fbx", - "startFrame": 1.0, - "endFrame": 6.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "landStand", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_standing_land_settle.fbx", - "startFrame": 6.0, - "endFrame": 68.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "LANDRUN", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", - "startFrame": 29.0, - "endFrame": 40.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - }, - { - "id": "userAnimA", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/idle.fbx", - "startFrame": 0.0, - "endFrame": 90.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "userAnimB", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/idle.fbx", - "startFrame": 0.0, - "endFrame": 90.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - } -} diff --git a/interface/resources/avatar/avatar-animation_withIKNode.json b/interface/resources/avatar/avatar-animation_withSplineIKNode.json similarity index 100% rename from interface/resources/avatar/avatar-animation_withIKNode.json rename to interface/resources/avatar/avatar-animation_withSplineIKNode.json From 2679a3a30d554805914ae974907a359d03d53748 Mon Sep 17 00:00:00 2001 From: Angus Antley Date: Sun, 27 Jan 2019 16:30:13 -0800 Subject: [PATCH 023/474] changed the naming of the middle joint from secondary target to midJoint, also generalized the handling of the neck head rotation after the middle spline is updated --- .../avatar-animation_withSplineIKNode.json | 6 +- libraries/animation/src/AnimNodeLoader.cpp | 13 ++-- libraries/animation/src/AnimSplineIK.cpp | 77 ++++++++----------- libraries/animation/src/AnimSplineIK.h | 13 +--- 4 files changed, 46 insertions(+), 63 deletions(-) diff --git a/interface/resources/avatar/avatar-animation_withSplineIKNode.json b/interface/resources/avatar/avatar-animation_withSplineIKNode.json index bd0baf473e..40572f698b 100644 --- a/interface/resources/avatar/avatar-animation_withSplineIKNode.json +++ b/interface/resources/avatar/avatar-animation_withSplineIKNode.json @@ -182,14 +182,14 @@ "enabled": false, "interpDuration": 15, "baseJointName": "Hips", + "midJointName": "Spine2", "tipJointName": "Head", - "secondaryTargetJointName": "Spine2", "basePositionVar": "hipsPosition", "baseRotationVar": "hipsRotation", + "midPositionVar": "spine2Position", + "midRotationVar": "spine2Rotation", "tipPositionVar": "headPosition", "tipRotationVar": "headRotation", - "secondaryTargetPositionVar": "spine2Position", - "secondaryTargetRotationVar": "spine2Rotation", "alphaVar": "splineIKAlpha", "enabledVar": "splineIKEnabled", "endEffectorRotationVarVar": "splineIKRotationVar", diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index bce242b50d..8a19b763bb 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -584,14 +584,14 @@ static AnimNode::Pointer loadSplineIKNode(const QJsonObject& jsonObj, const QStr READ_BOOL(enabled, jsonObj, id, jsonUrl, nullptr); READ_FLOAT(interpDuration, jsonObj, id, jsonUrl, nullptr); READ_STRING(baseJointName, jsonObj, id, jsonUrl, nullptr); + READ_STRING(midJointName, jsonObj, id, jsonUrl, nullptr); READ_STRING(tipJointName, jsonObj, id, jsonUrl, nullptr); - READ_STRING(secondaryTargetJointName, jsonObj, id, jsonUrl, nullptr); READ_STRING(basePositionVar, jsonObj, id, jsonUrl, nullptr); READ_STRING(baseRotationVar, jsonObj, id, jsonUrl, nullptr); + READ_STRING(midPositionVar, jsonObj, id, jsonUrl, nullptr); + READ_STRING(midRotationVar, jsonObj, id, jsonUrl, nullptr); READ_STRING(tipPositionVar, jsonObj, id, jsonUrl, nullptr); READ_STRING(tipRotationVar, jsonObj, id, jsonUrl, nullptr); - READ_STRING(secondaryTargetPositionVar, jsonObj, id, jsonUrl, nullptr); - READ_STRING(secondaryTargetRotationVar, jsonObj, id, jsonUrl, nullptr); READ_STRING(alphaVar, jsonObj, id, jsonUrl, nullptr); READ_STRING(enabledVar, jsonObj, id, jsonUrl, nullptr); READ_STRING(endEffectorRotationVarVar, jsonObj, id, jsonUrl, nullptr); @@ -600,11 +600,10 @@ static AnimNode::Pointer loadSplineIKNode(const QJsonObject& jsonObj, const QStr READ_STRING(secondaryFlexCoefficients, jsonObj, id, jsonUrl, nullptr); auto node = std::make_shared(id, alpha, enabled, interpDuration, - baseJointName, tipJointName, + baseJointName, midJointName, tipJointName, alphaVar, enabledVar, endEffectorRotationVarVar, endEffectorPositionVarVar, - basePositionVar, baseRotationVar, - tipPositionVar, tipRotationVar, secondaryTargetJointName, secondaryTargetPositionVar, - secondaryTargetRotationVar, primaryFlexCoefficients, secondaryFlexCoefficients); + basePositionVar, baseRotationVar, midPositionVar, midRotationVar, + tipPositionVar, tipRotationVar, primaryFlexCoefficients, secondaryFlexCoefficients); return node; } diff --git a/libraries/animation/src/AnimSplineIK.cpp b/libraries/animation/src/AnimSplineIK.cpp index b38850773d..27e295ffb0 100644 --- a/libraries/animation/src/AnimSplineIK.cpp +++ b/libraries/animation/src/AnimSplineIK.cpp @@ -19,16 +19,16 @@ static const float FRAMES_PER_SECOND = 30.0f; AnimSplineIK::AnimSplineIK(const QString& id, float alpha, bool enabled, float interpDuration, const QString& baseJointName, + const QString& midJointName, const QString& tipJointName, const QString& alphaVar, const QString& enabledVar, const QString& endEffectorRotationVarVar, const QString& endEffectorPositionVarVar, const QString& basePositionVar, const QString& baseRotationVar, + const QString& midPositionVar, + const QString& midRotationVar, const QString& tipPositionVar, const QString& tipRotationVar, - const QString& secondaryTargetJointName, - const QString& secondaryTargetPositionVar, - const QString& secondaryTargetRotationVar, const QString& primaryFlexCoefficients, const QString& secondaryFlexCoefficients) : AnimNode(AnimNode::Type::SplineIK, id), @@ -36,6 +36,7 @@ AnimSplineIK::AnimSplineIK(const QString& id, float alpha, bool enabled, float i _enabled(enabled), _interpDuration(interpDuration), _baseJointName(baseJointName), + _midJointName(midJointName), _tipJointName(tipJointName), _alphaVar(alphaVar), _enabledVar(enabledVar), @@ -45,11 +46,10 @@ AnimSplineIK::AnimSplineIK(const QString& id, float alpha, bool enabled, float i _prevEndEffectorPositionVar(), _basePositionVar(basePositionVar), _baseRotationVar(baseRotationVar), + _midPositionVar(midPositionVar), + _midRotationVar(midRotationVar), _tipPositionVar(tipPositionVar), - _tipRotationVar(tipRotationVar), - _secondaryTargetJointName(secondaryTargetJointName), - _secondaryTargetPositionVar(secondaryTargetPositionVar), - _secondaryTargetRotationVar(secondaryTargetRotationVar) + _tipRotationVar(tipRotationVar) { QStringList flexCoefficientsValues = primaryFlexCoefficients.split(',', QString::SkipEmptyParts); @@ -182,17 +182,17 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const qCDebug(animation) << "the orig target pose for head " << target.getPose(); jointChain.outputRelativePoses(_poses); - AnimPose afterSolveSecondaryTarget = _skeleton->getAbsolutePose(_secondaryTargetIndex, _poses); - glm::quat secondaryTargetRotation = animVars.lookupRigToGeometry(_secondaryTargetRotationVar, afterSolveSecondaryTarget.rot()); + AnimPose afterSolveSecondaryTarget = _skeleton->getAbsolutePose(_midJointIndex, _poses); + glm::quat secondaryTargetRotation = animVars.lookupRigToGeometry(_midRotationVar, afterSolveSecondaryTarget.rot()); updatedSecondaryTarget = AnimPose(secondaryTargetRotation, afterSolveSecondaryTarget.trans()); //updatedSecondaryTarget = AnimPose(afterSolveSecondaryTarget.rot(), afterSolveSecondaryTarget.trans()); } IKTarget secondaryTarget; computeAbsolutePoses(absolutePoses2); - if (_secondaryTargetIndex != -1) { + if (_midJointIndex != -1) { secondaryTarget.setType((int)IKTarget::Type::Spline); - secondaryTarget.setIndex(_secondaryTargetIndex); + secondaryTarget.setIndex(_midJointIndex); float weight2 = 1.0f; secondaryTarget.setPose(updatedSecondaryTarget.rot(), updatedSecondaryTarget.trans()); @@ -204,8 +204,17 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const AnimChain secondaryJointChain; AnimPose beforeSolveChestNeck; + int startJoint; + AnimPose correctJoint; if (_poses.size() > 0) { + // start at the tip + + for (startJoint = _tipJointIndex; _skeleton->getParentIndex(startJoint) != _midJointIndex; startJoint = _skeleton->getParentIndex(startJoint)) { + // find the child of the midJoint + } + correctJoint = _skeleton->getAbsolutePose(startJoint, _poses); + // fix this to deal with no neck AA beforeSolveChestNeck = _skeleton->getAbsolutePose(_skeleton->nameToJointIndex("Neck"), _poses); @@ -216,16 +225,19 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const // set the tip/head rotation to match the absolute rotation of the target. int tipParent = _skeleton->getParentIndex(_tipJointIndex); - int secondaryTargetParent = _skeleton->getParentIndex(_secondaryTargetIndex); + int secondaryTargetParent = _skeleton->getParentIndex(_midJointIndex); if ((secondaryTargetParent != -1) && (tipParent != -1) && (_poses.size() > 0)) { - AnimPose secondaryTargetPose(secondaryTarget.getRotation(), secondaryTarget.getTranslation()); - AnimPose neckAbsolute = _skeleton->getAbsolutePose(tipParent, _poses); - _poses[tipParent] = secondaryTargetPose.inverse() * beforeSolveChestNeck; + - AnimPose tipTarget(target.getRotation(),target.getTranslation()); - AnimPose tipRelativePose = _skeleton->getAbsolutePose(tipParent,_poses).inverse() * tipTarget; - _poses[_tipJointIndex] = tipRelativePose; + AnimPose secondaryTargetPose(secondaryTarget.getRotation(), secondaryTarget.getTranslation()); + //AnimPose neckAbsolute = _skeleton->getAbsolutePose(tipParent, _poses); + //_poses[tipParent] = secondaryTargetPose.inverse() * beforeSolveChestNeck; + _poses[startJoint] = secondaryTargetPose.inverse() * correctJoint; + + //AnimPose tipTarget(target.getRotation(),target.getTranslation()); + //AnimPose tipRelativePose = _skeleton->getAbsolutePose(tipParent,_poses).inverse() * tipTarget; + //_poses[_tipJointIndex] = tipRelativePose; } // compute chain @@ -311,12 +323,12 @@ void AnimSplineIK::lookUpIndices() { assert(_skeleton); // look up bone indices by name - std::vector indices = _skeleton->lookUpJointIndices({ _baseJointName, _tipJointName, _secondaryTargetJointName }); + std::vector indices = _skeleton->lookUpJointIndices({ _baseJointName, _tipJointName, _midJointName }); // cache the results _baseJointIndex = indices[0]; _tipJointIndex = indices[1]; - _secondaryTargetIndex = indices[2]; + _midJointIndex = indices[2]; if (_baseJointIndex != -1) { _baseParentJointIndex = _skeleton->getParentIndex(_baseJointIndex); @@ -362,7 +374,7 @@ static CubicHermiteSplineFunctorWithArcLength computeSplineFromTipAndBase(const void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug, AnimChain& chainInfoOut) const { const int baseIndex = _baseJointIndex; - const int tipBaseIndex = _secondaryTargetIndex; + const int tipBaseIndex = _midJointIndex; // build spline from tip to base AnimPose tipPose = AnimPose(glm::vec3(1.0f), target.getRotation(), target.getTranslation()); @@ -556,29 +568,6 @@ void AnimSplineIK::loadPoses(const AnimPoseVec& poses) { } } - -void AnimSplineIK::setTargetVars(const QString& jointName, const QString& positionVar, const QString& rotationVar, - const QString& typeVar, const QString& weightVar, float weight, const std::vector& flexCoefficients, - const QString& poleVectorEnabledVar, const QString& poleReferenceVectorVar, const QString& poleVectorVar) { - /* - IKTargetVar targetVar(jointName, positionVar, rotationVar, typeVar, weightVar, weight, flexCoefficients, poleVectorEnabledVar, poleReferenceVectorVar, poleVectorVar); - - // if there are dups, last one wins. - bool found = false; - for (auto& targetVarIter : _targetVarVec) { - if (targetVarIter.jointName == jointName) { - targetVarIter = targetVar; - found = true; - break; - } - } - if (!found) { - // create a new entry - _targetVarVec.push_back(targetVar); - } - */ -} - void AnimSplineIK::beginInterp(InterpType interpType, const AnimChain& chain) { // capture the current poses in a snapshot. _snapshotChain = chain; diff --git a/libraries/animation/src/AnimSplineIK.h b/libraries/animation/src/AnimSplineIK.h index c6d435fd6b..b05c5f4849 100644 --- a/libraries/animation/src/AnimSplineIK.h +++ b/libraries/animation/src/AnimSplineIK.h @@ -60,15 +60,13 @@ protected: float _interpDuration; QString _baseJointName; QString _tipJointName; - QString _secondaryTargetJointName; + QString _midJointName; QString _basePositionVar; QString _baseRotationVar; + QString _midPositionVar; + QString _midRotationVar; QString _tipPositionVar; QString _tipRotationVar; - QString _secondaryTargetPositionVar; - QString _secondaryTargetRotationVar; - //QString _primaryFlexCoefficients; - //QString _secondaryFlexCoefficients; static const int MAX_NUMBER_FLEX_VARIABLES = 10; float _primaryFlexCoefficients[MAX_NUMBER_FLEX_VARIABLES]; @@ -78,7 +76,7 @@ protected: int _baseParentJointIndex { -1 }; int _baseJointIndex { -1 }; - int _secondaryTargetIndex { -1 }; + int _midJointIndex { -1 }; int _tipJointIndex { -1 }; int _headIndex { -1 }; int _hipsIndex { -1 }; @@ -110,9 +108,6 @@ protected: bool _lastEnableDebugDrawIKTargets{ false }; void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug, AnimChain& chainInfoOut) const; - void AnimSplineIK::setTargetVars(const QString& jointName, const QString& positionVar, const QString& rotationVar, - const QString& typeVar, const QString& weightVar, float weight, const std::vector& flexCoefficients, - const QString& poleVectorEnabledVar, const QString& poleReferenceVectorVar, const QString& poleVectorVar); void computeAndCacheSplineJointInfosForIKTarget(const AnimContext& context, const IKTarget& target) const; const std::vector* findOrCreateSplineJointInfo(const AnimContext& context, const IKTarget& target) const; mutable std::map> _splineJointInfoMap; From f8bfef6dbd6d96232bb97012fe50aff26038e1bb Mon Sep 17 00:00:00 2001 From: amantley Date: Mon, 28 Jan 2019 11:50:23 -0800 Subject: [PATCH 024/474] cleaning up the _hipsIndex references, cleaning in general --- .../avatar-animation_withSplineIKNode.json | 4 +- libraries/animation/src/AnimNodeLoader.cpp | 6 +- libraries/animation/src/AnimSplineIK.cpp | 157 +++++++++--------- libraries/animation/src/AnimSplineIK.h | 18 +- 4 files changed, 85 insertions(+), 100 deletions(-) diff --git a/interface/resources/avatar/avatar-animation_withSplineIKNode.json b/interface/resources/avatar/avatar-animation_withSplineIKNode.json index 40572f698b..aac1312e72 100644 --- a/interface/resources/avatar/avatar-animation_withSplineIKNode.json +++ b/interface/resources/avatar/avatar-animation_withSplineIKNode.json @@ -194,8 +194,8 @@ "enabledVar": "splineIKEnabled", "endEffectorRotationVarVar": "splineIKRotationVar", "endEffectorPositionVarVar": "splineIKPositionVar", - "primaryFlexCoefficients": "1.0, 1.0, 1.0, 1.0, 1.0", - "secondaryFlexCoefficients": "1.0, 1.0, 1.0" + "tipTargetFlexCoefficients": "1.0, 1.0, 1.0, 1.0, 1.0", + "midTargetFlexCoefficients": "1.0, 1.0, 1.0" }, "children": [ { diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 8a19b763bb..47c8c7f0bb 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -596,14 +596,14 @@ static AnimNode::Pointer loadSplineIKNode(const QJsonObject& jsonObj, const QStr READ_STRING(enabledVar, jsonObj, id, jsonUrl, nullptr); READ_STRING(endEffectorRotationVarVar, jsonObj, id, jsonUrl, nullptr); READ_STRING(endEffectorPositionVarVar, jsonObj, id, jsonUrl, nullptr); - READ_STRING(primaryFlexCoefficients, jsonObj, id, jsonUrl, nullptr); - READ_STRING(secondaryFlexCoefficients, jsonObj, id, jsonUrl, nullptr); + READ_STRING(tipTargetFlexCoefficients, jsonObj, id, jsonUrl, nullptr); + READ_STRING(midTargetFlexCoefficients, jsonObj, id, jsonUrl, nullptr); auto node = std::make_shared(id, alpha, enabled, interpDuration, baseJointName, midJointName, tipJointName, alphaVar, enabledVar, endEffectorRotationVarVar, endEffectorPositionVarVar, basePositionVar, baseRotationVar, midPositionVar, midRotationVar, - tipPositionVar, tipRotationVar, primaryFlexCoefficients, secondaryFlexCoefficients); + tipPositionVar, tipRotationVar, tipTargetFlexCoefficients, midTargetFlexCoefficients); return node; } diff --git a/libraries/animation/src/AnimSplineIK.cpp b/libraries/animation/src/AnimSplineIK.cpp index 27e295ffb0..7c8f950537 100644 --- a/libraries/animation/src/AnimSplineIK.cpp +++ b/libraries/animation/src/AnimSplineIK.cpp @@ -29,8 +29,8 @@ AnimSplineIK::AnimSplineIK(const QString& id, float alpha, bool enabled, float i const QString& midRotationVar, const QString& tipPositionVar, const QString& tipRotationVar, - const QString& primaryFlexCoefficients, - const QString& secondaryFlexCoefficients) : + const QString& tipTargetFlexCoefficients, + const QString& midTargetFlexCoefficients) : AnimNode(AnimNode::Type::SplineIK, id), _alpha(alpha), _enabled(enabled), @@ -52,22 +52,22 @@ AnimSplineIK::AnimSplineIK(const QString& id, float alpha, bool enabled, float i _tipRotationVar(tipRotationVar) { - QStringList flexCoefficientsValues = primaryFlexCoefficients.split(',', QString::SkipEmptyParts); - for (int i = 0; i < flexCoefficientsValues.size(); i++) { + QStringList tipTargetFlexCoefficientsValues = tipTargetFlexCoefficients.split(',', QString::SkipEmptyParts); + for (int i = 0; i < tipTargetFlexCoefficientsValues.size(); i++) { if (i < MAX_NUMBER_FLEX_VARIABLES) { - qCDebug(animation) << "flex value " << flexCoefficientsValues[i].toDouble(); - _primaryFlexCoefficients[i] = (float)flexCoefficientsValues[i].toDouble(); + qCDebug(animation) << "tip target flex value " << tipTargetFlexCoefficientsValues[i].toDouble(); + _tipTargetFlexCoefficients[i] = (float)tipTargetFlexCoefficientsValues[i].toDouble(); } } - _numPrimaryFlexCoefficients = std::min(flexCoefficientsValues.size(), MAX_NUMBER_FLEX_VARIABLES); - QStringList secondaryFlexCoefficientsValues = secondaryFlexCoefficients.split(',', QString::SkipEmptyParts); - for (int i = 0; i < secondaryFlexCoefficientsValues.size(); i++) { + _numTipTargetFlexCoefficients = std::min(tipTargetFlexCoefficientsValues.size(), MAX_NUMBER_FLEX_VARIABLES); + QStringList midTargetFlexCoefficientsValues = midTargetFlexCoefficients.split(',', QString::SkipEmptyParts); + for (int i = 0; i < midTargetFlexCoefficientsValues.size(); i++) { if (i < MAX_NUMBER_FLEX_VARIABLES) { - qCDebug(animation) << "secondaryflex value " << secondaryFlexCoefficientsValues[i].toDouble(); - _secondaryFlexCoefficients[i] = (float)secondaryFlexCoefficientsValues[i].toDouble(); + qCDebug(animation) << "mid target flex value " << midTargetFlexCoefficientsValues[i].toDouble(); + _midTargetFlexCoefficients[i] = (float)midTargetFlexCoefficientsValues[i].toDouble(); } } - _numSecondaryFlexCoefficients = std::min(secondaryFlexCoefficientsValues.size(), MAX_NUMBER_FLEX_VARIABLES); + _numMidTargetFlexCoefficients = std::min(midTargetFlexCoefficientsValues.size(), MAX_NUMBER_FLEX_VARIABLES); } @@ -130,86 +130,79 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const AnimChain underChain; underChain.buildFromRelativePoses(_skeleton, underPoses, _tipJointIndex); - glm::quat hipsTargetRotation; - glm::vec3 hipsTargetTranslation; + glm::quat baseTargetRotation; + glm::vec3 baseTargetTranslation; // now we override the hips with the hips target pose. + // if there is a baseJoint ik target in animvars then use that + // otherwise use the underpose if (_poses.size() > 0) { - AnimPose hipsUnderPose = _skeleton->getAbsolutePose(_hipsIndex, _poses); - hipsTargetRotation = animVars.lookupRigToGeometry("hipsRotation", hipsUnderPose.rot()); - hipsTargetTranslation = animVars.lookupRigToGeometry("hipsPosition", hipsUnderPose.trans()); - AnimPose absHipsTargetPose(hipsTargetRotation, hipsTargetTranslation); + AnimPose baseJointUnderPose = _skeleton->getAbsolutePose(_baseJointIndex, _poses); + baseTargetRotation = animVars.lookupRigToGeometry(_baseRotationVar, baseJointUnderPose.rot()); + baseTargetTranslation = animVars.lookupRigToGeometry(_basePositionVar, baseJointUnderPose.trans()); + AnimPose absBaseTargetPose(baseTargetRotation, baseTargetTranslation); - int hipsParentIndex = _skeleton->getParentIndex(_hipsIndex); - AnimPose hipsParentAbsPose = _skeleton->getAbsolutePose(hipsParentIndex, _poses); + int baseParentIndex = _skeleton->getParentIndex(_baseJointIndex); + AnimPose baseParentAbsPose(Quaternions::IDENTITY,glm::vec3()); + if (baseParentIndex >= 0) { + baseParentAbsPose = _skeleton->getAbsolutePose(baseParentIndex, _poses); + } - _poses[_hipsIndex] = hipsParentAbsPose.inverse() * absHipsTargetPose; - _poses[_hipsIndex].scale() = glm::vec3(1.0f); + _poses[_baseJointIndex] = baseParentAbsPose.inverse() * absBaseTargetPose; + _poses[_baseJointIndex].scale() = glm::vec3(1.0f); } - AnimPoseVec absolutePoses; - absolutePoses.resize(_poses.size()); - computeAbsolutePoses(absolutePoses); - - IKTarget target; + IKTarget tipTarget; if (_tipJointIndex != -1) { - target.setType((int)IKTarget::Type::Spline); - target.setIndex(_tipJointIndex); + tipTarget.setType((int)IKTarget::Type::Spline); + tipTarget.setIndex(_tipJointIndex); + AnimPose absPose = _skeleton->getAbsolutePose(_tipJointIndex, _poses); glm::quat rotation = animVars.lookupRigToGeometry(_tipRotationVar, absPose.rot()); glm::vec3 translation = animVars.lookupRigToGeometry(_tipPositionVar, absPose.trans()); - float weight = 1.0f; - - target.setPose(rotation, translation); - target.setWeight(weight); - - - - const float* flexCoefficients = new float[5]{ 1.0f, 0.5f, 0.25f, 0.2f, 0.1f }; - target.setFlexCoefficients(_numPrimaryFlexCoefficients, _primaryFlexCoefficients); + tipTarget.setPose(rotation, translation); + tipTarget.setWeight(1.0f); + tipTarget.setFlexCoefficients(_numTipTargetFlexCoefficients, _tipTargetFlexCoefficients); } AnimChain jointChain; - AnimPose updatedSecondaryTarget; - AnimPoseVec absolutePoses2; - absolutePoses2.resize(_poses.size()); + if (_poses.size() > 0) { - jointChain.buildFromRelativePoses(_skeleton, _poses, target.getIndex()); - solveTargetWithSpline(context, target, absolutePoses, false, jointChain); + AnimPoseVec absolutePoses; + absolutePoses.resize(_poses.size()); + computeAbsolutePoses(absolutePoses); + jointChain.buildFromRelativePoses(_skeleton, _poses, tipTarget.getIndex()); + solveTargetWithSpline(context, tipTarget, absolutePoses, false, jointChain); jointChain.buildDirtyAbsolutePoses(); - qCDebug(animation) << "the jointChain Result for head " << jointChain.getAbsolutePoseFromJointIndex(_tipJointIndex); - qCDebug(animation) << "the orig target pose for head " << target.getPose(); jointChain.outputRelativePoses(_poses); - - AnimPose afterSolveSecondaryTarget = _skeleton->getAbsolutePose(_midJointIndex, _poses); - glm::quat secondaryTargetRotation = animVars.lookupRigToGeometry(_midRotationVar, afterSolveSecondaryTarget.rot()); - updatedSecondaryTarget = AnimPose(secondaryTargetRotation, afterSolveSecondaryTarget.trans()); - //updatedSecondaryTarget = AnimPose(afterSolveSecondaryTarget.rot(), afterSolveSecondaryTarget.trans()); } - IKTarget secondaryTarget; - computeAbsolutePoses(absolutePoses2); + IKTarget midTarget; if (_midJointIndex != -1) { - secondaryTarget.setType((int)IKTarget::Type::Spline); - secondaryTarget.setIndex(_midJointIndex); + midTarget.setType((int)IKTarget::Type::Spline); + midTarget.setIndex(_midJointIndex); - float weight2 = 1.0f; - secondaryTarget.setPose(updatedSecondaryTarget.rot(), updatedSecondaryTarget.trans()); - secondaryTarget.setWeight(weight2); - - const float* flexCoefficients2 = new float[3]{ 1.0f, 0.5f, 0.25f }; - secondaryTarget.setFlexCoefficients(_numSecondaryFlexCoefficients, _secondaryFlexCoefficients); + // set the middle joint to the position that is determined by the base-tip spline. + AnimPose afterSolveMidTarget = _skeleton->getAbsolutePose(_midJointIndex, _poses); + glm::quat midTargetRotation = animVars.lookupRigToGeometry(_midRotationVar, afterSolveMidTarget.rot()); + AnimPose updatedMidTarget = AnimPose(midTargetRotation, afterSolveMidTarget.trans()); + midTarget.setPose(updatedMidTarget.rot(), updatedMidTarget.trans()); + midTarget.setWeight(1.0f); + midTarget.setFlexCoefficients(_numMidTargetFlexCoefficients, _midTargetFlexCoefficients); } - AnimChain secondaryJointChain; + AnimChain midJointChain; AnimPose beforeSolveChestNeck; int startJoint; AnimPose correctJoint; if (_poses.size() > 0) { + AnimPoseVec absolutePoses2; + absolutePoses2.resize(_poses.size()); + computeAbsolutePoses(absolutePoses2); + // start at the tip - for (startJoint = _tipJointIndex; _skeleton->getParentIndex(startJoint) != _midJointIndex; startJoint = _skeleton->getParentIndex(startJoint)) { // find the child of the midJoint } @@ -218,25 +211,25 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const // fix this to deal with no neck AA beforeSolveChestNeck = _skeleton->getAbsolutePose(_skeleton->nameToJointIndex("Neck"), _poses); - secondaryJointChain.buildFromRelativePoses(_skeleton, _poses, secondaryTarget.getIndex()); - solveTargetWithSpline(context, secondaryTarget, absolutePoses2, false, secondaryJointChain); - secondaryJointChain.outputRelativePoses(_poses); + midJointChain.buildFromRelativePoses(_skeleton, _poses, midTarget.getIndex()); + solveTargetWithSpline(context, midTarget, absolutePoses2, false, midJointChain); + midJointChain.outputRelativePoses(_poses); } // set the tip/head rotation to match the absolute rotation of the target. int tipParent = _skeleton->getParentIndex(_tipJointIndex); - int secondaryTargetParent = _skeleton->getParentIndex(_midJointIndex); - if ((secondaryTargetParent != -1) && (tipParent != -1) && (_poses.size() > 0)) { + int midTargetParent = _skeleton->getParentIndex(_midJointIndex); + if ((midTargetParent != -1) && (tipParent != -1) && (_poses.size() > 0)) { - AnimPose secondaryTargetPose(secondaryTarget.getRotation(), secondaryTarget.getTranslation()); + AnimPose midTargetPose(midTarget.getRotation(), midTarget.getTranslation()); //AnimPose neckAbsolute = _skeleton->getAbsolutePose(tipParent, _poses); - //_poses[tipParent] = secondaryTargetPose.inverse() * beforeSolveChestNeck; - _poses[startJoint] = secondaryTargetPose.inverse() * correctJoint; + //_poses[tipParent] = midTargetPose.inverse() * beforeSolveChestNeck; + _poses[startJoint] = midTargetPose.inverse() * correctJoint; - //AnimPose tipTarget(target.getRotation(),target.getTranslation()); - //AnimPose tipRelativePose = _skeleton->getAbsolutePose(tipParent,_poses).inverse() * tipTarget; + //AnimPose tipTargetPose(tipTarget.getRotation(),tipTarget.getTranslation()); + //AnimPose tipRelativePose = _skeleton->getAbsolutePose(tipParent,_poses).inverse() * tipTargetPose; //_poses[_tipJointIndex] = tipRelativePose; } @@ -286,31 +279,31 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const const vec4 GREEN(0.0f, 1.0f, 0.0f, 1.0f); glm::mat4 rigToAvatarMat = createMatFromQuatAndPos(Quaternions::Y_180, glm::vec3()); - glm::mat4 geomTargetMat = createMatFromQuatAndPos(target.getRotation(), target.getTranslation()); + glm::mat4 geomTargetMat = createMatFromQuatAndPos(tipTarget.getRotation(), tipTarget.getTranslation()); glm::mat4 avatarTargetMat = rigToAvatarMat * context.getGeometryToRigMatrix() * geomTargetMat; - QString name = QString("ikTargetHead"); + QString name = QString("ikTargetSplineTip"); DebugDraw::getInstance().addMyAvatarMarker(name, glmExtractRotation(avatarTargetMat), extractTranslation(avatarTargetMat), WHITE); - glm::mat4 geomTargetMat2 = createMatFromQuatAndPos(secondaryTarget.getRotation(), secondaryTarget.getTranslation()); + glm::mat4 geomTargetMat2 = createMatFromQuatAndPos(midTarget.getRotation(), midTarget.getTranslation()); glm::mat4 avatarTargetMat2 = rigToAvatarMat * context.getGeometryToRigMatrix() * geomTargetMat2; - QString name2 = QString("ikTargetSpine2"); + QString name2 = QString("ikTargetSplineMid"); DebugDraw::getInstance().addMyAvatarMarker(name2, glmExtractRotation(avatarTargetMat2), extractTranslation(avatarTargetMat2), WHITE); - glm::mat4 geomTargetMat3 = createMatFromQuatAndPos(hipsTargetRotation, hipsTargetTranslation); + glm::mat4 geomTargetMat3 = createMatFromQuatAndPos(baseTargetRotation,baseTargetTranslation); glm::mat4 avatarTargetMat3 = rigToAvatarMat * context.getGeometryToRigMatrix() * geomTargetMat3; - QString name3 = QString("ikTargetHips"); + QString name3 = QString("ikTargetSplineBase"); DebugDraw::getInstance().addMyAvatarMarker(name3, glmExtractRotation(avatarTargetMat3), extractTranslation(avatarTargetMat3), WHITE); } else if (context.getEnableDebugDrawIKTargets() != _previousEnableDebugIKTargets) { // remove markers if they were added last frame. - QString name = QString("ikTargetHead"); + QString name = QString("ikTargetSplineTip"); DebugDraw::getInstance().removeMyAvatarMarker(name); - QString name2 = QString("ikTargetSpine2"); + QString name2 = QString("ikTargetSplineMid"); DebugDraw::getInstance().removeMyAvatarMarker(name2); - QString name3 = QString("ikTargetHips"); + QString name3 = QString("ikTargetSplineBase"); DebugDraw::getInstance().removeMyAvatarMarker(name3); } @@ -356,8 +349,6 @@ const AnimPoseVec& AnimSplineIK::getPosesInternal() const { void AnimSplineIK::setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) { AnimNode::setSkeletonInternal(skeleton); - //_headIndex = _skeleton->nameToJointIndex("Head"); - _hipsIndex = _skeleton->nameToJointIndex("Hips"); lookUpIndices(); } diff --git a/libraries/animation/src/AnimSplineIK.h b/libraries/animation/src/AnimSplineIK.h index b05c5f4849..abc34618b3 100644 --- a/libraries/animation/src/AnimSplineIK.h +++ b/libraries/animation/src/AnimSplineIK.h @@ -19,14 +19,12 @@ class AnimSplineIK : public AnimNode { public: AnimSplineIK(const QString& id, float alpha, bool enabled, float interpDuration, - const QString& baseJointName, const QString& tipJointName, + const QString& baseJointName, const QString& midJointName, const QString& tipJointName, const QString& alphaVar, const QString& enabledVar, const QString& endEffectorRotationVarVar, const QString& endEffectorPositionVarVar, const QString& basePositionVar, const QString& baseRotationVar, + const QString& midPositionVar, const QString& midRotationVar, const QString& tipPositionVar, const QString& tipRotationVar, - const QString& secondaryTargetJointName, - const QString& secondaryTargetPositionVar, - const QString& secondaryTargetRotationVar, const QString& primaryFlexCoefficients, const QString& secondaryFlexCoefficients); @@ -69,19 +67,15 @@ protected: QString _tipRotationVar; static const int MAX_NUMBER_FLEX_VARIABLES = 10; - float _primaryFlexCoefficients[MAX_NUMBER_FLEX_VARIABLES]; - float _secondaryFlexCoefficients[MAX_NUMBER_FLEX_VARIABLES]; - int _numPrimaryFlexCoefficients { 0 }; - int _numSecondaryFlexCoefficients { 0 }; + float _tipTargetFlexCoefficients[MAX_NUMBER_FLEX_VARIABLES]; + float _midTargetFlexCoefficients[MAX_NUMBER_FLEX_VARIABLES]; + int _numTipTargetFlexCoefficients { 0 }; + int _numMidTargetFlexCoefficients { 0 }; int _baseParentJointIndex { -1 }; int _baseJointIndex { -1 }; int _midJointIndex { -1 }; int _tipJointIndex { -1 }; - int _headIndex { -1 }; - int _hipsIndex { -1 }; - int _spine2Index { -1 }; - int _hipsTargetIndex { -1 }; QString _alphaVar; // float - (0, 1) 0 means underPoses only, 1 means IK only. QString _enabledVar; // bool From ffd3a24bf22c7f4e6cd2f4604693e7d83da40422 Mon Sep 17 00:00:00 2001 From: amantley Date: Mon, 28 Jan 2019 13:53:30 -0800 Subject: [PATCH 025/474] further cleaning, broke the arms --- .../avatar-animation_withSplineIKNode.json | 2 - libraries/animation/src/AnimNodeLoader.cpp | 6 +- libraries/animation/src/AnimSplineIK.cpp | 78 ++++++------------- libraries/animation/src/AnimSplineIK.h | 12 +-- 4 files changed, 29 insertions(+), 69 deletions(-) diff --git a/interface/resources/avatar/avatar-animation_withSplineIKNode.json b/interface/resources/avatar/avatar-animation_withSplineIKNode.json index aac1312e72..a6283f0771 100644 --- a/interface/resources/avatar/avatar-animation_withSplineIKNode.json +++ b/interface/resources/avatar/avatar-animation_withSplineIKNode.json @@ -192,8 +192,6 @@ "tipRotationVar": "headRotation", "alphaVar": "splineIKAlpha", "enabledVar": "splineIKEnabled", - "endEffectorRotationVarVar": "splineIKRotationVar", - "endEffectorPositionVarVar": "splineIKPositionVar", "tipTargetFlexCoefficients": "1.0, 1.0, 1.0, 1.0, 1.0", "midTargetFlexCoefficients": "1.0, 1.0, 1.0" }, diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 47c8c7f0bb..62e848872b 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -594,16 +594,14 @@ static AnimNode::Pointer loadSplineIKNode(const QJsonObject& jsonObj, const QStr READ_STRING(tipRotationVar, jsonObj, id, jsonUrl, nullptr); READ_STRING(alphaVar, jsonObj, id, jsonUrl, nullptr); READ_STRING(enabledVar, jsonObj, id, jsonUrl, nullptr); - READ_STRING(endEffectorRotationVarVar, jsonObj, id, jsonUrl, nullptr); - READ_STRING(endEffectorPositionVarVar, jsonObj, id, jsonUrl, nullptr); READ_STRING(tipTargetFlexCoefficients, jsonObj, id, jsonUrl, nullptr); READ_STRING(midTargetFlexCoefficients, jsonObj, id, jsonUrl, nullptr); auto node = std::make_shared(id, alpha, enabled, interpDuration, baseJointName, midJointName, tipJointName, - alphaVar, enabledVar, endEffectorRotationVarVar, endEffectorPositionVarVar, basePositionVar, baseRotationVar, midPositionVar, midRotationVar, - tipPositionVar, tipRotationVar, tipTargetFlexCoefficients, midTargetFlexCoefficients); + tipPositionVar, tipRotationVar, alphaVar, enabledVar, + tipTargetFlexCoefficients, midTargetFlexCoefficients); return node; } diff --git a/libraries/animation/src/AnimSplineIK.cpp b/libraries/animation/src/AnimSplineIK.cpp index 7c8f950537..5355a02b3e 100644 --- a/libraries/animation/src/AnimSplineIK.cpp +++ b/libraries/animation/src/AnimSplineIK.cpp @@ -21,14 +21,14 @@ AnimSplineIK::AnimSplineIK(const QString& id, float alpha, bool enabled, float i const QString& baseJointName, const QString& midJointName, const QString& tipJointName, - const QString& alphaVar, const QString& enabledVar, - const QString& endEffectorRotationVarVar, const QString& endEffectorPositionVarVar, const QString& basePositionVar, const QString& baseRotationVar, const QString& midPositionVar, const QString& midRotationVar, const QString& tipPositionVar, const QString& tipRotationVar, + const QString& alphaVar, + const QString& enabledVar, const QString& tipTargetFlexCoefficients, const QString& midTargetFlexCoefficients) : AnimNode(AnimNode::Type::SplineIK, id), @@ -38,18 +38,14 @@ AnimSplineIK::AnimSplineIK(const QString& id, float alpha, bool enabled, float i _baseJointName(baseJointName), _midJointName(midJointName), _tipJointName(tipJointName), - _alphaVar(alphaVar), - _enabledVar(enabledVar), - _endEffectorRotationVarVar(endEffectorRotationVarVar), - _endEffectorPositionVarVar(endEffectorPositionVarVar), - _prevEndEffectorRotationVar(), - _prevEndEffectorPositionVar(), _basePositionVar(basePositionVar), _baseRotationVar(baseRotationVar), _midPositionVar(midPositionVar), _midRotationVar(midRotationVar), _tipPositionVar(tipPositionVar), - _tipRotationVar(tipRotationVar) + _tipRotationVar(tipRotationVar), + _alphaVar(alphaVar), + _enabledVar(enabledVar) { QStringList tipTargetFlexCoefficientsValues = tipTargetFlexCoefficients.split(',', QString::SkipEmptyParts); @@ -101,7 +97,7 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const return _poses; } - // guard against size changes + // guard against size change if (underPoses.size() != _poses.size()) { _poses = underPoses; } @@ -130,17 +126,14 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const AnimChain underChain; underChain.buildFromRelativePoses(_skeleton, underPoses, _tipJointIndex); - glm::quat baseTargetRotation; - glm::vec3 baseTargetTranslation; - + AnimPose baseTargetAbsolutePose; // now we override the hips with the hips target pose. // if there is a baseJoint ik target in animvars then use that // otherwise use the underpose if (_poses.size() > 0) { AnimPose baseJointUnderPose = _skeleton->getAbsolutePose(_baseJointIndex, _poses); - baseTargetRotation = animVars.lookupRigToGeometry(_baseRotationVar, baseJointUnderPose.rot()); - baseTargetTranslation = animVars.lookupRigToGeometry(_basePositionVar, baseJointUnderPose.trans()); - AnimPose absBaseTargetPose(baseTargetRotation, baseTargetTranslation); + baseTargetAbsolutePose.rot() = animVars.lookupRigToGeometry(_baseRotationVar, baseJointUnderPose.rot()); + baseTargetAbsolutePose.trans() = animVars.lookupRigToGeometry(_basePositionVar, baseJointUnderPose.trans()); int baseParentIndex = _skeleton->getParentIndex(_baseJointIndex); AnimPose baseParentAbsPose(Quaternions::IDENTITY,glm::vec3()); @@ -148,7 +141,7 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const baseParentAbsPose = _skeleton->getAbsolutePose(baseParentIndex, _poses); } - _poses[_baseJointIndex] = baseParentAbsPose.inverse() * absBaseTargetPose; + _poses[_baseJointIndex] = baseParentAbsPose.inverse() * baseTargetAbsolutePose; _poses[_baseJointIndex].scale() = glm::vec3(1.0f); } @@ -193,26 +186,22 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const } AnimChain midJointChain; - AnimPose beforeSolveChestNeck; - int startJoint; - AnimPose correctJoint; + int childOfMiddleJointIndex = -1; + AnimPose childOfMiddleJointAbsolutePoseAfterBaseTipSpline; if (_poses.size() > 0) { - AnimPoseVec absolutePoses2; - absolutePoses2.resize(_poses.size()); - computeAbsolutePoses(absolutePoses2); - + childOfMiddleJointIndex = _tipJointIndex; // start at the tip - for (startJoint = _tipJointIndex; _skeleton->getParentIndex(startJoint) != _midJointIndex; startJoint = _skeleton->getParentIndex(startJoint)) { - // find the child of the midJoint + while (_skeleton->getParentIndex(childOfMiddleJointIndex) != _midJointIndex) { + childOfMiddleJointIndex = _skeleton->getParentIndex(childOfMiddleJointIndex); } - correctJoint = _skeleton->getAbsolutePose(startJoint, _poses); - - // fix this to deal with no neck AA - beforeSolveChestNeck = _skeleton->getAbsolutePose(_skeleton->nameToJointIndex("Neck"), _poses); + childOfMiddleJointAbsolutePoseAfterBaseTipSpline = _skeleton->getAbsolutePose(childOfMiddleJointIndex, _poses); + AnimPoseVec absolutePosesAfterBaseTipSpline; + absolutePosesAfterBaseTipSpline.resize(_poses.size()); + computeAbsolutePoses(absolutePosesAfterBaseTipSpline); midJointChain.buildFromRelativePoses(_skeleton, _poses, midTarget.getIndex()); - solveTargetWithSpline(context, midTarget, absolutePoses2, false, midJointChain); + solveTargetWithSpline(context, midTarget, absolutePosesAfterBaseTipSpline, false, midJointChain); midJointChain.outputRelativePoses(_poses); } @@ -220,17 +209,8 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const int tipParent = _skeleton->getParentIndex(_tipJointIndex); int midTargetParent = _skeleton->getParentIndex(_midJointIndex); if ((midTargetParent != -1) && (tipParent != -1) && (_poses.size() > 0)) { - - - AnimPose midTargetPose(midTarget.getRotation(), midTarget.getTranslation()); - //AnimPose neckAbsolute = _skeleton->getAbsolutePose(tipParent, _poses); - //_poses[tipParent] = midTargetPose.inverse() * beforeSolveChestNeck; - _poses[startJoint] = midTargetPose.inverse() * correctJoint; - - //AnimPose tipTargetPose(tipTarget.getRotation(),tipTarget.getTranslation()); - //AnimPose tipRelativePose = _skeleton->getAbsolutePose(tipParent,_poses).inverse() * tipTargetPose; - //_poses[_tipJointIndex] = tipRelativePose; + _poses[childOfMiddleJointIndex] = midTargetPose.inverse() * childOfMiddleJointAbsolutePoseAfterBaseTipSpline; } // compute chain @@ -290,7 +270,7 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const DebugDraw::getInstance().addMyAvatarMarker(name2, glmExtractRotation(avatarTargetMat2), extractTranslation(avatarTargetMat2), WHITE); - glm::mat4 geomTargetMat3 = createMatFromQuatAndPos(baseTargetRotation,baseTargetTranslation); + glm::mat4 geomTargetMat3 = createMatFromQuatAndPos(baseTargetAbsolutePose.rot(), baseTargetAbsolutePose.trans()); glm::mat4 avatarTargetMat3 = rigToAvatarMat * context.getGeometryToRigMatrix() * geomTargetMat3; QString name3 = QString("ikTargetSplineBase"); DebugDraw::getInstance().addMyAvatarMarker(name3, glmExtractRotation(avatarTargetMat3), extractTranslation(avatarTargetMat3), WHITE); @@ -369,12 +349,7 @@ void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, const IKTar // build spline from tip to base AnimPose tipPose = AnimPose(glm::vec3(1.0f), target.getRotation(), target.getTranslation()); - AnimPose basePose; - //if (target.getIndex() == _tipJointIndex) { - // basePose = absolutePoses[tipBaseIndex]; - //} else { - basePose = absolutePoses[baseIndex]; - //} + AnimPose basePose = absolutePoses[baseIndex]; CubicHermiteSplineFunctorWithArcLength spline; if (target.getIndex() == _tipJointIndex) { @@ -382,7 +357,6 @@ void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, const IKTar const float HIPS_GAIN = 0.5f; const float HEAD_GAIN = 1.0f; spline = computeSplineFromTipAndBase(tipPose, basePose, HIPS_GAIN, HEAD_GAIN); - // spline = computeSplineFromTipAndBase(tipPose, basePose); } else { spline = computeSplineFromTipAndBase(tipPose, basePose); } @@ -406,7 +380,7 @@ void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, const IKTar float t = spline.arcLengthInverse(splineJointInfo.ratio * totalArcLength); glm::vec3 trans = spline(t); - // for head splines, preform most twist toward the tip by using ease in function. t^2 + // for base->tip splines, preform most twist toward the tip by using ease in function. t^2 float rotT = t; if (target.getIndex() == _tipJointIndex) { rotT = t * t; @@ -454,9 +428,6 @@ void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, const IKTar if (!chainInfoOut.setRelativePoseAtJointIndex(splineJointInfo.jointIndex, relPose)) { qCDebug(animation) << "we didn't find the joint index for the spline!!!!"; } - if (splineJointInfo.jointIndex == _skeleton->nameToJointIndex("Neck")) { - qCDebug(animation) << "neck is " << relPose; - } parentAbsPose = flexedAbsPose; } @@ -518,7 +489,6 @@ void AnimSplineIK::computeAndCacheSplineJointInfosForIKTarget(const AnimContext& int index = target.getIndex(); int endIndex; if (target.getIndex() == _tipJointIndex) { - //endIndex = _skeleton->getParentIndex(_secondaryTargetIndex); endIndex = _skeleton->getParentIndex(_baseJointIndex); } else { endIndex = _skeleton->getParentIndex(_baseJointIndex); diff --git a/libraries/animation/src/AnimSplineIK.h b/libraries/animation/src/AnimSplineIK.h index abc34618b3..ea7a897362 100644 --- a/libraries/animation/src/AnimSplineIK.h +++ b/libraries/animation/src/AnimSplineIK.h @@ -20,13 +20,12 @@ class AnimSplineIK : public AnimNode { public: AnimSplineIK(const QString& id, float alpha, bool enabled, float interpDuration, const QString& baseJointName, const QString& midJointName, const QString& tipJointName, - const QString& alphaVar, const QString& enabledVar, - const QString& endEffectorRotationVarVar, const QString& endEffectorPositionVarVar, const QString& basePositionVar, const QString& baseRotationVar, const QString& midPositionVar, const QString& midRotationVar, const QString& tipPositionVar, const QString& tipRotationVar, - const QString& primaryFlexCoefficients, - const QString& secondaryFlexCoefficients); + const QString& alphaVar, const QString& enabledVar, + const QString& tipTargetFlexCoefficients, + const QString& midTargetFlexCoefficients); virtual ~AnimSplineIK() override; virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override; @@ -79,11 +78,6 @@ protected: QString _alphaVar; // float - (0, 1) 0 means underPoses only, 1 means IK only. QString _enabledVar; // bool - QString _endEffectorRotationVarVar; // string - QString _endEffectorPositionVarVar; // string - - QString _prevEndEffectorRotationVar; - QString _prevEndEffectorPositionVar; bool _previousEnableDebugIKTargets { false }; From f17cfbcbb188eb68bbdca695f50879d90a88a324 Mon Sep 17 00:00:00 2001 From: amantley Date: Mon, 28 Jan 2019 15:25:12 -0800 Subject: [PATCH 026/474] more cleaning, need to fix debug draw ik chain --- libraries/animation/src/AnimSplineIK.cpp | 141 ++++++++++------------- libraries/animation/src/AnimSplineIK.h | 4 +- 2 files changed, 60 insertions(+), 85 deletions(-) diff --git a/libraries/animation/src/AnimSplineIK.cpp b/libraries/animation/src/AnimSplineIK.cpp index 5355a02b3e..af1f3e9524 100644 --- a/libraries/animation/src/AnimSplineIK.cpp +++ b/libraries/animation/src/AnimSplineIK.cpp @@ -90,8 +90,8 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const // evaluate underPoses AnimPoseVec underPoses = _children[0]->evaluate(animVars, context, dt, triggersOut); - // if we don't have a skeleton, or jointName lookup failed. - if (!_skeleton || _baseJointIndex == -1 || _tipJointIndex == -1 || alpha == 0.0f || underPoses.size() == 0) { + // if we don't have a skeleton, or jointName lookup failed or the spline alpha is 0 or there are no underposes. + if (!_skeleton || _baseJointIndex == -1 || _midJointIndex == -1 || _tipJointIndex == -1 || alpha == 0.0f || underPoses.size() == 0) { // pass underPoses through unmodified. _poses = underPoses; return _poses; @@ -115,6 +115,7 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const } _enabled = enabled; + // now that we have saved the previous _poses in _snapshotChain, we can update to the current underposes _poses = underPoses; // don't build chains or do IK if we are disabled & not interping. @@ -127,91 +128,75 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const underChain.buildFromRelativePoses(_skeleton, underPoses, _tipJointIndex); AnimPose baseTargetAbsolutePose; - // now we override the hips with the hips target pose. - // if there is a baseJoint ik target in animvars then use that + // if there is a baseJoint ik target in animvars then set the joint to that // otherwise use the underpose - if (_poses.size() > 0) { - AnimPose baseJointUnderPose = _skeleton->getAbsolutePose(_baseJointIndex, _poses); - baseTargetAbsolutePose.rot() = animVars.lookupRigToGeometry(_baseRotationVar, baseJointUnderPose.rot()); - baseTargetAbsolutePose.trans() = animVars.lookupRigToGeometry(_basePositionVar, baseJointUnderPose.trans()); + AnimPose baseJointUnderPose = _skeleton->getAbsolutePose(_baseJointIndex, _poses); + baseTargetAbsolutePose.rot() = animVars.lookupRigToGeometry(_baseRotationVar, baseJointUnderPose.rot()); + baseTargetAbsolutePose.trans() = animVars.lookupRigToGeometry(_basePositionVar, baseJointUnderPose.trans()); - int baseParentIndex = _skeleton->getParentIndex(_baseJointIndex); - AnimPose baseParentAbsPose(Quaternions::IDENTITY,glm::vec3()); - if (baseParentIndex >= 0) { - baseParentAbsPose = _skeleton->getAbsolutePose(baseParentIndex, _poses); - } - - _poses[_baseJointIndex] = baseParentAbsPose.inverse() * baseTargetAbsolutePose; - _poses[_baseJointIndex].scale() = glm::vec3(1.0f); + int baseParentIndex = _skeleton->getParentIndex(_baseJointIndex); + AnimPose baseParentAbsPose(Quaternions::IDENTITY,glm::vec3()); + if (baseParentIndex >= 0) { + baseParentAbsPose = _skeleton->getAbsolutePose(baseParentIndex, _poses); } + _poses[_baseJointIndex] = baseParentAbsPose.inverse() * baseTargetAbsolutePose; + _poses[_baseJointIndex].scale() = glm::vec3(1.0f); + // initialize the tip target IKTarget tipTarget; - if (_tipJointIndex != -1) { - tipTarget.setType((int)IKTarget::Type::Spline); - tipTarget.setIndex(_tipJointIndex); - - AnimPose absPose = _skeleton->getAbsolutePose(_tipJointIndex, _poses); - glm::quat rotation = animVars.lookupRigToGeometry(_tipRotationVar, absPose.rot()); - glm::vec3 translation = animVars.lookupRigToGeometry(_tipPositionVar, absPose.trans()); - tipTarget.setPose(rotation, translation); - tipTarget.setWeight(1.0f); - tipTarget.setFlexCoefficients(_numTipTargetFlexCoefficients, _tipTargetFlexCoefficients); - } + tipTarget.setType((int)IKTarget::Type::Spline); + tipTarget.setIndex(_tipJointIndex); + AnimPose absPose = _skeleton->getAbsolutePose(_tipJointIndex, _poses); + glm::quat rotation = animVars.lookupRigToGeometry(_tipRotationVar, absPose.rot()); + glm::vec3 translation = animVars.lookupRigToGeometry(_tipPositionVar, absPose.trans()); + tipTarget.setPose(rotation, translation); + tipTarget.setWeight(1.0f); + tipTarget.setFlexCoefficients(_numTipTargetFlexCoefficients, _tipTargetFlexCoefficients); + AnimChain jointChain; - - if (_poses.size() > 0) { - - AnimPoseVec absolutePoses; - absolutePoses.resize(_poses.size()); - computeAbsolutePoses(absolutePoses); - jointChain.buildFromRelativePoses(_skeleton, _poses, tipTarget.getIndex()); - solveTargetWithSpline(context, tipTarget, absolutePoses, false, jointChain); - jointChain.buildDirtyAbsolutePoses(); - jointChain.outputRelativePoses(_poses); - } + AnimPoseVec absolutePoses; + absolutePoses.resize(_poses.size()); + computeAbsolutePoses(absolutePoses); + jointChain.buildFromRelativePoses(_skeleton, _poses, tipTarget.getIndex()); + solveTargetWithSpline(context, tipTarget, absolutePoses, false, jointChain); + jointChain.buildDirtyAbsolutePoses(); + jointChain.outputRelativePoses(_poses); + // initialize the middle joint target IKTarget midTarget; - if (_midJointIndex != -1) { - midTarget.setType((int)IKTarget::Type::Spline); - midTarget.setIndex(_midJointIndex); - - // set the middle joint to the position that is determined by the base-tip spline. - AnimPose afterSolveMidTarget = _skeleton->getAbsolutePose(_midJointIndex, _poses); - glm::quat midTargetRotation = animVars.lookupRigToGeometry(_midRotationVar, afterSolveMidTarget.rot()); - AnimPose updatedMidTarget = AnimPose(midTargetRotation, afterSolveMidTarget.trans()); - midTarget.setPose(updatedMidTarget.rot(), updatedMidTarget.trans()); - midTarget.setWeight(1.0f); - midTarget.setFlexCoefficients(_numMidTargetFlexCoefficients, _midTargetFlexCoefficients); - } + midTarget.setType((int)IKTarget::Type::Spline); + midTarget.setIndex(_midJointIndex); + // set the middle joint to the position that was just determined by the base-tip spline. + AnimPose afterSolveMidTarget = _skeleton->getAbsolutePose(_midJointIndex, _poses); + // use the rotation from the ik target value, if there is one. + glm::quat midTargetRotation = animVars.lookupRigToGeometry(_midRotationVar, afterSolveMidTarget.rot()); + AnimPose updatedMidTarget = AnimPose(midTargetRotation, afterSolveMidTarget.trans()); + midTarget.setPose(updatedMidTarget.rot(), updatedMidTarget.trans()); + midTarget.setWeight(1.0f); + midTarget.setFlexCoefficients(_numMidTargetFlexCoefficients, _midTargetFlexCoefficients); AnimChain midJointChain; + // Find the pose of the middle target's child. This is to correct the mid to tip + // after the base to mid spline is set. int childOfMiddleJointIndex = -1; AnimPose childOfMiddleJointAbsolutePoseAfterBaseTipSpline; - if (_poses.size() > 0) { - - childOfMiddleJointIndex = _tipJointIndex; - // start at the tip - while (_skeleton->getParentIndex(childOfMiddleJointIndex) != _midJointIndex) { - childOfMiddleJointIndex = _skeleton->getParentIndex(childOfMiddleJointIndex); - } - childOfMiddleJointAbsolutePoseAfterBaseTipSpline = _skeleton->getAbsolutePose(childOfMiddleJointIndex, _poses); - - AnimPoseVec absolutePosesAfterBaseTipSpline; - absolutePosesAfterBaseTipSpline.resize(_poses.size()); - computeAbsolutePoses(absolutePosesAfterBaseTipSpline); - midJointChain.buildFromRelativePoses(_skeleton, _poses, midTarget.getIndex()); - solveTargetWithSpline(context, midTarget, absolutePosesAfterBaseTipSpline, false, midJointChain); - midJointChain.outputRelativePoses(_poses); + childOfMiddleJointIndex = _tipJointIndex; + while (_skeleton->getParentIndex(childOfMiddleJointIndex) != _midJointIndex) { + childOfMiddleJointIndex = _skeleton->getParentIndex(childOfMiddleJointIndex); } + childOfMiddleJointAbsolutePoseAfterBaseTipSpline = _skeleton->getAbsolutePose(childOfMiddleJointIndex, _poses); - // set the tip/head rotation to match the absolute rotation of the target. - int tipParent = _skeleton->getParentIndex(_tipJointIndex); - int midTargetParent = _skeleton->getParentIndex(_midJointIndex); - if ((midTargetParent != -1) && (tipParent != -1) && (_poses.size() > 0)) { - AnimPose midTargetPose(midTarget.getRotation(), midTarget.getTranslation()); - _poses[childOfMiddleJointIndex] = midTargetPose.inverse() * childOfMiddleJointAbsolutePoseAfterBaseTipSpline; - } + AnimPoseVec absolutePosesAfterBaseTipSpline; + absolutePosesAfterBaseTipSpline.resize(_poses.size()); + computeAbsolutePoses(absolutePosesAfterBaseTipSpline); + midJointChain.buildFromRelativePoses(_skeleton, _poses, midTarget.getIndex()); + solveTargetWithSpline(context, midTarget, absolutePosesAfterBaseTipSpline, false, midJointChain); + midJointChain.outputRelativePoses(_poses); + + // set the mid to tip segment to match the absolute rotation of the target. + AnimPose midTargetPose(midTarget.getRotation(), midTarget.getTranslation()); + _poses[childOfMiddleJointIndex] = midTargetPose.inverse() * childOfMiddleJointAbsolutePoseAfterBaseTipSpline; // compute chain AnimChain ikChain; @@ -424,7 +409,6 @@ void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, const IKTar relPose.trans() = glm::vec3(0.0f); } } - // note we are ignoring the constrained info for now. if (!chainInfoOut.setRelativePoseAtJointIndex(splineJointInfo.jointIndex, relPose)) { qCDebug(animation) << "we didn't find the joint index for the spline!!!!"; } @@ -450,7 +434,6 @@ const std::vector* AnimSplineIK::findOrCreateSpli return &(iter->second); } } - return nullptr; } @@ -460,13 +443,7 @@ void AnimSplineIK::computeAndCacheSplineJointInfosForIKTarget(const AnimContext& // build spline between the default poses. AnimPose tipPose = _skeleton->getAbsoluteDefaultPose(target.getIndex()); - AnimPose basePose; - if (target.getIndex() == _tipJointIndex) { - basePose = _skeleton->getAbsoluteDefaultPose(_baseJointIndex); - //basePose = _skeleton->getAbsoluteDefaultPose(_secondaryTargetIndex); - } else { - basePose = _skeleton->getAbsoluteDefaultPose(_baseJointIndex); - } + AnimPose basePose = _skeleton->getAbsoluteDefaultPose(_baseJointIndex); CubicHermiteSplineFunctorWithArcLength spline; if (target.getIndex() == _tipJointIndex) { @@ -474,7 +451,6 @@ void AnimSplineIK::computeAndCacheSplineJointInfosForIKTarget(const AnimContext& const float HIPS_GAIN = 0.5f; const float HEAD_GAIN = 1.0f; spline = computeSplineFromTipAndBase(tipPose, basePose, HIPS_GAIN, HEAD_GAIN); - // spline = computeSplineFromTipAndBase(tipPose, basePose); } else { spline = computeSplineFromTipAndBase(tipPose, basePose); } @@ -516,7 +492,6 @@ void AnimSplineIK::computeAndCacheSplineJointInfosForIKTarget(const AnimContext& splineJointInfoVec.push_back(splineJointInfo); index = _skeleton->getParentIndex(index); } - _splineJointInfoMap[target.getIndex()] = splineJointInfoVec; } diff --git a/libraries/animation/src/AnimSplineIK.h b/libraries/animation/src/AnimSplineIK.h index ea7a897362..2109d43456 100644 --- a/libraries/animation/src/AnimSplineIK.h +++ b/libraries/animation/src/AnimSplineIK.h @@ -87,14 +87,14 @@ protected: AnimChain _snapshotChain; - // used to pre-compute information about each joint influeced by a spline IK target. + // used to pre-compute information about each joint influenced by a spline IK target. struct SplineJointInfo { int jointIndex; // joint in the skeleton that this information pertains to. float ratio; // percentage (0..1) along the spline for this joint. AnimPose offsetPose; // local offset from the spline to the joint. }; - bool _lastEnableDebugDrawIKTargets{ false }; + bool _lastEnableDebugDrawIKTargets { false }; void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug, AnimChain& chainInfoOut) const; void computeAndCacheSplineJointInfosForIKTarget(const AnimContext& context, const IKTarget& target) const; const std::vector* findOrCreateSplineJointInfo(const AnimContext& context, const IKTarget& target) const; From dffd41ecb0d1b1d3cac0a7ae081f2cf62d232a43 Mon Sep 17 00:00:00 2001 From: amantley Date: Mon, 28 Jan 2019 16:58:28 -0800 Subject: [PATCH 027/474] chain ik debug draw works for spline now --- libraries/animation/src/AnimSplineIK.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/libraries/animation/src/AnimSplineIK.cpp b/libraries/animation/src/AnimSplineIK.cpp index af1f3e9524..39686dd3a2 100644 --- a/libraries/animation/src/AnimSplineIK.cpp +++ b/libraries/animation/src/AnimSplineIK.cpp @@ -91,7 +91,7 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const AnimPoseVec underPoses = _children[0]->evaluate(animVars, context, dt, triggersOut); // if we don't have a skeleton, or jointName lookup failed or the spline alpha is 0 or there are no underposes. - if (!_skeleton || _baseJointIndex == -1 || _midJointIndex == -1 || _tipJointIndex == -1 || alpha == 0.0f || underPoses.size() == 0) { + if (!_skeleton || _baseJointIndex == -1 || _tipJointIndex == -1 || alpha == 0.0f || underPoses.size() == 0) { // pass underPoses through unmodified. _poses = underPoses; return _poses; @@ -159,7 +159,7 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const absolutePoses.resize(_poses.size()); computeAbsolutePoses(absolutePoses); jointChain.buildFromRelativePoses(_skeleton, _poses, tipTarget.getIndex()); - solveTargetWithSpline(context, tipTarget, absolutePoses, false, jointChain); + solveTargetWithSpline(context, tipTarget, absolutePoses, context.getEnableDebugDrawIKChains(), jointChain); jointChain.buildDirtyAbsolutePoses(); jointChain.outputRelativePoses(_poses); @@ -191,7 +191,7 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const absolutePosesAfterBaseTipSpline.resize(_poses.size()); computeAbsolutePoses(absolutePosesAfterBaseTipSpline); midJointChain.buildFromRelativePoses(_skeleton, _poses, midTarget.getIndex()); - solveTargetWithSpline(context, midTarget, absolutePosesAfterBaseTipSpline, false, midJointChain); + solveTargetWithSpline(context, midTarget, absolutePosesAfterBaseTipSpline, context.getEnableDebugDrawIKChains(), midJointChain); midJointChain.outputRelativePoses(_poses); // set the mid to tip segment to match the absolute rotation of the target. @@ -418,7 +418,8 @@ void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, const IKTar } if (debug) { - //debugDrawIKChain(jointChainInfoOut, context); + const vec4 CYAN(0.0f, 1.0f, 1.0f, 1.0f); + chainInfoOut.debugDraw(context.getRigToWorldMatrix() * context.getGeometryToRigMatrix(), CYAN); } } From 1919cc3b1aa5d8ab40cb1bbeb084034a577926b0 Mon Sep 17 00:00:00 2001 From: amantley Date: Mon, 28 Jan 2019 17:49:46 -0800 Subject: [PATCH 028/474] disable mid joint when not valid. more work on this tomorrow --- .../avatar-animation_withSplineIKNode.json | 2 +- libraries/animation/src/AnimSplineIK.cpp | 85 +++++++++++-------- 2 files changed, 49 insertions(+), 38 deletions(-) diff --git a/interface/resources/avatar/avatar-animation_withSplineIKNode.json b/interface/resources/avatar/avatar-animation_withSplineIKNode.json index a6283f0771..ce98368a87 100644 --- a/interface/resources/avatar/avatar-animation_withSplineIKNode.json +++ b/interface/resources/avatar/avatar-animation_withSplineIKNode.json @@ -182,7 +182,7 @@ "enabled": false, "interpDuration": 15, "baseJointName": "Hips", - "midJointName": "Spine2", + "midJointName": "none", "tipJointName": "Head", "basePositionVar": "hipsPosition", "baseRotationVar": "hipsRotation", diff --git a/libraries/animation/src/AnimSplineIK.cpp b/libraries/animation/src/AnimSplineIK.cpp index 39686dd3a2..cd2a0a19ee 100644 --- a/libraries/animation/src/AnimSplineIK.cpp +++ b/libraries/animation/src/AnimSplineIK.cpp @@ -163,40 +163,48 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const jointChain.buildDirtyAbsolutePoses(); jointChain.outputRelativePoses(_poses); - // initialize the middle joint target IKTarget midTarget; - midTarget.setType((int)IKTarget::Type::Spline); - midTarget.setIndex(_midJointIndex); - // set the middle joint to the position that was just determined by the base-tip spline. - AnimPose afterSolveMidTarget = _skeleton->getAbsolutePose(_midJointIndex, _poses); - // use the rotation from the ik target value, if there is one. - glm::quat midTargetRotation = animVars.lookupRigToGeometry(_midRotationVar, afterSolveMidTarget.rot()); - AnimPose updatedMidTarget = AnimPose(midTargetRotation, afterSolveMidTarget.trans()); - midTarget.setPose(updatedMidTarget.rot(), updatedMidTarget.trans()); - midTarget.setWeight(1.0f); - midTarget.setFlexCoefficients(_numMidTargetFlexCoefficients, _midTargetFlexCoefficients); + if (_midJointIndex != -1) { - AnimChain midJointChain; - // Find the pose of the middle target's child. This is to correct the mid to tip - // after the base to mid spline is set. - int childOfMiddleJointIndex = -1; - AnimPose childOfMiddleJointAbsolutePoseAfterBaseTipSpline; - childOfMiddleJointIndex = _tipJointIndex; - while (_skeleton->getParentIndex(childOfMiddleJointIndex) != _midJointIndex) { - childOfMiddleJointIndex = _skeleton->getParentIndex(childOfMiddleJointIndex); + // initialize the middle joint target + midTarget.setType((int)IKTarget::Type::Spline); + midTarget.setIndex(_midJointIndex); + // set the middle joint to the position that was just determined by the base-tip spline. + AnimPose afterSolveMidTarget = _skeleton->getAbsolutePose(_midJointIndex, _poses); + // use the rotation from the ik target value, if there is one. + glm::quat midTargetRotation = animVars.lookupRigToGeometry(_midRotationVar, afterSolveMidTarget.rot()); + AnimPose updatedMidTarget = AnimPose(midTargetRotation, afterSolveMidTarget.trans()); + midTarget.setPose(updatedMidTarget.rot(), updatedMidTarget.trans()); + midTarget.setWeight(1.0f); + midTarget.setFlexCoefficients(_numMidTargetFlexCoefficients, _midTargetFlexCoefficients); + + AnimChain midJointChain; + // Find the pose of the middle target's child. This is to correct the mid to tip + // after the base to mid spline is set. + int childOfMiddleJointIndex = -1; + AnimPose childOfMiddleJointAbsolutePoseAfterBaseTipSpline; + childOfMiddleJointIndex = _tipJointIndex; + while ((_skeleton->getParentIndex(childOfMiddleJointIndex) != _midJointIndex) && (childOfMiddleJointIndex != -1)) { + childOfMiddleJointIndex = _skeleton->getParentIndex(childOfMiddleJointIndex); + } + // if the middle joint is not actually between the base and the tip then don't create spline for it + if (childOfMiddleJointIndex != -1) { + childOfMiddleJointAbsolutePoseAfterBaseTipSpline = _skeleton->getAbsolutePose(childOfMiddleJointIndex, _poses); + + AnimPoseVec absolutePosesAfterBaseTipSpline; + absolutePosesAfterBaseTipSpline.resize(_poses.size()); + computeAbsolutePoses(absolutePosesAfterBaseTipSpline); + midJointChain.buildFromRelativePoses(_skeleton, _poses, midTarget.getIndex()); + solveTargetWithSpline(context, midTarget, absolutePosesAfterBaseTipSpline, context.getEnableDebugDrawIKChains(), midJointChain); + midJointChain.outputRelativePoses(_poses); + + // set the mid to tip segment to match the absolute rotation of the tip target. + AnimPose midTargetPose(midTarget.getRotation(), midTarget.getTranslation()); + _poses[childOfMiddleJointIndex] = midTargetPose.inverse() * childOfMiddleJointAbsolutePoseAfterBaseTipSpline; + } } - childOfMiddleJointAbsolutePoseAfterBaseTipSpline = _skeleton->getAbsolutePose(childOfMiddleJointIndex, _poses); - AnimPoseVec absolutePosesAfterBaseTipSpline; - absolutePosesAfterBaseTipSpline.resize(_poses.size()); - computeAbsolutePoses(absolutePosesAfterBaseTipSpline); - midJointChain.buildFromRelativePoses(_skeleton, _poses, midTarget.getIndex()); - solveTargetWithSpline(context, midTarget, absolutePosesAfterBaseTipSpline, context.getEnableDebugDrawIKChains(), midJointChain); - midJointChain.outputRelativePoses(_poses); - - // set the mid to tip segment to match the absolute rotation of the target. - AnimPose midTargetPose(midTarget.getRotation(), midTarget.getTranslation()); - _poses[childOfMiddleJointIndex] = midTargetPose.inverse() * childOfMiddleJointAbsolutePoseAfterBaseTipSpline; + // compute chain AnimChain ikChain; @@ -249,11 +257,12 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const QString name = QString("ikTargetSplineTip"); DebugDraw::getInstance().addMyAvatarMarker(name, glmExtractRotation(avatarTargetMat), extractTranslation(avatarTargetMat), WHITE); - glm::mat4 geomTargetMat2 = createMatFromQuatAndPos(midTarget.getRotation(), midTarget.getTranslation()); - glm::mat4 avatarTargetMat2 = rigToAvatarMat * context.getGeometryToRigMatrix() * geomTargetMat2; - QString name2 = QString("ikTargetSplineMid"); - DebugDraw::getInstance().addMyAvatarMarker(name2, glmExtractRotation(avatarTargetMat2), extractTranslation(avatarTargetMat2), WHITE); - + if (_midJointIndex != -1) { + glm::mat4 geomTargetMat2 = createMatFromQuatAndPos(midTarget.getRotation(), midTarget.getTranslation()); + glm::mat4 avatarTargetMat2 = rigToAvatarMat * context.getGeometryToRigMatrix() * geomTargetMat2; + QString name2 = QString("ikTargetSplineMid"); + DebugDraw::getInstance().addMyAvatarMarker(name2, glmExtractRotation(avatarTargetMat2), extractTranslation(avatarTargetMat2), WHITE); + } glm::mat4 geomTargetMat3 = createMatFromQuatAndPos(baseTargetAbsolutePose.rot(), baseTargetAbsolutePose.trans()); glm::mat4 avatarTargetMat3 = rigToAvatarMat * context.getGeometryToRigMatrix() * geomTargetMat3; @@ -266,8 +275,10 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const QString name = QString("ikTargetSplineTip"); DebugDraw::getInstance().removeMyAvatarMarker(name); - QString name2 = QString("ikTargetSplineMid"); - DebugDraw::getInstance().removeMyAvatarMarker(name2); + if (_midJointIndex != -1) { + QString name2 = QString("ikTargetSplineMid"); + DebugDraw::getInstance().removeMyAvatarMarker(name2); + } QString name3 = QString("ikTargetSplineBase"); DebugDraw::getInstance().removeMyAvatarMarker(name3); From 39943115832c8838cb49cbce096aa4ec5e131417 Mon Sep 17 00:00:00 2001 From: Angus Antley Date: Tue, 29 Jan 2019 08:54:35 -0800 Subject: [PATCH 029/474] starting the move of the initial head base spline to myskeletonmodel --- .../avatar-animation_withSplineIKNode.json | 2 +- libraries/animation/src/AnimSplineIK.cpp | 27 +++++++++++-------- libraries/animation/src/AnimSplineIK.h | 3 +-- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/interface/resources/avatar/avatar-animation_withSplineIKNode.json b/interface/resources/avatar/avatar-animation_withSplineIKNode.json index ce98368a87..a6283f0771 100644 --- a/interface/resources/avatar/avatar-animation_withSplineIKNode.json +++ b/interface/resources/avatar/avatar-animation_withSplineIKNode.json @@ -182,7 +182,7 @@ "enabled": false, "interpDuration": 15, "baseJointName": "Hips", - "midJointName": "none", + "midJointName": "Spine2", "tipJointName": "Head", "basePositionVar": "hipsPosition", "baseRotationVar": "hipsRotation", diff --git a/libraries/animation/src/AnimSplineIK.cpp b/libraries/animation/src/AnimSplineIK.cpp index cd2a0a19ee..220546f31e 100644 --- a/libraries/animation/src/AnimSplineIK.cpp +++ b/libraries/animation/src/AnimSplineIK.cpp @@ -71,12 +71,6 @@ AnimSplineIK::~AnimSplineIK() { } -//virtual -const AnimPoseVec& AnimSplineIK::overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut, const AnimPoseVec& underPoses) { - loadPoses(underPoses); - return _poses; -} - const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { assert(_children.size() == 1); if (_children.size() != 1) { @@ -159,7 +153,7 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const absolutePoses.resize(_poses.size()); computeAbsolutePoses(absolutePoses); jointChain.buildFromRelativePoses(_skeleton, _poses, tipTarget.getIndex()); - solveTargetWithSpline(context, tipTarget, absolutePoses, context.getEnableDebugDrawIKChains(), jointChain); + solveTargetWithSpline(context, _baseJointIndex, tipTarget, absolutePoses, context.getEnableDebugDrawIKChains(), jointChain); jointChain.buildDirtyAbsolutePoses(); jointChain.outputRelativePoses(_poses); @@ -195,15 +189,27 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const absolutePosesAfterBaseTipSpline.resize(_poses.size()); computeAbsolutePoses(absolutePosesAfterBaseTipSpline); midJointChain.buildFromRelativePoses(_skeleton, _poses, midTarget.getIndex()); - solveTargetWithSpline(context, midTarget, absolutePosesAfterBaseTipSpline, context.getEnableDebugDrawIKChains(), midJointChain); + solveTargetWithSpline(context, _baseJointIndex, midTarget, absolutePosesAfterBaseTipSpline, context.getEnableDebugDrawIKChains(), midJointChain); midJointChain.outputRelativePoses(_poses); // set the mid to tip segment to match the absolute rotation of the tip target. AnimPose midTargetPose(midTarget.getRotation(), midTarget.getTranslation()); _poses[childOfMiddleJointIndex] = midTargetPose.inverse() * childOfMiddleJointAbsolutePoseAfterBaseTipSpline; } + + + AnimChain upperJointChain; + AnimPoseVec finalAbsolutePoses; + finalAbsolutePoses.resize(_poses.size()); + computeAbsolutePoses(finalAbsolutePoses); + upperJointChain.buildFromRelativePoses(_skeleton, _poses, tipTarget.getIndex()); + solveTargetWithSpline(context, _midJointIndex, tipTarget, finalAbsolutePoses, context.getEnableDebugDrawIKChains(), upperJointChain); + upperJointChain.buildDirtyAbsolutePoses(); + upperJointChain.outputRelativePoses(_poses); + } + // compute chain @@ -338,10 +344,9 @@ static CubicHermiteSplineFunctorWithArcLength computeSplineFromTipAndBase(const return CubicHermiteSplineFunctorWithArcLength(p0, m0, p1, m1); } -void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug, AnimChain& chainInfoOut) const { +void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, int base, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug, AnimChain& chainInfoOut) const { - const int baseIndex = _baseJointIndex; - const int tipBaseIndex = _midJointIndex; + const int baseIndex = base; // build spline from tip to base AnimPose tipPose = AnimPose(glm::vec3(1.0f), target.getRotation(), target.getTranslation()); diff --git a/libraries/animation/src/AnimSplineIK.h b/libraries/animation/src/AnimSplineIK.h index 2109d43456..5d45d9528f 100644 --- a/libraries/animation/src/AnimSplineIK.h +++ b/libraries/animation/src/AnimSplineIK.h @@ -29,7 +29,6 @@ public: virtual ~AnimSplineIK() override; virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override; - virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut, const AnimPoseVec& underPoses) override; protected: @@ -95,7 +94,7 @@ protected: }; bool _lastEnableDebugDrawIKTargets { false }; - void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug, AnimChain& chainInfoOut) const; + void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, int base, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug, AnimChain& chainInfoOut) const; void computeAndCacheSplineJointInfosForIKTarget(const AnimContext& context, const IKTarget& target) const; const std::vector* findOrCreateSplineJointInfo(const AnimContext& context, const IKTarget& target) const; mutable std::map> _splineJointInfoMap; From e2a729b68b2edbd32d1e4e17cfab8e09101508c7 Mon Sep 17 00:00:00 2001 From: amantley Date: Tue, 29 Jan 2019 17:25:25 -0800 Subject: [PATCH 030/474] got the spline working in myskeleton model, need to clean up --- interface/src/avatar/MySkeletonModel.cpp | 44 ++++++++++++++++++++++++ interface/src/avatar/MySkeletonModel.h | 1 + libraries/animation/src/AnimSplineIK.cpp | 36 ++++++++++++------- libraries/animation/src/AnimSplineIK.h | 4 +-- 4 files changed, 70 insertions(+), 15 deletions(-) diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 26d69841d0..352e7ce6b0 100755 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -33,6 +33,39 @@ Rig::CharacterControllerState convertCharacterControllerState(CharacterControlle }; } +static CubicHermiteSplineFunctorWithArcLength computeSplineFromTipAndBase(const AnimPose& tipPose, const AnimPose& basePose, float baseGain = 1.0f, float tipGain = 1.0f) { + float linearDistance = glm::length(basePose.trans() - tipPose.trans()); + glm::vec3 p0 = basePose.trans(); + glm::vec3 m0 = baseGain * linearDistance * (basePose.rot() * Vectors::UNIT_Y); + glm::vec3 p1 = tipPose.trans(); + glm::vec3 m1 = tipGain * linearDistance * (tipPose.rot() * Vectors::UNIT_Y); + + return CubicHermiteSplineFunctorWithArcLength(p0, m0, p1, m1); +} + +static glm::vec3 computeSpine2WithHeadHipsSpline(MyAvatar* myAvatar, AnimPose hipsIKTargetPose, AnimPose headIKTargetPose) { + glm::vec3 basePosition = myAvatar->getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar->getJointIndex("Hips")); + glm::vec3 tipPosition = myAvatar->getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar->getJointIndex("Head")); + glm::vec3 spine2Position = myAvatar->getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar->getJointIndex("Spine2")); + glm::vec3 baseToTip = tipPosition - basePosition; + float baseToTipLength = glm::length(baseToTip); + glm::vec3 baseToTipNormal = baseToTip / baseToTipLength; + glm::vec3 baseToSpine2 = spine2Position - basePosition; + float ratio = glm::dot(baseToSpine2, baseToTipNormal) / baseToTipLength; + + // the the ik targets to compute the spline with + CubicHermiteSplineFunctorWithArcLength spline = computeSplineFromTipAndBase(headIKTargetPose, hipsIKTargetPose); + + // measure the total arc length along the spline + float totalArcLength = spline.arcLength(1.0f); + float t = spline.arcLengthInverse(ratio * totalArcLength); + + glm::vec3 spine2Translation = spline(t); + + return spine2Translation; + +} + static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) { glm::mat4 worldToSensorMat = glm::inverse(myAvatar->getSensorToWorldMatrix()); @@ -233,6 +266,12 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { myAvatar->getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND).isValid() && !(params.primaryControllerFlags[Rig::PrimaryControllerType_Spine2] & (uint8_t)Rig::ControllerFlags::Enabled)) { + controller::Pose headSplineControllerPose = myAvatar->getControllerPoseInSensorFrame(controller::Action::HEAD); + AnimPose headSplinePose(headSplineControllerPose.getRotation(), headSplineControllerPose.getTranslation()); + glm::vec3 spine2TargetTranslation = computeSpine2WithHeadHipsSpline(myAvatar, sensorHips, headSplinePose); + AnimPose sensorSpine2(Quaternions::IDENTITY, spine2TargetTranslation); + AnimPose rigSpine2 = sensorToRigPose * sensorSpine2; + const float SPINE2_ROTATION_FILTER = 0.5f; AnimPose currentSpine2Pose; AnimPose currentHeadPose; @@ -253,6 +292,11 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { } generateBasisVectors(up, fwd, u, v, w); AnimPose newSpinePose(glm::mat4(glm::vec4(w, 0.0f), glm::vec4(u, 0.0f), glm::vec4(v, 0.0f), glm::vec4(glm::vec3(0.0f, 0.0f, 0.0f), 1.0f))); + currentSpine2Pose.trans() = rigSpine2.trans(); + qCDebug(animation) << "my skeleton model spline spine2 " << rigSpine2.trans(); + qCDebug(animation) << "my skeleton model current spine2 " << currentSpine2Pose.trans(); + // qCDebug(animation) << "my skeleton model spline hips " << sensorToRigPose * sensorHips; + // qCDebug(animation) << "my skeleton model current hips " << currentHipsPose.trans(); currentSpine2Pose.rot() = safeLerp(currentSpine2Pose.rot(), newSpinePose.rot(), SPINE2_ROTATION_FILTER); params.primaryControllerPoses[Rig::PrimaryControllerType_Spine2] = currentSpine2Pose; params.primaryControllerFlags[Rig::PrimaryControllerType_Spine2] = (uint8_t)Rig::ControllerFlags::Enabled | (uint8_t)Rig::ControllerFlags::Estimated; diff --git a/interface/src/avatar/MySkeletonModel.h b/interface/src/avatar/MySkeletonModel.h index 9a3559ddf7..7ea142b011 100644 --- a/interface/src/avatar/MySkeletonModel.h +++ b/interface/src/avatar/MySkeletonModel.h @@ -12,6 +12,7 @@ #include #include #include "MyAvatar.h" +#include /// A skeleton loaded from a model. class MySkeletonModel : public SkeletonModel { diff --git a/libraries/animation/src/AnimSplineIK.cpp b/libraries/animation/src/AnimSplineIK.cpp index 220546f31e..3aca678fa3 100644 --- a/libraries/animation/src/AnimSplineIK.cpp +++ b/libraries/animation/src/AnimSplineIK.cpp @@ -147,7 +147,7 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const tipTarget.setWeight(1.0f); tipTarget.setFlexCoefficients(_numTipTargetFlexCoefficients, _tipTargetFlexCoefficients); - + /* AnimChain jointChain; AnimPoseVec absolutePoses; absolutePoses.resize(_poses.size()); @@ -156,6 +156,7 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const solveTargetWithSpline(context, _baseJointIndex, tipTarget, absolutePoses, context.getEnableDebugDrawIKChains(), jointChain); jointChain.buildDirtyAbsolutePoses(); jointChain.outputRelativePoses(_poses); + */ IKTarget midTarget; if (_midJointIndex != -1) { @@ -167,7 +168,9 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const AnimPose afterSolveMidTarget = _skeleton->getAbsolutePose(_midJointIndex, _poses); // use the rotation from the ik target value, if there is one. glm::quat midTargetRotation = animVars.lookupRigToGeometry(_midRotationVar, afterSolveMidTarget.rot()); - AnimPose updatedMidTarget = AnimPose(midTargetRotation, afterSolveMidTarget.trans()); + glm::vec3 midTargetPosition = animVars.lookupRigToGeometry(_midPositionVar, afterSolveMidTarget.trans()); + AnimPose updatedMidTarget = AnimPose(midTargetRotation, midTargetPosition); + //qCDebug(animation) << "spine2 in AnimSpline " << updatedMidTarget.trans(); midTarget.setPose(updatedMidTarget.rot(), updatedMidTarget.trans()); midTarget.setWeight(1.0f); midTarget.setFlexCoefficients(_numMidTargetFlexCoefficients, _midTargetFlexCoefficients); @@ -194,9 +197,10 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const // set the mid to tip segment to match the absolute rotation of the tip target. AnimPose midTargetPose(midTarget.getRotation(), midTarget.getTranslation()); - _poses[childOfMiddleJointIndex] = midTargetPose.inverse() * childOfMiddleJointAbsolutePoseAfterBaseTipSpline; + //_poses[childOfMiddleJointIndex] = midTargetPose.inverse() * childOfMiddleJointAbsolutePoseAfterBaseTipSpline; } + _splineJointInfoMap.clear(); AnimChain upperJointChain; AnimPoseVec finalAbsolutePoses; @@ -206,7 +210,7 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const solveTargetWithSpline(context, _midJointIndex, tipTarget, finalAbsolutePoses, context.getEnableDebugDrawIKChains(), upperJointChain); upperJointChain.buildDirtyAbsolutePoses(); upperJointChain.outputRelativePoses(_poses); - + } @@ -370,8 +374,10 @@ void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, int base, c tipPose.rot() = -tipPose.rot(); } // find or create splineJointInfo for this target - const std::vector* splineJointInfoVec = findOrCreateSplineJointInfo(context, target); + const std::vector* splineJointInfoVec = findOrCreateSplineJointInfo(context, base, target); + //qCDebug(animation) << "the size of the splineJointInfo is " << splineJointInfoVec->size() << " and the base index is "<size() > 0) { const int baseParentIndex = _skeleton->getParentIndex(baseIndex); AnimPose parentAbsPose = (baseParentIndex >= 0) ? absolutePoses[baseParentIndex] : AnimPose(); @@ -403,9 +409,9 @@ void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, int base, c ::blend(1, &absolutePoses[splineJointInfo.jointIndex], &desiredAbsPose, target.getFlexCoefficient(i), &flexedAbsPose); AnimPose relPose = parentAbsPose.inverse() * flexedAbsPose; - + /* bool constrained = false; - if (splineJointInfo.jointIndex != _baseJointIndex) { + if (splineJointInfo.jointIndex != base) { // constrain the amount the spine can stretch or compress float length = glm::length(relPose.trans()); const float EPSILON = 0.0001f; @@ -425,6 +431,7 @@ void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, int base, c relPose.trans() = glm::vec3(0.0f); } } + */ if (!chainInfoOut.setRelativePoseAtJointIndex(splineJointInfo.jointIndex, relPose)) { qCDebug(animation) << "we didn't find the joint index for the spline!!!!"; } @@ -439,13 +446,13 @@ void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, int base, c } } -const std::vector* AnimSplineIK::findOrCreateSplineJointInfo(const AnimContext& context, const IKTarget& target) const { +const std::vector* AnimSplineIK::findOrCreateSplineJointInfo(const AnimContext& context, int base, const IKTarget& target) const { // find or create splineJointInfo for this target auto iter = _splineJointInfoMap.find(target.getIndex()); if (iter != _splineJointInfoMap.end()) { return &(iter->second); } else { - computeAndCacheSplineJointInfosForIKTarget(context, target); + computeAndCacheSplineJointInfosForIKTarget(context, base, target); auto iter = _splineJointInfoMap.find(target.getIndex()); if (iter != _splineJointInfoMap.end()) { return &(iter->second); @@ -455,12 +462,14 @@ const std::vector* AnimSplineIK::findOrCreateSpli } // pre-compute information about each joint influenced by this spline IK target. -void AnimSplineIK::computeAndCacheSplineJointInfosForIKTarget(const AnimContext& context, const IKTarget& target) const { +void AnimSplineIK::computeAndCacheSplineJointInfosForIKTarget(const AnimContext& context, int base, const IKTarget& target) const { std::vector splineJointInfoVec; + //qCDebug(animation) << "the base in compute cache is " << base; + // build spline between the default poses. AnimPose tipPose = _skeleton->getAbsoluteDefaultPose(target.getIndex()); - AnimPose basePose = _skeleton->getAbsoluteDefaultPose(_baseJointIndex); + AnimPose basePose = _skeleton->getAbsoluteDefaultPose(base); CubicHermiteSplineFunctorWithArcLength spline; if (target.getIndex() == _tipJointIndex) { @@ -481,10 +490,11 @@ void AnimSplineIK::computeAndCacheSplineJointInfosForIKTarget(const AnimContext& int index = target.getIndex(); int endIndex; + // fix this statement. AA if (target.getIndex() == _tipJointIndex) { - endIndex = _skeleton->getParentIndex(_baseJointIndex); + endIndex = _skeleton->getParentIndex(base); } else { - endIndex = _skeleton->getParentIndex(_baseJointIndex); + endIndex = _skeleton->getParentIndex(base); } while (index != endIndex) { AnimPose defaultPose = _skeleton->getAbsoluteDefaultPose(index); diff --git a/libraries/animation/src/AnimSplineIK.h b/libraries/animation/src/AnimSplineIK.h index 5d45d9528f..d664cf37a4 100644 --- a/libraries/animation/src/AnimSplineIK.h +++ b/libraries/animation/src/AnimSplineIK.h @@ -95,8 +95,8 @@ protected: bool _lastEnableDebugDrawIKTargets { false }; void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, int base, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug, AnimChain& chainInfoOut) const; - void computeAndCacheSplineJointInfosForIKTarget(const AnimContext& context, const IKTarget& target) const; - const std::vector* findOrCreateSplineJointInfo(const AnimContext& context, const IKTarget& target) const; + void computeAndCacheSplineJointInfosForIKTarget(const AnimContext& context, int base, const IKTarget& target) const; + const std::vector* findOrCreateSplineJointInfo(const AnimContext& context, int base, const IKTarget& target) const; mutable std::map> _splineJointInfoMap; // no copies From fb0ad7768c9cfc5c82a68c1f16f243afede8ff15 Mon Sep 17 00:00:00 2001 From: amantley Date: Tue, 29 Jan 2019 17:31:20 -0800 Subject: [PATCH 031/474] removed clear map --- interface/src/avatar/MySkeletonModel.cpp | 4 ---- libraries/animation/src/AnimSplineIK.cpp | 2 -- 2 files changed, 6 deletions(-) diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 352e7ce6b0..38f18d2ac9 100755 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -293,10 +293,6 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { generateBasisVectors(up, fwd, u, v, w); AnimPose newSpinePose(glm::mat4(glm::vec4(w, 0.0f), glm::vec4(u, 0.0f), glm::vec4(v, 0.0f), glm::vec4(glm::vec3(0.0f, 0.0f, 0.0f), 1.0f))); currentSpine2Pose.trans() = rigSpine2.trans(); - qCDebug(animation) << "my skeleton model spline spine2 " << rigSpine2.trans(); - qCDebug(animation) << "my skeleton model current spine2 " << currentSpine2Pose.trans(); - // qCDebug(animation) << "my skeleton model spline hips " << sensorToRigPose * sensorHips; - // qCDebug(animation) << "my skeleton model current hips " << currentHipsPose.trans(); currentSpine2Pose.rot() = safeLerp(currentSpine2Pose.rot(), newSpinePose.rot(), SPINE2_ROTATION_FILTER); params.primaryControllerPoses[Rig::PrimaryControllerType_Spine2] = currentSpine2Pose; params.primaryControllerFlags[Rig::PrimaryControllerType_Spine2] = (uint8_t)Rig::ControllerFlags::Enabled | (uint8_t)Rig::ControllerFlags::Estimated; diff --git a/libraries/animation/src/AnimSplineIK.cpp b/libraries/animation/src/AnimSplineIK.cpp index 3aca678fa3..0cd646097c 100644 --- a/libraries/animation/src/AnimSplineIK.cpp +++ b/libraries/animation/src/AnimSplineIK.cpp @@ -199,8 +199,6 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const AnimPose midTargetPose(midTarget.getRotation(), midTarget.getTranslation()); //_poses[childOfMiddleJointIndex] = midTargetPose.inverse() * childOfMiddleJointAbsolutePoseAfterBaseTipSpline; } - - _splineJointInfoMap.clear(); AnimChain upperJointChain; AnimPoseVec finalAbsolutePoses; From e36877a86139cd6df4f2420f55931d2d6ff64df8 Mon Sep 17 00:00:00 2001 From: Angus Antley Date: Tue, 29 Jan 2019 22:02:32 -0800 Subject: [PATCH 032/474] added the interp for the possibility of extra flex coeffs --- libraries/animation/src/AnimSplineIK.cpp | 172 +++++++++-------------- libraries/animation/src/AnimSplineIK.h | 8 +- libraries/animation/src/IKTarget.h | 3 +- 3 files changed, 68 insertions(+), 115 deletions(-) diff --git a/libraries/animation/src/AnimSplineIK.cpp b/libraries/animation/src/AnimSplineIK.cpp index 0cd646097c..b9dd1b4f07 100644 --- a/libraries/animation/src/AnimSplineIK.cpp +++ b/libraries/animation/src/AnimSplineIK.cpp @@ -72,6 +72,7 @@ AnimSplineIK::~AnimSplineIK() { } const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { + assert(_children.size() == 1); if (_children.size() != 1) { return _poses; @@ -85,7 +86,7 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const AnimPoseVec underPoses = _children[0]->evaluate(animVars, context, dt, triggersOut); // if we don't have a skeleton, or jointName lookup failed or the spline alpha is 0 or there are no underposes. - if (!_skeleton || _baseJointIndex == -1 || _tipJointIndex == -1 || alpha == 0.0f || underPoses.size() == 0) { + if (!_skeleton || _baseJointIndex == -1 || _midJointIndex == -1 || _tipJointIndex == -1 || alpha == 0.0f || underPoses.size() == 0) { // pass underPoses through unmodified. _poses = underPoses; return _poses; @@ -136,6 +137,28 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const _poses[_baseJointIndex] = baseParentAbsPose.inverse() * baseTargetAbsolutePose; _poses[_baseJointIndex].scale() = glm::vec3(1.0f); + + + // initialize the middle joint target + IKTarget midTarget; + midTarget.setType((int)IKTarget::Type::Spline); + midTarget.setIndex(_midJointIndex); + AnimPose absPoseMid = _skeleton->getAbsolutePose(_midJointIndex, _poses); + glm::quat midTargetRotation = animVars.lookupRigToGeometry(_midRotationVar, absPoseMid.rot()); + glm::vec3 midTargetPosition = animVars.lookupRigToGeometry(_midPositionVar, absPoseMid.trans()); + midTarget.setPose(midTargetRotation, midTargetPosition); + midTarget.setWeight(1.0f); + midTarget.setFlexCoefficients(_numMidTargetFlexCoefficients, _midTargetFlexCoefficients); + + // solve the lower spine spline + AnimChain midJointChain; + AnimPoseVec absolutePosesAfterBaseTipSpline; + absolutePosesAfterBaseTipSpline.resize(_poses.size()); + computeAbsolutePoses(absolutePosesAfterBaseTipSpline); + midJointChain.buildFromRelativePoses(_skeleton, _poses, midTarget.getIndex()); + solveTargetWithSpline(context, _baseJointIndex, midTarget, absolutePosesAfterBaseTipSpline, context.getEnableDebugDrawIKChains(), midJointChain); + midJointChain.outputRelativePoses(_poses); + // initialize the tip target IKTarget tipTarget; tipTarget.setType((int)IKTarget::Type::Spline); @@ -147,72 +170,15 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const tipTarget.setWeight(1.0f); tipTarget.setFlexCoefficients(_numTipTargetFlexCoefficients, _tipTargetFlexCoefficients); - /* - AnimChain jointChain; - AnimPoseVec absolutePoses; - absolutePoses.resize(_poses.size()); - computeAbsolutePoses(absolutePoses); - jointChain.buildFromRelativePoses(_skeleton, _poses, tipTarget.getIndex()); - solveTargetWithSpline(context, _baseJointIndex, tipTarget, absolutePoses, context.getEnableDebugDrawIKChains(), jointChain); - jointChain.buildDirtyAbsolutePoses(); - jointChain.outputRelativePoses(_poses); - */ - - IKTarget midTarget; - if (_midJointIndex != -1) { - - // initialize the middle joint target - midTarget.setType((int)IKTarget::Type::Spline); - midTarget.setIndex(_midJointIndex); - // set the middle joint to the position that was just determined by the base-tip spline. - AnimPose afterSolveMidTarget = _skeleton->getAbsolutePose(_midJointIndex, _poses); - // use the rotation from the ik target value, if there is one. - glm::quat midTargetRotation = animVars.lookupRigToGeometry(_midRotationVar, afterSolveMidTarget.rot()); - glm::vec3 midTargetPosition = animVars.lookupRigToGeometry(_midPositionVar, afterSolveMidTarget.trans()); - AnimPose updatedMidTarget = AnimPose(midTargetRotation, midTargetPosition); - //qCDebug(animation) << "spine2 in AnimSpline " << updatedMidTarget.trans(); - midTarget.setPose(updatedMidTarget.rot(), updatedMidTarget.trans()); - midTarget.setWeight(1.0f); - midTarget.setFlexCoefficients(_numMidTargetFlexCoefficients, _midTargetFlexCoefficients); - - AnimChain midJointChain; - // Find the pose of the middle target's child. This is to correct the mid to tip - // after the base to mid spline is set. - int childOfMiddleJointIndex = -1; - AnimPose childOfMiddleJointAbsolutePoseAfterBaseTipSpline; - childOfMiddleJointIndex = _tipJointIndex; - while ((_skeleton->getParentIndex(childOfMiddleJointIndex) != _midJointIndex) && (childOfMiddleJointIndex != -1)) { - childOfMiddleJointIndex = _skeleton->getParentIndex(childOfMiddleJointIndex); - } - // if the middle joint is not actually between the base and the tip then don't create spline for it - if (childOfMiddleJointIndex != -1) { - childOfMiddleJointAbsolutePoseAfterBaseTipSpline = _skeleton->getAbsolutePose(childOfMiddleJointIndex, _poses); - - AnimPoseVec absolutePosesAfterBaseTipSpline; - absolutePosesAfterBaseTipSpline.resize(_poses.size()); - computeAbsolutePoses(absolutePosesAfterBaseTipSpline); - midJointChain.buildFromRelativePoses(_skeleton, _poses, midTarget.getIndex()); - solveTargetWithSpline(context, _baseJointIndex, midTarget, absolutePosesAfterBaseTipSpline, context.getEnableDebugDrawIKChains(), midJointChain); - midJointChain.outputRelativePoses(_poses); - - // set the mid to tip segment to match the absolute rotation of the tip target. - AnimPose midTargetPose(midTarget.getRotation(), midTarget.getTranslation()); - //_poses[childOfMiddleJointIndex] = midTargetPose.inverse() * childOfMiddleJointAbsolutePoseAfterBaseTipSpline; - } - - AnimChain upperJointChain; - AnimPoseVec finalAbsolutePoses; - finalAbsolutePoses.resize(_poses.size()); - computeAbsolutePoses(finalAbsolutePoses); - upperJointChain.buildFromRelativePoses(_skeleton, _poses, tipTarget.getIndex()); - solveTargetWithSpline(context, _midJointIndex, tipTarget, finalAbsolutePoses, context.getEnableDebugDrawIKChains(), upperJointChain); - upperJointChain.buildDirtyAbsolutePoses(); - upperJointChain.outputRelativePoses(_poses); - - } - - - + // solve the upper spine spline + AnimChain upperJointChain; + AnimPoseVec finalAbsolutePoses; + finalAbsolutePoses.resize(_poses.size()); + computeAbsolutePoses(finalAbsolutePoses); + upperJointChain.buildFromRelativePoses(_skeleton, _poses, tipTarget.getIndex()); + solveTargetWithSpline(context, _midJointIndex, tipTarget, finalAbsolutePoses, context.getEnableDebugDrawIKChains(), upperJointChain); + upperJointChain.buildDirtyAbsolutePoses(); + upperJointChain.outputRelativePoses(_poses); // compute chain AnimChain ikChain; @@ -279,17 +245,14 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const } else if (context.getEnableDebugDrawIKTargets() != _previousEnableDebugIKTargets) { + // remove markers if they were added last frame. - QString name = QString("ikTargetSplineTip"); DebugDraw::getInstance().removeMyAvatarMarker(name); - if (_midJointIndex != -1) { - QString name2 = QString("ikTargetSplineMid"); - DebugDraw::getInstance().removeMyAvatarMarker(name2); - } + QString name2 = QString("ikTargetSplineMid"); + DebugDraw::getInstance().removeMyAvatarMarker(name2); QString name3 = QString("ikTargetSplineBase"); DebugDraw::getInstance().removeMyAvatarMarker(name3); - } _previousEnableDebugIKTargets = context.getEnableDebugDrawIKTargets(); @@ -306,10 +269,6 @@ void AnimSplineIK::lookUpIndices() { _baseJointIndex = indices[0]; _tipJointIndex = indices[1]; _midJointIndex = indices[2]; - - if (_baseJointIndex != -1) { - _baseParentJointIndex = _skeleton->getParentIndex(_baseJointIndex); - } } void AnimSplineIK::computeAbsolutePoses(AnimPoseVec& absolutePoses) const { @@ -348,11 +307,9 @@ static CubicHermiteSplineFunctorWithArcLength computeSplineFromTipAndBase(const void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, int base, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug, AnimChain& chainInfoOut) const { - const int baseIndex = base; - // build spline from tip to base AnimPose tipPose = AnimPose(glm::vec3(1.0f), target.getRotation(), target.getTranslation()); - AnimPose basePose = absolutePoses[baseIndex]; + AnimPose basePose = absolutePoses[base]; CubicHermiteSplineFunctorWithArcLength spline; if (target.getIndex() == _tipJointIndex) { @@ -371,13 +328,12 @@ void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, int base, c if (glm::dot(halfRot * Vectors::UNIT_Z, basePose.rot() * Vectors::UNIT_Z) < 0.0f) { tipPose.rot() = -tipPose.rot(); } + // find or create splineJointInfo for this target const std::vector* splineJointInfoVec = findOrCreateSplineJointInfo(context, base, target); - //qCDebug(animation) << "the size of the splineJointInfo is " << splineJointInfoVec->size() << " and the base index is "<size() > 0) { - const int baseParentIndex = _skeleton->getParentIndex(baseIndex); + const int baseParentIndex = _skeleton->getParentIndex(base); AnimPose parentAbsPose = (baseParentIndex >= 0) ? absolutePoses[baseParentIndex] : AnimPose(); // go thru splineJointInfoVec backwards (base to tip) for (int i = (int)splineJointInfoVec->size() - 1; i >= 0; i--) { @@ -404,10 +360,27 @@ void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, int base, c // apply flex coefficent AnimPose flexedAbsPose; - ::blend(1, &absolutePoses[splineJointInfo.jointIndex], &desiredAbsPose, target.getFlexCoefficient(i), &flexedAbsPose); - + // get the number of flex coeff for this spline + float interpedCoefficient = 1.0f; + int numFlexCoeff = target.getNumFlexCoefficients(); + if (numFlexCoeff == splineJointInfoVec->size()) { + // then do nothing special + interpedCoefficient = target.getFlexCoefficient(i); + } else { + // interp based on ratio of the joint. + if (splineJointInfo.ratio < 1.0f) { + float flexInterp = splineJointInfo.ratio * (float)(numFlexCoeff - 1); + int startCoeff = floor(flexInterp); + float partial = flexInterp - startCoeff; + interpedCoefficient = target.getFlexCoefficient(startCoeff) * (1 - partial) + target.getFlexCoefficient(startCoeff + 1) * partial; + } else { + interpedCoefficient = target.getFlexCoefficient(numFlexCoeff - 1); + } + } + ::blend(1, &absolutePoses[splineJointInfo.jointIndex], &desiredAbsPose, interpedCoefficient, &flexedAbsPose); + AnimPose relPose = parentAbsPose.inverse() * flexedAbsPose; - /* + bool constrained = false; if (splineJointInfo.jointIndex != base) { // constrain the amount the spine can stretch or compress @@ -429,7 +402,7 @@ void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, int base, c relPose.trans() = glm::vec3(0.0f); } } - */ + if (!chainInfoOut.setRelativePoseAtJointIndex(splineJointInfo.jointIndex, relPose)) { qCDebug(animation) << "we didn't find the joint index for the spline!!!!"; } @@ -463,8 +436,6 @@ const std::vector* AnimSplineIK::findOrCreateSpli void AnimSplineIK::computeAndCacheSplineJointInfosForIKTarget(const AnimContext& context, int base, const IKTarget& target) const { std::vector splineJointInfoVec; - //qCDebug(animation) << "the base in compute cache is " << base; - // build spline between the default poses. AnimPose tipPose = _skeleton->getAbsoluteDefaultPose(target.getIndex()); AnimPose basePose = _skeleton->getAbsoluteDefaultPose(base); @@ -478,7 +449,6 @@ void AnimSplineIK::computeAndCacheSplineJointInfosForIKTarget(const AnimContext& } else { spline = computeSplineFromTipAndBase(tipPose, basePose); } - // measure the total arc length along the spline float totalArcLength = spline.arcLength(1.0f); @@ -487,17 +457,12 @@ void AnimSplineIK::computeAndCacheSplineJointInfosForIKTarget(const AnimContext& glm::vec3 baseToTipNormal = baseToTip / baseToTipLength; int index = target.getIndex(); - int endIndex; - // fix this statement. AA - if (target.getIndex() == _tipJointIndex) { - endIndex = _skeleton->getParentIndex(base); - } else { - endIndex = _skeleton->getParentIndex(base); - } + int endIndex = _skeleton->getParentIndex(base); + while (index != endIndex) { AnimPose defaultPose = _skeleton->getAbsoluteDefaultPose(index); - - float ratio = glm::dot(defaultPose.trans() - basePose.trans(), baseToTipNormal) / baseToTipLength; + glm::vec3 baseToCurrentJoint = defaultPose.trans() - basePose.trans(); + float ratio = glm::dot(baseToCurrentJoint, baseToTipNormal) / baseToTipLength; // compute offset from spline to the default pose. float t = spline.arcLengthInverse(ratio * totalArcLength); @@ -520,15 +485,6 @@ void AnimSplineIK::computeAndCacheSplineJointInfosForIKTarget(const AnimContext& _splineJointInfoMap[target.getIndex()] = splineJointInfoVec; } -void AnimSplineIK::loadPoses(const AnimPoseVec& poses) { - assert(_skeleton && ((poses.size() == 0) || (_skeleton->getNumJoints() == (int)poses.size()))); - if (_skeleton->getNumJoints() == (int)poses.size()) { - _poses = poses; - } else { - _poses.clear(); - } -} - void AnimSplineIK::beginInterp(InterpType interpType, const AnimChain& chain) { // capture the current poses in a snapshot. _snapshotChain = chain; diff --git a/libraries/animation/src/AnimSplineIK.h b/libraries/animation/src/AnimSplineIK.h index d664cf37a4..ee3f29d6df 100644 --- a/libraries/animation/src/AnimSplineIK.h +++ b/libraries/animation/src/AnimSplineIK.h @@ -63,6 +63,8 @@ protected: QString _midRotationVar; QString _tipPositionVar; QString _tipRotationVar; + QString _alphaVar; // float - (0, 1) 0 means underPoses only, 1 means IK only. + QString _enabledVar; static const int MAX_NUMBER_FLEX_VARIABLES = 10; float _tipTargetFlexCoefficients[MAX_NUMBER_FLEX_VARIABLES]; @@ -70,20 +72,15 @@ protected: int _numTipTargetFlexCoefficients { 0 }; int _numMidTargetFlexCoefficients { 0 }; - int _baseParentJointIndex { -1 }; int _baseJointIndex { -1 }; int _midJointIndex { -1 }; int _tipJointIndex { -1 }; - QString _alphaVar; // float - (0, 1) 0 means underPoses only, 1 means IK only. - QString _enabledVar; // bool - bool _previousEnableDebugIKTargets { false }; InterpType _interpType{ InterpType::None }; float _interpAlphaVel{ 0.0f }; float _interpAlpha{ 0.0f }; - AnimChain _snapshotChain; // used to pre-compute information about each joint influenced by a spline IK target. @@ -93,7 +90,6 @@ protected: AnimPose offsetPose; // local offset from the spline to the joint. }; - bool _lastEnableDebugDrawIKTargets { false }; void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, int base, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug, AnimChain& chainInfoOut) const; void computeAndCacheSplineJointInfosForIKTarget(const AnimContext& context, int base, const IKTarget& target) const; const std::vector* findOrCreateSplineJointInfo(const AnimContext& context, int base, const IKTarget& target) const; diff --git a/libraries/animation/src/IKTarget.h b/libraries/animation/src/IKTarget.h index 325a1b40b6..5c77b3c29a 100644 --- a/libraries/animation/src/IKTarget.h +++ b/libraries/animation/src/IKTarget.h @@ -35,6 +35,8 @@ public: bool getPoleVectorEnabled() const { return _poleVectorEnabled; } int getIndex() const { return _index; } Type getType() const { return _type; } + int getNumFlexCoefficients() const { return _numFlexCoefficients; } + float getFlexCoefficient(size_t chainDepth) const; void setPose(const glm::quat& rotation, const glm::vec3& translation); void setPoleVector(const glm::vec3& poleVector) { _poleVector = poleVector; } @@ -43,7 +45,6 @@ public: void setIndex(int index) { _index = index; } void setType(int); void setFlexCoefficients(size_t numFlexCoefficientsIn, const float* flexCoefficientsIn); - float getFlexCoefficient(size_t chainDepth) const; void setWeight(float weight) { _weight = weight; } float getWeight() const { return _weight; } From 2e1a4545c6dde3b4586a160e95132ae8c8405f01 Mon Sep 17 00:00:00 2001 From: amantley Date: Wed, 30 Jan 2019 14:47:03 -0800 Subject: [PATCH 033/474] cache the spine2 spline default offset and ratio --- interface/src/avatar/MySkeletonModel.cpp | 52 ++++++++++++++----- .../src/avatars-renderer/Avatar.cpp | 46 ++++++++++++++++ .../src/avatars-renderer/Avatar.h | 9 ++++ libraries/shared/src/AvatarConstants.h | 1 + 4 files changed, 94 insertions(+), 14 deletions(-) diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 38f18d2ac9..e77f74de47 100755 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -44,25 +44,46 @@ static CubicHermiteSplineFunctorWithArcLength computeSplineFromTipAndBase(const } static glm::vec3 computeSpine2WithHeadHipsSpline(MyAvatar* myAvatar, AnimPose hipsIKTargetPose, AnimPose headIKTargetPose) { - glm::vec3 basePosition = myAvatar->getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar->getJointIndex("Hips")); - glm::vec3 tipPosition = myAvatar->getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar->getJointIndex("Head")); - glm::vec3 spine2Position = myAvatar->getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar->getJointIndex("Spine2")); + /* + AnimPose avatarToRigPose(glm::vec3(1.0f), Quaternions::Y_180, glm::vec3(0.0f)); + + AnimPose hipsDefaultPoseAvatarSpace(myAvatar->getAbsoluteDefaultJointRotationInObjectFrame(myAvatar->getJointIndex("Hips")), myAvatar->getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar->getJointIndex("Hips"))); + AnimPose headDefaultPoseAvatarSpace(myAvatar->getAbsoluteDefaultJointRotationInObjectFrame(myAvatar->getJointIndex("Head")), myAvatar->getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar->getJointIndex("Head"))); + AnimPose spine2DefaultPoseAvatarSpace(myAvatar->getAbsoluteDefaultJointRotationInObjectFrame(myAvatar->getJointIndex("Spine2")), myAvatar->getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar->getJointIndex("Spine2"))); + AnimPose hipsDefaultPoseRigSpace = avatarToRigPose * hipsDefaultPoseAvatarSpace; + AnimPose headDefaultPoseRigSpace = avatarToRigPose * headDefaultPoseAvatarSpace; + AnimPose spine2DefaultPoseRigSpace = avatarToRigPose * spine2DefaultPoseAvatarSpace; + + + glm::vec3 basePosition = hipsDefaultPoseRigSpace.trans(); + glm::vec3 tipPosition = headDefaultPoseRigSpace.trans(); + glm::vec3 spine2Position = spine2DefaultPoseRigSpace.trans(); glm::vec3 baseToTip = tipPosition - basePosition; float baseToTipLength = glm::length(baseToTip); glm::vec3 baseToTipNormal = baseToTip / baseToTipLength; glm::vec3 baseToSpine2 = spine2Position - basePosition; float ratio = glm::dot(baseToSpine2, baseToTipNormal) / baseToTipLength; + CubicHermiteSplineFunctorWithArcLength defaultSpline = computeSplineFromTipAndBase(headDefaultPoseRigSpace, hipsDefaultPoseRigSpace); + // measure the total arc length along the spline + float totalDefaultArcLength = defaultSpline.arcLength(1.0f); + float t = defaultSpline.arcLengthInverse(ratio * totalDefaultArcLength); + glm::vec3 defaultSplineSpine2Translation = defaultSpline(t); + + glm::vec3 offset = spine2Position - defaultSplineSpine2Translation; + + qCDebug(animation) << "the my skeleton model numbers are " << ratio << " and " << offset; + */ // the the ik targets to compute the spline with - CubicHermiteSplineFunctorWithArcLength spline = computeSplineFromTipAndBase(headIKTargetPose, hipsIKTargetPose); + CubicHermiteSplineFunctorWithArcLength splineFinal = computeSplineFromTipAndBase(headIKTargetPose, hipsIKTargetPose); // measure the total arc length along the spline - float totalArcLength = spline.arcLength(1.0f); - float t = spline.arcLengthInverse(ratio * totalArcLength); + float totalArcLength = splineFinal.arcLength(1.0f); + float tFinal = splineFinal.arcLengthInverse(myAvatar->getSpine2SplineRatio() * totalArcLength); - glm::vec3 spine2Translation = spline(t); + glm::vec3 spine2Translation = splineFinal(tFinal); - return spine2Translation; + return spine2Translation + myAvatar->getSpine2SplineOffset(); } @@ -266,11 +287,14 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { myAvatar->getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND).isValid() && !(params.primaryControllerFlags[Rig::PrimaryControllerType_Spine2] & (uint8_t)Rig::ControllerFlags::Enabled)) { - controller::Pose headSplineControllerPose = myAvatar->getControllerPoseInSensorFrame(controller::Action::HEAD); - AnimPose headSplinePose(headSplineControllerPose.getRotation(), headSplineControllerPose.getTranslation()); - glm::vec3 spine2TargetTranslation = computeSpine2WithHeadHipsSpline(myAvatar, sensorHips, headSplinePose); - AnimPose sensorSpine2(Quaternions::IDENTITY, spine2TargetTranslation); - AnimPose rigSpine2 = sensorToRigPose * sensorSpine2; + // if (avatarHeadPose.isValid()) { + + AnimPose headAvatarSpace(avatarHeadPose.getRotation(), avatarHeadPose.getTranslation()); + AnimPose headRigSpace = avatarToRigPose * headAvatarSpace; + AnimPose hipsRigSpace = sensorToRigPose * sensorHips; + glm::vec3 spine2TargetTranslation = computeSpine2WithHeadHipsSpline(myAvatar, hipsRigSpace, headRigSpace); + //AnimPose rigSpine2(Quaternions::IDENTITY, spine2TargetTranslation); + //AnimPose rigSpine2 = sensorToRigPose * sensorSpine2; const float SPINE2_ROTATION_FILTER = 0.5f; AnimPose currentSpine2Pose; @@ -292,7 +316,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { } generateBasisVectors(up, fwd, u, v, w); AnimPose newSpinePose(glm::mat4(glm::vec4(w, 0.0f), glm::vec4(u, 0.0f), glm::vec4(v, 0.0f), glm::vec4(glm::vec3(0.0f, 0.0f, 0.0f), 1.0f))); - currentSpine2Pose.trans() = rigSpine2.trans(); + currentSpine2Pose.trans() = spine2TargetTranslation; currentSpine2Pose.rot() = safeLerp(currentSpine2Pose.rot(), newSpinePose.rot(), SPINE2_ROTATION_FILTER); params.primaryControllerPoses[Rig::PrimaryControllerType_Spine2] = currentSpine2Pose; params.primaryControllerFlags[Rig::PrimaryControllerType_Spine2] = (uint8_t)Rig::ControllerFlags::Enabled | (uint8_t)Rig::ControllerFlags::Estimated; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 17d10cdf49..1ae53e2023 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -1420,11 +1420,13 @@ void Avatar::setModelURLFinished(bool success) { // rig is ready void Avatar::rigReady() { buildUnscaledEyeHeightCache(); + buildSpine2SplineRatioCache(); } // rig has been reset. void Avatar::rigReset() { clearUnscaledEyeHeightCache(); + clearSpine2SplineRatioCache(); } // create new model, can return an instance of a SoftAttachmentModel rather then Model @@ -1821,10 +1823,54 @@ void Avatar::buildUnscaledEyeHeightCache() { } } +static CubicHermiteSplineFunctorWithArcLength computeSplineFromTipAndBase(const AnimPose& tipPose, const AnimPose& basePose, float baseGain = 1.0f, float tipGain = 1.0f) { + float linearDistance = glm::length(basePose.trans() - tipPose.trans()); + glm::vec3 p0 = basePose.trans(); + glm::vec3 m0 = baseGain * linearDistance * (basePose.rot() * Vectors::UNIT_Y); + glm::vec3 p1 = tipPose.trans(); + glm::vec3 m1 = tipGain * linearDistance * (tipPose.rot() * Vectors::UNIT_Y); + + return CubicHermiteSplineFunctorWithArcLength(p0, m0, p1, m1); +} + +void Avatar::buildSpine2SplineRatioCache() { + if (_skeletonModel) { + auto& rig = _skeletonModel->getRig(); + AnimPose hipsRigDefaultPose = rig.getAbsoluteDefaultPose(rig.indexOfJoint("Hips")); + AnimPose headRigDefaultPose(rig.getAbsoluteDefaultPose(rig.indexOfJoint("Head"))); + glm::vec3 basePosition = hipsRigDefaultPose.trans(); + glm::vec3 tipPosition = headRigDefaultPose.trans(); + glm::vec3 spine2Position = rig.getAbsoluteDefaultPose(rig.indexOfJoint("Spine2")).trans(); + + glm::vec3 baseToTip = tipPosition - basePosition; + float baseToTipLength = glm::length(baseToTip); + glm::vec3 baseToTipNormal = baseToTip / baseToTipLength; + glm::vec3 baseToSpine2 = spine2Position - basePosition; + + _spine2SplineRatio = glm::dot(baseToSpine2, baseToTipNormal) / baseToTipLength; + + CubicHermiteSplineFunctorWithArcLength defaultSpline = computeSplineFromTipAndBase(headRigDefaultPose, hipsRigDefaultPose); + // measure the total arc length along the spline + float totalDefaultArcLength = defaultSpline.arcLength(1.0f); + float t = defaultSpline.arcLengthInverse(_spine2SplineRatio * totalDefaultArcLength); + glm::vec3 defaultSplineSpine2Translation = defaultSpline(t); + + _spine2SplineOffset = spine2Position - defaultSplineSpine2Translation; + + qCDebug(avatars_renderer) << "the avatar spline numbers are " << _spine2SplineRatio << " and " << _spine2SplineOffset; + } + +} + void Avatar::clearUnscaledEyeHeightCache() { _unscaledEyeHeightCache.set(DEFAULT_AVATAR_EYE_HEIGHT); } +void Avatar::clearSpine2SplineRatioCache() { + _spine2SplineRatio = DEFAULT_AVATAR_EYE_HEIGHT; + _spine2SplineOffset = glm::vec3(); +} + float Avatar::getUnscaledEyeHeightFromSkeleton() const { // TODO: if performance becomes a concern we can cache this value rather then computing it everytime. diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index d5431ad2d2..acfcdc4ee4 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -22,6 +22,7 @@ #include #include #include +#include #include "Head.h" #include "SkeletonModel.h" @@ -228,6 +229,9 @@ public: virtual bool setAbsoluteJointRotationInObjectFrame(int index, const glm::quat& rotation) override { return false; } virtual bool setAbsoluteJointTranslationInObjectFrame(int index, const glm::vec3& translation) override { return false; } + virtual glm::vec3 getSpine2SplineOffset() const { return _spine2SplineOffset; } + virtual float getSpine2SplineRatio() const { return _spine2SplineRatio; } + virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override; virtual void setAttachmentData(const QVector& attachmentData) override; @@ -499,7 +503,9 @@ public slots: protected: float getUnscaledEyeHeightFromSkeleton() const; void buildUnscaledEyeHeightCache(); + void buildSpine2SplineRatioCache(); void clearUnscaledEyeHeightCache(); + void clearSpine2SplineRatioCache(); 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! @@ -552,6 +558,7 @@ protected: float getHeadHeight() const; float getPelvisFloatingHeight() const; glm::vec3 getDisplayNamePosition() const; + Transform calculateDisplayNameTransform(const ViewFrustum& view, const glm::vec3& textPosition) const; void renderDisplayName(gpu::Batch& batch, const ViewFrustum& view, const glm::vec3& textPosition) const; @@ -607,6 +614,8 @@ protected: float _displayNameAlpha { 1.0f }; ThreadSafeValueCache _unscaledEyeHeightCache { DEFAULT_AVATAR_EYE_HEIGHT }; + float _spine2SplineRatio { DEFAULT_SPINE2_SPLINE_PROPORTION }; + glm::vec3 _spine2SplineOffset; std::unordered_map _materials; std::mutex _materialsLock; diff --git a/libraries/shared/src/AvatarConstants.h b/libraries/shared/src/AvatarConstants.h index 87da47a27a..9b0ff678e6 100644 --- a/libraries/shared/src/AvatarConstants.h +++ b/libraries/shared/src/AvatarConstants.h @@ -20,6 +20,7 @@ const float DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD = 0.11f; // meters const float DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD = 0.185f; // meters const float DEFAULT_AVATAR_NECK_HEIGHT = DEFAULT_AVATAR_HEIGHT - DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD; const float DEFAULT_AVATAR_EYE_HEIGHT = DEFAULT_AVATAR_HEIGHT - DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD; +const float DEFAULT_SPINE2_SPLINE_PROPORTION = 0.75f; const float DEFAULT_AVATAR_SUPPORT_BASE_LEFT = -0.25f; const float DEFAULT_AVATAR_SUPPORT_BASE_RIGHT = 0.25f; const float DEFAULT_AVATAR_SUPPORT_BASE_FRONT = -0.20f; From c7e9f79200ac487f3acda117358d739e042c0893 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Wed, 30 Jan 2019 13:55:12 -0800 Subject: [PATCH 034/474] Include the new scale float in min remaining size calculation --- libraries/avatars/src/AvatarData.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 4e95774efb..c733cfa291 100755 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -632,9 +632,11 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent // include jointData if there is room for the most minimal section. i.e. no translations or rotations. IF_AVATAR_SPACE(PACKET_HAS_JOINT_DATA, AvatarDataPacket::minJointDataSize(numJoints)) { - // Allow for faux joints + translation bit-vector: - const ptrdiff_t minSizeForJoint = sizeof(AvatarDataPacket::SixByteQuat) - + jointBitVectorSize + AvatarDataPacket::FAUX_JOINTS_SIZE; + // Minimum space required for another rotation joint - + // size of joint + following translation bit-vector + translation scale + faux joints: + const ptrdiff_t minSizeForJoint = sizeof(AvatarDataPacket::SixByteQuat) + jointBitVectorSize + + sizeof(float) + AvatarDataPacket::FAUX_JOINTS_SIZE; + auto startSection = destinationBuffer; // compute maxTranslationDimension before we send any joint data. @@ -724,6 +726,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent const JointData& data = joints[i]; const JointData& last = lastSentJointData[i]; + // Note minSizeForJoint is conservative since there isn't a following bit-vector + scale. if (packetEnd - destinationBuffer >= minSizeForJoint) { if (!data.translationIsDefaultPose) { if (sendAll || last.translationIsDefaultPose || (!cullSmallChanges && last.translation != data.translation) From 7a1c1252ff336df24c2c6e0764137c8e854709c0 Mon Sep 17 00:00:00 2001 From: amantley Date: Wed, 30 Jan 2019 16:09:10 -0800 Subject: [PATCH 035/474] cleanup --- interface/src/avatar/MySkeletonModel.cpp | 36 +------------------ .../src/avatars-renderer/Avatar.cpp | 1 + 2 files changed, 2 insertions(+), 35 deletions(-) diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index e77f74de47..f4cfb79314 100755 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -44,43 +44,13 @@ static CubicHermiteSplineFunctorWithArcLength computeSplineFromTipAndBase(const } static glm::vec3 computeSpine2WithHeadHipsSpline(MyAvatar* myAvatar, AnimPose hipsIKTargetPose, AnimPose headIKTargetPose) { - /* - AnimPose avatarToRigPose(glm::vec3(1.0f), Quaternions::Y_180, glm::vec3(0.0f)); - - AnimPose hipsDefaultPoseAvatarSpace(myAvatar->getAbsoluteDefaultJointRotationInObjectFrame(myAvatar->getJointIndex("Hips")), myAvatar->getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar->getJointIndex("Hips"))); - AnimPose headDefaultPoseAvatarSpace(myAvatar->getAbsoluteDefaultJointRotationInObjectFrame(myAvatar->getJointIndex("Head")), myAvatar->getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar->getJointIndex("Head"))); - AnimPose spine2DefaultPoseAvatarSpace(myAvatar->getAbsoluteDefaultJointRotationInObjectFrame(myAvatar->getJointIndex("Spine2")), myAvatar->getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar->getJointIndex("Spine2"))); - AnimPose hipsDefaultPoseRigSpace = avatarToRigPose * hipsDefaultPoseAvatarSpace; - AnimPose headDefaultPoseRigSpace = avatarToRigPose * headDefaultPoseAvatarSpace; - AnimPose spine2DefaultPoseRigSpace = avatarToRigPose * spine2DefaultPoseAvatarSpace; - - - glm::vec3 basePosition = hipsDefaultPoseRigSpace.trans(); - glm::vec3 tipPosition = headDefaultPoseRigSpace.trans(); - glm::vec3 spine2Position = spine2DefaultPoseRigSpace.trans(); - glm::vec3 baseToTip = tipPosition - basePosition; - float baseToTipLength = glm::length(baseToTip); - glm::vec3 baseToTipNormal = baseToTip / baseToTipLength; - glm::vec3 baseToSpine2 = spine2Position - basePosition; - float ratio = glm::dot(baseToSpine2, baseToTipNormal) / baseToTipLength; - - CubicHermiteSplineFunctorWithArcLength defaultSpline = computeSplineFromTipAndBase(headDefaultPoseRigSpace, hipsDefaultPoseRigSpace); - // measure the total arc length along the spline - float totalDefaultArcLength = defaultSpline.arcLength(1.0f); - float t = defaultSpline.arcLengthInverse(ratio * totalDefaultArcLength); - glm::vec3 defaultSplineSpine2Translation = defaultSpline(t); - - glm::vec3 offset = spine2Position - defaultSplineSpine2Translation; - - qCDebug(animation) << "the my skeleton model numbers are " << ratio << " and " << offset; - */ + // the the ik targets to compute the spline with CubicHermiteSplineFunctorWithArcLength splineFinal = computeSplineFromTipAndBase(headIKTargetPose, hipsIKTargetPose); // measure the total arc length along the spline float totalArcLength = splineFinal.arcLength(1.0f); float tFinal = splineFinal.arcLengthInverse(myAvatar->getSpine2SplineRatio() * totalArcLength); - glm::vec3 spine2Translation = splineFinal(tFinal); return spine2Translation + myAvatar->getSpine2SplineOffset(); @@ -287,14 +257,10 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { myAvatar->getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND).isValid() && !(params.primaryControllerFlags[Rig::PrimaryControllerType_Spine2] & (uint8_t)Rig::ControllerFlags::Enabled)) { - // if (avatarHeadPose.isValid()) { - AnimPose headAvatarSpace(avatarHeadPose.getRotation(), avatarHeadPose.getTranslation()); AnimPose headRigSpace = avatarToRigPose * headAvatarSpace; AnimPose hipsRigSpace = sensorToRigPose * sensorHips; glm::vec3 spine2TargetTranslation = computeSpine2WithHeadHipsSpline(myAvatar, hipsRigSpace, headRigSpace); - //AnimPose rigSpine2(Quaternions::IDENTITY, spine2TargetTranslation); - //AnimPose rigSpine2 = sensorToRigPose * sensorSpine2; const float SPINE2_ROTATION_FILTER = 0.5f; AnimPose currentSpine2Pose; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 1ae53e2023..bc2cb8a0ba 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -1850,6 +1850,7 @@ void Avatar::buildSpine2SplineRatioCache() { _spine2SplineRatio = glm::dot(baseToSpine2, baseToTipNormal) / baseToTipLength; CubicHermiteSplineFunctorWithArcLength defaultSpline = computeSplineFromTipAndBase(headRigDefaultPose, hipsRigDefaultPose); + //CubicHermiteSplineFunctorWithArcLength defaultSpline(headRigDefaultPose, hipsRigDefaultPose); // measure the total arc length along the spline float totalDefaultArcLength = defaultSpline.arcLength(1.0f); float t = defaultSpline.arcLengthInverse(_spine2SplineRatio * totalDefaultArcLength); From 5054b54626e764de0a4cafa290313de5cbf516b5 Mon Sep 17 00:00:00 2001 From: amantley Date: Wed, 30 Jan 2019 16:20:50 -0800 Subject: [PATCH 036/474] removed armIK.cpp and armIK.h, they are for the next changes --- libraries/animation/src/AnimArmIK.cpp | 40 --------------------------- libraries/animation/src/AnimArmIK.h | 34 ----------------------- 2 files changed, 74 deletions(-) delete mode 100644 libraries/animation/src/AnimArmIK.cpp delete mode 100644 libraries/animation/src/AnimArmIK.h diff --git a/libraries/animation/src/AnimArmIK.cpp b/libraries/animation/src/AnimArmIK.cpp deleted file mode 100644 index 202e8c8420..0000000000 --- a/libraries/animation/src/AnimArmIK.cpp +++ /dev/null @@ -1,40 +0,0 @@ -// -// AnimArmIK.cpp -// -// Created by Angus Antley on 1/9/19. -// Copyright (c) 2019 High Fidelity, Inc. All rights reserved. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "AnimArmIK.h" - -#include - -#include "AnimationLogging.h" -#include "AnimUtil.h" - -AnimArmIK::AnimArmIK(const QString& id, float alpha, bool enabled, float interpDuration, - const QString& baseJointName, const QString& midJointName, - const QString& tipJointName, const glm::vec3& midHingeAxis, - const QString& alphaVar, const QString& enabledVar, - const QString& endEffectorRotationVarVar, const QString& endEffectorPositionVarVar) : - AnimTwoBoneIK(id, alpha, enabled, interpDuration, baseJointName, midJointName, tipJointName, midHingeAxis, alphaVar, enabledVar, endEffectorRotationVarVar, endEffectorPositionVarVar) { - -} - -AnimArmIK::~AnimArmIK() { - -} - -const AnimPoseVec& AnimArmIK::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { - qCDebug(animation) << "evaluating the arm IK"; - - assert(_children.size() == 1); - if (_children.size() != 1) { - return _poses; - } else { - return _poses; - } -} \ No newline at end of file diff --git a/libraries/animation/src/AnimArmIK.h b/libraries/animation/src/AnimArmIK.h deleted file mode 100644 index 26f79a1b9c..0000000000 --- a/libraries/animation/src/AnimArmIK.h +++ /dev/null @@ -1,34 +0,0 @@ -// -// AnimArmIK.h -// -// Created by Angus Antley on 1/9/19. -// Copyright (c) 2019 High Fidelity, Inc. All rights reserved. -// -// 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_AnimArmIK_h -#define hifi_AnimArmIK_h - -//#include "AnimNode.h" -#include "AnimTwoBoneIK.h" -//#include "AnimChain.h" - -// Simple two bone IK chain -class AnimArmIK : public AnimTwoBoneIK { -public: - AnimArmIK(const QString& id, float alpha, bool enabled, float interpDuration, - const QString& baseJointName, const QString& midJointName, - const QString& tipJointName, const glm::vec3& midHingeAxis, - const QString& alphaVar, const QString& enabledVar, - const QString& endEffectorRotationVarVar, const QString& endEffectorPositionVarVar); - virtual ~AnimArmIK(); - virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override; - -protected: - -}; - -#endif // hifi_AnimArmIK_h - From d174fb1b5ce17c690a88f9e11a083d1dceec108b Mon Sep 17 00:00:00 2001 From: amantley Date: Wed, 30 Jan 2019 16:59:12 -0800 Subject: [PATCH 037/474] removed print statements --- libraries/animation/src/AnimSplineIK.cpp | 27 +++++++++---------- .../src/avatars-renderer/Avatar.cpp | 4 +-- .../src/avatars-renderer/Avatar.h | 1 - 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/libraries/animation/src/AnimSplineIK.cpp b/libraries/animation/src/AnimSplineIK.cpp index b9dd1b4f07..e204a8f2d6 100644 --- a/libraries/animation/src/AnimSplineIK.cpp +++ b/libraries/animation/src/AnimSplineIK.cpp @@ -51,15 +51,14 @@ AnimSplineIK::AnimSplineIK(const QString& id, float alpha, bool enabled, float i QStringList tipTargetFlexCoefficientsValues = tipTargetFlexCoefficients.split(',', QString::SkipEmptyParts); for (int i = 0; i < tipTargetFlexCoefficientsValues.size(); i++) { if (i < MAX_NUMBER_FLEX_VARIABLES) { - qCDebug(animation) << "tip target flex value " << tipTargetFlexCoefficientsValues[i].toDouble(); _tipTargetFlexCoefficients[i] = (float)tipTargetFlexCoefficientsValues[i].toDouble(); } } _numTipTargetFlexCoefficients = std::min(tipTargetFlexCoefficientsValues.size(), MAX_NUMBER_FLEX_VARIABLES); + QStringList midTargetFlexCoefficientsValues = midTargetFlexCoefficients.split(',', QString::SkipEmptyParts); for (int i = 0; i < midTargetFlexCoefficientsValues.size(); i++) { if (i < MAX_NUMBER_FLEX_VARIABLES) { - qCDebug(animation) << "mid target flex value " << midTargetFlexCoefficientsValues[i].toDouble(); _midTargetFlexCoefficients[i] = (float)midTargetFlexCoefficientsValues[i].toDouble(); } } @@ -163,10 +162,10 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const IKTarget tipTarget; tipTarget.setType((int)IKTarget::Type::Spline); tipTarget.setIndex(_tipJointIndex); - AnimPose absPose = _skeleton->getAbsolutePose(_tipJointIndex, _poses); - glm::quat rotation = animVars.lookupRigToGeometry(_tipRotationVar, absPose.rot()); - glm::vec3 translation = animVars.lookupRigToGeometry(_tipPositionVar, absPose.trans()); - tipTarget.setPose(rotation, translation); + AnimPose absPoseTip = _skeleton->getAbsolutePose(_tipJointIndex, _poses); + glm::quat tipRotation = animVars.lookupRigToGeometry(_tipRotationVar, absPoseTip.rot()); + glm::vec3 tipTranslation = animVars.lookupRigToGeometry(_tipPositionVar, absPoseTip.trans()); + tipTarget.setPose(tipRotation, tipTranslation); tipTarget.setWeight(1.0f); tipTarget.setFlexCoefficients(_numTipTargetFlexCoefficients, _tipTargetFlexCoefficients); @@ -231,12 +230,10 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const QString name = QString("ikTargetSplineTip"); DebugDraw::getInstance().addMyAvatarMarker(name, glmExtractRotation(avatarTargetMat), extractTranslation(avatarTargetMat), WHITE); - if (_midJointIndex != -1) { - glm::mat4 geomTargetMat2 = createMatFromQuatAndPos(midTarget.getRotation(), midTarget.getTranslation()); - glm::mat4 avatarTargetMat2 = rigToAvatarMat * context.getGeometryToRigMatrix() * geomTargetMat2; - QString name2 = QString("ikTargetSplineMid"); - DebugDraw::getInstance().addMyAvatarMarker(name2, glmExtractRotation(avatarTargetMat2), extractTranslation(avatarTargetMat2), WHITE); - } + glm::mat4 geomTargetMat2 = createMatFromQuatAndPos(midTarget.getRotation(), midTarget.getTranslation()); + glm::mat4 avatarTargetMat2 = rigToAvatarMat * context.getGeometryToRigMatrix() * geomTargetMat2; + QString name2 = QString("ikTargetSplineMid"); + DebugDraw::getInstance().addMyAvatarMarker(name2, glmExtractRotation(avatarTargetMat2), extractTranslation(avatarTargetMat2), WHITE); glm::mat4 geomTargetMat3 = createMatFromQuatAndPos(baseTargetAbsolutePose.rot(), baseTargetAbsolutePose.trans()); glm::mat4 avatarTargetMat3 = rigToAvatarMat * context.getGeometryToRigMatrix() * geomTargetMat3; @@ -370,9 +367,9 @@ void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, int base, c // interp based on ratio of the joint. if (splineJointInfo.ratio < 1.0f) { float flexInterp = splineJointInfo.ratio * (float)(numFlexCoeff - 1); - int startCoeff = floor(flexInterp); + int startCoeff = (int)glm::floor(flexInterp); float partial = flexInterp - startCoeff; - interpedCoefficient = target.getFlexCoefficient(startCoeff) * (1 - partial) + target.getFlexCoefficient(startCoeff + 1) * partial; + interpedCoefficient = target.getFlexCoefficient(startCoeff) * (1.0f - partial) + target.getFlexCoefficient(startCoeff + 1) * partial; } else { interpedCoefficient = target.getFlexCoefficient(numFlexCoeff - 1); } @@ -404,7 +401,7 @@ void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, int base, c } if (!chainInfoOut.setRelativePoseAtJointIndex(splineJointInfo.jointIndex, relPose)) { - qCDebug(animation) << "we didn't find the joint index for the spline!!!!"; + qCDebug(animation) << "error: joint not found in spline chain"; } parentAbsPose = flexedAbsPose; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index bc2cb8a0ba..1552ef607f 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -1850,15 +1850,13 @@ void Avatar::buildSpine2SplineRatioCache() { _spine2SplineRatio = glm::dot(baseToSpine2, baseToTipNormal) / baseToTipLength; CubicHermiteSplineFunctorWithArcLength defaultSpline = computeSplineFromTipAndBase(headRigDefaultPose, hipsRigDefaultPose); - //CubicHermiteSplineFunctorWithArcLength defaultSpline(headRigDefaultPose, hipsRigDefaultPose); + // measure the total arc length along the spline float totalDefaultArcLength = defaultSpline.arcLength(1.0f); float t = defaultSpline.arcLengthInverse(_spine2SplineRatio * totalDefaultArcLength); glm::vec3 defaultSplineSpine2Translation = defaultSpline(t); _spine2SplineOffset = spine2Position - defaultSplineSpine2Translation; - - qCDebug(avatars_renderer) << "the avatar spline numbers are " << _spine2SplineRatio << " and " << _spine2SplineOffset; } } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index acfcdc4ee4..bc8df0ebe1 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -558,7 +558,6 @@ protected: float getHeadHeight() const; float getPelvisFloatingHeight() const; glm::vec3 getDisplayNamePosition() const; - Transform calculateDisplayNameTransform(const ViewFrustum& view, const glm::vec3& textPosition) const; void renderDisplayName(gpu::Batch& batch, const ViewFrustum& view, const glm::vec3& textPosition) const; From b09a9390666707506ba482c2999fe4d54bf3dccf Mon Sep 17 00:00:00 2001 From: Clement Date: Wed, 30 Jan 2019 17:03:16 -0800 Subject: [PATCH 038/474] Fix reload mechanic for entity server scripts --- .../src/scripts/EntityScriptServer.cpp | 16 ++++++++-------- .../src/scripts/EntityScriptServer.h | 2 +- .../entities-renderer/src/EntityTreeRenderer.cpp | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/assignment-client/src/scripts/EntityScriptServer.cpp b/assignment-client/src/scripts/EntityScriptServer.cpp index ef0c807bc4..f1a6c97831 100644 --- a/assignment-client/src/scripts/EntityScriptServer.cpp +++ b/assignment-client/src/scripts/EntityScriptServer.cpp @@ -112,7 +112,6 @@ void EntityScriptServer::handleReloadEntityServerScriptPacket(QSharedPointerunloadEntityScript(entityID); checkAndCallPreload(entityID, true); } } @@ -184,7 +183,6 @@ void EntityScriptServer::updateEntityPPS() { pps = std::min(_maxEntityPPS, pps); } _entityEditSender.setPacketsPerSecond(pps); - qDebug() << QString("Updating entity PPS to: %1 @ %2 PPS per script = %3 PPS").arg(numRunningScripts).arg(_entityPPSPerScript).arg(pps); } void EntityScriptServer::handleEntityServerScriptLogPacket(QSharedPointer message, SharedNodePointer senderNode) { @@ -525,23 +523,25 @@ void EntityScriptServer::deletingEntity(const EntityItemID& entityID) { void EntityScriptServer::entityServerScriptChanging(const EntityItemID& entityID, bool reload) { if (_entityViewer.getTree() && !_shuttingDown) { - _entitiesScriptEngine->unloadEntityScript(entityID, true); checkAndCallPreload(entityID, reload); } } -void EntityScriptServer::checkAndCallPreload(const EntityItemID& entityID, bool reload) { +void EntityScriptServer::checkAndCallPreload(const EntityItemID& entityID, bool forceRedownload) { if (_entityViewer.getTree() && !_shuttingDown && _entitiesScriptEngine) { EntityItemPointer entity = _entityViewer.getTree()->findEntityByEntityItemID(entityID); EntityScriptDetails details; - bool notRunning = !_entitiesScriptEngine->getEntityScriptDetails(entityID, details); - if (entity && (reload || notRunning || details.scriptText != entity->getServerScripts())) { + bool isRunning = _entitiesScriptEngine->getEntityScriptDetails(entityID, details); + if (entity && (forceRedownload || !isRunning || details.scriptText != entity->getServerScripts())) { + if (isRunning) { + _entitiesScriptEngine->unloadEntityScript(entityID, true); + } + QString scriptUrl = entity->getServerScripts(); if (!scriptUrl.isEmpty()) { scriptUrl = DependencyManager::get()->normalizeURL(scriptUrl); - qCDebug(entity_script_server) << "Loading entity server script" << scriptUrl << "for" << entityID; - _entitiesScriptEngine->loadEntityScript(entityID, scriptUrl, reload); + _entitiesScriptEngine->loadEntityScript(entityID, scriptUrl, forceRedownload); } } } diff --git a/assignment-client/src/scripts/EntityScriptServer.h b/assignment-client/src/scripts/EntityScriptServer.h index 9c6c4c752e..944fee36a3 100644 --- a/assignment-client/src/scripts/EntityScriptServer.h +++ b/assignment-client/src/scripts/EntityScriptServer.h @@ -67,7 +67,7 @@ private: void addingEntity(const EntityItemID& entityID); void deletingEntity(const EntityItemID& entityID); void entityServerScriptChanging(const EntityItemID& entityID, bool reload); - void checkAndCallPreload(const EntityItemID& entityID, bool reload = false); + void checkAndCallPreload(const EntityItemID& entityID, bool forceRedownload = false); void cleanupOldKilledListeners(); diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index c71b296a74..44025fc8f4 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -1048,7 +1048,7 @@ void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, bool QString scriptUrl = entity->getScript(); if ((shouldLoad && unloadFirst) || scriptUrl.isEmpty()) { if (_entitiesScriptEngine) { - _entitiesScriptEngine->unloadEntityScript(entityID); + _entitiesScriptEngine->unloadEntityScript(entityID); } entity->scriptHasUnloaded(); } From d547d5b854af84dcab4ff2311d646f2eaf47a895 Mon Sep 17 00:00:00 2001 From: amantley Date: Wed, 30 Jan 2019 18:15:47 -0800 Subject: [PATCH 039/474] changed the json reader to take an array not a string for the flex targets --- .../avatar-animation_withSplineIKNode.json | 4 ++-- libraries/animation/src/AnimNodeLoader.cpp | 24 +++++++++++++++++-- libraries/animation/src/AnimSplineIK.cpp | 21 ++++++++-------- libraries/animation/src/AnimSplineIK.h | 4 ++-- 4 files changed, 36 insertions(+), 17 deletions(-) diff --git a/interface/resources/avatar/avatar-animation_withSplineIKNode.json b/interface/resources/avatar/avatar-animation_withSplineIKNode.json index a6283f0771..fa67b6b24b 100644 --- a/interface/resources/avatar/avatar-animation_withSplineIKNode.json +++ b/interface/resources/avatar/avatar-animation_withSplineIKNode.json @@ -192,8 +192,8 @@ "tipRotationVar": "headRotation", "alphaVar": "splineIKAlpha", "enabledVar": "splineIKEnabled", - "tipTargetFlexCoefficients": "1.0, 1.0, 1.0, 1.0, 1.0", - "midTargetFlexCoefficients": "1.0, 1.0, 1.0" + "tipTargetFlexCoefficients": [ 1.0, 1.0, 1.0, 1.0, 1.0 ], + "midTargetFlexCoefficients": [ 1.0, 1.0, 1.0 ] }, "children": [ { diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 62e848872b..b637d131f8 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -594,8 +594,28 @@ static AnimNode::Pointer loadSplineIKNode(const QJsonObject& jsonObj, const QStr READ_STRING(tipRotationVar, jsonObj, id, jsonUrl, nullptr); READ_STRING(alphaVar, jsonObj, id, jsonUrl, nullptr); READ_STRING(enabledVar, jsonObj, id, jsonUrl, nullptr); - READ_STRING(tipTargetFlexCoefficients, jsonObj, id, jsonUrl, nullptr); - READ_STRING(midTargetFlexCoefficients, jsonObj, id, jsonUrl, nullptr); + + auto tipFlexCoefficientsValue = jsonObj.value("tipTargetFlexCoefficients"); + if (!tipFlexCoefficientsValue.isArray()) { + qCCritical(animation) << "AnimNodeLoader, bad or missing tip flex array"; + return nullptr; + } + auto tipFlexCoefficientsArray = tipFlexCoefficientsValue.toArray(); + std::vector tipTargetFlexCoefficients; + for (const auto& value : tipFlexCoefficientsArray) { + tipTargetFlexCoefficients.push_back((float)value.toDouble()); + } + + auto midFlexCoefficientsValue = jsonObj.value("midTargetFlexCoefficients"); + if (!midFlexCoefficientsValue.isArray()) { + qCCritical(animation) << "AnimNodeLoader, bad or missing mid flex array"; + return nullptr; + } + auto midFlexCoefficientsArray = midFlexCoefficientsValue.toArray(); + std::vector midTargetFlexCoefficients; + for (const auto& midValue : midFlexCoefficientsArray) { + midTargetFlexCoefficients.push_back((float)midValue.toDouble()); + } auto node = std::make_shared(id, alpha, enabled, interpDuration, baseJointName, midJointName, tipJointName, diff --git a/libraries/animation/src/AnimSplineIK.cpp b/libraries/animation/src/AnimSplineIK.cpp index e204a8f2d6..8b44ef4478 100644 --- a/libraries/animation/src/AnimSplineIK.cpp +++ b/libraries/animation/src/AnimSplineIK.cpp @@ -29,8 +29,8 @@ AnimSplineIK::AnimSplineIK(const QString& id, float alpha, bool enabled, float i const QString& tipRotationVar, const QString& alphaVar, const QString& enabledVar, - const QString& tipTargetFlexCoefficients, - const QString& midTargetFlexCoefficients) : + const std::vector tipTargetFlexCoefficients, + const std::vector midTargetFlexCoefficients) : AnimNode(AnimNode::Type::SplineIK, id), _alpha(alpha), _enabled(enabled), @@ -48,21 +48,20 @@ AnimSplineIK::AnimSplineIK(const QString& id, float alpha, bool enabled, float i _enabledVar(enabledVar) { - QStringList tipTargetFlexCoefficientsValues = tipTargetFlexCoefficients.split(',', QString::SkipEmptyParts); - for (int i = 0; i < tipTargetFlexCoefficientsValues.size(); i++) { + for (int i = 0; i < tipTargetFlexCoefficients.size(); i++) { if (i < MAX_NUMBER_FLEX_VARIABLES) { - _tipTargetFlexCoefficients[i] = (float)tipTargetFlexCoefficientsValues[i].toDouble(); + _tipTargetFlexCoefficients[i] = tipTargetFlexCoefficients[i]; } - } - _numTipTargetFlexCoefficients = std::min(tipTargetFlexCoefficientsValues.size(), MAX_NUMBER_FLEX_VARIABLES); + + } + _numTipTargetFlexCoefficients = std::min((int)tipTargetFlexCoefficients.size(), MAX_NUMBER_FLEX_VARIABLES); - QStringList midTargetFlexCoefficientsValues = midTargetFlexCoefficients.split(',', QString::SkipEmptyParts); - for (int i = 0; i < midTargetFlexCoefficientsValues.size(); i++) { + for (int i = 0; i < midTargetFlexCoefficients.size(); i++) { if (i < MAX_NUMBER_FLEX_VARIABLES) { - _midTargetFlexCoefficients[i] = (float)midTargetFlexCoefficientsValues[i].toDouble(); + _midTargetFlexCoefficients[i] = midTargetFlexCoefficients[i]; } } - _numMidTargetFlexCoefficients = std::min(midTargetFlexCoefficientsValues.size(), MAX_NUMBER_FLEX_VARIABLES); + _numMidTargetFlexCoefficients = std::min((int)midTargetFlexCoefficients.size(), MAX_NUMBER_FLEX_VARIABLES); } diff --git a/libraries/animation/src/AnimSplineIK.h b/libraries/animation/src/AnimSplineIK.h index ee3f29d6df..6a07ec5565 100644 --- a/libraries/animation/src/AnimSplineIK.h +++ b/libraries/animation/src/AnimSplineIK.h @@ -24,8 +24,8 @@ public: const QString& midPositionVar, const QString& midRotationVar, const QString& tipPositionVar, const QString& tipRotationVar, const QString& alphaVar, const QString& enabledVar, - const QString& tipTargetFlexCoefficients, - const QString& midTargetFlexCoefficients); + const std::vector tipTargetFlexCoefficients, + const std::vector midTargetFlexCoefficients); virtual ~AnimSplineIK() override; virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override; From e2f82eb94927cc306e802131112c3f0d2fbba6b8 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Thu, 31 Jan 2019 09:35:42 -0700 Subject: [PATCH 040/474] Fix assertion on shapeInfo --- .../src/avatars-renderer/Avatar.cpp | 18 ++++++++++-------- libraries/shared/src/ShapeInfo.cpp | 3 ++- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 07c1ca9a32..ba5529e1c0 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -1733,15 +1733,17 @@ void Avatar::computeShapeInfo(ShapeInfo& shapeInfo) { void Avatar::computeDetailedShapeInfo(ShapeInfo& shapeInfo, int jointIndex) { if (jointIndex > -1 && jointIndex < (int)_multiSphereShapes.size()) { auto& data = _multiSphereShapes[jointIndex].getSpheresData(); - std::vector positions; - std::vector radiuses; - positions.reserve(data.size()); - radiuses.reserve(data.size()); - for (auto& sphere : data) { - positions.push_back(sphere._position); - radiuses.push_back(sphere._radius); + if (data.size() > 0) { + std::vector positions; + std::vector radiuses; + positions.reserve(data.size()); + radiuses.reserve(data.size()); + for (auto& sphere : data) { + positions.push_back(sphere._position); + radiuses.push_back(sphere._radius); + } + shapeInfo.setMultiSphere(positions, radiuses); } - shapeInfo.setMultiSphere(positions, radiuses); } } diff --git a/libraries/shared/src/ShapeInfo.cpp b/libraries/shared/src/ShapeInfo.cpp index 564d79bfda..c256cf2b76 100644 --- a/libraries/shared/src/ShapeInfo.cpp +++ b/libraries/shared/src/ShapeInfo.cpp @@ -152,7 +152,8 @@ void ShapeInfo::setSphere(float radius) { void ShapeInfo::setMultiSphere(const std::vector& centers, const std::vector& radiuses) { _url = ""; _type = SHAPE_TYPE_MULTISPHERE; - assert(centers.size() == radiuses.size() && centers.size() > 0); + assert(centers.size() == radiuses.size()); + assert(centers.size() > 0); for (size_t i = 0; i < centers.size(); i++) { SphereData sphere = SphereData(centers[i], radiuses[i]); _sphereCollection.push_back(sphere); From ffd374e7d477358a4e445cd323a942e9345ca89d Mon Sep 17 00:00:00 2001 From: amantley Date: Thu, 31 Jan 2019 10:05:08 -0800 Subject: [PATCH 041/474] whitespace --- interface/src/avatar/MySkeletonModel.cpp | 2 +- libraries/animation/src/AnimSplineIK.cpp | 14 +++++--------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index f4cfb79314..73021fb3f4 100755 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -44,7 +44,7 @@ static CubicHermiteSplineFunctorWithArcLength computeSplineFromTipAndBase(const } static glm::vec3 computeSpine2WithHeadHipsSpline(MyAvatar* myAvatar, AnimPose hipsIKTargetPose, AnimPose headIKTargetPose) { - + // the the ik targets to compute the spline with CubicHermiteSplineFunctorWithArcLength splineFinal = computeSplineFromTipAndBase(headIKTargetPose, hipsIKTargetPose); diff --git a/libraries/animation/src/AnimSplineIK.cpp b/libraries/animation/src/AnimSplineIK.cpp index 8b44ef4478..a12d6fbd0d 100644 --- a/libraries/animation/src/AnimSplineIK.cpp +++ b/libraries/animation/src/AnimSplineIK.cpp @@ -52,7 +52,6 @@ AnimSplineIK::AnimSplineIK(const QString& id, float alpha, bool enabled, float i if (i < MAX_NUMBER_FLEX_VARIABLES) { _tipTargetFlexCoefficients[i] = tipTargetFlexCoefficients[i]; } - } _numTipTargetFlexCoefficients = std::min((int)tipTargetFlexCoefficients.size(), MAX_NUMBER_FLEX_VARIABLES); @@ -70,7 +69,6 @@ AnimSplineIK::~AnimSplineIK() { } const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { - assert(_children.size() == 1); if (_children.size() != 1) { return _poses; @@ -135,8 +133,6 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const _poses[_baseJointIndex] = baseParentAbsPose.inverse() * baseTargetAbsolutePose; _poses[_baseJointIndex].scale() = glm::vec3(1.0f); - - // initialize the middle joint target IKTarget midTarget; midTarget.setType((int)IKTarget::Type::Spline); @@ -167,7 +163,7 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const tipTarget.setPose(tipRotation, tipTranslation); tipTarget.setWeight(1.0f); tipTarget.setFlexCoefficients(_numTipTargetFlexCoefficients, _tipTargetFlexCoefficients); - + // solve the upper spine spline AnimChain upperJointChain; AnimPoseVec finalAbsolutePoses; @@ -241,7 +237,7 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const } else if (context.getEnableDebugDrawIKTargets() != _previousEnableDebugIKTargets) { - + // remove markers if they were added last frame. QString name = QString("ikTargetSplineTip"); DebugDraw::getInstance().removeMyAvatarMarker(name); @@ -374,9 +370,9 @@ void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, int base, c } } ::blend(1, &absolutePoses[splineJointInfo.jointIndex], &desiredAbsPose, interpedCoefficient, &flexedAbsPose); - + AnimPose relPose = parentAbsPose.inverse() * flexedAbsPose; - + bool constrained = false; if (splineJointInfo.jointIndex != base) { // constrain the amount the spine can stretch or compress @@ -398,7 +394,7 @@ void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, int base, c relPose.trans() = glm::vec3(0.0f); } } - + if (!chainInfoOut.setRelativePoseAtJointIndex(splineJointInfo.jointIndex, relPose)) { qCDebug(animation) << "error: joint not found in spline chain"; } From fbdae1824505bca0d4b831f17cb5a2c8200b1b98 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Thu, 31 Jan 2019 19:37:21 +0100 Subject: [PATCH 042/474] ignore pickRay for zone shape visualizers --- scripts/system/modules/entityShapeVisualizer.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/system/modules/entityShapeVisualizer.js b/scripts/system/modules/entityShapeVisualizer.js index fe950c2e2b..fdf8ee81e7 100644 --- a/scripts/system/modules/entityShapeVisualizer.js +++ b/scripts/system/modules/entityShapeVisualizer.js @@ -135,6 +135,7 @@ EntityShape.prototype = { overlayProperties.canCastShadows = false; overlayProperties.parentID = this.entityID; overlayProperties.collisionless = true; + overlayProperties.ignorePickIntersection = true; this.entity = Entities.addEntity(overlayProperties, "local"); var PROJECTED_MATERIALS = false; this.materialEntity = Entities.addEntity({ From 9082a3f4e5bed7b1f8365652a7dc1a086f2a9a6d Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 31 Jan 2019 10:52:48 -0800 Subject: [PATCH 043/474] to the spot on first launch --- interface/src/ui/AddressBarDialog.cpp | 2 +- libraries/networking/src/AddressManager.cpp | 3 ++- libraries/networking/src/AddressManager.h | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/interface/src/ui/AddressBarDialog.cpp b/interface/src/ui/AddressBarDialog.cpp index 789a2a2bdf..799d7ea182 100644 --- a/interface/src/ui/AddressBarDialog.cpp +++ b/interface/src/ui/AddressBarDialog.cpp @@ -59,7 +59,7 @@ void AddressBarDialog::loadHome() { auto locationBookmarks = DependencyManager::get(); QString homeLocation = locationBookmarks->addressForBookmark(LocationBookmarks::HOME_BOOKMARK); if (homeLocation == "") { - homeLocation = DEFAULT_HIFI_ADDRESS; + homeLocation = DEFAULT_HOME_ADDRESS; } DependencyManager::get()->handleLookupString(homeLocation); } diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index e6957728e8..9145b4a79e 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -30,7 +30,8 @@ #include "UserActivityLogger.h" #include "udt/PacketHeaders.h" -const QString DEFAULT_HIFI_ADDRESS = "file:///~/serverless/tutorial.json"; +const QString DEFAULT_HIFI_ADDRESS = "hifi://welcome"; +const QString DEFAULT_HOME_ADDRESS = "file:///~/serverless/tutorial.json"; const QString REDIRECT_HIFI_ADDRESS = "file:///~/serverless/redirect.json"; const QString ADDRESS_MANAGER_SETTINGS_GROUP = "AddressManager"; const QString SETTINGS_CURRENT_ADDRESS_KEY = "address"; diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index 5318822cdc..450b71023c 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -24,6 +24,7 @@ extern const QString DEFAULT_HIFI_ADDRESS; extern const QString REDIRECT_HIFI_ADDRESS; +extern const QString DEFAULT_HOME_ADDRESS; const QString SANDBOX_HIFI_ADDRESS = "hifi://localhost"; const QString INDEX_PATH = "/"; From ca4695377fe5db088a78b4fa78b428842f2665ad Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Thu, 31 Jan 2019 22:12:35 +0100 Subject: [PATCH 044/474] also ignore picking on the material entity --- scripts/system/modules/entityShapeVisualizer.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/system/modules/entityShapeVisualizer.js b/scripts/system/modules/entityShapeVisualizer.js index fdf8ee81e7..da28369cdd 100644 --- a/scripts/system/modules/entityShapeVisualizer.js +++ b/scripts/system/modules/entityShapeVisualizer.js @@ -147,6 +147,7 @@ EntityShape.prototype = { priority: 1, materialMappingMode: PROJECTED_MATERIALS ? "projected" : "uv", materialURL: Script.resolvePath("../assets/images/materials/GridPattern.json"), + ignorePickIntersection: true, }, "local"); }, update: function() { From 61b019d1763724c30bb75e8a98e429d8df7fc9de Mon Sep 17 00:00:00 2001 From: amantley Date: Thu, 31 Jan 2019 14:13:35 -0800 Subject: [PATCH 045/474] added new constructor for cubichermitespline that takes quat and vec3. this means we don't need computeSplineFromTipAndBase to be declared in multiple files --- interface/src/avatar/MySkeletonModel.cpp | 12 +---------- libraries/animation/src/AnimSplineIK.cpp | 18 ++++------------ libraries/animation/src/IKTarget.h | 2 +- .../src/avatars-renderer/Avatar.cpp | 12 +---------- libraries/shared/src/CubicHermiteSpline.h | 21 +++++++++++++++++++ 5 files changed, 28 insertions(+), 37 deletions(-) diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 73021fb3f4..953b8a4c73 100755 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -33,20 +33,10 @@ Rig::CharacterControllerState convertCharacterControllerState(CharacterControlle }; } -static CubicHermiteSplineFunctorWithArcLength computeSplineFromTipAndBase(const AnimPose& tipPose, const AnimPose& basePose, float baseGain = 1.0f, float tipGain = 1.0f) { - float linearDistance = glm::length(basePose.trans() - tipPose.trans()); - glm::vec3 p0 = basePose.trans(); - glm::vec3 m0 = baseGain * linearDistance * (basePose.rot() * Vectors::UNIT_Y); - glm::vec3 p1 = tipPose.trans(); - glm::vec3 m1 = tipGain * linearDistance * (tipPose.rot() * Vectors::UNIT_Y); - - return CubicHermiteSplineFunctorWithArcLength(p0, m0, p1, m1); -} - static glm::vec3 computeSpine2WithHeadHipsSpline(MyAvatar* myAvatar, AnimPose hipsIKTargetPose, AnimPose headIKTargetPose) { // the the ik targets to compute the spline with - CubicHermiteSplineFunctorWithArcLength splineFinal = computeSplineFromTipAndBase(headIKTargetPose, hipsIKTargetPose); + CubicHermiteSplineFunctorWithArcLength splineFinal(headIKTargetPose.rot(), headIKTargetPose.trans(), hipsIKTargetPose.rot(), hipsIKTargetPose.trans()); // measure the total arc length along the spline float totalArcLength = splineFinal.arcLength(1.0f); diff --git a/libraries/animation/src/AnimSplineIK.cpp b/libraries/animation/src/AnimSplineIK.cpp index a12d6fbd0d..4dab904c05 100644 --- a/libraries/animation/src/AnimSplineIK.cpp +++ b/libraries/animation/src/AnimSplineIK.cpp @@ -287,16 +287,6 @@ void AnimSplineIK::setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) { lookUpIndices(); } -static CubicHermiteSplineFunctorWithArcLength computeSplineFromTipAndBase(const AnimPose& tipPose, const AnimPose& basePose, float baseGain = 1.0f, float tipGain = 1.0f) { - float linearDistance = glm::length(basePose.trans() - tipPose.trans()); - glm::vec3 p0 = basePose.trans(); - glm::vec3 m0 = baseGain * linearDistance * (basePose.rot() * Vectors::UNIT_Y); - glm::vec3 p1 = tipPose.trans(); - glm::vec3 m1 = tipGain * linearDistance * (tipPose.rot() * Vectors::UNIT_Y); - - return CubicHermiteSplineFunctorWithArcLength(p0, m0, p1, m1); -} - void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, int base, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug, AnimChain& chainInfoOut) const { // build spline from tip to base @@ -308,9 +298,9 @@ void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, int base, c // set gain factors so that more curvature occurs near the tip of the spline. const float HIPS_GAIN = 0.5f; const float HEAD_GAIN = 1.0f; - spline = computeSplineFromTipAndBase(tipPose, basePose, HIPS_GAIN, HEAD_GAIN); + spline = CubicHermiteSplineFunctorWithArcLength(tipPose.rot(), tipPose.trans(), basePose.rot(), basePose.trans(), HIPS_GAIN, HEAD_GAIN); } else { - spline = computeSplineFromTipAndBase(tipPose, basePose); + spline = CubicHermiteSplineFunctorWithArcLength(tipPose.rot(),tipPose.trans(), basePose.rot(), basePose.trans()); } float totalArcLength = spline.arcLength(1.0f); @@ -437,9 +427,9 @@ void AnimSplineIK::computeAndCacheSplineJointInfosForIKTarget(const AnimContext& // set gain factors so that more curvature occurs near the tip of the spline. const float HIPS_GAIN = 0.5f; const float HEAD_GAIN = 1.0f; - spline = computeSplineFromTipAndBase(tipPose, basePose, HIPS_GAIN, HEAD_GAIN); + spline = CubicHermiteSplineFunctorWithArcLength(tipPose.rot(), tipPose.trans(), basePose.rot(), basePose.trans(), HIPS_GAIN, HEAD_GAIN); } else { - spline = computeSplineFromTipAndBase(tipPose, basePose); + spline = CubicHermiteSplineFunctorWithArcLength(tipPose.rot(), tipPose.trans(), basePose.rot(), basePose.trans()); } // measure the total arc length along the spline float totalArcLength = spline.arcLength(1.0f); diff --git a/libraries/animation/src/IKTarget.h b/libraries/animation/src/IKTarget.h index 5c77b3c29a..57eaff9c30 100644 --- a/libraries/animation/src/IKTarget.h +++ b/libraries/animation/src/IKTarget.h @@ -35,7 +35,7 @@ public: bool getPoleVectorEnabled() const { return _poleVectorEnabled; } int getIndex() const { return _index; } Type getType() const { return _type; } - int getNumFlexCoefficients() const { return _numFlexCoefficients; } + int getNumFlexCoefficients() const { return (int)_numFlexCoefficients; } float getFlexCoefficient(size_t chainDepth) const; void setPose(const glm::quat& rotation, const glm::vec3& translation); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index b77657dc29..ca5a61b3d6 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -1962,16 +1962,6 @@ void Avatar::buildUnscaledEyeHeightCache() { } } -static CubicHermiteSplineFunctorWithArcLength computeSplineFromTipAndBase(const AnimPose& tipPose, const AnimPose& basePose, float baseGain = 1.0f, float tipGain = 1.0f) { - float linearDistance = glm::length(basePose.trans() - tipPose.trans()); - glm::vec3 p0 = basePose.trans(); - glm::vec3 m0 = baseGain * linearDistance * (basePose.rot() * Vectors::UNIT_Y); - glm::vec3 p1 = tipPose.trans(); - glm::vec3 m1 = tipGain * linearDistance * (tipPose.rot() * Vectors::UNIT_Y); - - return CubicHermiteSplineFunctorWithArcLength(p0, m0, p1, m1); -} - void Avatar::buildSpine2SplineRatioCache() { if (_skeletonModel) { auto& rig = _skeletonModel->getRig(); @@ -1988,7 +1978,7 @@ void Avatar::buildSpine2SplineRatioCache() { _spine2SplineRatio = glm::dot(baseToSpine2, baseToTipNormal) / baseToTipLength; - CubicHermiteSplineFunctorWithArcLength defaultSpline = computeSplineFromTipAndBase(headRigDefaultPose, hipsRigDefaultPose); + CubicHermiteSplineFunctorWithArcLength defaultSpline(headRigDefaultPose.rot(), headRigDefaultPose.trans(), hipsRigDefaultPose.rot(), hipsRigDefaultPose.trans()); // measure the total arc length along the spline float totalDefaultArcLength = defaultSpline.arcLength(1.0f); diff --git a/libraries/shared/src/CubicHermiteSpline.h b/libraries/shared/src/CubicHermiteSpline.h index da2ed26de4..65fbcb75ed 100644 --- a/libraries/shared/src/CubicHermiteSpline.h +++ b/libraries/shared/src/CubicHermiteSpline.h @@ -79,6 +79,27 @@ public: } } + CubicHermiteSplineFunctorWithArcLength(const glm::quat& tipRot, const glm::vec3& tipTrans, const glm::quat& baseRot, const glm::vec3& baseTrans, float baseGain = 1.0f, float tipGain = 1.0f) : CubicHermiteSplineFunctor() { + + float linearDistance = glm::length(baseTrans - tipTrans); + _p0 = baseTrans; + _m0 = baseGain * linearDistance * (baseRot * Vectors::UNIT_Y); + _p1 = tipTrans; + _m1 = tipGain * linearDistance * (tipRot * Vectors::UNIT_Y); + + // initialize _values with the accumulated arcLength along the spline. + const float DELTA = 1.0f / NUM_SUBDIVISIONS; + float alpha = 0.0f; + float accum = 0.0f; + _values[0] = 0.0f; + for (int i = 1; i < NUM_SUBDIVISIONS + 1; i++) { + accum += glm::distance(this->operator()(alpha), + this->operator()(alpha + DELTA)); + alpha += DELTA; + _values[i] = accum; + } + } + CubicHermiteSplineFunctorWithArcLength(const CubicHermiteSplineFunctorWithArcLength& orig) : CubicHermiteSplineFunctor(orig) { memcpy(_values, orig._values, sizeof(float) * (NUM_SUBDIVISIONS + 1)); } From 25c5a2f41a6395646d2444fcc822cad626680400 Mon Sep 17 00:00:00 2001 From: amantley Date: Thu, 31 Jan 2019 17:23:56 -0800 Subject: [PATCH 046/474] fixed mac/linux build error --- libraries/animation/src/AnimSplineIK.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/animation/src/AnimSplineIK.h b/libraries/animation/src/AnimSplineIK.h index 6a07ec5565..9fefb32276 100644 --- a/libraries/animation/src/AnimSplineIK.h +++ b/libraries/animation/src/AnimSplineIK.h @@ -90,7 +90,7 @@ protected: AnimPose offsetPose; // local offset from the spline to the joint. }; - void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, int base, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug, AnimChain& chainInfoOut) const; + void solveTargetWithSpline(const AnimContext& context, int base, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug, AnimChain& chainInfoOut) const; void computeAndCacheSplineJointInfosForIKTarget(const AnimContext& context, int base, const IKTarget& target) const; const std::vector* findOrCreateSplineJointInfo(const AnimContext& context, int base, const IKTarget& target) const; mutable std::map> _splineJointInfoMap; From dd99f93d1ac9a703e7f864df72eff634dd323336 Mon Sep 17 00:00:00 2001 From: Angus Antley Date: Thu, 31 Jan 2019 19:52:17 -0800 Subject: [PATCH 047/474] fixed missplaced static const --- libraries/animation/src/AnimSplineIK.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/animation/src/AnimSplineIK.h b/libraries/animation/src/AnimSplineIK.h index 9fefb32276..bca0f7fe77 100644 --- a/libraries/animation/src/AnimSplineIK.h +++ b/libraries/animation/src/AnimSplineIK.h @@ -15,6 +15,8 @@ #include "IKTarget.h" #include "AnimChain.h" +static const int MAX_NUMBER_FLEX_VARIABLES = 10; + // Spline IK for the spine class AnimSplineIK : public AnimNode { public: @@ -66,7 +68,6 @@ protected: QString _alphaVar; // float - (0, 1) 0 means underPoses only, 1 means IK only. QString _enabledVar; - static const int MAX_NUMBER_FLEX_VARIABLES = 10; float _tipTargetFlexCoefficients[MAX_NUMBER_FLEX_VARIABLES]; float _midTargetFlexCoefficients[MAX_NUMBER_FLEX_VARIABLES]; int _numTipTargetFlexCoefficients { 0 }; From a2ef7edf104b6556b3a68cd0c4d6c2bc6d2c0f3f Mon Sep 17 00:00:00 2001 From: Angus Antley Date: Thu, 31 Jan 2019 20:43:13 -0800 Subject: [PATCH 048/474] cleaned up some warnings --- libraries/animation/src/AnimSplineIK.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/animation/src/AnimSplineIK.cpp b/libraries/animation/src/AnimSplineIK.cpp index 4dab904c05..d1f7bf8b3a 100644 --- a/libraries/animation/src/AnimSplineIK.cpp +++ b/libraries/animation/src/AnimSplineIK.cpp @@ -48,14 +48,14 @@ AnimSplineIK::AnimSplineIK(const QString& id, float alpha, bool enabled, float i _enabledVar(enabledVar) { - for (int i = 0; i < tipTargetFlexCoefficients.size(); i++) { + for (int i = 0; i < (int)tipTargetFlexCoefficients.size(); i++) { if (i < MAX_NUMBER_FLEX_VARIABLES) { _tipTargetFlexCoefficients[i] = tipTargetFlexCoefficients[i]; } } _numTipTargetFlexCoefficients = std::min((int)tipTargetFlexCoefficients.size(), MAX_NUMBER_FLEX_VARIABLES); - for (int i = 0; i < midTargetFlexCoefficients.size(); i++) { + for (int i = 0; i < (int)midTargetFlexCoefficients.size(); i++) { if (i < MAX_NUMBER_FLEX_VARIABLES) { _midTargetFlexCoefficients[i] = midTargetFlexCoefficients[i]; } @@ -345,7 +345,7 @@ void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, int base, c // get the number of flex coeff for this spline float interpedCoefficient = 1.0f; int numFlexCoeff = target.getNumFlexCoefficients(); - if (numFlexCoeff == splineJointInfoVec->size()) { + if (numFlexCoeff == (int)splineJointInfoVec->size()) { // then do nothing special interpedCoefficient = target.getFlexCoefficient(i); } else { From ab6048e92296b5594bf7f30bb09baf33352b7339 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Fri, 1 Feb 2019 10:12:04 -0800 Subject: [PATCH 049/474] give tablet root color --- .../qml/hifi/commerce/common/sendAsset/SendAsset.qml | 3 +-- interface/resources/qml/hifi/tablet/TabletRoot.qml | 5 ++++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml index bc8816e0ea..68d437a346 100644 --- a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml +++ b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml @@ -21,11 +21,10 @@ import "../../../../controls" as HifiControls import "../" as HifiCommerceCommon import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere. -Rectangle { +Item { HifiConstants { id: hifi; } id: root; - color: hifi.colors.baseGray property int parentAppTitleBarHeight; property int parentAppNavBarHeight; property string currentActiveView: "sendAssetHome"; diff --git a/interface/resources/qml/hifi/tablet/TabletRoot.qml b/interface/resources/qml/hifi/tablet/TabletRoot.qml index b19dcbb919..93a23f1b9d 100644 --- a/interface/resources/qml/hifi/tablet/TabletRoot.qml +++ b/interface/resources/qml/hifi/tablet/TabletRoot.qml @@ -3,10 +3,13 @@ import Hifi 1.0 import "../../dialogs" import "../../controls" +import stylesUit 1.0 -Item { +Rectangle { + HifiConstants { id: hifi; } id: tabletRoot objectName: "tabletRoot" + color: hifi.colors.baseGray property string username: "Unknown user" property string usernameShort: "Unknown user" property var rootMenu; From 5f3e3309b8a26fca581c7fbff49addb45764a9d2 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 4 Feb 2019 09:47:04 -0800 Subject: [PATCH 050/474] 0.79.0: trust the data in the packet, Luke --- libraries/octree/src/OctreePacketData.cpp | 27 ----------------------- 1 file changed, 27 deletions(-) diff --git a/libraries/octree/src/OctreePacketData.cpp b/libraries/octree/src/OctreePacketData.cpp index a79f0a0c2b..8ab502e951 100755 --- a/libraries/octree/src/OctreePacketData.cpp +++ b/libraries/octree/src/OctreePacketData.cpp @@ -729,12 +729,6 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char *dataBytes, QVecto uint16_t length; memcpy(&length, dataBytes, sizeof(uint16_t)); dataBytes += sizeof(length); - - // FIXME - this size check is wrong if we allow larger packets - if (length * sizeof(glm::vec3) > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) { - result.resize(0); - return sizeof(uint16_t); - } result.resize(length); memcpy(result.data(), dataBytes, length * sizeof(glm::vec3)); return sizeof(uint16_t) + length * sizeof(glm::vec3); @@ -744,14 +738,7 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char *dataBytes, QVecto uint16_t length; memcpy(&length, dataBytes, sizeof(uint16_t)); dataBytes += sizeof(length); - - // FIXME - this size check is wrong if we allow larger packets - if (length * sizeof(glm::quat) > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) { - result.resize(0); - return sizeof(uint16_t); - } result.resize(length); - const unsigned char *start = dataBytes; for (int i = 0; i < length; i++) { dataBytes += unpackOrientationQuatFromBytes(dataBytes, result[i]); @@ -764,12 +751,6 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QVecto uint16_t length; memcpy(&length, dataBytes, sizeof(uint16_t)); dataBytes += sizeof(length); - - // FIXME - this size check is wrong if we allow larger packets - if (length * sizeof(float) > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) { - result.resize(0); - return sizeof(uint16_t); - } result.resize(length); memcpy(result.data(), dataBytes, length * sizeof(float)); return sizeof(uint16_t) + length * sizeof(float); @@ -779,14 +760,7 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QVecto uint16_t length; memcpy(&length, dataBytes, sizeof(uint16_t)); dataBytes += sizeof(length); - - // FIXME - this size check is wrong if we allow larger packets - if (length / 8 > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) { - result.resize(0); - return sizeof(uint16_t); - } result.resize(length); - int bit = 0; unsigned char current = 0; const unsigned char *start = dataBytes; @@ -797,7 +771,6 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QVecto result[i] = (bool)(current & (1 << bit)); bit = (bit + 1) % BITS_IN_BYTE; } - return (dataBytes - start) + (int)sizeof(uint16_t); } From 34d32eeda87c3613622c06c48d85038ca5ef9fdf Mon Sep 17 00:00:00 2001 From: amantley Date: Mon, 4 Feb 2019 10:17:22 -0800 Subject: [PATCH 051/474] removed the extra ikoverlay in the avatar-animation.json --- .../avatar-animation_withSplineIKNode.json | 3434 ++++++++--------- 1 file changed, 1708 insertions(+), 1726 deletions(-) diff --git a/interface/resources/avatar/avatar-animation_withSplineIKNode.json b/interface/resources/avatar/avatar-animation_withSplineIKNode.json index fa67b6b24b..b1f198c52c 100644 --- a/interface/resources/avatar/avatar-animation_withSplineIKNode.json +++ b/interface/resources/avatar/avatar-animation_withSplineIKNode.json @@ -197,12 +197,13 @@ }, "children": [ { - "id": "ikOverlay", + "id": "defaultPoseOverlay", "type": "overlay", "data": { "alpha": 0.0, - "alphaVar": "ikOverlayAlpha", - "boneSet": "fullBody" + "alphaVar": "defaultPoseOverlayAlpha", + "boneSet": "fullBody", + "boneSetVar": "defaultPoseOverlayBoneSet" }, "children": [ { @@ -213,110 +214,325 @@ "children": [] }, { - "id": "defaultPoseOverlay", + "id": "rightHandOverlay", "type": "overlay", "data": { "alpha": 0.0, - "alphaVar": "defaultPoseOverlayAlpha", - "boneSet": "fullBody", - "boneSetVar": "defaultPoseOverlayBoneSet" + "boneSet": "rightHand", + "alphaVar": "rightHandOverlayAlpha" }, "children": [ { - "id": "defaultPose", - "type": "defaultPose", + "id": "rightHandStateMachine", + "type": "stateMachine", "data": { - }, - "children": [] - }, - { - "id": "rightHandOverlay", - "type": "overlay", - "data": { - "alpha": 0.0, - "boneSet": "rightHand", - "alphaVar": "rightHandOverlayAlpha" + "currentState": "rightHandGrasp", + "states": [ + { + "id": "rightHandGrasp", + "interpTarget": 3, + "interpDuration": 3, + "transitions": [ + { + "var": "isRightIndexPoint", + "state": "rightIndexPoint" + }, + { + "var": "isRightThumbRaise", + "state": "rightThumbRaise" + }, + { + "var": "isRightIndexPointAndThumbRaise", + "state": "rightIndexPointAndThumbRaise" + } + ] + }, + { + "id": "rightIndexPoint", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { + "var": "isRightHandGrasp", + "state": "rightHandGrasp" + }, + { + "var": "isRightThumbRaise", + "state": "rightThumbRaise" + }, + { + "var": "isRightIndexPointAndThumbRaise", + "state": "rightIndexPointAndThumbRaise" + } + ] + }, + { + "id": "rightThumbRaise", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { + "var": "isRightHandGrasp", + "state": "rightHandGrasp" + }, + { + "var": "isRightIndexPoint", + "state": "rightIndexPoint" + }, + { + "var": "isRightIndexPointAndThumbRaise", + "state": "rightIndexPointAndThumbRaise" + } + ] + }, + { + "id": "rightIndexPointAndThumbRaise", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { + "var": "isRightHandGrasp", + "state": "rightHandGrasp" + }, + { + "var": "isRightIndexPoint", + "state": "rightIndexPoint" + }, + { + "var": "isRightThumbRaise", + "state": "rightThumbRaise" + } + ] + } + ] }, "children": [ { - "id": "rightHandStateMachine", + "id": "rightHandGrasp", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "rightHandGraspAlpha" + }, + "children": [ + { + "id": "rightHandGraspOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/hydra_pose_open_right.fbx", + "startFrame": 0.0, + "endFrame": 0.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "rightHandGraspClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/hydra_pose_closed_right.fbx", + "startFrame": 0.0, + "endFrame": 0.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "rightIndexPoint", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "rightHandGraspAlpha" + }, + "children": [ + { + "id": "rightIndexPointOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_point_open_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "rightIndexPointClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_point_closed_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "rightThumbRaise", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "rightHandGraspAlpha" + }, + "children": [ + { + "id": "rightThumbRaiseOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_open_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "rightThumbRaiseClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_closed_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "rightIndexPointAndThumbRaise", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "rightHandGraspAlpha" + }, + "children": [ + { + "id": "rightIndexPointAndThumbRaiseOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_point_open_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "rightIndexPointAndThumbRaiseClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_point_closed_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + } + ] + }, + { + "id": "leftHandOverlay", + "type": "overlay", + "data": { + "alpha": 0.0, + "boneSet": "leftHand", + "alphaVar": "leftHandOverlayAlpha" + }, + "children": [ + { + "id": "leftHandStateMachine", "type": "stateMachine", "data": { - "currentState": "rightHandGrasp", + "currentState": "leftHandGrasp", "states": [ { - "id": "rightHandGrasp", + "id": "leftHandGrasp", "interpTarget": 3, "interpDuration": 3, "transitions": [ { - "var": "isRightIndexPoint", - "state": "rightIndexPoint" + "var": "isLeftIndexPoint", + "state": "leftIndexPoint" }, { - "var": "isRightThumbRaise", - "state": "rightThumbRaise" + "var": "isLeftThumbRaise", + "state": "leftThumbRaise" }, { - "var": "isRightIndexPointAndThumbRaise", - "state": "rightIndexPointAndThumbRaise" + "var": "isLeftIndexPointAndThumbRaise", + "state": "leftIndexPointAndThumbRaise" } ] }, { - "id": "rightIndexPoint", + "id": "leftIndexPoint", "interpTarget": 15, "interpDuration": 3, "transitions": [ { - "var": "isRightHandGrasp", - "state": "rightHandGrasp" + "var": "isLeftHandGrasp", + "state": "leftHandGrasp" }, { - "var": "isRightThumbRaise", - "state": "rightThumbRaise" + "var": "isLeftThumbRaise", + "state": "leftThumbRaise" }, { - "var": "isRightIndexPointAndThumbRaise", - "state": "rightIndexPointAndThumbRaise" + "var": "isLeftIndexPointAndThumbRaise", + "state": "leftIndexPointAndThumbRaise" } ] }, { - "id": "rightThumbRaise", + "id": "leftThumbRaise", "interpTarget": 15, "interpDuration": 3, "transitions": [ { - "var": "isRightHandGrasp", - "state": "rightHandGrasp" + "var": "isLeftHandGrasp", + "state": "leftHandGrasp" }, { - "var": "isRightIndexPoint", - "state": "rightIndexPoint" + "var": "isLeftIndexPoint", + "state": "leftIndexPoint" }, { - "var": "isRightIndexPointAndThumbRaise", - "state": "rightIndexPointAndThumbRaise" + "var": "isLeftIndexPointAndThumbRaise", + "state": "leftIndexPointAndThumbRaise" } ] }, { - "id": "rightIndexPointAndThumbRaise", + "id": "leftIndexPointAndThumbRaise", "interpTarget": 15, "interpDuration": 3, "transitions": [ { - "var": "isRightHandGrasp", - "state": "rightHandGrasp" + "var": "isLeftHandGrasp", + "state": "leftHandGrasp" }, { - "var": "isRightIndexPoint", - "state": "rightIndexPoint" + "var": "isLeftIndexPoint", + "state": "leftIndexPoint" }, { - "var": "isRightThumbRaise", - "state": "rightThumbRaise" + "var": "isLeftThumbRaise", + "state": "leftThumbRaise" } ] } @@ -324,18 +540,18 @@ }, "children": [ { - "id": "rightHandGrasp", + "id": "leftHandGrasp", "type": "blendLinear", "data": { "alpha": 0.0, - "alphaVar": "rightHandGraspAlpha" + "alphaVar": "leftHandGraspAlpha" }, "children": [ { - "id": "rightHandGraspOpen", + "id": "leftHandGraspOpen", "type": "clip", "data": { - "url": "qrc:///avatar/animations/hydra_pose_open_right.fbx", + "url": "qrc:///avatar/animations/hydra_pose_open_left.fbx", "startFrame": 0.0, "endFrame": 0.0, "timeScale": 1.0, @@ -344,12 +560,12 @@ "children": [] }, { - "id": "rightHandGraspClosed", + "id": "leftHandGraspClosed", "type": "clip", "data": { - "url": "qrc:///avatar/animations/hydra_pose_closed_right.fbx", - "startFrame": 0.0, - "endFrame": 0.0, + "url": "qrc:///avatar/animations/hydra_pose_closed_left.fbx", + "startFrame": 10.0, + "endFrame": 10.0, "timeScale": 1.0, "loopFlag": true }, @@ -358,18 +574,18 @@ ] }, { - "id": "rightIndexPoint", + "id": "leftIndexPoint", "type": "blendLinear", "data": { "alpha": 0.0, - "alphaVar": "rightHandGraspAlpha" + "alphaVar": "leftHandGraspAlpha" }, "children": [ { - "id": "rightIndexPointOpen", + "id": "leftIndexPointOpen", "type": "clip", "data": { - "url": "qrc:///avatar/animations/touch_point_open_right.fbx", + "url": "qrc:///avatar/animations/touch_point_open_left.fbx", "startFrame": 15.0, "endFrame": 15.0, "timeScale": 1.0, @@ -378,10 +594,10 @@ "children": [] }, { - "id": "rightIndexPointClosed", + "id": "leftIndexPointClosed", "type": "clip", "data": { - "url": "qrc:///avatar/animations/touch_point_closed_right.fbx", + "url": "qrc:///avatar/animations/touch_point_closed_left.fbx", "startFrame": 15.0, "endFrame": 15.0, "timeScale": 1.0, @@ -392,18 +608,18 @@ ] }, { - "id": "rightThumbRaise", + "id": "leftThumbRaise", "type": "blendLinear", "data": { "alpha": 0.0, - "alphaVar": "rightHandGraspAlpha" + "alphaVar": "leftHandGraspAlpha" }, "children": [ { - "id": "rightThumbRaiseOpen", + "id": "leftThumbRaiseOpen", "type": "clip", "data": { - "url": "qrc:///avatar/animations/touch_thumb_open_right.fbx", + "url": "qrc:///avatar/animations/touch_thumb_open_left.fbx", "startFrame": 15.0, "endFrame": 15.0, "timeScale": 1.0, @@ -412,10 +628,10 @@ "children": [] }, { - "id": "rightThumbRaiseClosed", + "id": "leftThumbRaiseClosed", "type": "clip", "data": { - "url": "qrc:///avatar/animations/touch_thumb_closed_right.fbx", + "url": "qrc:///avatar/animations/touch_thumb_closed_left.fbx", "startFrame": 15.0, "endFrame": 15.0, "timeScale": 1.0, @@ -426,18 +642,18 @@ ] }, { - "id": "rightIndexPointAndThumbRaise", + "id": "leftIndexPointAndThumbRaise", "type": "blendLinear", "data": { "alpha": 0.0, - "alphaVar": "rightHandGraspAlpha" + "alphaVar": "leftHandGraspAlpha" }, "children": [ { - "id": "rightIndexPointAndThumbRaiseOpen", + "id": "leftIndexPointAndThumbRaiseOpen", "type": "clip", "data": { - "url": "qrc:///avatar/animations/touch_thumb_point_open_right.fbx", + "url": "qrc:///avatar/animations/touch_thumb_point_open_left.fbx", "startFrame": 15.0, "endFrame": 15.0, "timeScale": 1.0, @@ -446,10 +662,10 @@ "children": [] }, { - "id": "rightIndexPointAndThumbRaiseClosed", + "id": "leftIndexPointAndThumbRaiseClosed", "type": "clip", "data": { - "url": "qrc:///avatar/animations/touch_thumb_point_closed_right.fbx", + "url": "qrc:///avatar/animations/touch_thumb_point_closed_left.fbx", "startFrame": 15.0, "endFrame": 15.0, "timeScale": 1.0, @@ -462,93 +678,871 @@ ] }, { - "id": "leftHandOverlay", - "type": "overlay", + "id": "mainStateMachine", + "type": "stateMachine", "data": { - "alpha": 0.0, - "boneSet": "leftHand", - "alphaVar": "leftHandOverlayAlpha" + "outputJoints": [ "LeftFoot", "RightFoot" ], + "currentState": "idle", + "states": [ + { + "id": "idle", + "interpTarget": 20, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "idleToWalkFwd", + "interpTarget": 12, + "interpDuration": 8, + "transitions": [ + { + "var": "idleToWalkFwdOnDone", + "state": "WALKFWD" + }, + { + "var": "isNotMoving", + "state": "idle" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "idleSettle", + "interpTarget": 15, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "idleSettleOnDone", + "state": "idle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + } + ] + }, + { + "id": "WALKFWD", + "interpTarget": 35, + "interpDuration": 10, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotMoving", + "state": "idleSettle" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "WALKBWD", + "interpTarget": 35, + "interpDuration": 10, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotMoving", + "state": "idleSettle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "STRAFERIGHT", + "interpTarget": 25, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotMoving", + "state": "idleSettle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "STRAFELEFT", + "interpTarget": 25, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotMoving", + "state": "idleSettle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "turnRight", + "interpTarget": 6, + "interpDuration": 8, + "transitions": [ + { + "var": "isNotTurning", + "state": "idle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "turnLeft", + "interpTarget": 6, + "interpDuration": 8, + "transitions": [ + { + "var": "isNotTurning", + "state": "idle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "strafeRightHmd", + "interpTarget": 5, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotMoving", + "state": "idleSettle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + } + ] + }, + { + "id": "strafeLeftHmd", + "interpTarget": 5, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotMoving", + "state": "idleSettle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + } + ] + }, + { + "id": "fly", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { + "var": "isNotFlying", + "state": "idleSettle" + } + ] + }, + { + "id": "takeoffStand", + "interpTarget": 2, + "interpDuration": 2, + "transitions": [ + { + "var": "isNotTakeoff", + "state": "inAirStand" + } + ] + }, + { + "id": "TAKEOFFRUN", + "interpTarget": 2, + "interpDuration": 2, + "transitions": [ + { + "var": "isNotTakeoff", + "state": "INAIRRUN" + } + ] + }, + { + "id": "inAirStand", + "interpTarget": 3, + "interpDuration": 3, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotInAir", + "state": "landStandImpact" + } + ] + }, + { + "id": "INAIRRUN", + "interpTarget": 3, + "interpDuration": 3, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotInAir", + "state": "WALKFWD" + } + ] + }, + { + "id": "landStandImpact", + "interpTarget": 1, + "interpDuration": 1, + "transitions": [ + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "landStandImpactOnDone", + "state": "landStand" + } + ] + }, + { + "id": "landStand", + "interpTarget": 1, + "interpDuration": 1, + "transitions": [ + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "landStandOnDone", + "state": "idle" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "LANDRUN", + "interpTarget": 2, + "interpDuration": 2, + "transitions": [ + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "landRunOnDone", + "state": "WALKFWD" + } + ] + } + ] }, "children": [ { - "id": "leftHandStateMachine", + "id": "idle", "type": "stateMachine", "data": { - "currentState": "leftHandGrasp", + "currentState": "idleStand", "states": [ { - "id": "leftHandGrasp", - "interpTarget": 3, - "interpDuration": 3, + "id": "idleStand", + "interpTarget": 6, + "interpDuration": 10, "transitions": [ { - "var": "isLeftIndexPoint", - "state": "leftIndexPoint" - }, - { - "var": "isLeftThumbRaise", - "state": "leftThumbRaise" - }, - { - "var": "isLeftIndexPointAndThumbRaise", - "state": "leftIndexPointAndThumbRaise" + "var": "isTalking", + "state": "idleTalk" } ] }, { - "id": "leftIndexPoint", - "interpTarget": 15, - "interpDuration": 3, + "id": "idleTalk", + "interpTarget": 6, + "interpDuration": 10, "transitions": [ { - "var": "isLeftHandGrasp", - "state": "leftHandGrasp" - }, - { - "var": "isLeftThumbRaise", - "state": "leftThumbRaise" - }, - { - "var": "isLeftIndexPointAndThumbRaise", - "state": "leftIndexPointAndThumbRaise" - } - ] - }, - { - "id": "leftThumbRaise", - "interpTarget": 15, - "interpDuration": 3, - "transitions": [ - { - "var": "isLeftHandGrasp", - "state": "leftHandGrasp" - }, - { - "var": "isLeftIndexPoint", - "state": "leftIndexPoint" - }, - { - "var": "isLeftIndexPointAndThumbRaise", - "state": "leftIndexPointAndThumbRaise" - } - ] - }, - { - "id": "leftIndexPointAndThumbRaise", - "interpTarget": 15, - "interpDuration": 3, - "transitions": [ - { - "var": "isLeftHandGrasp", - "state": "leftHandGrasp" - }, - { - "var": "isLeftIndexPoint", - "state": "leftIndexPoint" - }, - { - "var": "isLeftThumbRaise", - "state": "leftThumbRaise" + "var": "notIsTalking", + "state": "idleStand" } ] } @@ -556,1218 +1550,305 @@ }, "children": [ { - "id": "leftHandGrasp", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "leftHandGraspAlpha" - }, - "children": [ - { - "id": "leftHandGraspOpen", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/hydra_pose_open_left.fbx", - "startFrame": 0.0, - "endFrame": 0.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "leftHandGraspClosed", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/hydra_pose_closed_left.fbx", - "startFrame": 10.0, - "endFrame": 10.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "leftIndexPoint", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "leftHandGraspAlpha" - }, - "children": [ - { - "id": "leftIndexPointOpen", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_point_open_left.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "leftIndexPointClosed", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_point_closed_left.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "leftThumbRaise", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "leftHandGraspAlpha" - }, - "children": [ - { - "id": "leftThumbRaiseOpen", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_thumb_open_left.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "leftThumbRaiseClosed", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_thumb_closed_left.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "leftIndexPointAndThumbRaise", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "leftHandGraspAlpha" - }, - "children": [ - { - "id": "leftIndexPointAndThumbRaiseOpen", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_thumb_point_open_left.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "leftIndexPointAndThumbRaiseClosed", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_thumb_point_closed_left.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - } - ] - }, - { - "id": "mainStateMachine", - "type": "stateMachine", - "data": { - "outputJoints": [ "LeftFoot", "RightFoot" ], - "currentState": "idle", - "states": [ - { - "id": "idle", - "interpTarget": 20, - "interpDuration": 8, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "idleToWalkFwd", - "interpTarget": 12, - "interpDuration": 8, - "transitions": [ - { - "var": "idleToWalkFwdOnDone", - "state": "WALKFWD" - }, - { - "var": "isNotMoving", - "state": "idle" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "idleSettle", - "interpTarget": 15, - "interpDuration": 8, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "idleSettleOnDone", - "state": "idle" - }, - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - } - ] - }, - { - "id": "WALKFWD", - "interpTarget": 35, - "interpDuration": 10, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isNotMoving", - "state": "idleSettle" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "WALKBWD", - "interpTarget": 35, - "interpDuration": 10, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isNotMoving", - "state": "idleSettle" - }, - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "STRAFERIGHT", - "interpTarget": 25, - "interpDuration": 8, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isNotMoving", - "state": "idleSettle" - }, - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "STRAFELEFT", - "interpTarget": 25, - "interpDuration": 8, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isNotMoving", - "state": "idleSettle" - }, - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "turnRight", - "interpTarget": 6, - "interpDuration": 8, - "transitions": [ - { - "var": "isNotTurning", - "state": "idle" - }, - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "turnLeft", - "interpTarget": 6, - "interpDuration": 8, - "transitions": [ - { - "var": "isNotTurning", - "state": "idle" - }, - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "strafeRightHmd", - "interpTarget": 5, - "interpDuration": 8, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isNotMoving", - "state": "idleSettle" - }, - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - } - ] - }, - { - "id": "strafeLeftHmd", - "interpTarget": 5, - "interpDuration": 8, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isNotMoving", - "state": "idleSettle" - }, - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - } - ] - }, - { - "id": "fly", - "interpTarget": 6, - "interpDuration": 6, - "transitions": [ - { - "var": "isNotFlying", - "state": "idleSettle" - } - ] - }, - { - "id": "takeoffStand", - "interpTarget": 2, - "interpDuration": 2, - "transitions": [ - { - "var": "isNotTakeoff", - "state": "inAirStand" - } - ] - }, - { - "id": "TAKEOFFRUN", - "interpTarget": 2, - "interpDuration": 2, - "transitions": [ - { - "var": "isNotTakeoff", - "state": "INAIRRUN" - } - ] - }, - { - "id": "inAirStand", - "interpTarget": 3, - "interpDuration": 3, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isNotInAir", - "state": "landStandImpact" - } - ] - }, - { - "id": "INAIRRUN", - "interpTarget": 3, - "interpDuration": 3, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isNotInAir", - "state": "WALKFWD" - } - ] - }, - { - "id": "landStandImpact", - "interpTarget": 1, - "interpDuration": 1, - "transitions": [ - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "landStandImpactOnDone", - "state": "landStand" - } - ] - }, - { - "id": "landStand", - "interpTarget": 1, - "interpDuration": 1, - "transitions": [ - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "landStandOnDone", - "state": "idle" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "LANDRUN", - "interpTarget": 2, - "interpDuration": 2, - "transitions": [ - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "landRunOnDone", - "state": "WALKFWD" - } - ] - } - ] - }, - "children": [ - { - "id": "idle", - "type": "stateMachine", - "data": { - "currentState": "idleStand", - "states": [ - { - "id": "idleStand", - "interpTarget": 6, - "interpDuration": 10, - "transitions": [ - { - "var": "isTalking", - "state": "idleTalk" - } - ] - }, - { - "id": "idleTalk", - "interpTarget": 6, - "interpDuration": 10, - "transitions": [ - { - "var": "notIsTalking", - "state": "idleStand" - } - ] - } - ] - }, - "children": [ - { - "id": "idleStand", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/idle.fbx", - "startFrame": 0.0, - "endFrame": 300.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "idleTalk", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/talk.fbx", - "startFrame": 0.0, - "endFrame": 800.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "WALKFWD", - "type": "blendLinearMove", - "data": { - "alpha": 0.0, - "desiredSpeed": 1.4, - "characteristicSpeeds": [ 0.5, 1.8, 2.3, 3.2, 4.5 ], - "alphaVar": "moveForwardAlpha", - "desiredSpeedVar": "moveForwardSpeed" - }, - "children": [ - { - "id": "walkFwdShort_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_short_fwd.fbx", - "startFrame": 0.0, - "endFrame": 39.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "walkFwdNormal_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_fwd.fbx", - "startFrame": 0.0, - "endFrame": 30.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "walkFwdFast_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_fwd_fast.fbx", - "startFrame": 0.0, - "endFrame": 25.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "walkFwdJog_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jog_fwd.fbx", - "startFrame": 0.0, - "endFrame": 25.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "walkFwdRun_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/run_fwd.fbx", - "startFrame": 0.0, - "endFrame": 21.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "idleToWalkFwd", + "id": "idleStand", "type": "clip", "data": { - "url": "qrc:///avatar/animations/idle_to_walk.fbx", - "startFrame": 1.0, - "endFrame": 13.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "idleSettle", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/settle_to_idle.fbx", - "startFrame": 1.0, - "endFrame": 59.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "WALKBWD", - "type": "blendLinearMove", - "data": { - "alpha": 0.0, - "desiredSpeed": 1.4, - "characteristicSpeeds": [ 0.6, 1.6, 2.3, 3.1 ], - "alphaVar": "moveBackwardAlpha", - "desiredSpeedVar": "moveBackwardSpeed" - }, - "children": [ - { - "id": "walkBwdShort_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_short_bwd.fbx", - "startFrame": 0.0, - "endFrame": 38.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "walkBwdFast_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_bwd_fast.fbx", - "startFrame": 0.0, - "endFrame": 27.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "jogBwd_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jog_bwd.fbx", - "startFrame": 0.0, - "endFrame": 24.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "runBwd_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/run_bwd.fbx", - "startFrame": 0.0, - "endFrame": 16.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "turnLeft", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/turn_left.fbx", + "url": "qrc:///avatar/animations/idle.fbx", "startFrame": 0.0, - "endFrame": 32.0, + "endFrame": 300.0, "timeScale": 1.0, "loopFlag": true }, "children": [] }, { - "id": "turnRight", + "id": "idleTalk", "type": "clip", "data": { - "url": "qrc:///avatar/animations/turn_left.fbx", + "url": "qrc:///avatar/animations/talk.fbx", "startFrame": 0.0, - "endFrame": 32.0, + "endFrame": 800.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "WALKFWD", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [ 0.5, 1.8, 2.3, 3.2, 4.5 ], + "alphaVar": "moveForwardAlpha", + "desiredSpeedVar": "moveForwardSpeed" + }, + "children": [ + { + "id": "walkFwdShort_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_short_fwd.fbx", + "startFrame": 0.0, + "endFrame": 39.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkFwdNormal_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_fwd.fbx", + "startFrame": 0.0, + "endFrame": 30.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkFwdFast_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_fwd_fast.fbx", + "startFrame": 0.0, + "endFrame": 25.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkFwdJog_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jog_fwd.fbx", + "startFrame": 0.0, + "endFrame": 25.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkFwdRun_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/run_fwd.fbx", + "startFrame": 0.0, + "endFrame": 21.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "idleToWalkFwd", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle_to_walk.fbx", + "startFrame": 1.0, + "endFrame": 13.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "idleSettle", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/settle_to_idle.fbx", + "startFrame": 1.0, + "endFrame": 59.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "WALKBWD", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [ 0.6, 1.6, 2.3, 3.1 ], + "alphaVar": "moveBackwardAlpha", + "desiredSpeedVar": "moveBackwardSpeed" + }, + "children": [ + { + "id": "walkBwdShort_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_short_bwd.fbx", + "startFrame": 0.0, + "endFrame": 38.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkBwdFast_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_bwd_fast.fbx", + "startFrame": 0.0, + "endFrame": 27.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "jogBwd_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jog_bwd.fbx", + "startFrame": 0.0, + "endFrame": 24.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "runBwd_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/run_bwd.fbx", + "startFrame": 0.0, + "endFrame": 16.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "turnLeft", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/turn_left.fbx", + "startFrame": 0.0, + "endFrame": 32.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "turnRight", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/turn_left.fbx", + "startFrame": 0.0, + "endFrame": 32.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "STRAFELEFT", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [ 0.1, 0.5, 1.0, 2.6, 3.0 ], + "alphaVar": "moveLateralAlpha", + "desiredSpeedVar": "moveLateralSpeed" + }, + "children": [ + { + "id": "strafeLeftShortStep_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_short_left.fbx", + "startFrame": 0.0, + "endFrame": 29.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftStep_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left.fbx", + "startFrame": 0.0, + "endFrame": 20.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftWalk_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_left.fbx", + "startFrame": 0.0, + "endFrame": 35.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftWalkFast_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_left_fast.fbx", + "startFrame": 0.0, + "endFrame": 21.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftJog_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jog_left.fbx", + "startFrame": 0.0, + "endFrame": 24.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "STRAFERIGHT", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [ 0.1, 0.5, 1.0, 2.6, 3.0 ], + "alphaVar": "moveLateralAlpha", + "desiredSpeedVar": "moveLateralSpeed" + }, + "children": [ + { + "id": "strafeRightShortStep_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_short_left.fbx", + "startFrame": 0.0, + "endFrame": 29.0, "timeScale": 1.0, "loopFlag": true, "mirrorFlag": true @@ -1775,275 +1856,256 @@ "children": [] }, { - "id": "STRAFELEFT", - "type": "blendLinearMove", - "data": { - "alpha": 0.0, - "desiredSpeed": 1.4, - "characteristicSpeeds": [ 0.1, 0.5, 1.0, 2.6, 3.0 ], - "alphaVar": "moveLateralAlpha", - "desiredSpeedVar": "moveLateralSpeed" - }, - "children": [ - { - "id": "strafeLeftShortStep_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_short_left.fbx", - "startFrame": 0.0, - "endFrame": 29.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "strafeLeftStep_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_left.fbx", - "startFrame": 0.0, - "endFrame": 20.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "strafeLeftWalk_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_left.fbx", - "startFrame": 0.0, - "endFrame": 35.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "strafeLeftWalkFast_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_left_fast.fbx", - "startFrame": 0.0, - "endFrame": 21.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "strafeLeftJog_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jog_left.fbx", - "startFrame": 0.0, - "endFrame": 24.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "STRAFERIGHT", - "type": "blendLinearMove", - "data": { - "alpha": 0.0, - "desiredSpeed": 1.4, - "characteristicSpeeds": [ 0.1, 0.5, 1.0, 2.6, 3.0 ], - "alphaVar": "moveLateralAlpha", - "desiredSpeedVar": "moveLateralSpeed" - }, - "children": [ - { - "id": "strafeRightShortStep_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_short_left.fbx", - "startFrame": 0.0, - "endFrame": 29.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "strafeRightStep_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_left.fbx", - "startFrame": 0.0, - "endFrame": 20.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "strafeRightWalk_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_left.fbx", - "startFrame": 0.0, - "endFrame": 35.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "strafeRightFast_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_left_fast.fbx", - "startFrame": 0.0, - "endFrame": 21.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "strafeRightJog_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jog_left.fbx", - "startFrame": 0.0, - "endFrame": 24.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - } - ] - }, - { - "id": "strafeLeftHmd", - "type": "blendLinearMove", - "data": { - "alpha": 0.0, - "desiredSpeed": 1.4, - "characteristicSpeeds": [ 0, 0.5, 2.5 ], - "alphaVar": "moveLateralAlpha", - "desiredSpeedVar": "moveLateralSpeed" - }, - "children": [ - { - "id": "stepLeftShort_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_short_left.fbx", - "startFrame": 0.0, - "endFrame": 29.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "stepLeft_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_left.fbx", - "startFrame": 0.0, - "endFrame": 20.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "strafeLeftAnim_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_left_fast.fbx", - "startFrame": 0.0, - "endFrame": 16.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "strafeRightHmd", - "type": "blendLinearMove", - "data": { - "alpha": 0.0, - "desiredSpeed": 1.4, - "characteristicSpeeds": [ 0, 0.5, 2.5 ], - "alphaVar": "moveLateralAlpha", - "desiredSpeedVar": "moveLateralSpeed" - }, - "children": [ - { - "id": "stepRightShort_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_short_left.fbx", - "startFrame": 0.0, - "endFrame": 29.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "stepRight_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_left.fbx", - "startFrame": 0.0, - "endFrame": 20.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "strafeRightAnim_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_left_fast.fbx", - "startFrame": 0.0, - "endFrame": 16.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - } - ] - }, - { - "id": "fly", + "id": "strafeRightStep_c", "type": "clip", "data": { - "url": "qrc:///avatar/animations/fly.fbx", - "startFrame": 1.0, - "endFrame": 80.0, + "url": "qrc:///avatar/animations/side_step_left.fbx", + "startFrame": 0.0, + "endFrame": 20.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "strafeRightWalk_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_left.fbx", + "startFrame": 0.0, + "endFrame": 35.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "strafeRightFast_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_left_fast.fbx", + "startFrame": 0.0, + "endFrame": 21.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "strafeRightJog_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jog_left.fbx", + "startFrame": 0.0, + "endFrame": 24.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + } + ] + }, + { + "id": "strafeLeftHmd", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [ 0, 0.5, 2.5 ], + "alphaVar": "moveLateralAlpha", + "desiredSpeedVar": "moveLateralSpeed" + }, + "children": [ + { + "id": "stepLeftShort_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_short_left.fbx", + "startFrame": 0.0, + "endFrame": 29.0, "timeScale": 1.0, "loopFlag": true }, "children": [] }, { - "id": "takeoffStand", + "id": "stepLeft_c", "type": "clip", "data": { - "url": "qrc:///avatar/animations/jump_standing_launch.fbx", + "url": "qrc:///avatar/animations/side_step_left.fbx", + "startFrame": 0.0, + "endFrame": 20.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftAnim_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left_fast.fbx", + "startFrame": 0.0, + "endFrame": 16.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "strafeRightHmd", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [ 0, 0.5, 2.5 ], + "alphaVar": "moveLateralAlpha", + "desiredSpeedVar": "moveLateralSpeed" + }, + "children": [ + { + "id": "stepRightShort_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_short_left.fbx", + "startFrame": 0.0, + "endFrame": 29.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "stepRight_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left.fbx", + "startFrame": 0.0, + "endFrame": 20.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "strafeRightAnim_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left_fast.fbx", + "startFrame": 0.0, + "endFrame": 16.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + } + ] + }, + { + "id": "fly", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/fly.fbx", + "startFrame": 1.0, + "endFrame": 80.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "takeoffStand", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_launch.fbx", + "startFrame": 2.0, + "endFrame": 16.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "TAKEOFFRUN", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "startFrame": 4.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirStand", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "inAirAlpha" + }, + "children": [ + { + "id": "inAirStandPreApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_apex.fbx", + "startFrame": 0.0, + "endFrame": 0.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirStandApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_apex.fbx", + "startFrame": 1.0, + "endFrame": 1.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirStandPostApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_apex.fbx", "startFrame": 2.0, + "endFrame": 2.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + } + ] + }, + { + "id": "INAIRRUN", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "inAirAlpha" + }, + "children": [ + { + "id": "inAirRunPreApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "startFrame": 16.0, "endFrame": 16.0, "timeScale": 1.0, "loopFlag": false @@ -2051,146 +2113,66 @@ "children": [] }, { - "id": "TAKEOFFRUN", + "id": "inAirRunApex", "type": "clip", "data": { "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", - "startFrame": 4.0, - "endFrame": 15.0, + "startFrame": 22.0, + "endFrame": 22.0, "timeScale": 1.0, "loopFlag": false }, "children": [] }, { - "id": "inAirStand", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "inAirAlpha" - }, - "children": [ - { - "id": "inAirStandPreApex", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_standing_apex.fbx", - "startFrame": 0.0, - "endFrame": 0.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "inAirStandApex", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_standing_apex.fbx", - "startFrame": 1.0, - "endFrame": 1.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "inAirStandPostApex", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_standing_apex.fbx", - "startFrame": 2.0, - "endFrame": 2.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - } - ] - }, - { - "id": "INAIRRUN", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "inAirAlpha" - }, - "children": [ - { - "id": "inAirRunPreApex", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", - "startFrame": 16.0, - "endFrame": 16.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "inAirRunApex", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", - "startFrame": 22.0, - "endFrame": 22.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "inAirRunPostApex", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", - "startFrame": 33.0, - "endFrame": 33.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - } - ] - }, - { - "id": "landStandImpact", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_standing_land_settle.fbx", - "startFrame": 1.0, - "endFrame": 6.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "landStand", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_standing_land_settle.fbx", - "startFrame": 6.0, - "endFrame": 68.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "LANDRUN", + "id": "inAirRunPostApex", "type": "clip", "data": { "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", - "startFrame": 29.0, - "endFrame": 40.0, + "startFrame": 33.0, + "endFrame": 33.0, "timeScale": 1.0, "loopFlag": false }, "children": [] } ] + }, + { + "id": "landStandImpact", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_land_settle.fbx", + "startFrame": 1.0, + "endFrame": 6.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "landStand", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_land_settle.fbx", + "startFrame": 6.0, + "endFrame": 68.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "LANDRUN", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "startFrame": 29.0, + "endFrame": 40.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] } ] } From cf8f9fa1b68eabd9c1e75a47af685f126b95218d Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Mon, 4 Feb 2019 11:28:42 -0700 Subject: [PATCH 052/474] Threads created correctly --- interface/src/avatar/AvatarManager.cpp | 103 ++++++++++++++++++ interface/src/avatar/AvatarManager.h | 9 ++ libraries/animation/src/Rig.cpp | 6 +- libraries/animation/src/Rig.h | 3 + .../src/avatars-renderer/Avatar.h | 3 + libraries/entities/src/EntityTree.cpp | 6 + libraries/entities/src/EntityTree.h | 1 + 7 files changed, 129 insertions(+), 2 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 1eb87c16f0..c9e099df21 100755 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -760,6 +760,109 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic return result; } +RayToAvatarIntersectionResult AvatarManager::findRayIntersectionOld(const PickRay& ray, + const QScriptValue& avatarIdsToInclude, + const QScriptValue& avatarIdsToDiscard) { + QVector avatarsToInclude = qVectorEntityItemIDFromScriptValue(avatarIdsToInclude); + QVector avatarsToDiscard = qVectorEntityItemIDFromScriptValue(avatarIdsToDiscard); + + return findRayIntersectionVectorOld(ray, avatarsToInclude, avatarsToDiscard); +} + +RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVectorOld(const PickRay& ray, + const QVector& avatarsToInclude, + const QVector& avatarsToDiscard) { + RayToAvatarIntersectionResult result; + if (QThread::currentThread() != thread()) { + BLOCKING_INVOKE_METHOD(const_cast(this), "findRayIntersectionVector", + Q_RETURN_ARG(RayToAvatarIntersectionResult, result), + Q_ARG(const PickRay&, ray), + Q_ARG(const QVector&, avatarsToInclude), + Q_ARG(const QVector&, avatarsToDiscard)); + return result; + } + + // It's better to intersect the ray against the avatar's actual mesh, but this is currently difficult to + // do, because the transformed mesh data only exists over in GPU-land. As a compromise, this code + // intersects against the avatars capsule and then against the (T-pose) mesh. The end effect is that picking + // against the avatar is sort-of right, but you likely wont be able to pick against the arms. + + // TODO -- find a way to extract transformed avatar mesh data from the rendering engine. + + std::vector sortedAvatars; + auto avatarHashCopy = getHashCopy(); + for (auto avatarData : avatarHashCopy) { + auto avatar = std::static_pointer_cast(avatarData); + if ((avatarsToInclude.size() > 0 && !avatarsToInclude.contains(avatar->getID())) || + (avatarsToDiscard.size() > 0 && avatarsToDiscard.contains(avatar->getID()))) { + continue; + } + + float distance = FLT_MAX; +#if 0 + // if we weren't picking against the capsule, we would want to pick against the avatarBounds... + SkeletonModelPointer avatarModel = avatar->getSkeletonModel(); + AABox avatarBounds = avatarModel->getRenderableMeshBound(); + if (avatarBounds.contains(ray.origin)) { + distance = 0.0f; + } + else { + float boundDistance = FLT_MAX; + BoxFace face; + glm::vec3 surfaceNormal; + if (avatarBounds.findRayIntersection(ray.origin, ray.direction, boundDistance, face, surfaceNormal)) { + distance = boundDistance; + } + } +#else + glm::vec3 start; + glm::vec3 end; + float radius; + avatar->getCapsule(start, end, radius); + findRayCapsuleIntersection(ray.origin, ray.direction, start, end, radius, distance); +#endif + + if (distance < FLT_MAX) { + sortedAvatars.emplace_back(distance, avatar); + } + } + + if (sortedAvatars.size() > 1) { + static auto comparator = [](const SortedAvatar& left, const SortedAvatar& right) { return left.first < right.first; }; + std::sort(sortedAvatars.begin(), sortedAvatars.end(), comparator); + } + + for (auto it = sortedAvatars.begin(); it != sortedAvatars.end(); ++it) { + const SortedAvatar& sortedAvatar = *it; + // We can exit once avatarCapsuleDistance > bestDistance + if (sortedAvatar.first > result.distance) { + break; + } + + float distance = FLT_MAX; + BoxFace face; + glm::vec3 surfaceNormal; + QVariantMap extraInfo; + SkeletonModelPointer avatarModel = sortedAvatar.second->getSkeletonModel(); + if (avatarModel->findRayIntersectionAgainstSubMeshes(ray.origin, ray.direction, distance, face, surfaceNormal, extraInfo, true)) { + if (distance < result.distance) { + result.intersects = true; + result.avatarID = sortedAvatar.second->getID(); + result.distance = distance; + result.face = face; + result.surfaceNormal = surfaceNormal; + result.extraInfo = extraInfo; + } + } + } + + if (result.intersects) { + result.intersection = ray.origin + ray.direction * result.distance; + } + + return result; +} + ParabolaToAvatarIntersectionResult AvatarManager::findParabolaIntersectionVector(const PickParabola& pick, const QVector& avatarsToInclude, const QVector& avatarsToDiscard) { diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 50d9e80e8b..66f28206e0 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -157,6 +157,15 @@ public: const QVector& avatarsToDiscard, bool pickAgainstMesh); + Q_INVOKABLE RayToAvatarIntersectionResult findRayIntersectionOld(const PickRay& ray, + const QScriptValue& avatarIdsToInclude = QScriptValue(), + const QScriptValue& avatarIdsToDiscard = QScriptValue()); + + Q_INVOKABLE RayToAvatarIntersectionResult findRayIntersectionVectorOld(const PickRay& ray, + const QVector& avatarsToInclude, + const QVector& avatarsToDiscard); + + /**jsdoc * @function AvatarManager.findParabolaIntersectionVector * @param {PickParabola} pick diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index bc4dca54f2..8a05bdd018 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -34,7 +34,6 @@ #include "IKTarget.h" #include "PathUtils.h" - static int nextRigId = 1; static std::map rigRegistry; static std::mutex rigRegistryMutex; @@ -1871,7 +1870,7 @@ void Rig::initAnimGraph(const QUrl& url) { auto roleState = roleAnimState.second; overrideRoleAnimation(roleState.role, roleState.url, roleState.fps, roleState.loop, roleState.firstFrame, roleState.lastFrame); } - + _flow.setRig(this); emit onLoadComplete(); }); connect(_animLoader.get(), &AnimNodeLoader::error, [url](int error, QString str) { @@ -1903,6 +1902,9 @@ void Rig::initAnimGraph(const QUrl& url) { connect(_networkLoader.get(), &AnimNodeLoader::error, [networkUrl](int error, QString str) { qCritical(animation) << "Error loading: code = " << error << "str =" << str; }); + connect(this, &Rig::onLoadComplete, [&]() { + _flow.calculateConstraints(); + }); } } diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 41c25a3c3e..9bd2ed528a 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -25,6 +25,7 @@ #include "AnimNodeLoader.h" #include "SimpleMovingAverage.h" #include "AnimUtil.h" +#include "Flow.h" class Rig; class AnimInverseKinematics; @@ -233,6 +234,7 @@ public: const AnimContext::DebugAlphaMap& getDebugAlphaMap() const { return _lastContext.getDebugAlphaMap(); } const AnimVariantMap& getAnimVars() const { return _lastAnimVars; } const AnimContext::DebugStateMachineMap& getStateMachineMap() const { return _lastContext.getStateMachineMap(); } + void computeFlowSkeleton() { _flow.calculateConstraints(); } signals: void onLoadComplete(); @@ -424,6 +426,7 @@ protected: SnapshotBlendPoseHelper _hipsBlendHelper; ControllerParameters _previousControllerParameters; + Flow _flow; }; #endif /* defined(__hifi__Rig__) */ diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index b43fe012b7..4ba2ffc07d 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -35,6 +35,7 @@ #include "MetaModelPayload.h" #include "MultiSphereShape.h" +#include "Flow.h" namespace render { template <> const ItemKey payloadGetKey(const AvatarSharedPointer& avatar); @@ -285,6 +286,8 @@ public: */ Q_INVOKABLE glm::quat jointToWorldRotation(const glm::quat& rotation, const int jointIndex = -1) const; + Q_INVOKABLE void callFlow() { _skeletonModel->getRig().computeFlowSkeleton(); }; + virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override; virtual void setAttachmentData(const QVector& attachmentData) override; diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 954462a9f2..47064bdbd3 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -2943,6 +2943,12 @@ int EntityTree::getJointIndex(const QUuid& entityID, const QString& name) const } return entity->getJointIndex(name); } +void EntityTree::callFlowSkeleton(const QUuid& entityID) { + /* + EntityTree* nonConstThis = const_cast(this); + EntityItemPointer entity = nonConstThis->findEntityByEntityItemID(entityID); + */ +} QStringList EntityTree::getJointNames(const QUuid& entityID) const { EntityTree* nonConstThis = const_cast(this); diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index f9b7b8d67f..cc80083e0d 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -240,6 +240,7 @@ public: // these are used to call through to EntityItems Q_INVOKABLE int getJointIndex(const QUuid& entityID, const QString& name) const; Q_INVOKABLE QStringList getJointNames(const QUuid& entityID) const; + Q_INVOKABLE void callFlowSkeleton(const QUuid& entityID); void knowAvatarID(QUuid avatarID) { _avatarIDs += avatarID; } void forgetAvatarID(QUuid avatarID) { _avatarIDs -= avatarID; } From 17cafe7cce02e78fbe748292b646d133b053a00b Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 30 Jan 2019 17:17:39 -0800 Subject: [PATCH 053/474] fix getEntityProperties (cherry picked from commit 6e61c02d04b65bcc32211b7b6e1667d5cab06bcd) --- libraries/entities/src/EntityItem.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 41e4f43a5d..1fb1ebb1bc 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -88,13 +88,13 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param requestedProperties += PROP_REGISTRATION_POINT; requestedProperties += PROP_CREATED; requestedProperties += PROP_LAST_EDITED_BY; - //requestedProperties += PROP_ENTITY_HOST_TYPE; // not sent over the wire - //requestedProperties += PROP_OWNING_AVATAR_ID; // not sent over the wire + requestedProperties += PROP_ENTITY_HOST_TYPE; + requestedProperties += PROP_OWNING_AVATAR_ID; requestedProperties += PROP_PARENT_ID; requestedProperties += PROP_PARENT_JOINT_INDEX; requestedProperties += PROP_QUERY_AA_CUBE; requestedProperties += PROP_CAN_CAST_SHADOW; - // requestedProperties += PROP_VISIBLE_IN_SECONDARY_CAMERA; // not sent over the wire + requestedProperties += PROP_VISIBLE_IN_SECONDARY_CAMERA; requestedProperties += PROP_RENDER_LAYER; requestedProperties += PROP_PRIMITIVE_MODE; requestedProperties += PROP_IGNORE_PICK_INTERSECTION; @@ -180,6 +180,11 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet EntityPropertyFlags propertyFlags(PROP_LAST_ITEM); EntityPropertyFlags requestedProperties = getEntityProperties(params); + // these properties are not sent over the wire + requestedProperties -= PROP_ENTITY_HOST_TYPE; + requestedProperties -= PROP_OWNING_AVATAR_ID; + requestedProperties -= PROP_VISIBLE_IN_SECONDARY_CAMERA; + // If we are being called for a subsequent pass at appendEntityData() that failed to completely encode this item, // then our entityTreeElementExtraEncodeData should include data about which properties we need to append. if (entityTreeElementExtraEncodeData && entityTreeElementExtraEncodeData->entities.contains(getEntityItemID())) { From a04e6d55ff50834467944beb1cd08b3ca22af977 Mon Sep 17 00:00:00 2001 From: amantley Date: Mon, 4 Feb 2019 13:31:36 -0800 Subject: [PATCH 054/474] enabled/disable ik now works for the new animspline json --- libraries/animation/src/AnimSplineIK.cpp | 4 ++-- libraries/animation/src/Rig.cpp | 28 ++++++++++++++++++------ 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/libraries/animation/src/AnimSplineIK.cpp b/libraries/animation/src/AnimSplineIK.cpp index 4dab904c05..bf329ef055 100644 --- a/libraries/animation/src/AnimSplineIK.cpp +++ b/libraries/animation/src/AnimSplineIK.cpp @@ -82,7 +82,7 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const AnimPoseVec underPoses = _children[0]->evaluate(animVars, context, dt, triggersOut); // if we don't have a skeleton, or jointName lookup failed or the spline alpha is 0 or there are no underposes. - if (!_skeleton || _baseJointIndex == -1 || _midJointIndex == -1 || _tipJointIndex == -1 || alpha == 0.0f || underPoses.size() == 0) { + if (!_skeleton || _baseJointIndex == -1 || _midJointIndex == -1 || _tipJointIndex == -1 || alpha < EPSILON || underPoses.size() == 0) { // pass underPoses through unmodified. _poses = underPoses; return _poses; @@ -474,4 +474,4 @@ void AnimSplineIK::beginInterp(InterpType interpType, const AnimChain& chain) { _interpType = interpType; _interpAlphaVel = FRAMES_PER_SECOND / _interpDuration; _interpAlpha = 0.0f; -} \ No newline at end of file +} diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 713f9bc385..2faeddaa4f 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1068,16 +1068,30 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos t += deltaTime; - if (_enableInverseKinematics != _lastEnableInverseKinematics) { - if (_enableInverseKinematics) { - _animVars.set("ikOverlayAlpha", 1.0f); - } else { - _animVars.set("ikOverlayAlpha", 0.0f); - } + if (_enableInverseKinematics) { + _animVars.set("splineIKEnabled", true); + _animVars.set("leftHandIKEnabled", true); + _animVars.set("rightHandIKEnabled", true); + _animVars.set("leftFootIKEnabled", true); + _animVars.set("rightFootIKEnabled", true); + _animVars.set("leftHandPoleVectorEnabled", true); + _animVars.set("rightHandPoleVectorEnabled", true); + _animVars.set("leftFootPoleVectorEnabled", true); + _animVars.set("rightFootPoleVectorEnabled", true); + } else { + _animVars.set("splineIKEnabled", false); + _animVars.set("leftHandIKEnabled", false); + _animVars.set("rightHandIKEnabled", false); + _animVars.set("leftFootIKEnabled", false); + _animVars.set("rightFootIKEnabled", false); + _animVars.set("leftHandPoleVectorEnabled", false); + _animVars.set("rightHandPoleVectorEnabled", false); + _animVars.set("leftFootPoleVectorEnabled", false); + _animVars.set("rightFootPoleVectorEnabled", false); } _lastEnableInverseKinematics = _enableInverseKinematics; - } + } _lastForward = forward; _lastPosition = worldPosition; _lastVelocity = workingVelocity; From 78d6e42fc85725227be101d3c06fb3bbec2e5c03 Mon Sep 17 00:00:00 2001 From: amantley Date: Mon, 4 Feb 2019 13:42:19 -0800 Subject: [PATCH 055/474] made the ik enable/disable work for the old animIK node json --- libraries/animation/src/Rig.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 2faeddaa4f..f366c08cfe 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1069,6 +1069,10 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos t += deltaTime; if (_enableInverseKinematics) { + + // animIK node json + _animVars.set("ikOverlayAlpha", 1.0f); + // animSpline json _animVars.set("splineIKEnabled", true); _animVars.set("leftHandIKEnabled", true); _animVars.set("rightHandIKEnabled", true); @@ -1079,6 +1083,9 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _animVars.set("leftFootPoleVectorEnabled", true); _animVars.set("rightFootPoleVectorEnabled", true); } else { + // animIK node json + _animVars.set("ikOverlayAlpha", 0.0f); + // animSpline json _animVars.set("splineIKEnabled", false); _animVars.set("leftHandIKEnabled", false); _animVars.set("rightHandIKEnabled", false); From 031dd5639b69553fae579f4ded72f725b8538365 Mon Sep 17 00:00:00 2001 From: amantley Date: Mon, 4 Feb 2019 14:46:52 -0800 Subject: [PATCH 056/474] fixed pole vector over writing with the old json --- libraries/animation/src/Rig.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index f366c08cfe..faddfdebb3 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1069,23 +1069,16 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos t += deltaTime; if (_enableInverseKinematics) { - - // animIK node json _animVars.set("ikOverlayAlpha", 1.0f); - // animSpline json _animVars.set("splineIKEnabled", true); _animVars.set("leftHandIKEnabled", true); _animVars.set("rightHandIKEnabled", true); _animVars.set("leftFootIKEnabled", true); _animVars.set("rightFootIKEnabled", true); - _animVars.set("leftHandPoleVectorEnabled", true); - _animVars.set("rightHandPoleVectorEnabled", true); _animVars.set("leftFootPoleVectorEnabled", true); _animVars.set("rightFootPoleVectorEnabled", true); } else { - // animIK node json _animVars.set("ikOverlayAlpha", 0.0f); - // animSpline json _animVars.set("splineIKEnabled", false); _animVars.set("leftHandIKEnabled", false); _animVars.set("rightHandIKEnabled", false); @@ -1097,7 +1090,6 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _animVars.set("rightFootPoleVectorEnabled", false); } _lastEnableInverseKinematics = _enableInverseKinematics; - } _lastForward = forward; _lastPosition = worldPosition; From 3e553f015c7f5f6b87d79cd0980b8e88b1e6c321 Mon Sep 17 00:00:00 2001 From: amantley Date: Tue, 5 Feb 2019 15:54:37 -0800 Subject: [PATCH 057/474] added the arm ik files to the repo, this is for the shoulder and elbow extensions --- libraries/animation/src/AnimArmIK.cpp | 40 +++++++++++++++++++++++++++ libraries/animation/src/AnimArmIK.h | 34 +++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 libraries/animation/src/AnimArmIK.cpp create mode 100644 libraries/animation/src/AnimArmIK.h diff --git a/libraries/animation/src/AnimArmIK.cpp b/libraries/animation/src/AnimArmIK.cpp new file mode 100644 index 0000000000..202e8c8420 --- /dev/null +++ b/libraries/animation/src/AnimArmIK.cpp @@ -0,0 +1,40 @@ +// +// AnimArmIK.cpp +// +// Created by Angus Antley on 1/9/19. +// Copyright (c) 2019 High Fidelity, Inc. All rights reserved. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "AnimArmIK.h" + +#include + +#include "AnimationLogging.h" +#include "AnimUtil.h" + +AnimArmIK::AnimArmIK(const QString& id, float alpha, bool enabled, float interpDuration, + const QString& baseJointName, const QString& midJointName, + const QString& tipJointName, const glm::vec3& midHingeAxis, + const QString& alphaVar, const QString& enabledVar, + const QString& endEffectorRotationVarVar, const QString& endEffectorPositionVarVar) : + AnimTwoBoneIK(id, alpha, enabled, interpDuration, baseJointName, midJointName, tipJointName, midHingeAxis, alphaVar, enabledVar, endEffectorRotationVarVar, endEffectorPositionVarVar) { + +} + +AnimArmIK::~AnimArmIK() { + +} + +const AnimPoseVec& AnimArmIK::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { + qCDebug(animation) << "evaluating the arm IK"; + + assert(_children.size() == 1); + if (_children.size() != 1) { + return _poses; + } else { + return _poses; + } +} \ No newline at end of file diff --git a/libraries/animation/src/AnimArmIK.h b/libraries/animation/src/AnimArmIK.h new file mode 100644 index 0000000000..26f79a1b9c --- /dev/null +++ b/libraries/animation/src/AnimArmIK.h @@ -0,0 +1,34 @@ +// +// AnimArmIK.h +// +// Created by Angus Antley on 1/9/19. +// Copyright (c) 2019 High Fidelity, Inc. All rights reserved. +// +// 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_AnimArmIK_h +#define hifi_AnimArmIK_h + +//#include "AnimNode.h" +#include "AnimTwoBoneIK.h" +//#include "AnimChain.h" + +// Simple two bone IK chain +class AnimArmIK : public AnimTwoBoneIK { +public: + AnimArmIK(const QString& id, float alpha, bool enabled, float interpDuration, + const QString& baseJointName, const QString& midJointName, + const QString& tipJointName, const glm::vec3& midHingeAxis, + const QString& alphaVar, const QString& enabledVar, + const QString& endEffectorRotationVarVar, const QString& endEffectorPositionVarVar); + virtual ~AnimArmIK(); + virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override; + +protected: + +}; + +#endif // hifi_AnimArmIK_h + From c8a0d61fd15dedf0ec28f1970a0c58436f8d1e97 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 5 Feb 2019 17:00:18 -0800 Subject: [PATCH 058/474] don't flushRepeatedMessages() in LogHandler dtor --- libraries/shared/src/LogHandler.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/shared/src/LogHandler.cpp b/libraries/shared/src/LogHandler.cpp index 65651373be..c51d9bf611 100644 --- a/libraries/shared/src/LogHandler.cpp +++ b/libraries/shared/src/LogHandler.cpp @@ -38,7 +38,6 @@ LogHandler::LogHandler() { } LogHandler::~LogHandler() { - flushRepeatedMessages(); } const char* stringForLogType(LogMsgType msgType) { From 07a4f49c58196c3f53718bdc6e0e62f80f52af58 Mon Sep 17 00:00:00 2001 From: amantley Date: Tue, 5 Feb 2019 17:21:39 -0800 Subject: [PATCH 059/474] adding the armik nodes into the json --- .../avatar-animation_withSplineIKNode.json | 4 +-- libraries/animation/src/AnimArmIK.cpp | 16 ++++++------ libraries/animation/src/AnimNodeLoader.cpp | 25 +++++++++++++++++++ 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/interface/resources/avatar/avatar-animation_withSplineIKNode.json b/interface/resources/avatar/avatar-animation_withSplineIKNode.json index b1f198c52c..9fd64860bd 100644 --- a/interface/resources/avatar/avatar-animation_withSplineIKNode.json +++ b/interface/resources/avatar/avatar-animation_withSplineIKNode.json @@ -129,7 +129,7 @@ "children": [ { "id": "rightHandIK", - "type": "twoBoneIK", + "type": "armIK", "data": { "alpha": 1.0, "enabled": false, @@ -159,7 +159,7 @@ "children": [ { "id": "leftHandIK", - "type": "twoBoneIK", + "type": "armIK", "data": { "alpha": 1.0, "enabled": false, diff --git a/libraries/animation/src/AnimArmIK.cpp b/libraries/animation/src/AnimArmIK.cpp index 202e8c8420..ba720f1126 100644 --- a/libraries/animation/src/AnimArmIK.cpp +++ b/libraries/animation/src/AnimArmIK.cpp @@ -20,7 +20,8 @@ AnimArmIK::AnimArmIK(const QString& id, float alpha, bool enabled, float interpD const QString& tipJointName, const glm::vec3& midHingeAxis, const QString& alphaVar, const QString& enabledVar, const QString& endEffectorRotationVarVar, const QString& endEffectorPositionVarVar) : - AnimTwoBoneIK(id, alpha, enabled, interpDuration, baseJointName, midJointName, tipJointName, midHingeAxis, alphaVar, enabledVar, endEffectorRotationVarVar, endEffectorPositionVarVar) { + AnimTwoBoneIK(id, alpha, enabled, interpDuration, baseJointName, midJointName, tipJointName, midHingeAxis, alphaVar, enabledVar, endEffectorRotationVarVar, endEffectorPositionVarVar) +{ } @@ -29,12 +30,13 @@ AnimArmIK::~AnimArmIK() { } const AnimPoseVec& AnimArmIK::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { + qCDebug(animation) << "evaluating the arm IK"; + _poses = AnimTwoBoneIK::evaluate(animVars, context, dt, triggersOut); + + //assert(_children.size() == 1); + //if (_children.size() != 1) { + // return _poses; + //} - assert(_children.size() == 1); - if (_children.size() != 1) { - return _poses; - } else { - return _poses; - } } \ No newline at end of file diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index b637d131f8..707bf7b976 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -26,6 +26,7 @@ #include "AnimInverseKinematics.h" #include "AnimDefaultPose.h" #include "AnimTwoBoneIK.h" +#include "AnimArmIK.h" #include "AnimSplineIK.h" #include "AnimPoleVectorConstraint.h" @@ -42,6 +43,7 @@ static AnimNode::Pointer loadManipulatorNode(const QJsonObject& jsonObj, const Q static AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer loadDefaultPoseNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer loadTwoBoneIKNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); +static AnimNode::Pointer loadArmIKNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer loadSplineIKNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer loadPoleVectorConstraintNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); @@ -63,6 +65,7 @@ static const char* animNodeTypeToString(AnimNode::Type type) { case AnimNode::Type::InverseKinematics: return "inverseKinematics"; case AnimNode::Type::DefaultPose: return "defaultPose"; case AnimNode::Type::TwoBoneIK: return "twoBoneIK"; + case AnimNode::Type::ArmIK: return "armIK"; case AnimNode::Type::SplineIK: return "splineIK"; case AnimNode::Type::PoleVectorConstraint: return "poleVectorConstraint"; case AnimNode::Type::NumTypes: return nullptr; @@ -126,6 +129,7 @@ static NodeLoaderFunc animNodeTypeToLoaderFunc(AnimNode::Type type) { case AnimNode::Type::InverseKinematics: return loadInverseKinematicsNode; case AnimNode::Type::DefaultPose: return loadDefaultPoseNode; case AnimNode::Type::TwoBoneIK: return loadTwoBoneIKNode; + case AnimNode::Type::ArmIK: return loadArmIKNode; case AnimNode::Type::SplineIK: return loadSplineIKNode; case AnimNode::Type::PoleVectorConstraint: return loadPoleVectorConstraintNode; case AnimNode::Type::NumTypes: return nullptr; @@ -144,6 +148,7 @@ static NodeProcessFunc animNodeTypeToProcessFunc(AnimNode::Type type) { case AnimNode::Type::InverseKinematics: return processDoNothing; case AnimNode::Type::DefaultPose: return processDoNothing; case AnimNode::Type::TwoBoneIK: return processDoNothing; + case AnimNode::Type::ArmIK: return processDoNothing; case AnimNode::Type::SplineIK: return processDoNothing; case AnimNode::Type::PoleVectorConstraint: return processDoNothing; case AnimNode::Type::NumTypes: return nullptr; @@ -625,6 +630,26 @@ static AnimNode::Pointer loadSplineIKNode(const QJsonObject& jsonObj, const QStr return node; } +static AnimNode::Pointer loadArmIKNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { + READ_FLOAT(alpha, jsonObj, id, jsonUrl, nullptr); + READ_BOOL(enabled, jsonObj, id, jsonUrl, nullptr); + READ_FLOAT(interpDuration, jsonObj, id, jsonUrl, nullptr); + READ_STRING(baseJointName, jsonObj, id, jsonUrl, nullptr); + READ_STRING(midJointName, jsonObj, id, jsonUrl, nullptr); + READ_STRING(tipJointName, jsonObj, id, jsonUrl, nullptr); + READ_VEC3(midHingeAxis, jsonObj, id, jsonUrl, nullptr); + READ_STRING(alphaVar, jsonObj, id, jsonUrl, nullptr); + READ_STRING(enabledVar, jsonObj, id, jsonUrl, nullptr); + READ_STRING(endEffectorRotationVarVar, jsonObj, id, jsonUrl, nullptr); + READ_STRING(endEffectorPositionVarVar, jsonObj, id, jsonUrl, nullptr); + + auto node = std::make_shared(id, alpha, enabled, interpDuration, + baseJointName, midJointName, tipJointName, midHingeAxis, + alphaVar, enabledVar, + endEffectorRotationVarVar, endEffectorPositionVarVar); + return node; +} + static AnimNode::Pointer loadTwoBoneIKNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { READ_FLOAT(alpha, jsonObj, id, jsonUrl, nullptr); READ_BOOL(enabled, jsonObj, id, jsonUrl, nullptr); From 9ad20b81296f80a972fc1b3e96c0ee8e9314828f Mon Sep 17 00:00:00 2001 From: amantley Date: Tue, 5 Feb 2019 17:22:00 -0800 Subject: [PATCH 060/474] adding the animcontext to the commit --- libraries/animation/src/AnimContext.h | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/animation/src/AnimContext.h b/libraries/animation/src/AnimContext.h index e3ab5d9788..2c5b010f1b 100644 --- a/libraries/animation/src/AnimContext.h +++ b/libraries/animation/src/AnimContext.h @@ -28,6 +28,7 @@ enum class AnimNodeType { InverseKinematics, DefaultPose, TwoBoneIK, + ArmIK, SplineIK, PoleVectorConstraint, NumTypes From 22cfcc4ac99c54cce44259d5e40ac39d44856860 Mon Sep 17 00:00:00 2001 From: amantley Date: Tue, 5 Feb 2019 17:25:11 -0800 Subject: [PATCH 061/474] deleted some unity files --- .../unity-avatar-exporter/Assets/Editor.meta | 8 ------- tools/unity-avatar-exporter/Assets/README.txt | 23 ------------------- .../Assets/README.txt.meta | 7 ------ 3 files changed, 38 deletions(-) delete mode 100644 tools/unity-avatar-exporter/Assets/Editor.meta delete mode 100644 tools/unity-avatar-exporter/Assets/README.txt delete mode 100644 tools/unity-avatar-exporter/Assets/README.txt.meta diff --git a/tools/unity-avatar-exporter/Assets/Editor.meta b/tools/unity-avatar-exporter/Assets/Editor.meta deleted file mode 100644 index cf7dcf12dd..0000000000 --- a/tools/unity-avatar-exporter/Assets/Editor.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 02111c50e71dd664da8ad5c6a6eca767 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/tools/unity-avatar-exporter/Assets/README.txt b/tools/unity-avatar-exporter/Assets/README.txt deleted file mode 100644 index f02bc688ae..0000000000 --- a/tools/unity-avatar-exporter/Assets/README.txt +++ /dev/null @@ -1,23 +0,0 @@ -High Fidelity, Inc. -Avatar Exporter -Version 0.1 - -Note: It is recommended to use Unity versions between 2017.4.17f1 and 2018.2.12f1 for this Avatar Exporter. - -To create a new avatar project: -1. Import your .fbx avatar model into your Unity project's Assets by either dragging and dropping the file into the Assets window or by using Assets menu > Import New Assets. -2. Select the .fbx avatar that you imported in step 1 in the Assets window, and in the Rig section of the Inspector window set the Animation Type to Humanoid and choose Apply. -3. With the .fbx avatar still selected in the Assets window, choose High Fidelity menu > Export New Avatar. -4. Select a name for your avatar project (this will be used to create a directory with that name), as well as the target location for your project folder. -5. Once it is exported, your project directory will open in File Explorer. - -To update an existing avatar project: -1. Select the existing .fbx avatar in the Assets window that you would like to re-export. -2. Choose High Fidelity menu > Update Existing Avatar and browse to the .fst file you would like to update. -3. If the .fbx file in your Unity Assets folder is newer than the existing .fbx file in your selected avatar project or vice-versa, you will be prompted if you wish to replace the older file with the newer file before performing the update. -4. Once it is updated, your project directory will open in File Explorer. - -* WARNING * -If you are using any external textures as part of your .fbx model, be sure they are copied into the textures folder that is created in the project folder after exporting a new avatar. - -For further details including troubleshooting tips, see the full documentation at https://docs.highfidelity.com/create-and-explore/avatars/create-avatars/unity-extension diff --git a/tools/unity-avatar-exporter/Assets/README.txt.meta b/tools/unity-avatar-exporter/Assets/README.txt.meta deleted file mode 100644 index 148fd21fdd..0000000000 --- a/tools/unity-avatar-exporter/Assets/README.txt.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 30b2b6221fd08234eb07c4d6d525d32e -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: From e2c9058f0a36535bc1c5c8a4523b6312cfe45d0d Mon Sep 17 00:00:00 2001 From: amantley Date: Wed, 6 Feb 2019 18:29:33 -0800 Subject: [PATCH 062/474] first try at the new elbow code --- .../src/AnimPoleVectorConstraint.cpp | 58 ++++++++++++++++++- .../animation/src/AnimPoleVectorConstraint.h | 2 + 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/libraries/animation/src/AnimPoleVectorConstraint.cpp b/libraries/animation/src/AnimPoleVectorConstraint.cpp index f017fe2348..7431b6620b 100644 --- a/libraries/animation/src/AnimPoleVectorConstraint.cpp +++ b/libraries/animation/src/AnimPoleVectorConstraint.cpp @@ -34,6 +34,53 @@ AnimPoleVectorConstraint::~AnimPoleVectorConstraint() { } +float AnimPoleVectorConstraint::findThetaNewWay(const glm::vec3& hand, const glm::vec3& shoulder) const { + // get the default poses for the upper and lower arm + // then use this length to judge how far the arm is away from the shoulder. + // then create weights that make the elbow angle less when the x value is large in either direction. + // make the angle less when z is small. + // lower y with x center lower angle + // lower y with x out higher angle + AnimPose shoulderPose = _skeleton->getAbsoluteDefaultPose(_skeleton->nameToJointIndex("RightShoulder")); + AnimPose handPose = _skeleton->getAbsoluteDefaultPose(_skeleton->nameToJointIndex("RightHand")); + // subtract 10 centimeters from the arm length for some reason actual arm position is clamped to length - 10cm. + float defaultArmLength = glm::length( handPose.trans() - shoulderPose.trans() ) - 10.0f; + qCDebug(animation) << "default arm length " << defaultArmLength; + + // phi_0 is the lowest angle we can have + const float phi_0 = 15.0f; + // biases + const glm::vec3 biases(60.0f, 120.0f, -30.0f); + // weights + const glm::vec3 weights(-70.0f, 60.0f, 210.0f); + glm::vec3 armToHand = hand - shoulder; + qCDebug(animation) << "current arm length " << glm::length(armToHand); + float initial_valuesX = ((fabsf(armToHand[0]) / defaultArmLength) * weights[0]) + biases[0]; + float initial_valuesY = ((armToHand[1] / defaultArmLength) * weights[1]) + biases[1]; + float initial_valuesZ = ((armToHand[2] / defaultArmLength) * weights[2]) + biases[2]; + if (initial_valuesX < 0.0f) { + initial_valuesX = 0.0f; + } + if (initial_valuesY < 0.0f) { + initial_valuesY = 0.0f; + } + if (initial_valuesZ < 0.0f) { + initial_valuesZ = 0.0f; + } + + float theta = initial_valuesX + initial_valuesY + initial_valuesZ; + + if (theta < 13.0f) { + theta = 13.0f; + } + if (theta > 175.0f) { + theta = 175.0f; + } + + qCDebug(animation) << "relative hand" << initial_valuesX << " " << initial_valuesY << " " << initial_valuesZ << "theta value for pole vector is " << theta; + return theta; +} + const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { assert(_children.size() == 1); @@ -121,7 +168,14 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim float sideDot = glm::dot(poleVector, sideVector); float theta = copysignf(1.0f, sideDot) * acosf(dot); - glm::quat deltaRot = glm::angleAxis(theta, unitAxis); + float fred; + if (_skeleton->nameToJointIndex("RightHand") == _tipJointIndex) { + qCDebug(animation) << "theta from the old code " << theta; + fred = findThetaNewWay(tipPose.trans(), basePose.trans()); + } + + //glm::quat deltaRot = glm::angleAxis(theta, unitAxis); + glm::quat deltaRot = glm::angleAxis(((180.0f - fred)/180.0f)*PI, unitAxis); // transform result back into parent relative frame. glm::quat relBaseRot = glm::inverse(baseParentPose.rot()) * deltaRot * basePose.rot(); @@ -208,6 +262,8 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim return _poses; } + + // for AnimDebugDraw rendering const AnimPoseVec& AnimPoleVectorConstraint::getPosesInternal() const { return _poses; diff --git a/libraries/animation/src/AnimPoleVectorConstraint.h b/libraries/animation/src/AnimPoleVectorConstraint.h index 44e22671c1..df8cddec7d 100644 --- a/libraries/animation/src/AnimPoleVectorConstraint.h +++ b/libraries/animation/src/AnimPoleVectorConstraint.h @@ -25,6 +25,8 @@ public: const QString& enabledVar, const QString& poleVectorVar); virtual ~AnimPoleVectorConstraint() override; + float findThetaNewWay(const glm::vec3& hand, const glm::vec3& shoulder) const; + virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override; protected: From e0cb37af4b680b3754eb1b6acc92583a0ce64b5a Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 7 Feb 2019 10:14:10 -0800 Subject: [PATCH 063/474] fix black albedo coloring (cherry picked from commit 7fe0e5909e7ef4e13eaeb8cfe7b94e5c40f36465) --- libraries/graphics/src/graphics/Material.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/graphics/src/graphics/Material.cpp b/libraries/graphics/src/graphics/Material.cpp index 7befb7e053..7743c4bf50 100755 --- a/libraries/graphics/src/graphics/Material.cpp +++ b/libraries/graphics/src/graphics/Material.cpp @@ -87,7 +87,7 @@ void Material::setUnlit(bool value) { } void Material::setAlbedo(const glm::vec3& albedo, bool isSRGB) { - _key.setAlbedo(glm::any(glm::greaterThan(albedo, glm::vec3(0.0f)))); + _key.setAlbedo(true); _albedo = (isSRGB ? ColorUtils::sRGBToLinearVec3(albedo) : albedo); } From 4d9d597b4f0b4ca04e6031b787be4a563abd7bb9 Mon Sep 17 00:00:00 2001 From: amantley Date: Thu, 7 Feb 2019 11:14:33 -0800 Subject: [PATCH 064/474] tweaked the weights for the arms and negated the theta for the left arm --- .../animation/src/AnimPoleVectorConstraint.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/libraries/animation/src/AnimPoleVectorConstraint.cpp b/libraries/animation/src/AnimPoleVectorConstraint.cpp index 7431b6620b..55fbf20691 100644 --- a/libraries/animation/src/AnimPoleVectorConstraint.cpp +++ b/libraries/animation/src/AnimPoleVectorConstraint.cpp @@ -50,13 +50,13 @@ float AnimPoleVectorConstraint::findThetaNewWay(const glm::vec3& hand, const glm // phi_0 is the lowest angle we can have const float phi_0 = 15.0f; // biases - const glm::vec3 biases(60.0f, 120.0f, -30.0f); + const glm::vec3 biases(30.0f, 120.0f, -30.0f); // weights - const glm::vec3 weights(-70.0f, 60.0f, 210.0f); + const glm::vec3 weights(-30.0f, 30.0f, 210.0f); glm::vec3 armToHand = hand - shoulder; qCDebug(animation) << "current arm length " << glm::length(armToHand); float initial_valuesX = ((fabsf(armToHand[0]) / defaultArmLength) * weights[0]) + biases[0]; - float initial_valuesY = ((armToHand[1] / defaultArmLength) * weights[1]) + biases[1]; + float initial_valuesY = ((fabs(armToHand[1]) / defaultArmLength) * weights[1]) + biases[1]; float initial_valuesZ = ((armToHand[2] / defaultArmLength) * weights[2]) + biases[2]; if (initial_valuesX < 0.0f) { initial_valuesX = 0.0f; @@ -169,13 +169,17 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim float theta = copysignf(1.0f, sideDot) * acosf(dot); float fred; - if (_skeleton->nameToJointIndex("RightHand") == _tipJointIndex) { + if ((_skeleton->nameToJointIndex("RightHand") == _tipJointIndex) || (_skeleton->nameToJointIndex("LeftHand") == _tipJointIndex)) { qCDebug(animation) << "theta from the old code " << theta; fred = findThetaNewWay(tipPose.trans(), basePose.trans()); + if (_skeleton->nameToJointIndex("LeftHand") == _tipJointIndex) { + fred = -1.0f * fred; + } + theta = ((180.0f - fred) / 180.0f)*PI; } //glm::quat deltaRot = glm::angleAxis(theta, unitAxis); - glm::quat deltaRot = glm::angleAxis(((180.0f - fred)/180.0f)*PI, unitAxis); + glm::quat deltaRot = glm::angleAxis(theta, unitAxis); // transform result back into parent relative frame. glm::quat relBaseRot = glm::inverse(baseParentPose.rot()) * deltaRot * basePose.rot(); From 0e9e4b33321edb0ae14629d206511258f6592d04 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Thu, 7 Feb 2019 11:22:33 -0800 Subject: [PATCH 065/474] Fix normal textures not being visible --- .../model-baker/src/model-baker/CalculateMeshTangentsTask.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp b/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp index e94e15507e..c561a745b5 100644 --- a/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp +++ b/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp @@ -47,7 +47,7 @@ void CalculateMeshTangentsTask::run(const baker::BakeContextPointer& context, co break; } } - if (needTangents) { + if (!needTangents) { continue; } From 466c2261937dd532e493bb1b4ae14525badd15e9 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Thu, 7 Feb 2019 11:27:08 -0800 Subject: [PATCH 066/474] Do not use continues in logic checking if we should calculate mesh tangents --- .../model-baker/CalculateMeshTangentsTask.cpp | 53 ++++++++----------- 1 file changed, 23 insertions(+), 30 deletions(-) diff --git a/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp b/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp index c561a745b5..6e12ec546d 100644 --- a/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp +++ b/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp @@ -13,6 +13,17 @@ #include "ModelMath.h" +bool needTangents(const hfm::Mesh& mesh, const QHash& materials) { + // Check if we actually need to calculate the tangents + for (const auto& meshPart : mesh.parts) { + auto materialIt = materials.find(meshPart.materialID); + if (materialIt != materials.end() && (*materialIt).needTangentSpace()) { + return true; + } + } + return false; +} + void CalculateMeshTangentsTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) { const auto& normalsPerMesh = input.get0(); const std::vector& meshes = input.get1(); @@ -28,38 +39,20 @@ void CalculateMeshTangentsTask::run(const baker::BakeContextPointer& context, co auto& tangentsOut = tangentsPerMeshOut[tangentsPerMeshOut.size()-1]; // Check if we already have tangents and therefore do not need to do any calculation + // Otherwise confirm if we have the normals needed, and need to calculate the tangents if (!tangentsIn.empty()) { tangentsOut = tangentsIn.toStdVector(); - continue; + } else if (!normals.empty() && needTangents(mesh, materials)) { + tangentsOut.resize(normals.size()); + baker::calculateTangents(mesh, + [&mesh, &normals, &tangentsOut](int firstIndex, int secondIndex, glm::vec3* outVertices, glm::vec2* outTexCoords, glm::vec3& outNormal) { + outVertices[0] = mesh.vertices[firstIndex]; + outVertices[1] = mesh.vertices[secondIndex]; + outNormal = normals[firstIndex]; + outTexCoords[0] = mesh.texCoords[firstIndex]; + outTexCoords[1] = mesh.texCoords[secondIndex]; + return &(tangentsOut[firstIndex]); + }); } - - // Check if we have normals, and if not then tangents can't be calculated - if (normals.empty()) { - continue; - } - - // Check if we actually need to calculate the tangents - bool needTangents = false; - for (const auto& meshPart : mesh.parts) { - auto materialIt = materials.find(meshPart.materialID); - if (materialIt != materials.end() && (*materialIt).needTangentSpace()) { - needTangents = true; - break; - } - } - if (!needTangents) { - continue; - } - - tangentsOut.resize(normals.size()); - baker::calculateTangents(mesh, - [&mesh, &normals, &tangentsOut](int firstIndex, int secondIndex, glm::vec3* outVertices, glm::vec2* outTexCoords, glm::vec3& outNormal) { - outVertices[0] = mesh.vertices[firstIndex]; - outVertices[1] = mesh.vertices[secondIndex]; - outNormal = normals[firstIndex]; - outTexCoords[0] = mesh.texCoords[firstIndex]; - outTexCoords[1] = mesh.texCoords[secondIndex]; - return &(tangentsOut[firstIndex]); - }); } } From 02646fb8a9fffe8ba36c05103f29852ad0db26e5 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Thu, 7 Feb 2019 14:09:53 -0700 Subject: [PATCH 067/474] Working flow as rig param --- libraries/animation/src/Flow.cpp | 714 ++++++++++++++++++ libraries/animation/src/Flow.h | 315 ++++++++ libraries/animation/src/Rig.cpp | 5 +- libraries/animation/src/Rig.h | 1 + .../src/avatars-renderer/Avatar.cpp | 18 + 5 files changed, 1052 insertions(+), 1 deletion(-) create mode 100644 libraries/animation/src/Flow.cpp create mode 100644 libraries/animation/src/Flow.h diff --git a/libraries/animation/src/Flow.cpp b/libraries/animation/src/Flow.cpp new file mode 100644 index 0000000000..a10d413d90 --- /dev/null +++ b/libraries/animation/src/Flow.cpp @@ -0,0 +1,714 @@ +// +// Flow.cpp +// +// Created by Luis Cuenca on 1/21/2019. +// Copyright 2019 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 "Flow.h" +#include "Rig.h" +#include "AnimSkeleton.h" + +FlowCollisionSphere::FlowCollisionSphere(const int& jointIndex, const FlowCollisionSettings& settings) { + _jointIndex = jointIndex; + _radius = _initialRadius = settings._radius; + _offset = _initialOffset = settings._offset; + _entityID = settings._entityID; +} + +FlowCollisionResult FlowCollisionSphere::computeSphereCollision(const glm::vec3& point, float radius) const { + FlowCollisionResult result; + auto centerToJoint = point - _position; + result._distance = glm::length(centerToJoint) - radius; + result._offset = _radius - result._distance; + result._normal = glm::normalize(centerToJoint); + result._radius = _radius; + result._position = _position; + return result; +} + +FlowCollisionResult FlowCollisionSphere::checkSegmentCollision(const glm::vec3& point1, const glm::vec3& point2, const FlowCollisionResult& collisionResult1, const FlowCollisionResult& collisionResult2) { + FlowCollisionResult result; + auto segment = point2 - point1; + auto segmentLength = glm::length(segment); + auto maxDistance = glm::sqrt(glm::pow(collisionResult1._radius, 2) + glm::pow(segmentLength, 2)); + if (collisionResult1._distance < maxDistance && collisionResult2._distance < maxDistance) { + float segmentPercentage = collisionResult1._distance / (collisionResult1._distance + collisionResult2._distance); + glm::vec3 collisionPoint = point1 + segment * segmentPercentage; + glm::vec3 centerToSegment = collisionPoint - _position; + float distance = glm::length(centerToSegment); + if (distance < _radius) { + result._offset = _radius - distance; + result._position = _position; + result._radius = _radius; + result._normal = glm::normalize(centerToSegment); + result._distance = distance; + } + } + return result; +} + +void FlowCollisionSphere::update() { + // TODO + // Fill this +} + +void FlowCollisionSystem::addCollisionSphere(int jointIndex, const FlowCollisionSettings& settings) { + _collisionSpheres.push_back(FlowCollisionSphere(jointIndex, settings)); +}; + +void FlowCollisionSystem::addCollisionShape(int jointIndex, const FlowCollisionSettings& settings) { + auto name = JOINT_COLLISION_PREFIX + jointIndex; + switch (settings._type) { + case FlowCollisionType::CollisionSphere: + addCollisionSphere(jointIndex, settings); + break; + default: + break; + } +}; + +bool FlowCollisionSystem::addCollisionToJoint(int jointIndex) { + if (_collisionSpheres.size() >= COLLISION_SHAPES_LIMIT) { + return false; + } + int collisionIndex = findCollisionWithJoint(jointIndex); + if (collisionIndex == -1) { + addCollisionShape(jointIndex, FlowCollisionSettings()); + return true; + } + else { + return false; + } +}; + +void FlowCollisionSystem::removeCollisionFromJoint(int jointIndex) { + int collisionIndex = findCollisionWithJoint(jointIndex); + if (collisionIndex > -1) { + _collisionSpheres.erase(_collisionSpheres.begin() + collisionIndex); + } +}; + +void FlowCollisionSystem::update() { + for (size_t i = 0; i < _collisionSpheres.size(); i++) { + _collisionSpheres[i].update(); + } +}; + +FlowCollisionResult FlowCollisionSystem::computeCollision(const std::vector collisions) { + FlowCollisionResult result; + if (collisions.size() > 1) { + for (size_t i = 0; i < collisions.size(); i++) { + result._offset += collisions[i]._offset; + result._normal = result._normal + collisions[i]._normal * collisions[i]._distance; + result._position = result._position + collisions[i]._position; + result._radius += collisions[i]._radius; + result._distance += collisions[i]._distance; + } + result._offset = result._offset / collisions.size(); + result._radius = 0.5f * glm::length(result._normal); + result._normal = glm::normalize(result._normal); + result._position = result._position / (float)collisions.size(); + result._distance = result._distance / collisions.size(); + } + else if (collisions.size() == 1) { + result = collisions[0]; + } + result._count = (int)collisions.size(); + return result; +}; + +void FlowCollisionSystem::setScale(float scale) { + _scale = scale; + for (size_t j = 0; j < _collisionSpheres.size(); j++) { + _collisionSpheres[j]._radius = _collisionSpheres[j]._initialRadius * scale; + _collisionSpheres[j]._offset = _collisionSpheres[j]._initialOffset * scale; + } +}; + +std::vector FlowCollisionSystem::checkFlowThreadCollisions(FlowThread* flowThread, bool checkSegments) { + std::vector> FlowThreadResults; + FlowThreadResults.resize(flowThread->_joints.size()); + for (size_t j = 0; j < _collisionSpheres.size(); j++) { + FlowCollisionSphere &sphere = _collisionSpheres[j]; + FlowCollisionResult rootCollision = sphere.computeSphereCollision(flowThread->_positions[0], flowThread->_radius); + std::vector collisionData = { rootCollision }; + bool tooFar = rootCollision._distance >(flowThread->_length + rootCollision._radius); + FlowCollisionResult nextCollision; + if (!tooFar) { + if (checkSegments) { + for (size_t i = 1; i < flowThread->_joints.size(); i++) { + auto prevCollision = collisionData[i - 1]; + nextCollision = _collisionSpheres[j].computeSphereCollision(flowThread->_positions[i], flowThread->_radius); + collisionData.push_back(nextCollision); + if (prevCollision._offset > 0.0f) { + if (i == 1) { + FlowThreadResults[i - 1].push_back(prevCollision); + } + } + else if (nextCollision._offset > 0.0f) { + FlowThreadResults[i].push_back(nextCollision); + } + else { + FlowCollisionResult segmentCollision = _collisionSpheres[j].checkSegmentCollision(flowThread->_positions[i - 1], flowThread->_positions[i], prevCollision, nextCollision); + if (segmentCollision._offset > 0) { + FlowThreadResults[i - 1].push_back(segmentCollision); + FlowThreadResults[i].push_back(segmentCollision); + } + } + } + } + else { + if (rootCollision._offset > 0.0f) { + FlowThreadResults[0].push_back(rootCollision); + } + for (size_t i = 1; i < flowThread->_joints.size(); i++) { + nextCollision = _collisionSpheres[j].computeSphereCollision(flowThread->_positions[i], flowThread->_radius); + if (nextCollision._offset > 0.0f) { + FlowThreadResults[i].push_back(nextCollision); + } + } + } + } + } + + std::vector results; + for (size_t i = 0; i < flowThread->_joints.size(); i++) { + results.push_back(computeCollision(FlowThreadResults[i])); + } + return results; +}; + +int FlowCollisionSystem::findCollisionWithJoint(int jointIndex) { + for (size_t i = 0; i < _collisionSpheres.size(); i++) { + if (_collisionSpheres[i]._jointIndex == jointIndex) { + return (int)i; + } + } + return -1; +}; + +void FlowCollisionSystem::modifyCollisionRadius(int jointIndex, float radius) { + int collisionIndex = findCollisionWithJoint(jointIndex); + if (collisionIndex > -1) { + _collisionSpheres[collisionIndex]._initialRadius = radius; + _collisionSpheres[collisionIndex]._radius = _scale * radius; + } +}; + +void FlowCollisionSystem::modifyCollisionYOffset(int jointIndex, float offset) { + int collisionIndex = findCollisionWithJoint(jointIndex); + if (collisionIndex > -1) { + auto currentOffset = _collisionSpheres[collisionIndex]._offset; + _collisionSpheres[collisionIndex]._initialOffset = glm::vec3(currentOffset.x, offset, currentOffset.z); + _collisionSpheres[collisionIndex]._offset = _collisionSpheres[collisionIndex]._initialOffset * _scale; + } +}; + +void FlowCollisionSystem::modifyCollisionOffset(int jointIndex, const glm::vec3& offset) { + int collisionIndex = findCollisionWithJoint(jointIndex); + if (collisionIndex > -1) { + _collisionSpheres[collisionIndex]._initialOffset = offset; + _collisionSpheres[collisionIndex]._offset = _collisionSpheres[collisionIndex]._initialOffset * _scale; + } +}; + +void FlowNode::update(const glm::vec3& accelerationOffset) { + _acceleration = glm::vec3(0.0f, _settings._gravity, 0.0f); + _previousVelocity = _currentVelocity; + _currentVelocity = _currentPosition - _previousPosition; + _previousPosition = _currentPosition; + if (!_anchored) { + // Add inertia + auto deltaVelocity = _previousVelocity - _currentVelocity; + auto centrifugeVector = glm::length(deltaVelocity) != 0.0f ? glm::normalize(deltaVelocity) : glm::vec3(); + _acceleration = _acceleration + centrifugeVector * _settings._inertia * glm::length(_currentVelocity); + + // Add offset + _acceleration += accelerationOffset; + // Calculate new position + _currentPosition = (_currentPosition + _currentVelocity * _settings._damping) + + (_acceleration * glm::pow(_settings._delta * _scale, 2)); + } + else { + _acceleration = glm::vec3(0.0f); + _currentVelocity = glm::vec3(0.0f); + } +}; + + +void FlowNode::solve(const glm::vec3& constrainPoint, float maxDistance, const FlowCollisionResult& collision) { + solveConstraints(constrainPoint, maxDistance); + solveCollisions(collision); +}; + +void FlowNode::solveConstraints(const glm::vec3& constrainPoint, float maxDistance) { + glm::vec3 constrainVector = _currentPosition - constrainPoint; + float difference = maxDistance / glm::length(constrainVector); + _currentPosition = difference < 1.0 ? constrainPoint + constrainVector * difference : _currentPosition; +}; + +void FlowNode::solveCollisions(const FlowCollisionResult& collision) { + _colliding = collision._offset > 0.0f; + _collision = collision; + if (_colliding) { + _currentPosition = _currentPosition + collision._normal * collision._offset; + _previousCollision = collision; + } + else { + _previousCollision = FlowCollisionResult(); + } +}; + +void FlowNode::apply(const QString& name, bool forceRendering) { + // TODO + // Render here ?? + /* + jointDebug.setDebugSphere(name, self.currentPosition, 2 * self.radius, { red: self.collision && self.collision.collisionCount > 1 ? 0 : 255, + green : self.colliding ? 0 : 255, + blue : 0 }, forceRendering); + */ +}; + +FlowJoint::FlowJoint(int jointIndex, int parentIndex, int childIndex, const QString& name, const QString& group, float scale, const FlowPhysicsSettings& settings) { + _index = jointIndex; + _name = name; + _group = group; + _scale = scale; + _childIndex = childIndex; + _parentIndex = parentIndex; + _node = FlowNode(glm::vec3(), settings); +}; + +void FlowJoint::setInitialData(const glm::vec3& initialPosition, const glm::vec3& initialTranslation, const glm::quat& initialRotation, const glm::vec3& parentPosition) { + _initialPosition = initialPosition; + _node._initialPosition = initialPosition; + _node._previousPosition = initialPosition; + _node._currentPosition = initialPosition; + _initialTranslation = initialTranslation; + _initialRotation = initialRotation; + _translationDirection = glm::normalize(_initialTranslation); + _parentPosition = parentPosition; + _length = glm::length(_initialPosition - parentPosition); + _originalLength = _length / _scale; +} + +void FlowJoint::setUpdatedData(const glm::vec3& updatedPosition, const glm::vec3& updatedTranslation, const glm::quat& updatedRotation, const glm::vec3& parentPosition, const glm::quat& parentWorldRotation) { + _updatedPosition = updatedPosition; + _updatedRotation = updatedRotation; + _updatedTranslation = updatedTranslation; + _parentPosition = parentPosition; + _parentWorldRotation = parentWorldRotation; +} + +void FlowJoint::setRecoveryPosition(const glm::vec3& recoveryPosition) { + _recoveryPosition = recoveryPosition; + _applyRecovery = true; +} + +void FlowJoint::update() { + glm::vec3 accelerationOffset = glm::vec3(0.0f); + if (_node._settings._stiffness > 0.0f) { + glm::vec3 recoveryVector = _recoveryPosition - _node._currentPosition; + accelerationOffset = recoveryVector * glm::pow(_node._settings._stiffness, 3); + } + _node.update(accelerationOffset); + if (_node._anchored) { + if (!_isDummy) { + _node._currentPosition = _updatedPosition; + } else { + _node._currentPosition = _parentPosition; + } + } +}; + +void FlowJoint::solve(const FlowCollisionResult& collision) { + _node.solve(_parentPosition, _length, collision); +}; + +FlowDummyJoint::FlowDummyJoint(const glm::vec3& initialPosition, int index, int parentIndex, int childIndex, float scale, FlowPhysicsSettings settings) : + FlowJoint(index, parentIndex, childIndex, DUMMY_KEYWORD + "_" + index, DUMMY_KEYWORD, scale, settings) { + _isDummy = true; + _initialPosition = initialPosition; + _node = FlowNode(_initialPosition, settings); + _length = DUMMY_JOINT_DISTANCE; +} + + +FlowThread::FlowThread(int rootIndex, std::map* joints) { + _jointsPointer = joints; + computeFlowThread(rootIndex); +} + +void FlowThread::resetLength() { + _length = 0.0f; + for (size_t i = 1; i < _joints.size(); i++) { + int index = _joints[i]; + _length += (*_jointsPointer)[index]._length; + } +} + +void FlowThread::computeFlowThread(int rootIndex) { + int parentIndex = rootIndex; + if (_jointsPointer->size() == 0) { + return; + } + int childIndex = (*_jointsPointer)[parentIndex]._childIndex; + std::vector indexes = { parentIndex }; + for (size_t i = 0; i < _jointsPointer->size(); i++) { + if (childIndex > -1) { + indexes.push_back(childIndex); + childIndex = (*_jointsPointer)[childIndex]._childIndex; + } else { + break; + } + } + for (size_t i = 0; i < indexes.size(); i++) { + int index = indexes[i]; + _joints.push_back(index); + if (i > 0) { + _length += (*_jointsPointer)[index]._length; + } + } +}; + +void FlowThread::computeRecovery() { + int parentIndex = _joints[0]; + auto parentJoint = (*_jointsPointer)[parentIndex]; + (*_jointsPointer)[parentIndex]._recoveryPosition = parentJoint._recoveryPosition = parentJoint._node._currentPosition; + glm::quat parentRotation = parentJoint._parentWorldRotation * parentJoint._initialRotation; + for (size_t i = 1; i < _joints.size(); i++) { + auto joint = (*_jointsPointer)[_joints[i]]; + glm::quat rotation = i == 1 ? parentRotation : rotation * parentJoint._initialRotation; + (*_jointsPointer)[_joints[i]]._recoveryPosition = joint._recoveryPosition = parentJoint._recoveryPosition + (rotation * (joint._initialTranslation * 0.01f)); + parentJoint = joint; + } +}; + +void FlowThread::update() { + if (getActive()) { + _positions.clear(); + _radius = (*_jointsPointer)[_joints[0]]._node._settings._radius; + computeRecovery(); + for (size_t i = 0; i < _joints.size(); i++) { + auto &joint = (*_jointsPointer)[_joints[i]]; + joint.update(); + _positions.push_back(joint._node._currentPosition); + } + } +}; + +void FlowThread::solve(bool useCollisions, FlowCollisionSystem& collisionSystem) { + if (getActive()) { + if (useCollisions) { + auto bodyCollisions = collisionSystem.checkFlowThreadCollisions(this, false); + int handTouchedJoint = -1; + for (size_t i = 0; i < _joints.size(); i++) { + int index = _joints[i]; + if (bodyCollisions[i]._offset > 0.0f) { + (*_jointsPointer)[index].solve(bodyCollisions[i]); + } else { + (*_jointsPointer)[index].solve(FlowCollisionResult()); + } + } + } else { + for (size_t i = 0; i < _joints.size(); i++) { + int index = _joints[i]; + (*_jointsPointer)[index].solve(FlowCollisionResult()); + } + } + } +}; + +void FlowThread::computeJointRotations() { + + auto pos0 = _rootFramePositions[0]; + auto pos1 = _rootFramePositions[1]; + + auto joint0 = (*_jointsPointer)[_joints[0]]; + auto joint1 = (*_jointsPointer)[_joints[1]]; + + auto initial_pos1 = pos0 + (joint0._initialRotation * (joint1._initialTranslation * 0.01f)); + + auto vec0 = initial_pos1 - pos0; + auto vec1 = pos1 - pos0; + + auto delta = rotationBetween(vec0, vec1); + + joint0._currentRotation = (*_jointsPointer)[_joints[0]]._currentRotation = delta * joint0._initialRotation; + + for (size_t i = 1; i < _joints.size() - 1; i++) { + auto nextJoint = (*_jointsPointer)[_joints[i + 1]]; + for (size_t j = i; j < _joints.size(); j++) { + _rootFramePositions[j] = glm::inverse(joint0._currentRotation) * _rootFramePositions[j] - (joint0._initialTranslation * 0.01f); + } + pos0 = _rootFramePositions[i]; + pos1 = _rootFramePositions[i + 1]; + initial_pos1 = pos0 + joint1._initialRotation * (nextJoint._initialTranslation * 0.01f); + + vec0 = initial_pos1 - pos0; + vec1 = pos1 - pos0; + + delta = rotationBetween(vec0, vec1); + + joint1._currentRotation = (*_jointsPointer)[joint1._index]._currentRotation = delta * joint1._initialRotation; + joint0 = joint1; + joint1 = nextJoint; + } +}; + +void FlowThread::apply() { + if (!getActive()) { + return; + } + computeJointRotations(); +}; + +bool FlowThread::getActive() { + return (*_jointsPointer)[_joints[0]]._node._active; +}; + +void Flow::setRig(Rig* rig) { + _rig = rig; +}; + +void Flow::calculateConstraints() { + cleanUp(); + if (!_rig) { + return; + } + int rightHandIndex = -1; + auto flowPrefix = FLOW_JOINT_PREFIX.toUpper(); + auto simPrefix = SIM_JOINT_PREFIX.toUpper(); + auto skeleton = _rig->getAnimSkeleton(); + if (skeleton) { + for (int i = 0; i < skeleton->getNumJoints(); i++) { + auto name = skeleton->getJointName(i); + if (name == "RightHand") { + rightHandIndex = i; + } + auto parentIndex = skeleton->getParentIndex(i); + if (parentIndex == -1) { + continue; + } + auto jointChildren = skeleton->getChildrenOfJoint(i); + // auto childIndex = jointChildren.size() > 0 ? jointChildren[0] : -1; + auto group = QStringRef(&name, 0, 3).toString().toUpper(); + auto split = name.split("_"); + bool isSimJoint = (group == simPrefix); + bool isFlowJoint = split.size() > 2 && split[0].toUpper() == flowPrefix; + if (isFlowJoint || isSimJoint) { + group = ""; + if (isSimJoint) { + qDebug() << "FLOW is sim: " << name; + for (size_t j = 1; j < name.size() - 1; j++) { + bool toFloatSuccess; + auto subname = (QStringRef(&name, (int)(name.size() - j), 1)).toString().toFloat(&toFloatSuccess); + if (!toFloatSuccess && (name.size() - j) > simPrefix.size()) { + group = QStringRef(&name, simPrefix.size(), (int)(name.size() - j + 1)).toString(); + break; + } + } + if (group.isEmpty()) { + group = QStringRef(&name, simPrefix.size(), name.size() - 1).toString(); + } + } else { + group = split[1]; + } + if (!group.isEmpty()) { + _flowJointKeywords.push_back(group); + FlowPhysicsSettings jointSettings; + if (PRESET_FLOW_DATA.find(group) != PRESET_FLOW_DATA.end()) { + jointSettings = PRESET_FLOW_DATA.at(group); + } else { + jointSettings = DEFAULT_JOINT_SETTINGS; + } + if (_flowJointData.find(i) == _flowJointData.end()) { + auto flowJoint = FlowJoint(i, parentIndex, -1, name, group, _scale, jointSettings); + _flowJointData.insert(std::pair(i, flowJoint)); + } + } + } else { + if (PRESET_COLLISION_DATA.find(name) != PRESET_COLLISION_DATA.end()) { + _collisionSystem.addCollisionShape(i, PRESET_COLLISION_DATA.at(name)); + } + } + if (isFlowJoint || isSimJoint) { + auto jointInfo = FlowJointInfo(i, parentIndex, -1, name); + _flowJointInfos.push_back(jointInfo); + } + } + } + + for (auto &jointData : _flowJointData) { + int jointIndex = jointData.first; + glm::vec3 jointPosition, parentPosition, jointTranslation; + glm::quat jointRotation; + _rig->getJointPositionInWorldFrame(jointIndex, jointPosition, _entityPosition, _entityRotation); + _rig->getJointPositionInWorldFrame(jointData.second._parentIndex, parentPosition, _entityPosition, _entityRotation); + _rig->getJointTranslation(jointIndex, jointTranslation); + _rig->getJointRotation(jointIndex, jointRotation); + + jointData.second.setInitialData(jointPosition, jointTranslation, jointRotation, parentPosition); + } + + std::vector roots; + + for (auto& itr = _flowJointData.begin(); itr != _flowJointData.end(); itr++) { + if (_flowJointData.find(itr->second._parentIndex) == _flowJointData.end()) { + itr->second._node._anchored = true; + roots.push_back(itr->first); + } else { + _flowJointData[itr->second._parentIndex]._childIndex = itr->first; + } + } + + for (size_t i = 0; i < roots.size(); i++) { + FlowThread thread = FlowThread(roots[i], &_flowJointData); + // add threads with at least 2 joints + if (thread._joints.size() > 0) { + if (thread._joints.size() == 1) { + int jointIndex = roots[i]; + auto joint = _flowJointData[jointIndex]; + auto jointPosition = joint._updatedPosition; + auto newSettings = FlowPhysicsSettings(joint._node._settings); + newSettings._stiffness = ISOLATED_JOINT_STIFFNESS; + int extraIndex = (int)_flowJointData.size(); + auto newJoint = FlowDummyJoint(jointPosition, extraIndex, jointIndex, -1, _scale, newSettings); + newJoint._isDummy = false; + newJoint._length = ISOLATED_JOINT_LENGTH; + newJoint._childIndex = extraIndex; + newJoint._group = _flowJointData[jointIndex]._group; + thread = FlowThread(jointIndex, &_flowJointData); + } + _jointThreads.push_back(thread); + } + } + + if (_jointThreads.size() == 0) { + _rig->clearJointStates(); + } + + + if (SHOW_DUMMY_JOINTS && rightHandIndex > -1) { + int jointCount = (int)_flowJointData.size(); + int extraIndex = (int)_flowJointData.size(); + glm::vec3 rightHandPosition; + _rig->getJointPositionInWorldFrame(rightHandIndex, rightHandPosition, _entityPosition, _entityRotation); + int parentIndex = rightHandIndex; + for (int i = 0; i < DUMMY_JOINT_COUNT; i++) { + int childIndex = (i == (DUMMY_JOINT_COUNT - 1)) ? -1 : extraIndex + 1; + auto newJoint = FlowDummyJoint(rightHandPosition, extraIndex, parentIndex, childIndex, _scale, DEFAULT_JOINT_SETTINGS); + _flowJointData.insert(std::pair(extraIndex, newJoint)); + parentIndex = extraIndex; + extraIndex++; + } + _flowJointData[jointCount]._node._anchored = true; + + auto extraThread = FlowThread(jointCount, &_flowJointData); + _jointThreads.push_back(extraThread); + } + _initialized = _jointThreads.size() > 0; +} + +void Flow::cleanUp() { + _flowJointData.clear(); + _jointThreads.clear(); + _flowJointKeywords.clear(); + _flowJointInfos.clear(); + } + +void Flow::setTransform(float scale, const glm::vec3& position, const glm::quat& rotation) { + _scale = scale; + for (auto &joint : _flowJointData) { + joint.second._scale = scale; + joint.second._node._scale = scale; + } + _entityPosition = position; + _entityRotation = rotation; + _active = true; +} + +void Flow::update() { + _timer.start(); + if (_initialized && _active) { + updateJoints(); + if (USE_COLLISIONS) { + _collisionSystem.update(); + } + int count = 0; + for (auto &thread : _jointThreads) { + thread.update(); + thread.solve(USE_COLLISIONS, _collisionSystem); + if (!updateRootFramePositions(count++)) { + return; + } + thread.apply(); + } + setJoints(); + } + _elapsedTime = ((1.0 - _tau) * _elapsedTime + _tau * _timer.nsecsElapsed()); + _deltaTime += _elapsedTime; + if (_deltaTime > _deltaTimeLimit) { + qDebug() << "Flow C++ update" << _elapsedTime; + _deltaTime = 0.0; + } + +} + +bool Flow::worldToJointPoint(const glm::vec3& position, const int jointIndex, glm::vec3& jointSpacePosition) const { + glm::vec3 jointPos; + glm::quat jointRot; + if (_rig->getJointPositionInWorldFrame(jointIndex, jointPos, _entityPosition, _entityRotation) && _rig->getJointRotationInWorldFrame(jointIndex, jointRot, _entityRotation)) { + glm::vec3 modelOffset = position - jointPos; + jointSpacePosition = glm::inverse(jointRot) * modelOffset; + return true; + } + return false; +} + +bool Flow::updateRootFramePositions(int threadIndex) { + auto &joints = _jointThreads[threadIndex]._joints; + int rootIndex = _flowJointData[joints[0]]._parentIndex; + _jointThreads[threadIndex]._rootFramePositions.clear(); + for (size_t j = 0; j < joints.size(); j++) { + glm::vec3 jointPos; + if (worldToJointPoint(_flowJointData[joints[j]]._node._currentPosition, rootIndex, jointPos)) { + _jointThreads[threadIndex]._rootFramePositions.push_back(jointPos); + } else { + return false; + } + } + return true; +} + +void Flow::updateJoints() { + for (auto &jointData : _flowJointData) { + int jointIndex = jointData.first; + glm::vec3 jointPosition, parentPosition, jointTranslation; + glm::quat jointRotation, parentWorldRotation; + _rig->getJointPositionInWorldFrame(jointIndex, jointPosition, _entityPosition, _entityRotation); + _rig->getJointPositionInWorldFrame(jointData.second._parentIndex, parentPosition, _entityPosition, _entityRotation); + _rig->getJointTranslation(jointIndex, jointTranslation); + _rig->getJointRotation(jointIndex, jointRotation); + _rig->getJointRotationInWorldFrame(jointData.second._parentIndex, parentWorldRotation, _entityRotation); + jointData.second.setUpdatedData(jointPosition, jointTranslation, jointRotation, parentPosition, parentWorldRotation); + } + auto &collisions = _collisionSystem.getCollisions(); + for (auto &collision : collisions) { + _rig->getJointPositionInWorldFrame(collision._jointIndex, collision._position, _entityPosition, _entityRotation); + } +} + +void Flow::setJoints() { + for (auto &thread : _jointThreads) { + auto &joints = thread._joints; + for (int jointIndex : joints) { + auto &joint = _flowJointData[jointIndex]; + _rig->setJointRotation(jointIndex, true, joint._currentRotation, 2.0f); + } + } +} diff --git a/libraries/animation/src/Flow.h b/libraries/animation/src/Flow.h new file mode 100644 index 0000000000..c93cf91673 --- /dev/null +++ b/libraries/animation/src/Flow.h @@ -0,0 +1,315 @@ +// +// Flow.h +// +// Created by Luis Cuenca on 1/21/2019. +// Copyright 2019 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_Flow_h +#define hifi_Flow_h + +#include +#include +#include +#include +#include +#include +#include + +class Rig; +class AnimSkeleton; + +const bool SHOW_AVATAR = true; +const bool SHOW_DEBUG_SHAPES = false; +const bool SHOW_SOLID_SHAPES = false; +const bool SHOW_DUMMY_JOINTS = false; +const bool USE_COLLISIONS = true; + +const int LEFT_HAND = 0; +const int RIGHT_HAND = 1; + +const float HAPTIC_TOUCH_STRENGTH = 0.25f; +const float HAPTIC_TOUCH_DURATION = 10.0f; +const float HAPTIC_SLOPE = 0.18f; +const float HAPTIC_THRESHOLD = 40.0f; + +const QString FLOW_JOINT_PREFIX = "flow"; +const QString SIM_JOINT_PREFIX = "sim"; + +const QString JOINT_COLLISION_PREFIX = "joint_"; +const QString HAND_COLLISION_PREFIX = "hand_"; +const float HAND_COLLISION_RADIUS = 0.03f; +const float HAND_TOUCHING_DISTANCE = 2.0f; + +const int COLLISION_SHAPES_LIMIT = 4; + +const QString DUMMY_KEYWORD = "Extra"; +const int DUMMY_JOINT_COUNT = 8; +const float DUMMY_JOINT_DISTANCE = 0.05f; + +const float ISOLATED_JOINT_STIFFNESS = 0.0f; +const float ISOLATED_JOINT_LENGTH = 0.05f; + +const float DEFAULT_STIFFNESS = 0.8f; +const float DEFAULT_GRAVITY = -0.0096f; +const float DEFAULT_DAMPING = 0.85f; +const float DEFAULT_INERTIA = 0.8f; +const float DEFAULT_DELTA = 0.55f; +const float DEFAULT_RADIUS = 0.01f; + +struct FlowPhysicsSettings { + FlowPhysicsSettings() {}; + FlowPhysicsSettings(bool active, float stiffness, float gravity, float damping, float inertia, float delta, float radius) { + _active = active; + _stiffness = stiffness; + _gravity = gravity; + _damping = damping; + _inertia = inertia; + _delta = delta; + _radius = radius; + } + bool _active{ true }; + float _stiffness{ 0.0f }; + float _gravity{ DEFAULT_GRAVITY }; + float _damping{ DEFAULT_DAMPING }; + float _inertia{ DEFAULT_INERTIA }; + float _delta{ DEFAULT_DELTA }; + float _radius{ DEFAULT_RADIUS }; +}; + +enum FlowCollisionType { + CollisionSphere = 0 +}; + +struct FlowCollisionSettings { + FlowCollisionSettings() {}; + FlowCollisionSettings(const QUuid& id, const FlowCollisionType& type, const glm::vec3& offset, float radius) { + _entityID = id; + _type = type; + _offset = offset; + _radius = radius; + }; + QUuid _entityID; + FlowCollisionType _type { FlowCollisionType::CollisionSphere }; + float _radius { 0.05f }; + glm::vec3 _offset; +}; + +const FlowPhysicsSettings DEFAULT_JOINT_SETTINGS; + +const std::map PRESET_FLOW_DATA = { {"hair", FlowPhysicsSettings()}, + {"skirt", FlowPhysicsSettings(true, 1.0f, DEFAULT_GRAVITY, 0.65f, 0.8f, 0.45f, 0.01f)}, + {"breast", FlowPhysicsSettings(true, 1.0f, DEFAULT_GRAVITY, 0.65f, 0.8f, 0.45f, 0.01f)} }; + +const std::map PRESET_COLLISION_DATA = { + { "Head", FlowCollisionSettings(QUuid(), FlowCollisionType::CollisionSphere, glm::vec3(0.0f, 0.06f, 0.0f), 0.08f)}, + { "LeftArm", FlowCollisionSettings(QUuid(), FlowCollisionType::CollisionSphere, glm::vec3(0.0f, 0.02f, 0.0f), 0.05f) }, + { "Head", FlowCollisionSettings(QUuid(), FlowCollisionType::CollisionSphere, glm::vec3(0.0f, 0.04f, 0.0f), 0.0f) } +}; + +const std::vector HAND_COLLISION_JOINTS = { "RightHandMiddle1", "RightHandThumb3", "LeftHandMiddle1", "LeftHandThumb3", "RightHandMiddle3", "LeftHandMiddle3" }; + +struct FlowJointInfo { + FlowJointInfo() {}; + FlowJointInfo(int index, int parentIndex, int childIndex, const QString& name) { + _index = index; + _parentIndex = parentIndex; + _childIndex = childIndex; + _name = name; + } + int _index { -1 }; + QString _name; + int _parentIndex { -1 }; + int _childIndex { -1 }; +}; + +struct FlowCollisionResult { + int _count { 0 }; + float _offset { 0.0f }; + glm::vec3 _position; + float _radius { 0.0f }; + glm::vec3 _normal; + float _distance { 0.0f }; +}; + +class FlowCollisionSphere { +public: + FlowCollisionSphere() {}; + FlowCollisionSphere(const int& jointIndex, const FlowCollisionSettings& settings); + void setPosition(const glm::vec3& position) { _position = position; } + FlowCollisionResult computeSphereCollision(const glm::vec3& point, float radius) const; + FlowCollisionResult checkSegmentCollision(const glm::vec3& point1, const glm::vec3& point2, const FlowCollisionResult& collisionResult1, const FlowCollisionResult& collisionResult2); + void update(); + + QUuid _entityID; + int _jointIndex { -1 }; + float _radius { 0.0f }; + float _initialRadius{ 0.0f }; + glm::vec3 _offset; + glm::vec3 _initialOffset; + glm::vec3 _position; +}; + +class FlowThread; + +class FlowCollisionSystem { +public: + FlowCollisionSystem() {}; + void addCollisionSphere(int jointIndex, const FlowCollisionSettings& settings); + void addCollisionShape(int jointIndex, const FlowCollisionSettings& settings); + bool addCollisionToJoint(int jointIndex); + void removeCollisionFromJoint(int jointIndex); + void update(); + FlowCollisionResult computeCollision(const std::vector collisions); + void setScale(float scale); + + std::vector checkFlowThreadCollisions(FlowThread* flowThread, bool checkSegments); + + int findCollisionWithJoint(int jointIndex); + void modifyCollisionRadius(int jointIndex, float radius); + void modifyCollisionYOffset(int jointIndex, float offset); + void modifyCollisionOffset(int jointIndex, const glm::vec3& offset); + + std::vector& getCollisions() { return _collisionSpheres; }; +private: + std::vector _collisionSpheres; + float _scale{ 1.0f }; +}; + +class FlowNode { +public: + FlowNode() {}; + FlowNode(const glm::vec3& initialPosition, FlowPhysicsSettings settings) : + _initialPosition(initialPosition), _previousPosition(initialPosition), _currentPosition(initialPosition){}; + + FlowPhysicsSettings _settings; + glm::vec3 _initialPosition; + glm::vec3 _previousPosition; + glm::vec3 _currentPosition; + + glm::vec3 _currentVelocity; + glm::vec3 _previousVelocity; + glm::vec3 _acceleration; + + FlowCollisionResult _collision; + FlowCollisionResult _previousCollision; + + float _initialRadius { 0.0f }; + float _scale{ 1.0f }; + + bool _anchored { false }; + bool _colliding { false }; + bool _active { true }; + + void update(const glm::vec3& accelerationOffset); + void solve(const glm::vec3& constrainPoint, float maxDistance, const FlowCollisionResult& collision); + void solveConstraints(const glm::vec3& constrainPoint, float maxDistance); + void solveCollisions(const FlowCollisionResult& collision); + void apply(const QString& name, bool forceRendering); +}; + +class FlowJoint { +public: + FlowJoint() {}; + FlowJoint(int jointIndex, int parentIndex, int childIndex, const QString& name, const QString& group, float scale, const FlowPhysicsSettings& settings); + void setInitialData(const glm::vec3& initialPosition, const glm::vec3& initialTranslation, const glm::quat& initialRotation, const glm::vec3& parentPosition); + void setUpdatedData(const glm::vec3& updatedPosition, const glm::vec3& updatedTranslation, const glm::quat& updatedRotation, const glm::vec3& parentPosition, const glm::quat& parentWorldRotation); + void setRecoveryPosition(const glm::vec3& recoveryPosition); + void update(); + void solve(const FlowCollisionResult& collision); + + int _index{ -1 }; + int _parentIndex{ -1 }; + int _childIndex{ -1 }; + QString _name; + QString _group; + bool _isDummy{ false }; + glm::vec3 _initialPosition; + glm::vec3 _initialTranslation; + glm::quat _initialRotation; + + glm::vec3 _updatedPosition; + glm::vec3 _updatedTranslation; + glm::quat _updatedRotation; + + glm::quat _currentRotation; + glm::vec3 _recoveryPosition; + + glm::vec3 _parentPosition; + glm::quat _parentWorldRotation; + + FlowNode _node; + glm::vec3 _translationDirection; + float _scale { 1.0f }; + float _length { 0.0f }; + float _originalLength { 0.0f }; + bool _applyRecovery { false }; +}; + +class FlowDummyJoint : public FlowJoint { +public: + FlowDummyJoint(const glm::vec3& initialPosition, int index, int parentIndex, int childIndex, float scale, FlowPhysicsSettings settings); +}; + + +class FlowThread { +public: + FlowThread() {}; + FlowThread(int rootIndex, std::map* joints); + + void resetLength(); + void computeFlowThread(int rootIndex); + void computeRecovery(); + void update(); + void solve(bool useCollisions, FlowCollisionSystem& collisionSystem); + void computeJointRotations(); + void apply(); + bool getActive(); + void setRootFramePositions(const std::vector& rootFramePositions) { _rootFramePositions = rootFramePositions; }; + + std::vector _joints; + std::vector _positions; + float _radius{ 0.0f }; + float _length{ 0.0f }; + std::map* _jointsPointer; + std::vector _rootFramePositions; + +}; + +class Flow { +public: + Flow() {}; + void setRig(Rig* rig); + void calculateConstraints(); + void update(); + void setTransform(float scale, const glm::vec3& position, const glm::quat& rotation); + const std::map& getJoints() const { return _flowJointData; } + const std::vector& getThreads() const { return _jointThreads; } +private: + void setJoints(); + void cleanUp(); + void updateJoints(); + bool updateRootFramePositions(int threadIndex); + bool worldToJointPoint(const glm::vec3& position, const int jointIndex, glm::vec3& jointSpacePosition) const; + Rig* _rig; + float _scale { 1.0f }; + glm::vec3 _entityPosition; + glm::quat _entityRotation; + std::map _flowJointData; + std::vector _jointThreads; + std::vector _flowJointKeywords; + std::vector _flowJointInfos; + FlowCollisionSystem _collisionSystem; + bool _initialized { false }; + bool _active { false }; + int _deltaTime{ 0 }; + int _deltaTimeLimit{ 4000 }; + int _elapsedTime{ 0 }; + float _tau = 0.1f; + QElapsedTimer _timer; +}; + +#endif // hifi_Flow_h diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 8a05bdd018..5042c00be6 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -748,7 +748,7 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos glm::vec3 forward = worldRotation * IDENTITY_FORWARD; glm::vec3 workingVelocity = worldVelocity; - + _flow.setTransform(sensorToWorldScale, worldPosition, worldRotation * Quaternions::Y_180); { glm::vec3 localVel = glm::inverse(worldRotation) * workingVelocity; @@ -1212,12 +1212,15 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons _networkVars = networkTriggersOut; _lastContext = context; } + applyOverridePoses(); buildAbsoluteRigPoses(_internalPoseSet._relativePoses, _internalPoseSet._absolutePoses); buildAbsoluteRigPoses(_networkPoseSet._relativePoses, _networkPoseSet._absolutePoses); // copy internal poses to external poses + _flow.update(); { QWriteLocker writeLock(&_externalPoseSetLock); + _externalPoseSet = _internalPoseSet; } } diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 9bd2ed528a..50d13d348e 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -235,6 +235,7 @@ public: const AnimVariantMap& getAnimVars() const { return _lastAnimVars; } const AnimContext::DebugStateMachineMap& getStateMachineMap() const { return _lastContext.getStateMachineMap(); } void computeFlowSkeleton() { _flow.calculateConstraints(); } + const Flow& getFlow() const { return _flow; } signals: void onLoadComplete(); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 07c1ca9a32..223813eecb 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -722,6 +722,24 @@ void Avatar::postUpdate(float deltaTime, const render::ScenePointer& scene) { DebugDraw::getInstance().drawRay(rightEyePosition, rightEyePosition + rightEyeRotation * Vectors::UNIT_Z * EYE_RAY_LENGTH, RED); } } + const bool DEBUG_FLOW = true; + if (_skeletonModel->isLoaded() && DEBUG_FLOW) { + auto flow = _skeletonModel->getRig().getFlow(); + auto joints = flow.getJoints(); + auto threads = flow.getThreads(); + for (auto &thread : threads) { + auto& jointIndexes = thread._joints; + for (size_t i = 1; i < jointIndexes.size(); i++) { + auto index1 = jointIndexes[i - 1]; + auto index2 = jointIndexes[i]; + // glm::vec3 pos1 = joint.second._node._currentPosition; + // glm::vec3 pos2 = joints.find(joint.second._parentIndex) != joints.end() ? joints[joint.second._parentIndex]._node._currentPosition : getJointPosition(joint.second._parentIndex); + // DebugDraw::getInstance().drawRay(pos1, pos2, glm::vec4(1.0f, 1.0f, 1.0f, 1.0f)); + DebugDraw::getInstance().drawRay(joints[index1]._node._currentPosition, joints[index2]._node._currentPosition, glm::vec4(1.0f, 1.0f, 1.0f, 1.0f)); + + } + } + } fixupModelsInScene(scene); updateFitBoundingBox(); From 9eceb1d0bd71759f51090ad70c52dc5a1e9add72 Mon Sep 17 00:00:00 2001 From: amantley Date: Thu, 7 Feb 2019 17:45:58 -0800 Subject: [PATCH 068/474] implemented the code for the heuristic elbows including code from the paper authors. to do: dampen the twist of the spine caused by the hand azimuth and put in the constraints for the wrists on the pole vector theta computation. --- .../src/AnimPoleVectorConstraint.cpp | 80 ++++++++++++++----- .../animation/src/AnimPoleVectorConstraint.h | 2 +- 2 files changed, 62 insertions(+), 20 deletions(-) diff --git a/libraries/animation/src/AnimPoleVectorConstraint.cpp b/libraries/animation/src/AnimPoleVectorConstraint.cpp index 55fbf20691..343d6338be 100644 --- a/libraries/animation/src/AnimPoleVectorConstraint.cpp +++ b/libraries/animation/src/AnimPoleVectorConstraint.cpp @@ -34,7 +34,31 @@ AnimPoleVectorConstraint::~AnimPoleVectorConstraint() { } -float AnimPoleVectorConstraint::findThetaNewWay(const glm::vec3& hand, const glm::vec3& shoulder) const { +static float correctElbowForHandRotation(const AnimPose& hand, const AnimPose& lowerArm) { + + // first calculate the ulnar/radial deviation + // use the lower arm x-axis and the hand x-axis + glm::vec3 xAxisLowerArm = lowerArm.rot() * glm::vec3(1.0f, 0.0f, 0.0f); + glm::vec3 yAxisLowerArm = lowerArm.rot() * glm::vec3(0.0f, 1.0f, 0.0f); + glm::vec3 zAxisLowerArm = lowerArm.rot() * glm::vec3(0.0f, 0.0f, 1.0f); + glm::vec3 xAxisHand = hand.rot() * glm::vec3(1.0f, 0.0f, 0.0f); + glm::vec3 yAxisHand = hand.rot() * glm::vec3(0.0f, 1.0f, 0.0f); + + float ulnarRadialDeviation = atan2(glm::dot(xAxisHand, xAxisLowerArm), glm::dot(xAxisHand, yAxisLowerArm)); + float flexionExtension = atan2(glm::dot(yAxisHand, zAxisLowerArm), glm::dot(yAxisHand, yAxisLowerArm)); + + qCDebug(animation) << "flexion angle " << flexionExtension; + + + float deltaInDegrees = (flexionExtension / PI) * 180.0f; + + qCDebug(animation) << "delta in degrees " << deltaInDegrees; + + float deltaFinal = glm::sign(deltaInDegrees) * powf(fabsf(deltaInDegrees/180.0f), 1.5f) * 180.0f * -0.3f; + return deltaFinal; +} + +float AnimPoleVectorConstraint::findThetaNewWay(const glm::vec3& hand, const glm::vec3& shoulder, bool left) const { // get the default poses for the upper and lower arm // then use this length to judge how far the arm is away from the shoulder. // then create weights that make the elbow angle less when the x value is large in either direction. @@ -45,27 +69,34 @@ float AnimPoleVectorConstraint::findThetaNewWay(const glm::vec3& hand, const glm AnimPose handPose = _skeleton->getAbsoluteDefaultPose(_skeleton->nameToJointIndex("RightHand")); // subtract 10 centimeters from the arm length for some reason actual arm position is clamped to length - 10cm. float defaultArmLength = glm::length( handPose.trans() - shoulderPose.trans() ) - 10.0f; - qCDebug(animation) << "default arm length " << defaultArmLength; + // qCDebug(animation) << "default arm length " << defaultArmLength; // phi_0 is the lowest angle we can have const float phi_0 = 15.0f; + const float zStart = 0.6f; + const float xStart = 0.1f; // biases - const glm::vec3 biases(30.0f, 120.0f, -30.0f); + //const glm::vec3 biases(30.0f, 120.0f, -30.0f); + const glm::vec3 biases(0.0f, 135.0f, 0.0f); // weights - const glm::vec3 weights(-30.0f, 30.0f, 210.0f); + const float zWeightBottom = -100.0f; + //const glm::vec3 weights(-30.0f, 30.0f, 210.0f); + const glm::vec3 weights(-50.0f, -60.0f, 260.0f); glm::vec3 armToHand = hand - shoulder; - qCDebug(animation) << "current arm length " << glm::length(armToHand); - float initial_valuesX = ((fabsf(armToHand[0]) / defaultArmLength) * weights[0]) + biases[0]; - float initial_valuesY = ((fabs(armToHand[1]) / defaultArmLength) * weights[1]) + biases[1]; - float initial_valuesZ = ((armToHand[2] / defaultArmLength) * weights[2]) + biases[2]; - if (initial_valuesX < 0.0f) { - initial_valuesX = 0.0f; + // qCDebug(animation) << "current arm length " << glm::length(armToHand); + float initial_valuesY = ((armToHand[1] / defaultArmLength) * weights[1]) + biases[1]; + float initial_valuesZ; + if (armToHand[1] > 0.0f) { + initial_valuesZ = weights[2] * glm::max(zStart - (armToHand[2] / defaultArmLength), 0.0f) * fabs(armToHand[1] / defaultArmLength); + } else { + initial_valuesZ = zWeightBottom * glm::max(zStart - (armToHand[2] / defaultArmLength), 0.0f) * fabs(armToHand[1] / defaultArmLength); } - if (initial_valuesY < 0.0f) { - initial_valuesY = 0.0f; - } - if (initial_valuesZ < 0.0f) { - initial_valuesZ = 0.0f; + + float initial_valuesX; + if (left) { + initial_valuesX = weights[0] * glm::max(-1.0f * (armToHand[0] / defaultArmLength) + xStart, 0.0f); + } else { + initial_valuesX = weights[0] * glm::max((armToHand[0] / defaultArmLength) + xStart, 0.0f); } float theta = initial_valuesX + initial_valuesY + initial_valuesZ; @@ -77,7 +108,7 @@ float AnimPoleVectorConstraint::findThetaNewWay(const glm::vec3& hand, const glm theta = 175.0f; } - qCDebug(animation) << "relative hand" << initial_valuesX << " " << initial_valuesY << " " << initial_valuesZ << "theta value for pole vector is " << theta; + // qCDebug(animation) << "relative hand" << initial_valuesX << " " << initial_valuesY << " " << initial_valuesZ << "theta value for pole vector is " << theta; return theta; } @@ -170,14 +201,25 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim float fred; if ((_skeleton->nameToJointIndex("RightHand") == _tipJointIndex) || (_skeleton->nameToJointIndex("LeftHand") == _tipJointIndex)) { - qCDebug(animation) << "theta from the old code " << theta; - fred = findThetaNewWay(tipPose.trans(), basePose.trans()); + //qCDebug(animation) << "theta from the old code " << theta; + bool isLeft = false; if (_skeleton->nameToJointIndex("LeftHand") == _tipJointIndex) { - fred = -1.0f * fred; + isLeft = true; + } + fred = findThetaNewWay(tipPose.trans(), basePose.trans(), isLeft); + if (isLeft){ + fred *= -1.0f; } theta = ((180.0f - fred) / 180.0f)*PI; + + // here is where we would do the wrist correction. + float deltaTheta = correctElbowForHandRotation(tipPose, midPose); + qCDebug(animation) << "the wrist correction theta is -----> " << deltaTheta; + } + + //glm::quat deltaRot = glm::angleAxis(theta, unitAxis); glm::quat deltaRot = glm::angleAxis(theta, unitAxis); diff --git a/libraries/animation/src/AnimPoleVectorConstraint.h b/libraries/animation/src/AnimPoleVectorConstraint.h index df8cddec7d..be3346974e 100644 --- a/libraries/animation/src/AnimPoleVectorConstraint.h +++ b/libraries/animation/src/AnimPoleVectorConstraint.h @@ -25,7 +25,7 @@ public: const QString& enabledVar, const QString& poleVectorVar); virtual ~AnimPoleVectorConstraint() override; - float findThetaNewWay(const glm::vec3& hand, const glm::vec3& shoulder) const; + float findThetaNewWay(const glm::vec3& hand, const glm::vec3& shoulder, bool left) const; virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override; From 822ec1c529fa6c5b82acba36e4bda7e491ad2b39 Mon Sep 17 00:00:00 2001 From: amantley Date: Fri, 8 Feb 2019 17:53:23 -0800 Subject: [PATCH 069/474] working on the wrist tweak, dampened the spine twist --- interface/src/avatar/MySkeletonModel.cpp | 1 + libraries/animation/src/AnimArmIK.cpp | 2 +- .../src/AnimPoleVectorConstraint.cpp | 77 +++++++++++++++---- 3 files changed, 65 insertions(+), 15 deletions(-) diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 953b8a4c73..51a2c3767b 100755 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -262,6 +262,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { if (spine2Exists && headExists && hipsExists) { AnimPose rigSpaceYaw(myAvatar->getSpine2RotationRigSpace()); + rigSpaceYaw.rot() = safeLerp(Quaternions::IDENTITY, rigSpaceYaw.rot(), 0.5f); glm::vec3 u, v, w; glm::vec3 fwd = rigSpaceYaw.rot() * glm::vec3(0.0f, 0.0f, 1.0f); glm::vec3 up = currentHeadPose.trans() - currentHipsPose.trans(); diff --git a/libraries/animation/src/AnimArmIK.cpp b/libraries/animation/src/AnimArmIK.cpp index ba720f1126..87606fe5d2 100644 --- a/libraries/animation/src/AnimArmIK.cpp +++ b/libraries/animation/src/AnimArmIK.cpp @@ -31,7 +31,7 @@ AnimArmIK::~AnimArmIK() { const AnimPoseVec& AnimArmIK::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { - qCDebug(animation) << "evaluating the arm IK"; + //qCDebug(animation) << "evaluating the arm IK"; _poses = AnimTwoBoneIK::evaluate(animVars, context, dt, triggersOut); //assert(_children.size() == 1); diff --git a/libraries/animation/src/AnimPoleVectorConstraint.cpp b/libraries/animation/src/AnimPoleVectorConstraint.cpp index 343d6338be..c3078dab02 100644 --- a/libraries/animation/src/AnimPoleVectorConstraint.cpp +++ b/libraries/animation/src/AnimPoleVectorConstraint.cpp @@ -34,7 +34,7 @@ AnimPoleVectorConstraint::~AnimPoleVectorConstraint() { } -static float correctElbowForHandRotation(const AnimPose& hand, const AnimPose& lowerArm) { +static float correctElbowForHandFlexionExtension(const AnimPose& hand, const AnimPose& lowerArm) { // first calculate the ulnar/radial deviation // use the lower arm x-axis and the hand x-axis @@ -44,18 +44,57 @@ static float correctElbowForHandRotation(const AnimPose& hand, const AnimPose& l glm::vec3 xAxisHand = hand.rot() * glm::vec3(1.0f, 0.0f, 0.0f); glm::vec3 yAxisHand = hand.rot() * glm::vec3(0.0f, 1.0f, 0.0f); - float ulnarRadialDeviation = atan2(glm::dot(xAxisHand, xAxisLowerArm), glm::dot(xAxisHand, yAxisLowerArm)); + //float ulnarRadialDeviation = atan2(glm::dot(xAxisHand, xAxisLowerArm), glm::dot(xAxisHand, yAxisLowerArm)); float flexionExtension = atan2(glm::dot(yAxisHand, zAxisLowerArm), glm::dot(yAxisHand, yAxisLowerArm)); - qCDebug(animation) << "flexion angle " << flexionExtension; + //qCDebug(animation) << "flexion angle " << flexionExtension; float deltaInDegrees = (flexionExtension / PI) * 180.0f; - qCDebug(animation) << "delta in degrees " << deltaInDegrees; + //qCDebug(animation) << "delta in degrees " << deltaInDegrees; float deltaFinal = glm::sign(deltaInDegrees) * powf(fabsf(deltaInDegrees/180.0f), 1.5f) * 180.0f * -0.3f; - return deltaFinal; + return deltaInDegrees;// deltaFinal; +} + +static float correctElbowForHandUlnarRadialDeviation(const AnimPose& hand, const AnimPose& lowerArm) { + + const float DEAD_ZONE = 0.3f; + const float FILTER_EXPONENT = 2.0f; + // first calculate the ulnar/radial deviation + // use the lower arm x-axis and the hand x-axis + glm::vec3 xAxisLowerArm = lowerArm.rot() * glm::vec3(1.0f, 0.0f, 0.0f); + glm::vec3 yAxisLowerArm = lowerArm.rot() * glm::vec3(0.0f, 1.0f, 0.0f); + glm::vec3 zAxisLowerArm = lowerArm.rot() * glm::vec3(0.0f, 0.0f, 1.0f); + glm::vec3 xAxisHand = hand.rot() * glm::vec3(1.0f, 0.0f, 0.0f); + glm::vec3 yAxisHand = hand.rot() * glm::vec3(0.0f, 1.0f, 0.0f); + + float ulnarRadialDeviation = atan2(glm::dot(xAxisHand, xAxisLowerArm), glm::dot(xAxisHand, yAxisLowerArm)); + //float flexionExtension = atan2(glm::dot(yAxisHand, zAxisLowerArm), glm::dot(yAxisHand, yAxisLowerArm)); + + + + float makeForwardZeroRadians = ulnarRadialDeviation - (PI / 2.0f); + + qCDebug(animation) << "calibrated ulnar " << makeForwardZeroRadians; + + float deltaFractionOfPi = (makeForwardZeroRadians / PI); + float deltaUlnarRadial; + if (fabsf(deltaFractionOfPi) < DEAD_ZONE) { + deltaUlnarRadial = 0.0f; + } else { + deltaUlnarRadial = (deltaFractionOfPi - glm::sign(deltaFractionOfPi) * DEAD_ZONE) / (1.0f - DEAD_ZONE); + } + + float deltaUlnarRadialDegrees = glm::sign(deltaUlnarRadial) * powf(fabsf(deltaUlnarRadial), FILTER_EXPONENT) * 180.0f; + + + + qCDebug(animation) << "ulnar delta in degrees " << deltaUlnarRadialDegrees; + + float deltaFinal = deltaUlnarRadialDegrees; + return deltaFractionOfPi * 180.0f; // deltaFinal; } float AnimPoleVectorConstraint::findThetaNewWay(const glm::vec3& hand, const glm::vec3& shoulder, bool left) const { @@ -81,10 +120,10 @@ float AnimPoleVectorConstraint::findThetaNewWay(const glm::vec3& hand, const glm // weights const float zWeightBottom = -100.0f; //const glm::vec3 weights(-30.0f, 30.0f, 210.0f); - const glm::vec3 weights(-50.0f, -60.0f, 260.0f); + const glm::vec3 weights(-50.0f, 60.0f, 260.0f); glm::vec3 armToHand = hand - shoulder; // qCDebug(animation) << "current arm length " << glm::length(armToHand); - float initial_valuesY = ((armToHand[1] / defaultArmLength) * weights[1]) + biases[1]; + float initial_valuesY = (fabsf(armToHand[1] / defaultArmLength) * weights[1]) + biases[1]; float initial_valuesZ; if (armToHand[1] > 0.0f) { initial_valuesZ = weights[2] * glm::max(zStart - (armToHand[2] / defaultArmLength), 0.0f) * fabs(armToHand[1] / defaultArmLength); @@ -96,7 +135,7 @@ float AnimPoleVectorConstraint::findThetaNewWay(const glm::vec3& hand, const glm if (left) { initial_valuesX = weights[0] * glm::max(-1.0f * (armToHand[0] / defaultArmLength) + xStart, 0.0f); } else { - initial_valuesX = weights[0] * glm::max((armToHand[0] / defaultArmLength) + xStart, 0.0f); + initial_valuesX = weights[0] * ((armToHand[0] / defaultArmLength) + xStart); } float theta = initial_valuesX + initial_valuesY + initial_valuesZ; @@ -108,7 +147,9 @@ float AnimPoleVectorConstraint::findThetaNewWay(const glm::vec3& hand, const glm theta = 175.0f; } - // qCDebug(animation) << "relative hand" << initial_valuesX << " " << initial_valuesY << " " << initial_valuesZ << "theta value for pole vector is " << theta; + if (!left) { + //qCDebug(animation) << "relative hand x " << initial_valuesX << " y " << initial_valuesY << " z " << initial_valuesZ << "theta value for pole vector is " << theta; + } return theta; } @@ -207,14 +248,22 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim isLeft = true; } fred = findThetaNewWay(tipPose.trans(), basePose.trans(), isLeft); - if (isLeft){ + + + // here is where we would do the wrist correction. + float deltaTheta = correctElbowForHandFlexionExtension(tipPose, midPose); + float deltaThetaUlnar; + if (!isLeft) { + deltaThetaUlnar = correctElbowForHandUlnarRadialDeviation(tipPose, midPose); + } + //fred -= deltaThetaUlnar; + fred -= deltaTheta; + + if (isLeft) { fred *= -1.0f; } theta = ((180.0f - fred) / 180.0f)*PI; - - // here is where we would do the wrist correction. - float deltaTheta = correctElbowForHandRotation(tipPose, midPose); - qCDebug(animation) << "the wrist correction theta is -----> " << deltaTheta; + //qCDebug(animation) << "the wrist correction theta is -----> " << isLeft << " theta: " << deltaTheta; } From 68480f67607728ae080557fc8ce7b75b378e1c39 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Sat, 9 Feb 2019 10:49:18 -0800 Subject: [PATCH 070/474] Replace glm::packSnorm3x10_1x2() with fast SIMD implementation --- .../src/graphics/BufferViewHelpers.cpp | 4 +-- .../src/model-baker/BuildGraphicsMeshTask.cpp | 4 +-- libraries/render-utils/src/Model.cpp | 6 ++-- libraries/shared/src/GLMHelpers.h | 36 +++++++++++++++++++ 4 files changed, 43 insertions(+), 7 deletions(-) diff --git a/libraries/graphics/src/graphics/BufferViewHelpers.cpp b/libraries/graphics/src/graphics/BufferViewHelpers.cpp index 4c57abdfd4..301f5d8d73 100644 --- a/libraries/graphics/src/graphics/BufferViewHelpers.cpp +++ b/libraries/graphics/src/graphics/BufferViewHelpers.cpp @@ -257,7 +257,7 @@ template struct GpuVec3ToGlm : GpuToGlmAdapter { static T get(con case gpu::FLOAT: view.edit(index) = value; return true; case gpu::NUINT8: CHECK_SIZE(glm::uint32); view.edit(index) = glm::packUnorm4x8(glm::fvec4(value,0.0f)); return true; case gpu::UINT8: view.edit(index) = value; return true; - case gpu::NINT2_10_10_10: view.edit(index) = glm::packSnorm3x10_1x2(glm::fvec4(value,0.0f)); return true; + case gpu::NINT2_10_10_10: view.edit(index) = glm_packSnorm3x10_1x2(glm::fvec4(value,0.0f)); return true; default: break; } error("GpuVec3ToGlm::set", view, index, hint); return false; } @@ -295,7 +295,7 @@ template struct GpuVec4ToGlm : GpuToGlmAdapter { static T get(const case gpu::FLOAT: view.edit(index) = value; return true; case gpu::HALF: CHECK_SIZE(glm::uint64); view.edit(index) = glm::packHalf4x16(value); return true; case gpu::UINT8: view.edit(index) = value; return true; - case gpu::NINT2_10_10_10: view.edit(index) = glm::packSnorm3x10_1x2(value); return true; + case gpu::NINT2_10_10_10: view.edit(index) = glm_packSnorm3x10_1x2(value); return true; case gpu::NUINT16: CHECK_SIZE(glm::uint64); view.edit(index) = glm::packUnorm4x16(value); return true; case gpu::NUINT8: CHECK_SIZE(glm::uint32); view.edit(index) = glm::packUnorm4x8(value); return true; default: break; diff --git a/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.cpp b/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.cpp index 370add2c2e..c41431f940 100644 --- a/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.cpp +++ b/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.cpp @@ -125,8 +125,8 @@ void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphics #if HFM_PACK_NORMALS const auto normal = normalizeDirForPacking(*normalIt); const auto tangent = normalizeDirForPacking(*tangentIt); - const auto packedNormal = glm::packSnorm3x10_1x2(glm::vec4(normal, 0.0f)); - const auto packedTangent = glm::packSnorm3x10_1x2(glm::vec4(tangent, 0.0f)); + const auto packedNormal = glm_packSnorm3x10_1x2(glm::vec4(normal, 0.0f)); + const auto packedTangent = glm_packSnorm3x10_1x2(glm::vec4(tangent, 0.0f)); #else const auto packedNormal = *normalIt; const auto packedTangent = *tangentIt; diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index da8dceb176..9489166f43 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1594,9 +1594,9 @@ void packBlendshapeOffsetTo_Pos_F32_3xSN10_Nor_3xSN10_Tan_3xSN10(glm::uvec4& pac packed = glm::uvec4( glm::floatBitsToUint(len), - glm::packSnorm3x10_1x2(glm::vec4(normalizedPos, 0.0f)), - glm::packSnorm3x10_1x2(glm::vec4(unpacked.normalOffset, 0.0f)), - glm::packSnorm3x10_1x2(glm::vec4(unpacked.tangentOffset, 0.0f)) + glm_packSnorm3x10_1x2(glm::vec4(normalizedPos, 0.0f)), + glm_packSnorm3x10_1x2(glm::vec4(unpacked.normalOffset, 0.0f)), + glm_packSnorm3x10_1x2(glm::vec4(unpacked.tangentOffset, 0.0f)) ); } diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h index e7aaace1ae..e50162d8a4 100644 --- a/libraries/shared/src/GLMHelpers.h +++ b/libraries/shared/src/GLMHelpers.h @@ -315,6 +315,42 @@ inline void glm_mat4u_mul(const glm::mat4& m1, const glm::mat4& m2, glm::mat4& r #endif } +// +// Fast replacement of glm::packSnorm3x10_1x2() +// The SSE2 version quantizes using round to nearest even. +// The glm version quantizes using round away from zero. +// +inline uint32_t glm_packSnorm3x10_1x2(vec4 const& v) { + + union i10i10i10i2 { + struct { + int x : 10; + int y : 10; + int z : 10; + int w : 2; + } data; + uint32_t pack; + } Result; + +#if GLM_ARCH & GLM_ARCH_SSE2_BIT + __m128 vclamp = _mm_min_ps(_mm_max_ps(_mm_loadu_ps((float*)&v[0]), _mm_set1_ps(-1.0f)), _mm_set1_ps(1.0f)); + __m128i vpack = _mm_cvtps_epi32(_mm_mul_ps(vclamp, _mm_setr_ps(511.f, 511.f, 511.f, 1.f))); + + Result.data.x = _mm_cvtsi128_si32(vpack); + Result.data.y = _mm_cvtsi128_si32(_mm_shuffle_epi32(vpack, _MM_SHUFFLE(1,1,1,1))); + Result.data.z = _mm_cvtsi128_si32(_mm_shuffle_epi32(vpack, _MM_SHUFFLE(2,2,2,2))); + Result.data.w = _mm_cvtsi128_si32(_mm_shuffle_epi32(vpack, _MM_SHUFFLE(3,3,3,3))); +#else + ivec4 const Pack(round(clamp(v, -1.0f, 1.0f) * vec4(511.f, 511.f, 511.f, 1.f))); + + Result.data.x = Pack.x; + Result.data.y = Pack.y; + Result.data.z = Pack.z; + Result.data.w = Pack.w; +#endif + return Result.pack; +} + // convert float to int, using round-to-nearest-even (undefined on overflow) inline int fastLrintf(float x) { #if GLM_ARCH & GLM_ARCH_SSE2_BIT From 6f5514b5e3cfe920c3b634c4370c52071eaf30ab Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Sat, 9 Feb 2019 10:57:06 -0800 Subject: [PATCH 071/474] Remove dead code --- .../graphics/src/graphics/BufferViewHelpers.h | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/libraries/graphics/src/graphics/BufferViewHelpers.h b/libraries/graphics/src/graphics/BufferViewHelpers.h index 8a48c17007..3635ef64e5 100644 --- a/libraries/graphics/src/graphics/BufferViewHelpers.h +++ b/libraries/graphics/src/graphics/BufferViewHelpers.h @@ -46,30 +46,6 @@ namespace buffer_helpers { gpu::BufferView clone(const gpu::BufferView& input); gpu::BufferView resized(const gpu::BufferView& input, glm::uint32 numElements); - inline void packNormalAndTangent(glm::vec3 normal, glm::vec3 tangent, glm::uint32& packedNormal, glm::uint32& packedTangent) { - auto absNormal = glm::abs(normal); - auto absTangent = glm::abs(tangent); - normal /= glm::max(1e-6f, glm::max(glm::max(absNormal.x, absNormal.y), absNormal.z)); - tangent /= glm::max(1e-6f, glm::max(glm::max(absTangent.x, absTangent.y), absTangent.z)); - normal = glm::clamp(normal, -1.0f, 1.0f); - tangent = glm::clamp(tangent, -1.0f, 1.0f); - normal *= 511.0f; - tangent *= 511.0f; - - glm::detail::i10i10i10i2 normalStruct; - glm::detail::i10i10i10i2 tangentStruct; - normalStruct.data.x = fastLrintf(normal.x); - normalStruct.data.y = fastLrintf(normal.y); - normalStruct.data.z = fastLrintf(normal.z); - normalStruct.data.w = 0; - tangentStruct.data.x = fastLrintf(tangent.x); - tangentStruct.data.y = fastLrintf(tangent.y); - tangentStruct.data.z = fastLrintf(tangent.z); - tangentStruct.data.w = 0; - packedNormal = normalStruct.pack; - packedTangent = tangentStruct.pack; - } - namespace mesh { glm::uint32 forEachVertex(const graphics::MeshPointer& mesh, std::function func); bool setVertexAttributes(const graphics::MeshPointer& mesh, glm::uint32 index, const QVariantMap& attributes); From 7236d63da0c544c4196095dcbeac151fcbb52de6 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Sat, 9 Feb 2019 12:16:26 -0800 Subject: [PATCH 072/474] Fix tabs --- libraries/shared/src/GLMHelpers.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h index e50162d8a4..6deae695cd 100644 --- a/libraries/shared/src/GLMHelpers.h +++ b/libraries/shared/src/GLMHelpers.h @@ -322,7 +322,7 @@ inline void glm_mat4u_mul(const glm::mat4& m1, const glm::mat4& m2, glm::mat4& r // inline uint32_t glm_packSnorm3x10_1x2(vec4 const& v) { - union i10i10i10i2 { + union i10i10i10i2 { struct { int x : 10; int y : 10; From f125e90449326c927aa861512ebb17cfbfe42150 Mon Sep 17 00:00:00 2001 From: Angus Antley Date: Mon, 11 Feb 2019 07:41:41 -0800 Subject: [PATCH 073/474] worked on the swing twist decomp to get the angles of the wrist for elbow adjustments --- .../src/AnimPoleVectorConstraint.cpp | 37 ++++++++++++++++--- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/libraries/animation/src/AnimPoleVectorConstraint.cpp b/libraries/animation/src/AnimPoleVectorConstraint.cpp index c3078dab02..09fadf2da5 100644 --- a/libraries/animation/src/AnimPoleVectorConstraint.cpp +++ b/libraries/animation/src/AnimPoleVectorConstraint.cpp @@ -77,7 +77,7 @@ static float correctElbowForHandUlnarRadialDeviation(const AnimPose& hand, const float makeForwardZeroRadians = ulnarRadialDeviation - (PI / 2.0f); - qCDebug(animation) << "calibrated ulnar " << makeForwardZeroRadians; + //qCDebug(animation) << "calibrated ulnar " << makeForwardZeroRadians; float deltaFractionOfPi = (makeForwardZeroRadians / PI); float deltaUlnarRadial; @@ -91,7 +91,7 @@ static float correctElbowForHandUlnarRadialDeviation(const AnimPose& hand, const - qCDebug(animation) << "ulnar delta in degrees " << deltaUlnarRadialDegrees; + //qCDebug(animation) << "ulnar delta in degrees " << deltaUlnarRadialDegrees; float deltaFinal = deltaUlnarRadialDegrees; return deltaFractionOfPi * 180.0f; // deltaFinal; @@ -99,7 +99,7 @@ static float correctElbowForHandUlnarRadialDeviation(const AnimPose& hand, const float AnimPoleVectorConstraint::findThetaNewWay(const glm::vec3& hand, const glm::vec3& shoulder, bool left) const { // get the default poses for the upper and lower arm - // then use this length to judge how far the arm is away from the shoulder. + // then use this length to judge how far the hand is away from the shoulder. // then create weights that make the elbow angle less when the x value is large in either direction. // make the angle less when z is small. // lower y with x center lower angle @@ -248,7 +248,34 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim isLeft = true; } fred = findThetaNewWay(tipPose.trans(), basePose.trans(), isLeft); - + + //get the swingTwist of the hand to lower arm + glm::quat yTwist; + glm::quat flexUlnarSwing; + glm::quat relativeHandRotation = (midPose.inverse() * tipPose).rot(); + swingTwistDecomposition(relativeHandRotation, Vectors::UNIT_X, flexUlnarSwing, yTwist); + glm::vec3 twistAxis = glm::axis(yTwist); + glm::vec3 flexUlnarAxis = glm::axis(flexUlnarSwing); + float swingTheta = glm::angle(flexUlnarSwing); + float twistTheta = glm::angle(yTwist); + glm::quat flex; + glm::quat ulnarDeviation; + swingTwistDecomposition(flexUlnarSwing, Vectors::UNIT_Z, flex, ulnarDeviation); + float flexTheta = glm::angle(flex); + glm::vec3 ulnarAxis = glm::axis(ulnarDeviation); + float ulnarDeviationTheta = glm::angle(ulnarDeviation); + + glm::vec3 eulerVersion = glm::eulerAngles(relativeHandRotation); + if (!isLeft) { + //qCDebug(animation) << "wrist thetas -----> X " << twistTheta << " twist: " << flexTheta << "ulnar deviation: " << ulnarDeviationTheta; + qCDebug(animation) << "0: " << eulerVersion[0] << " 1: " << eulerVersion[1] << " 2: " << eulerVersion[2]; + //qCDebug(animation) << "ulnarAxis " << flexUlnarAxis; + //qCDebug(animation) << "twistAxis " << twistAxis; + } + + //QString name = QString("wrist_target").arg(_id); + //glm::vec4 markerColor(1.0f, 1.0f, 0.0f, 0.0f); + //DebugDraw::getInstance().addMyAvatarMarker(name, midPose.rot(), midPose.trans(), markerColor); // here is where we would do the wrist correction. float deltaTheta = correctElbowForHandFlexionExtension(tipPose, midPose); @@ -262,7 +289,7 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim if (isLeft) { fred *= -1.0f; } - theta = ((180.0f - fred) / 180.0f)*PI; + // theta = ((180.0f - fred) / 180.0f)*PI; //qCDebug(animation) << "the wrist correction theta is -----> " << isLeft << " theta: " << deltaTheta; } From ff746c31413d2aebe4a651dd1681a8cff31f227c Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 11 Feb 2019 12:19:33 -0800 Subject: [PATCH 074/474] flag collision group/mask dirty after grab --- libraries/entities/src/EntityItem.cpp | 33 +++++++++++++++++++-------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 1fb1ebb1bc..777f9ba167 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1897,7 +1897,7 @@ glm::vec3 EntityItem::getUnscaledDimensions() const { void EntityItem::setRotation(glm::quat rotation) { if (getLocalOrientation() != rotation) { setLocalOrientation(rotation); - _flags |= Simulation::DIRTY_ROTATION; + markDirtyFlags(Simulation::DIRTY_ROTATION); forEachDescendant([&](SpatiallyNestablePointer object) { if (object->getNestableType() == NestableType::Entity) { EntityItemPointer entity = std::static_pointer_cast(object); @@ -1927,7 +1927,7 @@ void EntityItem::setVelocity(const glm::vec3& value) { velocity = value; } setLocalVelocity(velocity); - _flags |= Simulation::DIRTY_LINEAR_VELOCITY; + markDirtyFlags(Simulation::DIRTY_LINEAR_VELOCITY); } } } @@ -1982,7 +1982,7 @@ void EntityItem::setAngularVelocity(const glm::vec3& value) { angularVelocity = value; } setLocalAngularVelocity(angularVelocity); - _flags |= Simulation::DIRTY_ANGULAR_VELOCITY; + markDirtyFlags(Simulation::DIRTY_ANGULAR_VELOCITY); } } } @@ -2168,8 +2168,14 @@ bool EntityItem::addAction(EntitySimulationPointer simulation, EntityDynamicPoin void EntityItem::enableNoBootstrap() { if (!(bool)(_flags & Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING)) { - _flags |= Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING; - _flags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar + markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP); + markSpecialFlags(Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING); + + // NOTE: unlike disableNoBootstrap() below, we do not call simulation->changeEntity() here + // because most enableNoBootstrap() cases are already correctly handled outside this scope + // and I didn't want to add redundant work. + // TODO: cleanup Grabs & dirtySimulationFlags to be more efficient and make more sense. + forEachDescendant([&](SpatiallyNestablePointer child) { if (child->getNestableType() == NestableType::Entity) { EntityItemPointer entity = std::static_pointer_cast(child); @@ -2182,13 +2188,21 @@ void EntityItem::enableNoBootstrap() { void EntityItem::disableNoBootstrap() { if (!stillHasGrabActions()) { - _flags &= ~Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING; - _flags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar + markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP); + clearSpecialFlags(Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING); + + EntityTreePointer entityTree = getTree(); + assert(entityTree); + EntitySimulationPointer simulation = entityTree->getSimulation(); + assert(simulation); + simulation->changeEntity(getThisPointer()); + forEachDescendant([&](SpatiallyNestablePointer child) { if (child->getNestableType() == NestableType::Entity) { EntityItemPointer entity = std::static_pointer_cast(child); entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP); entity->clearSpecialFlags(Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING); + simulation->changeEntity(entity); } }); } @@ -2574,7 +2588,7 @@ QList EntityItem::getActionsOfType(EntityDynamicType typeT void EntityItem::locationChanged(bool tellPhysics) { requiresRecalcBoxes(); if (tellPhysics) { - _flags |= Simulation::DIRTY_TRANSFORM; + markDirtyFlags(Simulation::DIRTY_TRANSFORM); EntityTreePointer tree = getTree(); if (tree) { tree->entityChanged(getThisPointer()); @@ -3445,7 +3459,7 @@ void EntityItem::addGrab(GrabPointer grab) { if (useAction) { EntityTreePointer entityTree = getTree(); assert(entityTree); - EntitySimulationPointer simulation = entityTree ? entityTree->getSimulation() : nullptr; + EntitySimulationPointer simulation = entityTree->getSimulation(); assert(simulation); auto actionFactory = DependencyManager::get(); @@ -3494,7 +3508,6 @@ void EntityItem::removeGrab(GrabPointer grab) { setLocalVelocity(glm::vec3(0.0f)); setAngularVelocity(glm::vec3(0.0f)); } - markDirtyFlags(Simulation::DIRTY_MOTION_TYPE); QUuid actionID = grab->getActionID(); if (!actionID.isNull()) { From fab3e5e3fd32a8b61f7c110f045f10eed9cc20ba Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 11 Feb 2019 16:13:56 -0800 Subject: [PATCH 075/474] remember hash of AvatarEntityItemData --- interface/src/avatar/OtherAvatar.cpp | 5 +++++ interface/src/avatar/OtherAvatar.h | 10 ++++++++++ .../avatars-renderer/src/avatars-renderer/Avatar.cpp | 5 ----- .../avatars-renderer/src/avatars-renderer/Avatar.h | 6 ++---- libraries/avatars/src/AvatarData.h | 2 +- 5 files changed, 18 insertions(+), 10 deletions(-) diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp index a3950c8e96..67eb919b73 100755 --- a/interface/src/avatar/OtherAvatar.cpp +++ b/interface/src/avatar/OtherAvatar.cpp @@ -510,6 +510,11 @@ void OtherAvatar::handleChangedAvatarEntityData() { } } stateItr.value().success = success; + if (success) { + stateItr.value().hash = newHash; + } else { + stateItr.value().hash = 0; + } } AvatarEntityIDs recentlyRemovedAvatarEntities = getAndClearRecentlyRemovedIDs(); diff --git a/interface/src/avatar/OtherAvatar.h b/interface/src/avatar/OtherAvatar.h index 3ecd35413f..6461a0107c 100644 --- a/interface/src/avatar/OtherAvatar.h +++ b/interface/src/avatar/OtherAvatar.h @@ -76,6 +76,16 @@ protected: void onAddAttachedAvatarEntity(const QUuid& id); void onRemoveAttachedAvatarEntity(const QUuid& id); + class AvatarEntityDataHash { + public: + AvatarEntityDataHash(uint32_t h) : hash(h) {}; + uint32_t hash { 0 }; + bool success { false }; + }; + + using MapOfAvatarEntityDataHashes = QMap; + MapOfAvatarEntityDataHashes _avatarEntityDataHashes; + std::vector _attachedAvatarEntities; std::shared_ptr _otherAvatarOrbMeshPlaceholder { nullptr }; OverlayID _otherAvatarOrbMeshPlaceholderID { UNKNOWN_OVERLAY_ID }; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index ba5529e1c0..36ad6c6b82 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -305,11 +305,6 @@ void Avatar::setTargetScale(float targetScale) { } } -void Avatar::setAvatarEntityDataChanged(bool value) { - AvatarData::setAvatarEntityDataChanged(value); - _avatarEntityDataHashes.clear(); -} - void Avatar::removeAvatarEntitiesFromTree() { auto treeRenderer = DependencyManager::get(); EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index b43fe012b7..39b77f2b65 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -480,8 +480,6 @@ public: virtual void setModelScale(float scale) { _modelScale = scale; } virtual glm::vec3 scaleForChildren() const override { return glm::vec3(getModelScale()); } - virtual void setAvatarEntityDataChanged(bool value) override; - // Show hide the model representation of the avatar virtual void setEnableMeshVisible(bool isEnabled); virtual bool getEnableMeshVisible() const; @@ -647,8 +645,8 @@ protected: bool success { false }; }; - using MapOfAvatarEntityDataHashes = QMap; - MapOfAvatarEntityDataHashes _avatarEntityDataHashes; + //using MapOfAvatarEntityDataHashes = QMap; + //MapOfAvatarEntityDataHashes _avatarEntityDataHashes; uint64_t _lastRenderUpdateTime { 0 }; int _leftPointerGeometryID { 0 }; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index d071b74d6e..63396a59ac 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -1147,7 +1147,7 @@ public: */ Q_INVOKABLE virtual void setAvatarEntityData(const AvatarEntityMap& avatarEntityData); - virtual void setAvatarEntityDataChanged(bool value) { _avatarEntityDataChanged = value; } + void setAvatarEntityDataChanged(bool value) { _avatarEntityDataChanged = value; } AvatarEntityIDs getAndClearRecentlyRemovedIDs(); /**jsdoc From a7e28f7a6618bc733c666184ff6b316d437ff515 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Mon, 11 Feb 2019 16:48:34 -0800 Subject: [PATCH 076/474] fix spatially nestable parent overwrite --- interface/src/avatar/OtherAvatar.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp index a3950c8e96..8ad9c6b121 100755 --- a/interface/src/avatar/OtherAvatar.cpp +++ b/interface/src/avatar/OtherAvatar.cpp @@ -489,6 +489,9 @@ void OtherAvatar::handleChangedAvatarEntityData() { bool success = true; if (entity) { QUuid oldParentID = entity->getParentID(); + const QUuid NULL_ID = QUuid("{00000000-0000-0000-0000-000000000005}"); + entity->setParentID(NULL_ID); + entity->setParentID(oldParentID); if (entityTree->updateEntity(entityID, properties)) { entity->updateLastEditedFromRemote(); } else { From 942e9ccdfd4ddf7d920a07f1ce37f9a1ed6ed797 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Mon, 11 Feb 2019 18:21:00 -0700 Subject: [PATCH 077/474] Hand collisions working --- interface/src/avatar/AvatarManager.cpp | 8 + interface/src/avatar/OtherAvatar.cpp | 1 - interface/src/avatar/OtherAvatar.h | 1 + libraries/animation/src/Flow.cpp | 245 ++++++++++++------------- libraries/animation/src/Flow.h | 69 ++++--- libraries/animation/src/Rig.h | 2 +- 6 files changed, 162 insertions(+), 164 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index c9e099df21..50e8546979 100755 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -271,6 +271,14 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { if (avatar->getSkeletonModel()->isLoaded()) { // remove the orb if it is there avatar->removeOrb(); + if (avatar->getWorkloadRegion() == workload::Region::R1) { + auto &flow = _myAvatar->getSkeletonModel()->getRig().getFlow(); + for (auto &handJointName : HAND_COLLISION_JOINTS) { + int jointIndex = avatar->getJointIndex(handJointName); + glm::vec3 position = avatar->getJointPosition(jointIndex); + flow.setOthersCollision(avatar->getID(), jointIndex, position); + } + } if (avatar->needsPhysicsUpdate()) { _avatarsToChangeInPhysics.insert(avatar); } diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp index a3950c8e96..695481b709 100755 --- a/interface/src/avatar/OtherAvatar.cpp +++ b/interface/src/avatar/OtherAvatar.cpp @@ -365,7 +365,6 @@ void OtherAvatar::simulate(float deltaTime, bool inView) { PROFILE_RANGE(simulation, "grabs"); applyGrabChanges(); } - updateFadingStatus(); } diff --git a/interface/src/avatar/OtherAvatar.h b/interface/src/avatar/OtherAvatar.h index 3ecd35413f..6398be9313 100644 --- a/interface/src/avatar/OtherAvatar.h +++ b/interface/src/avatar/OtherAvatar.h @@ -50,6 +50,7 @@ public: void rebuildCollisionShape() override; void setWorkloadRegion(uint8_t region); + uint8_t getWorkloadRegion() { return _workloadRegion; } bool shouldBeInPhysicsSimulation() const; bool needsPhysicsUpdate() const; diff --git a/libraries/animation/src/Flow.cpp b/libraries/animation/src/Flow.cpp index a10d413d90..87d7155abd 100644 --- a/libraries/animation/src/Flow.cpp +++ b/libraries/animation/src/Flow.cpp @@ -12,11 +12,23 @@ #include "Rig.h" #include "AnimSkeleton.h" -FlowCollisionSphere::FlowCollisionSphere(const int& jointIndex, const FlowCollisionSettings& settings) { +const std::map PRESET_FLOW_DATA = { { "hair", FlowPhysicsSettings() }, +{ "skirt", FlowPhysicsSettings(true, 1.0f, DEFAULT_GRAVITY, 0.65f, 0.8f, 0.45f, 0.01f) }, +{ "breast", FlowPhysicsSettings(true, 1.0f, DEFAULT_GRAVITY, 0.65f, 0.8f, 0.45f, 0.01f) } }; + +const std::map PRESET_COLLISION_DATA = { + { "Spine2", FlowCollisionSettings(QUuid(), FlowCollisionType::CollisionSphere, glm::vec3(0.0f, 0.2f, 0.0f), 0.14f) }, + { "LeftArm", FlowCollisionSettings(QUuid(), FlowCollisionType::CollisionSphere, glm::vec3(0.0f, 0.02f, 0.0f), 0.05f) }, + { "RightArm", FlowCollisionSettings(QUuid(), FlowCollisionType::CollisionSphere, glm::vec3(0.0f, 0.02f, 0.0f), 0.05f) }, + { "HeadTop_End", FlowCollisionSettings(QUuid(), FlowCollisionType::CollisionSphere, glm::vec3(0.0f, -0.15f, 0.0f), 0.09f) } +}; + +FlowCollisionSphere::FlowCollisionSphere(const int& jointIndex, const FlowCollisionSettings& settings, bool isTouch) { _jointIndex = jointIndex; _radius = _initialRadius = settings._radius; _offset = _initialOffset = settings._offset; _entityID = settings._entityID; + _isTouch = isTouch; } FlowCollisionResult FlowCollisionSphere::computeSphereCollision(const glm::vec3& point, float radius) const { @@ -51,53 +63,21 @@ FlowCollisionResult FlowCollisionSphere::checkSegmentCollision(const glm::vec3& return result; } -void FlowCollisionSphere::update() { - // TODO - // Fill this +void FlowCollisionSystem::addCollisionSphere(int jointIndex, const FlowCollisionSettings& settings, const glm::vec3& position, bool isSelfCollision, bool isTouch) { + auto collision = FlowCollisionSphere(jointIndex, settings, isTouch); + collision.setPosition(position); + if (isSelfCollision) { + _selfCollisions.push_back(collision); + } else { + _othersCollisions.push_back(collision); + } + +}; +void FlowCollisionSystem::resetCollisions() { + _allCollisions.clear(); + _othersCollisions.clear(); + _selfCollisions.clear(); } - -void FlowCollisionSystem::addCollisionSphere(int jointIndex, const FlowCollisionSettings& settings) { - _collisionSpheres.push_back(FlowCollisionSphere(jointIndex, settings)); -}; - -void FlowCollisionSystem::addCollisionShape(int jointIndex, const FlowCollisionSettings& settings) { - auto name = JOINT_COLLISION_PREFIX + jointIndex; - switch (settings._type) { - case FlowCollisionType::CollisionSphere: - addCollisionSphere(jointIndex, settings); - break; - default: - break; - } -}; - -bool FlowCollisionSystem::addCollisionToJoint(int jointIndex) { - if (_collisionSpheres.size() >= COLLISION_SHAPES_LIMIT) { - return false; - } - int collisionIndex = findCollisionWithJoint(jointIndex); - if (collisionIndex == -1) { - addCollisionShape(jointIndex, FlowCollisionSettings()); - return true; - } - else { - return false; - } -}; - -void FlowCollisionSystem::removeCollisionFromJoint(int jointIndex) { - int collisionIndex = findCollisionWithJoint(jointIndex); - if (collisionIndex > -1) { - _collisionSpheres.erase(_collisionSpheres.begin() + collisionIndex); - } -}; - -void FlowCollisionSystem::update() { - for (size_t i = 0; i < _collisionSpheres.size(); i++) { - _collisionSpheres[i].update(); - } -}; - FlowCollisionResult FlowCollisionSystem::computeCollision(const std::vector collisions) { FlowCollisionResult result; if (collisions.size() > 1) { @@ -123,50 +103,51 @@ FlowCollisionResult FlowCollisionSystem::computeCollision(const std::vector FlowCollisionSystem::checkFlowThreadCollisions(FlowThread* flowThread, bool checkSegments) { +std::vector FlowCollisionSystem::checkFlowThreadCollisions(FlowThread* flowThread) { std::vector> FlowThreadResults; FlowThreadResults.resize(flowThread->_joints.size()); - for (size_t j = 0; j < _collisionSpheres.size(); j++) { - FlowCollisionSphere &sphere = _collisionSpheres[j]; + for (size_t j = 0; j < _allCollisions.size(); j++) { + FlowCollisionSphere &sphere = _allCollisions[j]; FlowCollisionResult rootCollision = sphere.computeSphereCollision(flowThread->_positions[0], flowThread->_radius); std::vector collisionData = { rootCollision }; bool tooFar = rootCollision._distance >(flowThread->_length + rootCollision._radius); FlowCollisionResult nextCollision; if (!tooFar) { - if (checkSegments) { + if (sphere._isTouch) { for (size_t i = 1; i < flowThread->_joints.size(); i++) { auto prevCollision = collisionData[i - 1]; - nextCollision = _collisionSpheres[j].computeSphereCollision(flowThread->_positions[i], flowThread->_radius); + nextCollision = _allCollisions[j].computeSphereCollision(flowThread->_positions[i], flowThread->_radius); collisionData.push_back(nextCollision); + bool isTouching = false; if (prevCollision._offset > 0.0f) { if (i == 1) { FlowThreadResults[i - 1].push_back(prevCollision); + isTouching = true; } - } - else if (nextCollision._offset > 0.0f) { + } else if (nextCollision._offset > 0.0f) { FlowThreadResults[i].push_back(nextCollision); - } - else { - FlowCollisionResult segmentCollision = _collisionSpheres[j].checkSegmentCollision(flowThread->_positions[i - 1], flowThread->_positions[i], prevCollision, nextCollision); + isTouching = true; + } else { + FlowCollisionResult segmentCollision = _allCollisions[j].checkSegmentCollision(flowThread->_positions[i - 1], flowThread->_positions[i], prevCollision, nextCollision); if (segmentCollision._offset > 0) { FlowThreadResults[i - 1].push_back(segmentCollision); FlowThreadResults[i].push_back(segmentCollision); + isTouching = true; } } } - } - else { + } else { if (rootCollision._offset > 0.0f) { FlowThreadResults[0].push_back(rootCollision); } for (size_t i = 1; i < flowThread->_joints.size(); i++) { - nextCollision = _collisionSpheres[j].computeSphereCollision(flowThread->_positions[i], flowThread->_radius); + nextCollision = _allCollisions[j].computeSphereCollision(flowThread->_positions[i], flowThread->_radius); if (nextCollision._offset > 0.0f) { FlowThreadResults[i].push_back(nextCollision); } @@ -182,40 +163,49 @@ std::vector FlowCollisionSystem::checkFlowThreadCollisions( return results; }; -int FlowCollisionSystem::findCollisionWithJoint(int jointIndex) { - for (size_t i = 0; i < _collisionSpheres.size(); i++) { - if (_collisionSpheres[i]._jointIndex == jointIndex) { +int FlowCollisionSystem::findSelfCollisionWithJoint(int jointIndex) { + for (size_t i = 0; i < _selfCollisions.size(); i++) { + if (_selfCollisions[i]._jointIndex == jointIndex) { return (int)i; } } return -1; }; -void FlowCollisionSystem::modifyCollisionRadius(int jointIndex, float radius) { - int collisionIndex = findCollisionWithJoint(jointIndex); +void FlowCollisionSystem::modifySelfCollisionRadius(int jointIndex, float radius) { + int collisionIndex = findSelfCollisionWithJoint(jointIndex); if (collisionIndex > -1) { - _collisionSpheres[collisionIndex]._initialRadius = radius; - _collisionSpheres[collisionIndex]._radius = _scale * radius; + _selfCollisions[collisionIndex]._initialRadius = radius; + _selfCollisions[collisionIndex]._radius = _scale * radius; } }; -void FlowCollisionSystem::modifyCollisionYOffset(int jointIndex, float offset) { - int collisionIndex = findCollisionWithJoint(jointIndex); +void FlowCollisionSystem::modifySelfCollisionYOffset(int jointIndex, float offset) { + int collisionIndex = findSelfCollisionWithJoint(jointIndex); if (collisionIndex > -1) { - auto currentOffset = _collisionSpheres[collisionIndex]._offset; - _collisionSpheres[collisionIndex]._initialOffset = glm::vec3(currentOffset.x, offset, currentOffset.z); - _collisionSpheres[collisionIndex]._offset = _collisionSpheres[collisionIndex]._initialOffset * _scale; + auto currentOffset = _selfCollisions[collisionIndex]._offset; + _selfCollisions[collisionIndex]._initialOffset = glm::vec3(currentOffset.x, offset, currentOffset.z); + _selfCollisions[collisionIndex]._offset = _selfCollisions[collisionIndex]._initialOffset * _scale; } }; -void FlowCollisionSystem::modifyCollisionOffset(int jointIndex, const glm::vec3& offset) { - int collisionIndex = findCollisionWithJoint(jointIndex); +void FlowCollisionSystem::modifySelfCollisionOffset(int jointIndex, const glm::vec3& offset) { + int collisionIndex = findSelfCollisionWithJoint(jointIndex); if (collisionIndex > -1) { - _collisionSpheres[collisionIndex]._initialOffset = offset; - _collisionSpheres[collisionIndex]._offset = _collisionSpheres[collisionIndex]._initialOffset * _scale; + _selfCollisions[collisionIndex]._initialOffset = offset; + _selfCollisions[collisionIndex]._offset = _selfCollisions[collisionIndex]._initialOffset * _scale; } }; + +void FlowCollisionSystem::prepareCollisions() { + _allCollisions.clear(); + _allCollisions.resize(_selfCollisions.size() + _othersCollisions.size()); + std::copy(_selfCollisions.begin(), _selfCollisions.begin() + _selfCollisions.size(), _allCollisions.begin()); + std::copy(_othersCollisions.begin(), _othersCollisions.begin() + _othersCollisions.size(), _allCollisions.begin() + _selfCollisions.size()); + _othersCollisions.clear(); +} + void FlowNode::update(const glm::vec3& accelerationOffset) { _acceleration = glm::vec3(0.0f, _settings._gravity, 0.0f); _previousVelocity = _currentVelocity; @@ -263,16 +253,6 @@ void FlowNode::solveCollisions(const FlowCollisionResult& collision) { } }; -void FlowNode::apply(const QString& name, bool forceRendering) { - // TODO - // Render here ?? - /* - jointDebug.setDebugSphere(name, self.currentPosition, 2 * self.radius, { red: self.collision && self.collision.collisionCount > 1 ? 0 : 255, - green : self.colliding ? 0 : 255, - blue : 0 }, forceRendering); - */ -}; - FlowJoint::FlowJoint(int jointIndex, int parentIndex, int childIndex, const QString& name, const QString& group, float scale, const FlowPhysicsSettings& settings) { _index = jointIndex; _name = name; @@ -347,7 +327,7 @@ void FlowThread::resetLength() { _length = 0.0f; for (size_t i = 1; i < _joints.size(); i++) { int index = _joints[i]; - _length += (*_jointsPointer)[index]._length; + _length += _jointsPointer->at(index)._length; } } @@ -356,12 +336,12 @@ void FlowThread::computeFlowThread(int rootIndex) { if (_jointsPointer->size() == 0) { return; } - int childIndex = (*_jointsPointer)[parentIndex]._childIndex; + int childIndex = _jointsPointer->at(parentIndex)._childIndex; std::vector indexes = { parentIndex }; for (size_t i = 0; i < _jointsPointer->size(); i++) { if (childIndex > -1) { indexes.push_back(childIndex); - childIndex = (*_jointsPointer)[childIndex]._childIndex; + childIndex = _jointsPointer->at(childIndex)._childIndex; } else { break; } @@ -370,20 +350,20 @@ void FlowThread::computeFlowThread(int rootIndex) { int index = indexes[i]; _joints.push_back(index); if (i > 0) { - _length += (*_jointsPointer)[index]._length; + _length += _jointsPointer->at(index)._length; } } }; void FlowThread::computeRecovery() { int parentIndex = _joints[0]; - auto parentJoint = (*_jointsPointer)[parentIndex]; - (*_jointsPointer)[parentIndex]._recoveryPosition = parentJoint._recoveryPosition = parentJoint._node._currentPosition; + auto parentJoint = _jointsPointer->at(parentIndex); + _jointsPointer->at(parentIndex)._recoveryPosition = parentJoint._recoveryPosition = parentJoint._node._currentPosition; glm::quat parentRotation = parentJoint._parentWorldRotation * parentJoint._initialRotation; for (size_t i = 1; i < _joints.size(); i++) { - auto joint = (*_jointsPointer)[_joints[i]]; + auto joint = _jointsPointer->at(_joints[i]); glm::quat rotation = i == 1 ? parentRotation : rotation * parentJoint._initialRotation; - (*_jointsPointer)[_joints[i]]._recoveryPosition = joint._recoveryPosition = parentJoint._recoveryPosition + (rotation * (joint._initialTranslation * 0.01f)); + _jointsPointer->at(_joints[i])._recoveryPosition = joint._recoveryPosition = parentJoint._recoveryPosition + (rotation * (joint._initialTranslation * 0.01f)); parentJoint = joint; } }; @@ -391,10 +371,10 @@ void FlowThread::computeRecovery() { void FlowThread::update() { if (getActive()) { _positions.clear(); - _radius = (*_jointsPointer)[_joints[0]]._node._settings._radius; + _radius = _jointsPointer->at(_joints[0])._node._settings._radius; computeRecovery(); for (size_t i = 0; i < _joints.size(); i++) { - auto &joint = (*_jointsPointer)[_joints[i]]; + auto &joint = _jointsPointer->at(_joints[i]); joint.update(); _positions.push_back(joint._node._currentPosition); } @@ -404,20 +384,16 @@ void FlowThread::update() { void FlowThread::solve(bool useCollisions, FlowCollisionSystem& collisionSystem) { if (getActive()) { if (useCollisions) { - auto bodyCollisions = collisionSystem.checkFlowThreadCollisions(this, false); + auto bodyCollisions = collisionSystem.checkFlowThreadCollisions(this); int handTouchedJoint = -1; for (size_t i = 0; i < _joints.size(); i++) { int index = _joints[i]; - if (bodyCollisions[i]._offset > 0.0f) { - (*_jointsPointer)[index].solve(bodyCollisions[i]); - } else { - (*_jointsPointer)[index].solve(FlowCollisionResult()); - } + _jointsPointer->at(index).solve(bodyCollisions[i]); } } else { for (size_t i = 0; i < _joints.size(); i++) { int index = _joints[i]; - (*_jointsPointer)[index].solve(FlowCollisionResult()); + _jointsPointer->at(index).solve(FlowCollisionResult()); } } } @@ -428,8 +404,8 @@ void FlowThread::computeJointRotations() { auto pos0 = _rootFramePositions[0]; auto pos1 = _rootFramePositions[1]; - auto joint0 = (*_jointsPointer)[_joints[0]]; - auto joint1 = (*_jointsPointer)[_joints[1]]; + auto joint0 = _jointsPointer->at(_joints[0]); + auto joint1 = _jointsPointer->at(_joints[1]); auto initial_pos1 = pos0 + (joint0._initialRotation * (joint1._initialTranslation * 0.01f)); @@ -438,10 +414,10 @@ void FlowThread::computeJointRotations() { auto delta = rotationBetween(vec0, vec1); - joint0._currentRotation = (*_jointsPointer)[_joints[0]]._currentRotation = delta * joint0._initialRotation; + joint0._currentRotation = _jointsPointer->at(_joints[0])._currentRotation = delta * joint0._initialRotation; for (size_t i = 1; i < _joints.size() - 1; i++) { - auto nextJoint = (*_jointsPointer)[_joints[i + 1]]; + auto nextJoint = _jointsPointer->at(_joints[i + 1]); for (size_t j = i; j < _joints.size(); j++) { _rootFramePositions[j] = glm::inverse(joint0._currentRotation) * _rootFramePositions[j] - (joint0._initialTranslation * 0.01f); } @@ -454,7 +430,7 @@ void FlowThread::computeJointRotations() { delta = rotationBetween(vec0, vec1); - joint1._currentRotation = (*_jointsPointer)[joint1._index]._currentRotation = delta * joint1._initialRotation; + joint1._currentRotation = _jointsPointer->at(joint1._index)._currentRotation = delta * joint1._initialRotation; joint0 = joint1; joint1 = nextJoint; } @@ -468,7 +444,7 @@ void FlowThread::apply() { }; bool FlowThread::getActive() { - return (*_jointsPointer)[_joints[0]]._node._active; + return _jointsPointer->at(_joints[0])._node._active; }; void Flow::setRig(Rig* rig) { @@ -484,12 +460,17 @@ void Flow::calculateConstraints() { auto flowPrefix = FLOW_JOINT_PREFIX.toUpper(); auto simPrefix = SIM_JOINT_PREFIX.toUpper(); auto skeleton = _rig->getAnimSkeleton(); + std::vector handsIndices; + _collisionSystem.resetCollisions(); if (skeleton) { for (int i = 0; i < skeleton->getNumJoints(); i++) { auto name = skeleton->getJointName(i); if (name == "RightHand") { rightHandIndex = i; } + if (std::find(HAND_COLLISION_JOINTS.begin(), HAND_COLLISION_JOINTS.end(), name) != HAND_COLLISION_JOINTS.end()) { + handsIndices.push_back(i); + } auto parentIndex = skeleton->getParentIndex(i); if (parentIndex == -1) { continue; @@ -533,7 +514,7 @@ void Flow::calculateConstraints() { } } else { if (PRESET_COLLISION_DATA.find(name) != PRESET_COLLISION_DATA.end()) { - _collisionSystem.addCollisionShape(i, PRESET_COLLISION_DATA.at(name)); + _collisionSystem.addCollisionSphere(i, PRESET_COLLISION_DATA.at(name)); } } if (isFlowJoint || isSimJoint) { @@ -591,8 +572,6 @@ void Flow::calculateConstraints() { if (_jointThreads.size() == 0) { _rig->clearJointStates(); } - - if (SHOW_DUMMY_JOINTS && rightHandIndex > -1) { int jointCount = (int)_flowJointData.size(); int extraIndex = (int)_flowJointData.size(); @@ -611,6 +590,13 @@ void Flow::calculateConstraints() { auto extraThread = FlowThread(jointCount, &_flowJointData); _jointThreads.push_back(extraThread); } + if (handsIndices.size() > 0) { + FlowCollisionSettings handSettings; + handSettings._radius = HAND_COLLISION_RADIUS; + for (size_t i = 0; i < handsIndices.size(); i++) { + _collisionSystem.addCollisionSphere(handsIndices[i], handSettings, glm::vec3(), true, true); + } + } _initialized = _jointThreads.size() > 0; } @@ -633,12 +619,10 @@ void Flow::setTransform(float scale, const glm::vec3& position, const glm::quat& } void Flow::update() { + QElapsedTimer _timer; _timer.start(); if (_initialized && _active) { updateJoints(); - if (USE_COLLISIONS) { - _collisionSystem.update(); - } int count = 0; for (auto &thread : _jointThreads) { thread.update(); @@ -650,11 +634,12 @@ void Flow::update() { } setJoints(); } - _elapsedTime = ((1.0 - _tau) * _elapsedTime + _tau * _timer.nsecsElapsed()); - _deltaTime += _elapsedTime; + _deltaTime += _timer.nsecsElapsed(); + _updates++; if (_deltaTime > _deltaTimeLimit) { - qDebug() << "Flow C++ update" << _elapsedTime; - _deltaTime = 0.0; + qDebug() << "Flow C++ update " << _deltaTime / _updates << " nanoSeconds"; + _deltaTime = 0; + _updates = 0; } } @@ -697,10 +682,15 @@ void Flow::updateJoints() { _rig->getJointRotationInWorldFrame(jointData.second._parentIndex, parentWorldRotation, _entityRotation); jointData.second.setUpdatedData(jointPosition, jointTranslation, jointRotation, parentPosition, parentWorldRotation); } - auto &collisions = _collisionSystem.getCollisions(); - for (auto &collision : collisions) { + auto &selfCollisions = _collisionSystem.getSelfCollisions(); + for (auto &collision : selfCollisions) { + glm::quat jointRotation; _rig->getJointPositionInWorldFrame(collision._jointIndex, collision._position, _entityPosition, _entityRotation); + _rig->getJointRotationInWorldFrame(collision._jointIndex, jointRotation, _entityRotation); + glm::vec3 worldOffset = jointRotation * collision._offset; + collision._position = collision._position + worldOffset; } + _collisionSystem.prepareCollisions(); } void Flow::setJoints() { @@ -712,3 +702,10 @@ void Flow::setJoints() { } } } + +void Flow::setOthersCollision(const QUuid& otherId, int jointIndex, const glm::vec3& position) { + FlowCollisionSettings settings; + settings._entityID = otherId; + settings._radius = HAND_COLLISION_RADIUS; + _collisionSystem.addCollisionSphere(jointIndex, settings, position, false, true); +} \ No newline at end of file diff --git a/libraries/animation/src/Flow.h b/libraries/animation/src/Flow.h index c93cf91673..ec98b32265 100644 --- a/libraries/animation/src/Flow.h +++ b/libraries/animation/src/Flow.h @@ -39,6 +39,8 @@ const float HAPTIC_THRESHOLD = 40.0f; const QString FLOW_JOINT_PREFIX = "flow"; const QString SIM_JOINT_PREFIX = "sim"; +const std::vector HAND_COLLISION_JOINTS = { "RightHandMiddle1", "RightHandThumb3", "LeftHandMiddle1", "LeftHandThumb3", "RightHandMiddle3", "LeftHandMiddle3" }; + const QString JOINT_COLLISION_PREFIX = "joint_"; const QString HAND_COLLISION_PREFIX = "hand_"; const float HAND_COLLISION_RADIUS = 0.03f; @@ -100,18 +102,6 @@ struct FlowCollisionSettings { const FlowPhysicsSettings DEFAULT_JOINT_SETTINGS; -const std::map PRESET_FLOW_DATA = { {"hair", FlowPhysicsSettings()}, - {"skirt", FlowPhysicsSettings(true, 1.0f, DEFAULT_GRAVITY, 0.65f, 0.8f, 0.45f, 0.01f)}, - {"breast", FlowPhysicsSettings(true, 1.0f, DEFAULT_GRAVITY, 0.65f, 0.8f, 0.45f, 0.01f)} }; - -const std::map PRESET_COLLISION_DATA = { - { "Head", FlowCollisionSettings(QUuid(), FlowCollisionType::CollisionSphere, glm::vec3(0.0f, 0.06f, 0.0f), 0.08f)}, - { "LeftArm", FlowCollisionSettings(QUuid(), FlowCollisionType::CollisionSphere, glm::vec3(0.0f, 0.02f, 0.0f), 0.05f) }, - { "Head", FlowCollisionSettings(QUuid(), FlowCollisionType::CollisionSphere, glm::vec3(0.0f, 0.04f, 0.0f), 0.0f) } -}; - -const std::vector HAND_COLLISION_JOINTS = { "RightHandMiddle1", "RightHandThumb3", "LeftHandMiddle1", "LeftHandThumb3", "RightHandMiddle3", "LeftHandMiddle3" }; - struct FlowJointInfo { FlowJointInfo() {}; FlowJointInfo(int index, int parentIndex, int childIndex, const QString& name) { @@ -138,19 +128,22 @@ struct FlowCollisionResult { class FlowCollisionSphere { public: FlowCollisionSphere() {}; - FlowCollisionSphere(const int& jointIndex, const FlowCollisionSettings& settings); + FlowCollisionSphere(const int& jointIndex, const FlowCollisionSettings& settings, bool isTouch = false); void setPosition(const glm::vec3& position) { _position = position; } FlowCollisionResult computeSphereCollision(const glm::vec3& point, float radius) const; FlowCollisionResult checkSegmentCollision(const glm::vec3& point1, const glm::vec3& point2, const FlowCollisionResult& collisionResult1, const FlowCollisionResult& collisionResult2); - void update(); QUuid _entityID; - int _jointIndex { -1 }; - float _radius { 0.0f }; - float _initialRadius{ 0.0f }; + glm::vec3 _offset; glm::vec3 _initialOffset; glm::vec3 _position; + + bool _isTouch { false }; + int _jointIndex { -1 }; + int collisionIndex { -1 }; + float _radius { 0.0f }; + float _initialRadius{ 0.0f }; }; class FlowThread; @@ -158,24 +151,26 @@ class FlowThread; class FlowCollisionSystem { public: FlowCollisionSystem() {}; - void addCollisionSphere(int jointIndex, const FlowCollisionSettings& settings); - void addCollisionShape(int jointIndex, const FlowCollisionSettings& settings); - bool addCollisionToJoint(int jointIndex); - void removeCollisionFromJoint(int jointIndex); - void update(); + void addCollisionSphere(int jointIndex, const FlowCollisionSettings& settings, const glm::vec3& position = { 0.0f, 0.0f, 0.0f }, bool isSelfCollision = true, bool isTouch = false); FlowCollisionResult computeCollision(const std::vector collisions); void setScale(float scale); - std::vector checkFlowThreadCollisions(FlowThread* flowThread, bool checkSegments); + std::vector checkFlowThreadCollisions(FlowThread* flowThread); - int findCollisionWithJoint(int jointIndex); - void modifyCollisionRadius(int jointIndex, float radius); - void modifyCollisionYOffset(int jointIndex, float offset); - void modifyCollisionOffset(int jointIndex, const glm::vec3& offset); + int findSelfCollisionWithJoint(int jointIndex); + void modifySelfCollisionRadius(int jointIndex, float radius); + void modifySelfCollisionYOffset(int jointIndex, float offset); + void modifySelfCollisionOffset(int jointIndex, const glm::vec3& offset); - std::vector& getCollisions() { return _collisionSpheres; }; -private: - std::vector _collisionSpheres; + std::vector& getSelfCollisions() { return _selfCollisions; }; + void setOthersCollisions(const std::vector& othersCollisions) { _othersCollisions = othersCollisions; } + void prepareCollisions(); + void resetCollisions(); + void resetOthersCollisions() { _othersCollisions.clear(); } +protected: + std::vector _selfCollisions; + std::vector _othersCollisions; + std::vector _allCollisions; float _scale{ 1.0f }; }; @@ -208,7 +203,6 @@ public: void solve(const glm::vec3& constrainPoint, float maxDistance, const FlowCollisionResult& collision); void solveConstraints(const glm::vec3& constrainPoint, float maxDistance); void solveCollisions(const FlowCollisionResult& collision); - void apply(const QString& name, bool forceRendering); }; class FlowJoint { @@ -268,7 +262,7 @@ public: void computeJointRotations(); void apply(); bool getActive(); - void setRootFramePositions(const std::vector& rootFramePositions) { _rootFramePositions = rootFramePositions; }; + void setRootFramePositions(const std::vector& rootFramePositions) { _rootFramePositions = rootFramePositions; } std::vector _joints; std::vector _positions; @@ -276,7 +270,6 @@ public: float _length{ 0.0f }; std::map* _jointsPointer; std::vector _rootFramePositions; - }; class Flow { @@ -288,6 +281,8 @@ public: void setTransform(float scale, const glm::vec3& position, const glm::quat& rotation); const std::map& getJoints() const { return _flowJointData; } const std::vector& getThreads() const { return _jointThreads; } + void setOthersCollision(const QUuid& otherId, int jointIndex, const glm::vec3& position); + FlowCollisionSystem& getCollisionSystem() { return _collisionSystem; } private: void setJoints(); void cleanUp(); @@ -305,11 +300,9 @@ private: FlowCollisionSystem _collisionSystem; bool _initialized { false }; bool _active { false }; - int _deltaTime{ 0 }; - int _deltaTimeLimit{ 4000 }; - int _elapsedTime{ 0 }; - float _tau = 0.1f; - QElapsedTimer _timer; + int _deltaTime { 0 }; + int _deltaTimeLimit { 4000000 }; + int _updates { 0 }; }; #endif // hifi_Flow_h diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 50d13d348e..47c9697dac 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -235,7 +235,7 @@ public: const AnimVariantMap& getAnimVars() const { return _lastAnimVars; } const AnimContext::DebugStateMachineMap& getStateMachineMap() const { return _lastContext.getStateMachineMap(); } void computeFlowSkeleton() { _flow.calculateConstraints(); } - const Flow& getFlow() const { return _flow; } + Flow& getFlow() { return _flow; } signals: void onLoadComplete(); From bb29e382f0bf40f96ff1418d1f0637f62cceadb7 Mon Sep 17 00:00:00 2001 From: amantley Date: Mon, 11 Feb 2019 18:05:29 -0800 Subject: [PATCH 078/474] tweaking the hand pose correction on the pole vector --- .../src/AnimPoleVectorConstraint.cpp | 84 +++++++++++++++---- 1 file changed, 66 insertions(+), 18 deletions(-) diff --git a/libraries/animation/src/AnimPoleVectorConstraint.cpp b/libraries/animation/src/AnimPoleVectorConstraint.cpp index 09fadf2da5..0fa2ce0ee0 100644 --- a/libraries/animation/src/AnimPoleVectorConstraint.cpp +++ b/libraries/animation/src/AnimPoleVectorConstraint.cpp @@ -250,27 +250,51 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim fred = findThetaNewWay(tipPose.trans(), basePose.trans(), isLeft); //get the swingTwist of the hand to lower arm - glm::quat yTwist; - glm::quat flexUlnarSwing; - glm::quat relativeHandRotation = (midPose.inverse() * tipPose).rot(); - swingTwistDecomposition(relativeHandRotation, Vectors::UNIT_X, flexUlnarSwing, yTwist); - glm::vec3 twistAxis = glm::axis(yTwist); - glm::vec3 flexUlnarAxis = glm::axis(flexUlnarSwing); - float swingTheta = glm::angle(flexUlnarSwing); - float twistTheta = glm::angle(yTwist); glm::quat flex; + glm::quat twistUlnarSwing; + glm::quat relativeHandRotation = (midPose.inverse() * tipPose).rot(); + swingTwistDecomposition(relativeHandRotation, Vectors::UNIT_X, twistUlnarSwing, flex); + glm::vec3 flexAxis = glm::axis(flex); + + //float swingTheta = glm::angle(twistUlnarSwing); + float flexTheta = glm::sign(flexAxis[0]) * glm::angle(flex); + glm::quat twist; glm::quat ulnarDeviation; - swingTwistDecomposition(flexUlnarSwing, Vectors::UNIT_Z, flex, ulnarDeviation); - float flexTheta = glm::angle(flex); + swingTwistDecomposition(twistUlnarSwing, Vectors::UNIT_Z, twist, ulnarDeviation); + + glm::vec3 twistAxis = glm::axis(twist); glm::vec3 ulnarAxis = glm::axis(ulnarDeviation); - float ulnarDeviationTheta = glm::angle(ulnarDeviation); + float twistTheta = glm::sign(twistAxis[1]) * glm::angle(twist); + float ulnarDeviationTheta = glm::sign(ulnarAxis[2]) * glm::angle(ulnarDeviation); - glm::vec3 eulerVersion = glm::eulerAngles(relativeHandRotation); + glm::quat trueTwist; + glm::quat nonTwist; + swingTwistDecomposition(relativeHandRotation, Vectors::UNIT_Y, nonTwist, trueTwist); + if (trueTwist.w < 0.0f) { + trueTwist.x *= -1.0f; + trueTwist.y *= -1.0f; + trueTwist.z *= -1.0f; + trueTwist.w *= -1.0f; + } + glm::vec3 trueTwistAxis = glm::axis(trueTwist); + float trueTwistTheta = glm::angle(trueTwist); + trueTwistTheta *= glm::sign(trueTwistAxis[1]) * glm::angle(trueTwist); + //while (trueTwistTheta > PI) { + // trueTwistTheta -= 2.0f * PI; + //} + + + // glm::vec3 eulerVersion = glm::eulerAngles(relativeHandRotation); if (!isLeft) { - //qCDebug(animation) << "wrist thetas -----> X " << twistTheta << " twist: " << flexTheta << "ulnar deviation: " << ulnarDeviationTheta; - qCDebug(animation) << "0: " << eulerVersion[0] << " 1: " << eulerVersion[1] << " 2: " << eulerVersion[2]; + //qCDebug(animation) << "flex: " << (flexTheta / PI) * 180.0f << " twist: " << (twistTheta / PI) * 180.0f << " ulnar deviation: " << (ulnarDeviationTheta / PI) * 180.0f; + qCDebug(animation) << "trueTwist: " << (trueTwistTheta / PI) * 180.0f;// << " old twist: " << (twistTheta / PI) * 180.0f; //qCDebug(animation) << "ulnarAxis " << flexUlnarAxis; - //qCDebug(animation) << "twistAxis " << twistAxis; + qCDebug(animation) << "relative hand rotation " << relativeHandRotation; + qCDebug(animation) << "twist rotation " << trueTwist; + + //QString name = QString("handMarker3"); + //const vec4 WHITE(1.0f); + //DebugDraw::getInstance().addMyAvatarMarker(name, relativeHandRotation, midPose.trans(), WHITE); } //QString name = QString("wrist_target").arg(_id); @@ -283,13 +307,37 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim if (!isLeft) { deltaThetaUlnar = correctElbowForHandUlnarRadialDeviation(tipPose, midPose); } - //fred -= deltaThetaUlnar; - fred -= deltaTheta; + // make the dead zone PI/6.0 + + const float POWER = 4.0f; + /* + if (fabsf(flexTheta) > (PI / 6.0f)) { + fred -= glm::sign(flexTheta) * pow(flexTheta / PI, POWER) * 180.0f; + } + if (fabsf(ulnarDeviationTheta) > (PI / 6.0f)) { + if (trueTwistTheta > 0.0f) { + fred -= glm::sign(ulnarDeviationTheta) * pow(ulnarDeviationTheta / PI, POWER) * 180.0f; + } else { + fred += glm::sign(ulnarDeviationTheta) * pow(ulnarDeviationTheta / PI, POWER) * 180.0f; + } + } + */ + // remember direction of travel. + const float TWIST_DEADZONE = PI / 2.0f; + if (trueTwistTheta < -TWIST_DEADZONE) { + fred += glm::sign(trueTwistTheta) * pow((trueTwistTheta / PI), POWER) * 180.0f + pow(TWIST_DEADZONE / PI, POWER) * 180.0f; + } else { + if (trueTwistTheta > (PI / 2.0f)) { + fred += glm::sign(trueTwistTheta) * pow((trueTwistTheta / PI), POWER) * 180.0f - pow(TWIST_DEADZONE / PI, POWER) * 180.0f; + } + } + if (isLeft) { fred *= -1.0f; } - // theta = ((180.0f - fred) / 180.0f)*PI; + theta = ((180.0f - fred) / 180.0f)*PI; + //qCDebug(animation) << "the wrist correction theta is -----> " << isLeft << " theta: " << deltaTheta; } From 7bb353742aae95fe34c8415c03c760fca4e4e43c Mon Sep 17 00:00:00 2001 From: Angus Antley Date: Tue, 12 Feb 2019 07:40:18 -0800 Subject: [PATCH 079/474] more tweaks to get the wrist action right --- .../src/AnimPoleVectorConstraint.cpp | 49 ++++++++++++++----- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/libraries/animation/src/AnimPoleVectorConstraint.cpp b/libraries/animation/src/AnimPoleVectorConstraint.cpp index 0fa2ce0ee0..b50f6dc280 100644 --- a/libraries/animation/src/AnimPoleVectorConstraint.cpp +++ b/libraries/animation/src/AnimPoleVectorConstraint.cpp @@ -310,29 +310,54 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim // make the dead zone PI/6.0 - const float POWER = 4.0f; + const float POWER = 2.0f; + const float FLEX_BOUNDARY = PI / 2.0f; + const float EXTEND_BOUNDARY = -PI / 4.0f; /* - if (fabsf(flexTheta) > (PI / 6.0f)) { - fred -= glm::sign(flexTheta) * pow(flexTheta / PI, POWER) * 180.0f; + if (flexTheta > FLEX_BOUNDARY) { + fred -= glm::sign(flexTheta) * pow((flexTheta - FLEX_BOUNDARY) / PI, POWER) * 180.0f; + } else if (flexTheta < EXTEND_BOUNDARY) { + fred -= glm::sign(flexTheta) * pow((flexTheta - EXTEND_BOUNDARY) / PI, POWER) * 180.0f; } - if (fabsf(ulnarDeviationTheta) > (PI / 6.0f)) { + + const float ULNAR_BOUNDARY = PI / 6.0f; + if (fabsf(ulnarDeviationTheta) > ULNAR_BOUNDARY) { if (trueTwistTheta > 0.0f) { - fred -= glm::sign(ulnarDeviationTheta) * pow(ulnarDeviationTheta / PI, POWER) * 180.0f; + fred -= glm::sign(ulnarDeviationTheta) * pow(fabsf(ulnarDeviationTheta) - ULNAR_BOUNDARY / PI, POWER) * 180.0f; } else { - fred += glm::sign(ulnarDeviationTheta) * pow(ulnarDeviationTheta / PI, POWER) * 180.0f; + fred += glm::sign(ulnarDeviationTheta) * pow(fabsf(ulnarDeviationTheta) - ULNAR_BOUNDARY/ PI, POWER) * 180.0f; } } */ // remember direction of travel. const float TWIST_DEADZONE = PI / 2.0f; - if (trueTwistTheta < -TWIST_DEADZONE) { - fred += glm::sign(trueTwistTheta) * pow((trueTwistTheta / PI), POWER) * 180.0f + pow(TWIST_DEADZONE / PI, POWER) * 180.0f; - } else { - if (trueTwistTheta > (PI / 2.0f)) { - fred += glm::sign(trueTwistTheta) * pow((trueTwistTheta / PI), POWER) * 180.0f - pow(TWIST_DEADZONE / PI, POWER) * 180.0f; + if (!isLeft) { + if (trueTwistTheta < -TWIST_DEADZONE) { + fred += glm::sign(trueTwistTheta) * pow((fabsf(trueTwistTheta) - TWIST_DEADZONE) / PI, POWER) * 90.0f; + } else { + if (trueTwistTheta > TWIST_DEADZONE) { + fred += glm::sign(trueTwistTheta) * pow((fabsf(trueTwistTheta) - TWIST_DEADZONE) / PI, POWER) * 90.0f; + } } + } else { + if (trueTwistTheta < -TWIST_DEADZONE) { + fred -= glm::sign(trueTwistTheta) * pow((fabsf(trueTwistTheta) - TWIST_DEADZONE) / PI, POWER) * 90.0f; + } else { + if (trueTwistTheta > TWIST_DEADZONE) { + fred -= glm::sign(trueTwistTheta) * pow((fabsf(trueTwistTheta) - TWIST_DEADZONE) / PI, POWER) * 90.0f; + } + } + + } - + /* + if (fred < 70.0f) { + fred = 70.0f; + } + if (fred > 175.0f) { + fred = 175.0f; + } + */ if (isLeft) { fred *= -1.0f; } From 3ea6241cc91e9eafe224ebcaf1d313d3624dad2a Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 12 Feb 2019 12:50:47 -0800 Subject: [PATCH 080/474] send update for AvatarEntity on deleteGrab --- interface/src/avatar/MyAvatar.cpp | 12 ++++++++++++ interface/src/avatar/MyAvatar.h | 1 + .../avatars-renderer/src/avatars-renderer/Avatar.cpp | 7 +++++++ .../avatars-renderer/src/avatars-renderer/Avatar.h | 5 ++--- 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 92d9270d20..1c7f2ae400 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -5312,3 +5312,15 @@ void MyAvatar::releaseGrab(const QUuid& grabID) { } } +void MyAvatar::sendPacket(const QUuid& entityID, const EntityItemProperties& properties) const { + auto treeRenderer = DependencyManager::get(); + EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; + if (entityTree) { + entityTree->withWriteLock([&] { + // force an update packet + EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender(); + packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree, entityID, properties); + }); + } +} + diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 0d27988543..c279673486 100755 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1882,6 +1882,7 @@ private: bool didTeleport(); bool getIsAway() const { return _isAway; } void setAway(bool value); + void sendPacket(const QUuid& entityID, const EntityItemProperties& properties) const override; std::mutex _pinnedJointsMutex; std::vector _pinnedJoints; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 36ad6c6b82..c17c7d9fc5 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -363,6 +363,13 @@ bool Avatar::applyGrabChanges() { target->removeGrab(grab); _avatarGrabs.erase(itr); grabAddedOrRemoved = true; + if (isMyAvatar()) { + const EntityItemPointer& entity = std::dynamic_pointer_cast(target); + if (entity && entity->getEntityHostType() == entity::HostType::AVATAR && entity->getSimulationOwner().getID() == getID()) { + EntityItemProperties properties = entity->getProperties(); + sendPacket(entity->getID(), properties); + } + } } else { undeleted.push_back(id); } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 39b77f2b65..06942a13d8 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -601,6 +602,7 @@ protected: // protected methods... bool isLookingAtMe(AvatarSharedPointer avatar) const; + virtual void sendPacket(const QUuid& entityID, const EntityItemProperties& properties) const { } bool applyGrabChanges(); void relayJointDataToChildren(); @@ -645,9 +647,6 @@ protected: bool success { false }; }; - //using MapOfAvatarEntityDataHashes = QMap; - //MapOfAvatarEntityDataHashes _avatarEntityDataHashes; - uint64_t _lastRenderUpdateTime { 0 }; int _leftPointerGeometryID { 0 }; int _rightPointerGeometryID { 0 }; From 9f9f512c59f9132bcc3799d573536f85a3df1a5e Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 12 Feb 2019 13:22:22 -0800 Subject: [PATCH 081/474] undoing markDirty() overhead because _flags is atomic --- libraries/entities/src/EntityItem.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 777f9ba167..5c423b2fe5 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1897,7 +1897,7 @@ glm::vec3 EntityItem::getUnscaledDimensions() const { void EntityItem::setRotation(glm::quat rotation) { if (getLocalOrientation() != rotation) { setLocalOrientation(rotation); - markDirtyFlags(Simulation::DIRTY_ROTATION); + _flags |= Simulation::DIRTY_ROTATION; forEachDescendant([&](SpatiallyNestablePointer object) { if (object->getNestableType() == NestableType::Entity) { EntityItemPointer entity = std::static_pointer_cast(object); @@ -1927,7 +1927,7 @@ void EntityItem::setVelocity(const glm::vec3& value) { velocity = value; } setLocalVelocity(velocity); - markDirtyFlags(Simulation::DIRTY_LINEAR_VELOCITY); + _flags |= Simulation::DIRTY_LINEAR_VELOCITY; } } } @@ -1982,7 +1982,7 @@ void EntityItem::setAngularVelocity(const glm::vec3& value) { angularVelocity = value; } setLocalAngularVelocity(angularVelocity); - markDirtyFlags(Simulation::DIRTY_ANGULAR_VELOCITY); + _flags |= Simulation::DIRTY_ANGULAR_VELOCITY; } } } @@ -2168,8 +2168,8 @@ bool EntityItem::addAction(EntitySimulationPointer simulation, EntityDynamicPoin void EntityItem::enableNoBootstrap() { if (!(bool)(_flags & Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING)) { - markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP); - markSpecialFlags(Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING); + _flags |= Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING; + _flags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar // NOTE: unlike disableNoBootstrap() below, we do not call simulation->changeEntity() here // because most enableNoBootstrap() cases are already correctly handled outside this scope @@ -2188,8 +2188,8 @@ void EntityItem::enableNoBootstrap() { void EntityItem::disableNoBootstrap() { if (!stillHasGrabActions()) { - markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP); - clearSpecialFlags(Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING); + _flags &= ~Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING; + _flags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar EntityTreePointer entityTree = getTree(); assert(entityTree); @@ -2588,7 +2588,7 @@ QList EntityItem::getActionsOfType(EntityDynamicType typeT void EntityItem::locationChanged(bool tellPhysics) { requiresRecalcBoxes(); if (tellPhysics) { - markDirtyFlags(Simulation::DIRTY_TRANSFORM); + _flags |= Simulation::DIRTY_TRANSFORM; EntityTreePointer tree = getTree(); if (tree) { tree->entityChanged(getThisPointer()); From 954cac907dfa002317308d30c213860a3be249a8 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Tue, 12 Feb 2019 15:02:56 -0700 Subject: [PATCH 082/474] Other avatars after update and mod timer when active --- interface/src/avatar/AvatarManager.cpp | 11 +++------- interface/src/avatar/MyAvatar.cpp | 8 +++++++ interface/src/avatar/MyAvatar.h | 2 ++ libraries/animation/src/Flow.cpp | 30 +++++++++++--------------- libraries/animation/src/Flow.h | 1 - 5 files changed, 26 insertions(+), 26 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 50e8546979..d2ba7149f2 100755 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -271,14 +271,6 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { if (avatar->getSkeletonModel()->isLoaded()) { // remove the orb if it is there avatar->removeOrb(); - if (avatar->getWorkloadRegion() == workload::Region::R1) { - auto &flow = _myAvatar->getSkeletonModel()->getRig().getFlow(); - for (auto &handJointName : HAND_COLLISION_JOINTS) { - int jointIndex = avatar->getJointIndex(handJointName); - glm::vec3 position = avatar->getJointPosition(jointIndex); - flow.setOthersCollision(avatar->getID(), jointIndex, position); - } - } if (avatar->needsPhysicsUpdate()) { _avatarsToChangeInPhysics.insert(avatar); } @@ -305,6 +297,9 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { avatar->setIsNewAvatar(false); } avatar->simulate(deltaTime, inView); + if (avatar->getSkeletonModel()->isLoaded() && avatar->getWorkloadRegion() == workload::Region::R1) { + _myAvatar->addAvatarHandsToFlow(avatar); + } avatar->updateRenderItem(renderTransaction); avatar->updateSpaceProxy(workloadTransaction); avatar->setLastRenderUpdateTime(startTime); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 92d9270d20..d5f8f8a3ec 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -5312,3 +5312,11 @@ void MyAvatar::releaseGrab(const QUuid& grabID) { } } +void MyAvatar::addAvatarHandsToFlow(const std::shared_ptr& otherAvatar) { + auto &flow = _skeletonModel->getRig().getFlow(); + for (auto &handJointName : HAND_COLLISION_JOINTS) { + int jointIndex = otherAvatar->getJointIndex(handJointName); + glm::vec3 position = otherAvatar->getJointPosition(jointIndex); + flow.setOthersCollision(otherAvatar->getID(), jointIndex, position); + } +} diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 0d27988543..996abcabf2 100755 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1182,6 +1182,8 @@ public: void updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData) override; void avatarEntityDataToJson(QJsonObject& root) const override; int sendAvatarDataPacket(bool sendAll = false) override; + + void addAvatarHandsToFlow(const std::shared_ptr& otherAvatar); public slots: diff --git a/libraries/animation/src/Flow.cpp b/libraries/animation/src/Flow.cpp index 87d7155abd..8aee4dc53c 100644 --- a/libraries/animation/src/Flow.cpp +++ b/libraries/animation/src/Flow.cpp @@ -461,7 +461,6 @@ void Flow::calculateConstraints() { auto simPrefix = SIM_JOINT_PREFIX.toUpper(); auto skeleton = _rig->getAnimSkeleton(); std::vector handsIndices; - _collisionSystem.resetCollisions(); if (skeleton) { for (int i = 0; i < skeleton->getNumJoints(); i++) { auto name = skeleton->getJointName(i); @@ -517,10 +516,6 @@ void Flow::calculateConstraints() { _collisionSystem.addCollisionSphere(i, PRESET_COLLISION_DATA.at(name)); } } - if (isFlowJoint || isSimJoint) { - auto jointInfo = FlowJointInfo(i, parentIndex, -1, name); - _flowJointInfos.push_back(jointInfo); - } } } @@ -604,7 +599,9 @@ void Flow::cleanUp() { _flowJointData.clear(); _jointThreads.clear(); _flowJointKeywords.clear(); - _flowJointInfos.clear(); + _collisionSystem.resetCollisions(); + _initialized = false; + _active = false; } void Flow::setTransform(float scale, const glm::vec3& position, const glm::quat& rotation) { @@ -619,9 +616,9 @@ void Flow::setTransform(float scale, const glm::vec3& position, const glm::quat& } void Flow::update() { - QElapsedTimer _timer; - _timer.start(); if (_initialized && _active) { + QElapsedTimer _timer; + _timer.start(); updateJoints(); int count = 0; for (auto &thread : _jointThreads) { @@ -633,15 +630,14 @@ void Flow::update() { thread.apply(); } setJoints(); - } - _deltaTime += _timer.nsecsElapsed(); - _updates++; - if (_deltaTime > _deltaTimeLimit) { - qDebug() << "Flow C++ update " << _deltaTime / _updates << " nanoSeconds"; - _deltaTime = 0; - _updates = 0; - } - + _deltaTime += _timer.nsecsElapsed(); + _updates++; + if (_deltaTime > _deltaTimeLimit) { + qDebug() << "Flow C++ update " << _deltaTime / _updates << " nanoSeconds"; + _deltaTime = 0; + _updates = 0; + } + } } bool Flow::worldToJointPoint(const glm::vec3& position, const int jointIndex, glm::vec3& jointSpacePosition) const { diff --git a/libraries/animation/src/Flow.h b/libraries/animation/src/Flow.h index ec98b32265..9c088bf263 100644 --- a/libraries/animation/src/Flow.h +++ b/libraries/animation/src/Flow.h @@ -296,7 +296,6 @@ private: std::map _flowJointData; std::vector _jointThreads; std::vector _flowJointKeywords; - std::vector _flowJointInfos; FlowCollisionSystem _collisionSystem; bool _initialized { false }; bool _active { false }; From 9baed717f9afa5efd92110e3067b690ca98c89d0 Mon Sep 17 00:00:00 2001 From: amantley Date: Tue, 12 Feb 2019 17:55:12 -0800 Subject: [PATCH 083/474] integrated flex, ulnar and twist behaviour to pole vector theta computation --- .../src/AnimPoleVectorConstraint.cpp | 173 +++++++++++++----- .../animation/src/AnimPoleVectorConstraint.h | 5 + 2 files changed, 133 insertions(+), 45 deletions(-) diff --git a/libraries/animation/src/AnimPoleVectorConstraint.cpp b/libraries/animation/src/AnimPoleVectorConstraint.cpp index b50f6dc280..590750961e 100644 --- a/libraries/animation/src/AnimPoleVectorConstraint.cpp +++ b/libraries/animation/src/AnimPoleVectorConstraint.cpp @@ -249,23 +249,51 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim } fred = findThetaNewWay(tipPose.trans(), basePose.trans(), isLeft); + glm::quat relativeHandRotation = (midPose.inverse() * tipPose).rot(); + + relativeHandRotation = glm::normalize(relativeHandRotation); + if (relativeHandRotation.w < 0.0f) { + relativeHandRotation.x *= -1.0f; + relativeHandRotation.y *= -1.0f; + relativeHandRotation.z *= -1.0f; + relativeHandRotation.w *= -1.0f; + } + + glm::quat twist; + glm::quat ulnarDeviation; + //swingTwistDecomposition(twistUlnarSwing, Vectors::UNIT_Z, twist, ulnarDeviation); + swingTwistDecomposition(relativeHandRotation, Vectors::UNIT_Z, twist, ulnarDeviation); + + if (ulnarDeviation.w < 0.0f) { + ulnarDeviation.x *= -1.0f; + ulnarDeviation.y *= -1.0f; + ulnarDeviation.z *= -1.0f; + ulnarDeviation.w *= -1.0f; + } + //glm::vec3 twistAxis = glm::axis(twist); + glm::vec3 ulnarAxis = glm::axis(ulnarDeviation); + //float twistTheta = glm::sign(twistAxis[1]) * glm::angle(twist); + float ulnarDeviationTheta = glm::sign(ulnarAxis[2]) * glm::angle(ulnarDeviation); + _ulnarRadialThetaRunningAverage = 0.5f * _ulnarRadialThetaRunningAverage + 0.5f * ulnarDeviationTheta; + //get the swingTwist of the hand to lower arm glm::quat flex; glm::quat twistUlnarSwing; - glm::quat relativeHandRotation = (midPose.inverse() * tipPose).rot(); - swingTwistDecomposition(relativeHandRotation, Vectors::UNIT_X, twistUlnarSwing, flex); + + swingTwistDecomposition(twist, Vectors::UNIT_X, twistUlnarSwing, flex); + if (flex.w < 0.0f) { + flex.x *= -1.0f; + flex.y *= -1.0f; + flex.z *= -1.0f; + flex.w *= -1.0f; + } + glm::vec3 flexAxis = glm::axis(flex); - + //float swingTheta = glm::angle(twistUlnarSwing); float flexTheta = glm::sign(flexAxis[0]) * glm::angle(flex); - glm::quat twist; - glm::quat ulnarDeviation; - swingTwistDecomposition(twistUlnarSwing, Vectors::UNIT_Z, twist, ulnarDeviation); - - glm::vec3 twistAxis = glm::axis(twist); - glm::vec3 ulnarAxis = glm::axis(ulnarDeviation); - float twistTheta = glm::sign(twistAxis[1]) * glm::angle(twist); - float ulnarDeviationTheta = glm::sign(ulnarAxis[2]) * glm::angle(ulnarDeviation); + _flexThetaRunningAverage = 0.5f * _flexThetaRunningAverage + 0.5f * flexTheta; + glm::quat trueTwist; glm::quat nonTwist; @@ -279,6 +307,12 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim glm::vec3 trueTwistAxis = glm::axis(trueTwist); float trueTwistTheta = glm::angle(trueTwist); trueTwistTheta *= glm::sign(trueTwistAxis[1]) * glm::angle(trueTwist); + _twistThetaRunningAverage = 0.5f * _twistThetaRunningAverage + 0.5f * trueTwistTheta; + + + + + //while (trueTwistTheta > PI) { // trueTwistTheta -= 2.0f * PI; //} @@ -286,11 +320,11 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim // glm::vec3 eulerVersion = glm::eulerAngles(relativeHandRotation); if (!isLeft) { - //qCDebug(animation) << "flex: " << (flexTheta / PI) * 180.0f << " twist: " << (twistTheta / PI) * 180.0f << " ulnar deviation: " << (ulnarDeviationTheta / PI) * 180.0f; - qCDebug(animation) << "trueTwist: " << (trueTwistTheta / PI) * 180.0f;// << " old twist: " << (twistTheta / PI) * 180.0f; + qCDebug(animation) << "flex: " << (flexTheta / PI) * 180.0f << " twist: " << (trueTwistTheta / PI) * 180.0f << " ulnar deviation: " << (ulnarDeviationTheta / PI) * 180.0f; + //qCDebug(animation) << "trueTwist: " << (trueTwistTheta / PI) * 180.0f;// << " old twist: " << (twistTheta / PI) * 180.0f; //qCDebug(animation) << "ulnarAxis " << flexUlnarAxis; - qCDebug(animation) << "relative hand rotation " << relativeHandRotation; - qCDebug(animation) << "twist rotation " << trueTwist; + // qCDebug(animation) << "relative hand rotation " << relativeHandRotation; + // qCDebug(animation) << "twist rotation " << trueTwist; //QString name = QString("handMarker3"); //const vec4 WHITE(1.0f); @@ -308,38 +342,88 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim deltaThetaUlnar = correctElbowForHandUlnarRadialDeviation(tipPose, midPose); } + if (isLeft) { + fred *= -1.0f; + } + // make the dead zone PI/6.0 const float POWER = 2.0f; - const float FLEX_BOUNDARY = PI / 2.0f; - const float EXTEND_BOUNDARY = -PI / 4.0f; - /* - if (flexTheta > FLEX_BOUNDARY) { - fred -= glm::sign(flexTheta) * pow((flexTheta - FLEX_BOUNDARY) / PI, POWER) * 180.0f; - } else if (flexTheta < EXTEND_BOUNDARY) { - fred -= glm::sign(flexTheta) * pow((flexTheta - EXTEND_BOUNDARY) / PI, POWER) * 180.0f; - } - - const float ULNAR_BOUNDARY = PI / 6.0f; - if (fabsf(ulnarDeviationTheta) > ULNAR_BOUNDARY) { - if (trueTwistTheta > 0.0f) { - fred -= glm::sign(ulnarDeviationTheta) * pow(fabsf(ulnarDeviationTheta) - ULNAR_BOUNDARY / PI, POWER) * 180.0f; - } else { - fred += glm::sign(ulnarDeviationTheta) * pow(fabsf(ulnarDeviationTheta) - ULNAR_BOUNDARY/ PI, POWER) * 180.0f; - } - } - */ - // remember direction of travel. - const float TWIST_DEADZONE = PI / 2.0f; - if (!isLeft) { - if (trueTwistTheta < -TWIST_DEADZONE) { - fred += glm::sign(trueTwistTheta) * pow((fabsf(trueTwistTheta) - TWIST_DEADZONE) / PI, POWER) * 90.0f; - } else { - if (trueTwistTheta > TWIST_DEADZONE) { - fred += glm::sign(trueTwistTheta) * pow((fabsf(trueTwistTheta) - TWIST_DEADZONE) / PI, POWER) * 90.0f; + const float FLEX_BOUNDARY = PI / 4.0f; + const float EXTEND_BOUNDARY = -PI / 6.0f; + float flexCorrection = 0.0f; + if (isLeft) { + if (_flexThetaRunningAverage > FLEX_BOUNDARY) { + flexCorrection = ((_flexThetaRunningAverage - FLEX_BOUNDARY) / PI) * 180.0f; // glm::sign(flexTheta) * pow((flexTheta - FLEX_BOUNDARY) / PI, POWER) * 180.0f; + if (flexCorrection > 30.0f) { + flexCorrection = 30.0f; } + } else if (_flexThetaRunningAverage < EXTEND_BOUNDARY) { + flexCorrection = ((_flexThetaRunningAverage - EXTEND_BOUNDARY) / PI) * 180.0f; // glm::sign(flexTheta) * pow((flexTheta - EXTEND_BOUNDARY) / PI, POWER) * 180.0f; } + fred += flexCorrection; } else { + if (_flexThetaRunningAverage > FLEX_BOUNDARY) { + flexCorrection = ((_flexThetaRunningAverage - FLEX_BOUNDARY) / PI) * 180.0f; // glm::sign(flexTheta) * pow((flexTheta - FLEX_BOUNDARY) / PI, POWER) * 180.0f; + if (flexCorrection > 30.0f) { + flexCorrection = 30.0f; + } + } else if (_flexThetaRunningAverage < EXTEND_BOUNDARY) { + flexCorrection = ((_flexThetaRunningAverage - EXTEND_BOUNDARY) / PI) * 180.0f; // glm::sign(flexTheta) * pow((flexTheta - EXTEND_BOUNDARY) / PI, POWER) * 180.0f; + } + fred -= flexCorrection; + } + + const float TWIST_ULNAR_DEADZONE = 0.0f; + const float ULNAR_BOUNDARY = PI / 12.0f; + if (fabsf(_ulnarRadialThetaRunningAverage) > ULNAR_BOUNDARY) { + if (fabs(_twistThetaRunningAverage) > TWIST_ULNAR_DEADZONE) { + float twistCoefficient = pow((fabs(_twistThetaRunningAverage) - TWIST_ULNAR_DEADZONE) / (PI / 20.0f), POWER); + float ulnarCorrection = 0.0f; + if (isLeft) { + if (trueTwistTheta < 0.0f) { + ulnarCorrection -= glm::sign(_ulnarRadialThetaRunningAverage) * pow((fabsf(_ulnarRadialThetaRunningAverage) - ULNAR_BOUNDARY) / PI, POWER) * 80.0f * twistCoefficient; + } else { + ulnarCorrection += glm::sign(_ulnarRadialThetaRunningAverage) * pow((fabsf(_ulnarRadialThetaRunningAverage) - ULNAR_BOUNDARY) / PI, POWER) * 80.0f * twistCoefficient; + } + } else { + // right hand + if (trueTwistTheta > 0.0f) { + ulnarCorrection -= glm::sign(_ulnarRadialThetaRunningAverage) * pow((fabsf(_ulnarRadialThetaRunningAverage) - ULNAR_BOUNDARY) / PI, POWER) * 80.0f * twistCoefficient; + } else { + ulnarCorrection += glm::sign(_ulnarRadialThetaRunningAverage) * pow((fabsf(_ulnarRadialThetaRunningAverage) - ULNAR_BOUNDARY) / PI, POWER) * 80.0f * twistCoefficient; + } + + } + if (fabsf(ulnarCorrection) > 20.0f) { + ulnarCorrection = glm::sign(ulnarCorrection) * 20.0f; + } + fred += ulnarCorrection; + } + } + + // remember direction of travel. + const float TWIST_DEADZONE = (4.0f * PI) / 9.0f; + //if (!isLeft) { + float twistCorrection = 0.0f; + if (_twistThetaRunningAverage < -TWIST_DEADZONE) { + twistCorrection = glm::sign(_twistThetaRunningAverage) * pow((fabsf(_twistThetaRunningAverage) - TWIST_DEADZONE) / PI, POWER) * 80.0f; + } else { + if (_twistThetaRunningAverage > TWIST_DEADZONE) { + twistCorrection = glm::sign(_twistThetaRunningAverage) * pow((fabsf(_twistThetaRunningAverage) - TWIST_DEADZONE) / PI, POWER) * 80.0f; + } + } + if (fabsf(twistCorrection) > 45.0f) { + fred += glm::sign(twistCorrection) * 45.0f; + } else { + fred += twistCorrection; + } + + _lastTheta = 0.5f * _lastTheta + 0.5f * fred; + + //} + + /*else { if (trueTwistTheta < -TWIST_DEADZONE) { fred -= glm::sign(trueTwistTheta) * pow((fabsf(trueTwistTheta) - TWIST_DEADZONE) / PI, POWER) * 90.0f; } else { @@ -350,6 +434,7 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim } + */ /* if (fred < 70.0f) { fred = 70.0f; @@ -358,10 +443,8 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim fred = 175.0f; } */ - if (isLeft) { - fred *= -1.0f; - } - theta = ((180.0f - fred) / 180.0f)*PI; + + theta = ((180.0f - _lastTheta) / 180.0f)*PI; //qCDebug(animation) << "the wrist correction theta is -----> " << isLeft << " theta: " << deltaTheta; diff --git a/libraries/animation/src/AnimPoleVectorConstraint.h b/libraries/animation/src/AnimPoleVectorConstraint.h index be3346974e..078676d4f9 100644 --- a/libraries/animation/src/AnimPoleVectorConstraint.h +++ b/libraries/animation/src/AnimPoleVectorConstraint.h @@ -66,6 +66,11 @@ protected: float _interpAlphaVel { 0.0f }; float _interpAlpha { 0.0f }; + float _twistThetaRunningAverage { 0.0f }; + float _flexThetaRunningAverage { 0.0f }; + float _ulnarRadialThetaRunningAverage { 0.0f }; + float _lastTheta { 0.0f }; + AnimChain _snapshotChain; // no copies From bebbbc643b46132b1aacce363e3c66b3942197fb Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Wed, 13 Feb 2019 06:27:49 -0700 Subject: [PATCH 084/474] add deltaTime to simulation --- libraries/animation/src/Flow.cpp | 43 ++++++++++++++++++-------------- libraries/animation/src/Flow.h | 13 +++++----- libraries/animation/src/Rig.cpp | 5 ++-- 3 files changed, 33 insertions(+), 28 deletions(-) diff --git a/libraries/animation/src/Flow.cpp b/libraries/animation/src/Flow.cpp index 8aee4dc53c..ec17a56a4a 100644 --- a/libraries/animation/src/Flow.cpp +++ b/libraries/animation/src/Flow.cpp @@ -206,22 +206,26 @@ void FlowCollisionSystem::prepareCollisions() { _othersCollisions.clear(); } -void FlowNode::update(const glm::vec3& accelerationOffset) { +void FlowNode::update(float deltaTime, const glm::vec3& accelerationOffset) { _acceleration = glm::vec3(0.0f, _settings._gravity, 0.0f); _previousVelocity = _currentVelocity; _currentVelocity = _currentPosition - _previousPosition; _previousPosition = _currentPosition; if (!_anchored) { // Add inertia + const float FPS = 60.0f; + float timeRatio = (FPS * deltaTime); + auto deltaVelocity = _previousVelocity - _currentVelocity; auto centrifugeVector = glm::length(deltaVelocity) != 0.0f ? glm::normalize(deltaVelocity) : glm::vec3(); - _acceleration = _acceleration + centrifugeVector * _settings._inertia * glm::length(_currentVelocity); + _acceleration = _acceleration + centrifugeVector * _settings._inertia * glm::length(_currentVelocity) * (1 / timeRatio); // Add offset _acceleration += accelerationOffset; - // Calculate new position - _currentPosition = (_currentPosition + _currentVelocity * _settings._damping) + - (_acceleration * glm::pow(_settings._delta * _scale, 2)); + + glm::vec3 deltaAcceleration = timeRatio * _acceleration * glm::pow(_settings._delta * _scale, 2); + // Calculate new position + _currentPosition = (_currentPosition + _currentVelocity * _settings._damping) + deltaAcceleration; } else { _acceleration = glm::vec3(0.0f); @@ -289,13 +293,13 @@ void FlowJoint::setRecoveryPosition(const glm::vec3& recoveryPosition) { _applyRecovery = true; } -void FlowJoint::update() { +void FlowJoint::update(float deltaTime) { glm::vec3 accelerationOffset = glm::vec3(0.0f); if (_node._settings._stiffness > 0.0f) { glm::vec3 recoveryVector = _recoveryPosition - _node._currentPosition; accelerationOffset = recoveryVector * glm::pow(_node._settings._stiffness, 3); } - _node.update(accelerationOffset); + _node.update(deltaTime, accelerationOffset); if (_node._anchored) { if (!_isDummy) { _node._currentPosition = _updatedPosition; @@ -368,14 +372,14 @@ void FlowThread::computeRecovery() { } }; -void FlowThread::update() { +void FlowThread::update(float deltaTime) { if (getActive()) { _positions.clear(); _radius = _jointsPointer->at(_joints[0])._node._settings._radius; computeRecovery(); for (size_t i = 0; i < _joints.size(); i++) { auto &joint = _jointsPointer->at(_joints[i]); - joint.update(); + joint.update(deltaTime); _positions.push_back(joint._node._currentPosition); } } @@ -447,10 +451,6 @@ bool FlowThread::getActive() { return _jointsPointer->at(_joints[0])._node._active; }; -void Flow::setRig(Rig* rig) { - _rig = rig; -}; - void Flow::calculateConstraints() { cleanUp(); if (!_rig) { @@ -593,6 +593,9 @@ void Flow::calculateConstraints() { } } _initialized = _jointThreads.size() > 0; + if (_initialized) { + _mtimer.restart(); + } } void Flow::cleanUp() { @@ -615,14 +618,14 @@ void Flow::setTransform(float scale, const glm::vec3& position, const glm::quat& _active = true; } -void Flow::update() { +void Flow::update(float deltaTime) { if (_initialized && _active) { - QElapsedTimer _timer; - _timer.start(); + QElapsedTimer timer; + timer.start(); updateJoints(); int count = 0; for (auto &thread : _jointThreads) { - thread.update(); + thread.update(deltaTime); thread.solve(USE_COLLISIONS, _collisionSystem); if (!updateRootFramePositions(count++)) { return; @@ -630,10 +633,12 @@ void Flow::update() { thread.apply(); } setJoints(); - _deltaTime += _timer.nsecsElapsed(); + _deltaTime += timer.nsecsElapsed(); _updates++; if (_deltaTime > _deltaTimeLimit) { - qDebug() << "Flow C++ update " << _deltaTime / _updates << " nanoSeconds"; + qint64 currentTime = _mtimer.elapsed(); + qDebug() << "Flow C++ update " << _deltaTime / _updates << " nanoSeconds " << (currentTime - _lastTime) / _updates << " miliseconds since last update"; + _lastTime = currentTime; _deltaTime = 0; _updates = 0; } diff --git a/libraries/animation/src/Flow.h b/libraries/animation/src/Flow.h index 9c088bf263..ff21d4a776 100644 --- a/libraries/animation/src/Flow.h +++ b/libraries/animation/src/Flow.h @@ -199,7 +199,7 @@ public: bool _colliding { false }; bool _active { true }; - void update(const glm::vec3& accelerationOffset); + void update(float deltaTime, const glm::vec3& accelerationOffset); void solve(const glm::vec3& constrainPoint, float maxDistance, const FlowCollisionResult& collision); void solveConstraints(const glm::vec3& constrainPoint, float maxDistance); void solveCollisions(const FlowCollisionResult& collision); @@ -212,7 +212,7 @@ public: void setInitialData(const glm::vec3& initialPosition, const glm::vec3& initialTranslation, const glm::quat& initialRotation, const glm::vec3& parentPosition); void setUpdatedData(const glm::vec3& updatedPosition, const glm::vec3& updatedTranslation, const glm::quat& updatedRotation, const glm::vec3& parentPosition, const glm::quat& parentWorldRotation); void setRecoveryPosition(const glm::vec3& recoveryPosition); - void update(); + void update(float deltaTime); void solve(const FlowCollisionResult& collision); int _index{ -1 }; @@ -257,7 +257,7 @@ public: void resetLength(); void computeFlowThread(int rootIndex); void computeRecovery(); - void update(); + void update(float deltaTime); void solve(bool useCollisions, FlowCollisionSystem& collisionSystem); void computeJointRotations(); void apply(); @@ -274,10 +274,9 @@ public: class Flow { public: - Flow() {}; - void setRig(Rig* rig); + Flow(Rig* rig) { _rig = rig; }; void calculateConstraints(); - void update(); + void update(float deltaTime); void setTransform(float scale, const glm::vec3& position, const glm::quat& rotation); const std::map& getJoints() const { return _flowJointData; } const std::vector& getThreads() const { return _jointThreads; } @@ -302,6 +301,8 @@ private: int _deltaTime { 0 }; int _deltaTimeLimit { 4000000 }; int _updates { 0 }; + QElapsedTimer _mtimer; + long _lastTime { 0 }; }; #endif // hifi_Flow_h diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 5042c00be6..e6e4f74c11 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -75,7 +75,7 @@ static const QString MAIN_STATE_MACHINE_RIGHT_FOOT_ROTATION("mainStateMachineRig static const QString MAIN_STATE_MACHINE_RIGHT_FOOT_POSITION("mainStateMachineRightFootPosition"); -Rig::Rig() { +Rig::Rig() : _flow(this) { // Ensure thread-safe access to the rigRegistry. std::lock_guard guard(rigRegistryMutex); @@ -1217,7 +1217,7 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons buildAbsoluteRigPoses(_internalPoseSet._relativePoses, _internalPoseSet._absolutePoses); buildAbsoluteRigPoses(_networkPoseSet._relativePoses, _networkPoseSet._absolutePoses); // copy internal poses to external poses - _flow.update(); + _flow.update(deltaTime); { QWriteLocker writeLock(&_externalPoseSetLock); @@ -1873,7 +1873,6 @@ void Rig::initAnimGraph(const QUrl& url) { auto roleState = roleAnimState.second; overrideRoleAnimation(roleState.role, roleState.url, roleState.fps, roleState.loop, roleState.firstFrame, roleState.lastFrame); } - _flow.setRig(this); emit onLoadComplete(); }); connect(_animLoader.get(), &AnimNodeLoader::error, [url](int error, QString str) { From 05d50f32ba974a7700cbc5bdc0c15b5cee7691a6 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Wed, 13 Feb 2019 08:16:22 -0700 Subject: [PATCH 085/474] time budget --- libraries/animation/src/Flow.cpp | 27 ++++++++++++++------------- libraries/animation/src/Flow.h | 7 ++++--- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/libraries/animation/src/Flow.cpp b/libraries/animation/src/Flow.cpp index ec17a56a4a..ab22e25326 100644 --- a/libraries/animation/src/Flow.cpp +++ b/libraries/animation/src/Flow.cpp @@ -593,9 +593,6 @@ void Flow::calculateConstraints() { } } _initialized = _jointThreads.size() > 0; - if (_initialized) { - _mtimer.restart(); - } } void Flow::cleanUp() { @@ -620,25 +617,29 @@ void Flow::setTransform(float scale, const glm::vec3& position, const glm::quat& void Flow::update(float deltaTime) { if (_initialized && _active) { - QElapsedTimer timer; - timer.start(); + QElapsedTimer _timer; + _timer.start(); updateJoints(); - int count = 0; - for (auto &thread : _jointThreads) { + for (size_t i = 0; i < _jointThreads.size(); i++) { + size_t index = _invertThreadLoop ? _jointThreads.size() - 1 - i : i; + auto &thread = _jointThreads[index]; thread.update(deltaTime); thread.solve(USE_COLLISIONS, _collisionSystem); - if (!updateRootFramePositions(count++)) { + if (!updateRootFramePositions(index)) { return; } thread.apply(); + if (_timer.elapsed() > MAX_UPDATE_FLOW_TIME_BUDGET) { + break; + qWarning(animation) << "Flow Bones ran out of time updating threads"; + } } setJoints(); - _deltaTime += timer.nsecsElapsed(); + _invertThreadLoop = !_invertThreadLoop; + _deltaTime += _timer.nsecsElapsed(); _updates++; if (_deltaTime > _deltaTimeLimit) { - qint64 currentTime = _mtimer.elapsed(); - qDebug() << "Flow C++ update " << _deltaTime / _updates << " nanoSeconds " << (currentTime - _lastTime) / _updates << " miliseconds since last update"; - _lastTime = currentTime; + qDebug() << "Flow C++ update " << _deltaTime / _updates << " nanoSeconds "; _deltaTime = 0; _updates = 0; } @@ -656,7 +657,7 @@ bool Flow::worldToJointPoint(const glm::vec3& position, const int jointIndex, gl return false; } -bool Flow::updateRootFramePositions(int threadIndex) { +bool Flow::updateRootFramePositions(size_t threadIndex) { auto &joints = _jointThreads[threadIndex]._joints; int rootIndex = _flowJointData[joints[0]]._parentIndex; _jointThreads[threadIndex]._rootFramePositions.clear(); diff --git a/libraries/animation/src/Flow.h b/libraries/animation/src/Flow.h index ff21d4a776..a06f785da2 100644 --- a/libraries/animation/src/Flow.h +++ b/libraries/animation/src/Flow.h @@ -62,6 +62,8 @@ const float DEFAULT_INERTIA = 0.8f; const float DEFAULT_DELTA = 0.55f; const float DEFAULT_RADIUS = 0.01f; +const uint64_t MAX_UPDATE_FLOW_TIME_BUDGET = 2000; + struct FlowPhysicsSettings { FlowPhysicsSettings() {}; FlowPhysicsSettings(bool active, float stiffness, float gravity, float damping, float inertia, float delta, float radius) { @@ -286,7 +288,7 @@ private: void setJoints(); void cleanUp(); void updateJoints(); - bool updateRootFramePositions(int threadIndex); + bool updateRootFramePositions(size_t threadIndex); bool worldToJointPoint(const glm::vec3& position, const int jointIndex, glm::vec3& jointSpacePosition) const; Rig* _rig; float _scale { 1.0f }; @@ -301,8 +303,7 @@ private: int _deltaTime { 0 }; int _deltaTimeLimit { 4000000 }; int _updates { 0 }; - QElapsedTimer _mtimer; - long _lastTime { 0 }; + bool _invertThreadLoop { false }; }; #endif // hifi_Flow_h From c6da7cc41df6c86eb5596dd8df7288365c34da36 Mon Sep 17 00:00:00 2001 From: amantley Date: Wed, 13 Feb 2019 17:12:28 -0800 Subject: [PATCH 086/474] everything looks pretty good now but need to move the code to rig and need to get rig of wrap around problem with the twist decomp --- .../src/AnimPoleVectorConstraint.cpp | 82 ++++++++++++------- 1 file changed, 52 insertions(+), 30 deletions(-) diff --git a/libraries/animation/src/AnimPoleVectorConstraint.cpp b/libraries/animation/src/AnimPoleVectorConstraint.cpp index 590750961e..bf246cf51a 100644 --- a/libraries/animation/src/AnimPoleVectorConstraint.cpp +++ b/libraries/animation/src/AnimPoleVectorConstraint.cpp @@ -131,11 +131,13 @@ float AnimPoleVectorConstraint::findThetaNewWay(const glm::vec3& hand, const glm initial_valuesZ = zWeightBottom * glm::max(zStart - (armToHand[2] / defaultArmLength), 0.0f) * fabs(armToHand[1] / defaultArmLength); } + //1.0f + armToHand[1]/defaultArmLength + float initial_valuesX; if (left) { initial_valuesX = weights[0] * glm::max(-1.0f * (armToHand[0] / defaultArmLength) + xStart, 0.0f); } else { - initial_valuesX = weights[0] * ((armToHand[0] / defaultArmLength) + xStart); + initial_valuesX = weights[0] * (((armToHand[0] / defaultArmLength) + xStart) - (0.3f) * ((1.0f + (armToHand[1] / defaultArmLength))/2.0f)); } float theta = initial_valuesX + initial_valuesY + initial_valuesZ; @@ -264,6 +266,7 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim //swingTwistDecomposition(twistUlnarSwing, Vectors::UNIT_Z, twist, ulnarDeviation); swingTwistDecomposition(relativeHandRotation, Vectors::UNIT_Z, twist, ulnarDeviation); + ulnarDeviation = glm::normalize(ulnarDeviation); if (ulnarDeviation.w < 0.0f) { ulnarDeviation.x *= -1.0f; ulnarDeviation.y *= -1.0f; @@ -280,7 +283,9 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim glm::quat flex; glm::quat twistUlnarSwing; - swingTwistDecomposition(twist, Vectors::UNIT_X, twistUlnarSwing, flex); + swingTwistDecomposition(relativeHandRotation, Vectors::UNIT_X, twistUlnarSwing, flex); + + flex = glm::normalize(flex); if (flex.w < 0.0f) { flex.x *= -1.0f; flex.y *= -1.0f; @@ -298,6 +303,7 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim glm::quat trueTwist; glm::quat nonTwist; swingTwistDecomposition(relativeHandRotation, Vectors::UNIT_Y, nonTwist, trueTwist); + trueTwist = glm::normalize(trueTwist); if (trueTwist.w < 0.0f) { trueTwist.x *= -1.0f; trueTwist.y *= -1.0f; @@ -320,6 +326,7 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim // glm::vec3 eulerVersion = glm::eulerAngles(relativeHandRotation); if (!isLeft) { + qCDebug(animation) << "flex ave: " << (_flexThetaRunningAverage / PI) * 180.0f << " twist ave: " << (_twistThetaRunningAverage / PI) * 180.0f << " ulnar deviation ave: " << (_ulnarRadialThetaRunningAverage / PI) * 180.0f; qCDebug(animation) << "flex: " << (flexTheta / PI) * 180.0f << " twist: " << (trueTwistTheta / PI) * 180.0f << " ulnar deviation: " << (ulnarDeviationTheta / PI) * 180.0f; //qCDebug(animation) << "trueTwist: " << (trueTwistTheta / PI) * 180.0f;// << " old twist: " << (twistTheta / PI) * 180.0f; //qCDebug(animation) << "ulnarAxis " << flexUlnarAxis; @@ -350,48 +357,59 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim const float POWER = 2.0f; const float FLEX_BOUNDARY = PI / 4.0f; - const float EXTEND_BOUNDARY = -PI / 6.0f; + const float EXTEND_BOUNDARY = -PI / 5.0f; float flexCorrection = 0.0f; if (isLeft) { if (_flexThetaRunningAverage > FLEX_BOUNDARY) { - flexCorrection = ((_flexThetaRunningAverage - FLEX_BOUNDARY) / PI) * 180.0f; // glm::sign(flexTheta) * pow((flexTheta - FLEX_BOUNDARY) / PI, POWER) * 180.0f; - if (flexCorrection > 30.0f) { - flexCorrection = 30.0f; - } + flexCorrection = ((_flexThetaRunningAverage - FLEX_BOUNDARY) / PI) * 90.0f; // glm::sign(flexTheta) * pow((flexTheta - FLEX_BOUNDARY) / PI, POWER) * 180.0f; } else if (_flexThetaRunningAverage < EXTEND_BOUNDARY) { - flexCorrection = ((_flexThetaRunningAverage - EXTEND_BOUNDARY) / PI) * 180.0f; // glm::sign(flexTheta) * pow((flexTheta - EXTEND_BOUNDARY) / PI, POWER) * 180.0f; + flexCorrection = ((_flexThetaRunningAverage - EXTEND_BOUNDARY) / PI) * 90.0f; // glm::sign(flexTheta) * pow((flexTheta - EXTEND_BOUNDARY) / PI, POWER) * 180.0f; + } + if (fabs(flexCorrection) > 30.0f) { + flexCorrection = glm::sign(flexCorrection) * 30.0f; } fred += flexCorrection; } else { if (_flexThetaRunningAverage > FLEX_BOUNDARY) { flexCorrection = ((_flexThetaRunningAverage - FLEX_BOUNDARY) / PI) * 180.0f; // glm::sign(flexTheta) * pow((flexTheta - FLEX_BOUNDARY) / PI, POWER) * 180.0f; - if (flexCorrection > 30.0f) { - flexCorrection = 30.0f; - } } else if (_flexThetaRunningAverage < EXTEND_BOUNDARY) { flexCorrection = ((_flexThetaRunningAverage - EXTEND_BOUNDARY) / PI) * 180.0f; // glm::sign(flexTheta) * pow((flexTheta - EXTEND_BOUNDARY) / PI, POWER) * 180.0f; } + if (fabs(flexCorrection) > 30.0f) { + flexCorrection = glm::sign(flexCorrection) * 30.0f; + } fred -= flexCorrection; } const float TWIST_ULNAR_DEADZONE = 0.0f; - const float ULNAR_BOUNDARY = PI / 12.0f; - if (fabsf(_ulnarRadialThetaRunningAverage) > ULNAR_BOUNDARY) { + const float ULNAR_BOUNDARY_MINUS = -PI / 12.0f; + const float ULNAR_BOUNDARY_PLUS = PI / 24.0f; + float ulnarDiff = 0.0f; + float ulnarCorrection = 0.0f; + if (_ulnarRadialThetaRunningAverage > ULNAR_BOUNDARY_PLUS) { + ulnarDiff = _ulnarRadialThetaRunningAverage - ULNAR_BOUNDARY_PLUS; + } else if (_ulnarRadialThetaRunningAverage < ULNAR_BOUNDARY_MINUS) { + ulnarDiff = _ulnarRadialThetaRunningAverage - ULNAR_BOUNDARY_MINUS; + } + if(fabs(ulnarDiff) > 0.0f){ if (fabs(_twistThetaRunningAverage) > TWIST_ULNAR_DEADZONE) { - float twistCoefficient = pow((fabs(_twistThetaRunningAverage) - TWIST_ULNAR_DEADZONE) / (PI / 20.0f), POWER); - float ulnarCorrection = 0.0f; + float twistCoefficient = (fabs(_twistThetaRunningAverage) / (PI / 20.0f)); + if (twistCoefficient > 1.0f) { + twistCoefficient = 1.0f; + } + if (isLeft) { if (trueTwistTheta < 0.0f) { - ulnarCorrection -= glm::sign(_ulnarRadialThetaRunningAverage) * pow((fabsf(_ulnarRadialThetaRunningAverage) - ULNAR_BOUNDARY) / PI, POWER) * 80.0f * twistCoefficient; + ulnarCorrection -= glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 90.0f * twistCoefficient; } else { - ulnarCorrection += glm::sign(_ulnarRadialThetaRunningAverage) * pow((fabsf(_ulnarRadialThetaRunningAverage) - ULNAR_BOUNDARY) / PI, POWER) * 80.0f * twistCoefficient; + ulnarCorrection += glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 90.0f * twistCoefficient; } } else { // right hand if (trueTwistTheta > 0.0f) { - ulnarCorrection -= glm::sign(_ulnarRadialThetaRunningAverage) * pow((fabsf(_ulnarRadialThetaRunningAverage) - ULNAR_BOUNDARY) / PI, POWER) * 80.0f * twistCoefficient; + ulnarCorrection -= glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 90.0f * twistCoefficient; } else { - ulnarCorrection += glm::sign(_ulnarRadialThetaRunningAverage) * pow((fabsf(_ulnarRadialThetaRunningAverage) - ULNAR_BOUNDARY) / PI, POWER) * 80.0f * twistCoefficient; + ulnarCorrection += glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 90.0f * twistCoefficient; } } @@ -403,24 +421,26 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim } // remember direction of travel. - const float TWIST_DEADZONE = (4.0f * PI) / 9.0f; + const float TWIST_DEADZONE = PI / 2.0f; //if (!isLeft) { float twistCorrection = 0.0f; if (_twistThetaRunningAverage < -TWIST_DEADZONE) { - twistCorrection = glm::sign(_twistThetaRunningAverage) * pow((fabsf(_twistThetaRunningAverage) - TWIST_DEADZONE) / PI, POWER) * 80.0f; + twistCorrection = glm::sign(_twistThetaRunningAverage) * ((fabsf(_twistThetaRunningAverage) - TWIST_DEADZONE) / PI) * 60.0f; } else { if (_twistThetaRunningAverage > TWIST_DEADZONE) { - twistCorrection = glm::sign(_twistThetaRunningAverage) * pow((fabsf(_twistThetaRunningAverage) - TWIST_DEADZONE) / PI, POWER) * 80.0f; + twistCorrection = glm::sign(_twistThetaRunningAverage) * ((fabsf(_twistThetaRunningAverage) - TWIST_DEADZONE) / PI) * 60.0f; } } - if (fabsf(twistCorrection) > 45.0f) { - fred += glm::sign(twistCorrection) * 45.0f; + if (fabsf(twistCorrection) > 30.0f) { + fred += glm::sign(twistCorrection) * 30.0f; } else { fred += twistCorrection; } _lastTheta = 0.5f * _lastTheta + 0.5f * fred; + qCDebug(animation) << "twist correction: " << twistCorrection << " flex correction: " << flexCorrection << " ulnar correction " << ulnarCorrection; + //} /*else { @@ -435,14 +455,16 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim } */ - /* - if (fred < 70.0f) { - fred = 70.0f; + + if (fabsf(_lastTheta) < 50.0f) { + if (fabsf(_lastTheta) < 50.0f) { + _lastTheta = glm::sign(_lastTheta) * 50.0f; + } } - if (fred > 175.0f) { - fred = 175.0f; + if (fabsf(_lastTheta) > 175.0f) { + _lastTheta = glm::sign(_lastTheta) * 175.0f; } - */ + theta = ((180.0f - _lastTheta) / 180.0f)*PI; From fc978f0ee72b9c1c34995c5cb98004238feb5124 Mon Sep 17 00:00:00 2001 From: amantley Date: Wed, 13 Feb 2019 17:31:48 -0800 Subject: [PATCH 087/474] fixed twist angle bug. need to fix wrap around 180 to -180 behaviour --- libraries/animation/src/AnimPoleVectorConstraint.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/animation/src/AnimPoleVectorConstraint.cpp b/libraries/animation/src/AnimPoleVectorConstraint.cpp index bf246cf51a..22d45c5c85 100644 --- a/libraries/animation/src/AnimPoleVectorConstraint.cpp +++ b/libraries/animation/src/AnimPoleVectorConstraint.cpp @@ -311,8 +311,8 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim trueTwist.w *= -1.0f; } glm::vec3 trueTwistAxis = glm::axis(trueTwist); - float trueTwistTheta = glm::angle(trueTwist); - trueTwistTheta *= glm::sign(trueTwistAxis[1]) * glm::angle(trueTwist); + float trueTwistTheta; + trueTwistTheta = glm::sign(trueTwistAxis[1]) * glm::angle(trueTwist); _twistThetaRunningAverage = 0.5f * _twistThetaRunningAverage + 0.5f * trueTwistTheta; From aa0c52abd717c67008fc7a98e417aa066f726c4f Mon Sep 17 00:00:00 2001 From: Angus Antley Date: Wed, 13 Feb 2019 18:33:01 -0800 Subject: [PATCH 088/474] fixed wrap around 180 degrees --- .../animation/src/AnimPoleVectorConstraint.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/libraries/animation/src/AnimPoleVectorConstraint.cpp b/libraries/animation/src/AnimPoleVectorConstraint.cpp index 22d45c5c85..bfb63c052a 100644 --- a/libraries/animation/src/AnimPoleVectorConstraint.cpp +++ b/libraries/animation/src/AnimPoleVectorConstraint.cpp @@ -277,6 +277,10 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim glm::vec3 ulnarAxis = glm::axis(ulnarDeviation); //float twistTheta = glm::sign(twistAxis[1]) * glm::angle(twist); float ulnarDeviationTheta = glm::sign(ulnarAxis[2]) * glm::angle(ulnarDeviation); + if (glm::sign(ulnarDeviationTheta) != glm::sign(_ulnarRadialThetaRunningAverage) && fabsf(ulnarDeviationTheta) >(5.0f * PI) / 6.0f) { + // don't allow the theta to cross the 180 degree limit. + ulnarDeviationTheta = -1.0f * ulnarDeviationTheta; + } _ulnarRadialThetaRunningAverage = 0.5f * _ulnarRadialThetaRunningAverage + 0.5f * ulnarDeviationTheta; //get the swingTwist of the hand to lower arm @@ -297,6 +301,10 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim //float swingTheta = glm::angle(twistUlnarSwing); float flexTheta = glm::sign(flexAxis[0]) * glm::angle(flex); + if (glm::sign(flexTheta) != glm::sign(_flexThetaRunningAverage) && fabsf(flexTheta) > (5.0f * PI) / 6.0f) { + // don't allow the theta to cross the 180 degree limit. + flexTheta = -1.0f * flexTheta; + } _flexThetaRunningAverage = 0.5f * _flexThetaRunningAverage + 0.5f * flexTheta; @@ -313,6 +321,11 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim glm::vec3 trueTwistAxis = glm::axis(trueTwist); float trueTwistTheta; trueTwistTheta = glm::sign(trueTwistAxis[1]) * glm::angle(trueTwist); + if (glm::sign(trueTwistTheta) != glm::sign(_twistThetaRunningAverage) && fabsf(trueTwistTheta) >(5.0f * PI) / 6.0f) { + // don't allow the theta to cross the 180 degree limit. + trueTwistTheta = -1.0f * trueTwistTheta; + } + _twistThetaRunningAverage = 0.5f * _twistThetaRunningAverage + 0.5f * trueTwistTheta; From 1924018d2cb5ee24d5c1c6c3af353533097d51bb Mon Sep 17 00:00:00 2001 From: Angus Antley Date: Thu, 14 Feb 2019 07:15:58 -0800 Subject: [PATCH 089/474] added code to convert theta to a projected pole vector --- .../src/AnimPoleVectorConstraint.cpp | 69 +++++++------------ 1 file changed, 23 insertions(+), 46 deletions(-) diff --git a/libraries/animation/src/AnimPoleVectorConstraint.cpp b/libraries/animation/src/AnimPoleVectorConstraint.cpp index bfb63c052a..f230cfdad8 100644 --- a/libraries/animation/src/AnimPoleVectorConstraint.cpp +++ b/libraries/animation/src/AnimPoleVectorConstraint.cpp @@ -230,6 +230,19 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim glm::vec3 refVectorProj = refVector - glm::dot(refVector, unitAxis) * unitAxis; float refVectorProjLength = glm::length(refVectorProj); + float lastDot; + if ((_skeleton->nameToJointIndex("RightHand") == _tipJointIndex) || (_skeleton->nameToJointIndex("LeftHand") == _tipJointIndex)) { + //fake pole vector computation. + lastDot = cosf(((180.0f - _lastTheta) / 180.0f)*PI); + float lastSideDot = sqrt(1.0f - (lastDot*lastDot)); + glm::vec3 pretendPoleVector; + if (refVectorLength > MIN_LENGTH && sideVectorLength > MIN_LENGTH) { + poleVector = lastDot * (refVectorProj / refVectorProjLength) + glm::sign(_lastTheta) * lastSideDot * (sideVector / sideVectorLength); + } else { + poleVector = glm::vec3(1.0f, 0.0f, 0.0f); + } + } + // project poleVector on plane formed by axis. glm::vec3 poleVectorProj = poleVector - glm::dot(poleVector, unitAxis) * unitAxis; float poleVectorProjLength = glm::length(poleVectorProj); @@ -328,33 +341,13 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim _twistThetaRunningAverage = 0.5f * _twistThetaRunningAverage + 0.5f * trueTwistTheta; - - - - //while (trueTwistTheta > PI) { - // trueTwistTheta -= 2.0f * PI; - //} - - - // glm::vec3 eulerVersion = glm::eulerAngles(relativeHandRotation); if (!isLeft) { - qCDebug(animation) << "flex ave: " << (_flexThetaRunningAverage / PI) * 180.0f << " twist ave: " << (_twistThetaRunningAverage / PI) * 180.0f << " ulnar deviation ave: " << (_ulnarRadialThetaRunningAverage / PI) * 180.0f; - qCDebug(animation) << "flex: " << (flexTheta / PI) * 180.0f << " twist: " << (trueTwistTheta / PI) * 180.0f << " ulnar deviation: " << (ulnarDeviationTheta / PI) * 180.0f; - //qCDebug(animation) << "trueTwist: " << (trueTwistTheta / PI) * 180.0f;// << " old twist: " << (twistTheta / PI) * 180.0f; - //qCDebug(animation) << "ulnarAxis " << flexUlnarAxis; - // qCDebug(animation) << "relative hand rotation " << relativeHandRotation; - // qCDebug(animation) << "twist rotation " << trueTwist; - - //QString name = QString("handMarker3"); - //const vec4 WHITE(1.0f); - //DebugDraw::getInstance().addMyAvatarMarker(name, relativeHandRotation, midPose.trans(), WHITE); + //qCDebug(animation) << "flex ave: " << (_flexThetaRunningAverage / PI) * 180.0f << " twist ave: " << (_twistThetaRunningAverage / PI) * 180.0f << " ulnar deviation ave: " << (_ulnarRadialThetaRunningAverage / PI) * 180.0f; + //qCDebug(animation) << "flex: " << (flexTheta / PI) * 180.0f << " twist: " << (trueTwistTheta / PI) * 180.0f << " ulnar deviation: " << (ulnarDeviationTheta / PI) * 180.0f; + } - //QString name = QString("wrist_target").arg(_id); - //glm::vec4 markerColor(1.0f, 1.0f, 0.0f, 0.0f); - //DebugDraw::getInstance().addMyAvatarMarker(name, midPose.rot(), midPose.trans(), markerColor); - // here is where we would do the wrist correction. float deltaTheta = correctElbowForHandFlexionExtension(tipPose, midPose); float deltaThetaUlnar; @@ -374,9 +367,9 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim float flexCorrection = 0.0f; if (isLeft) { if (_flexThetaRunningAverage > FLEX_BOUNDARY) { - flexCorrection = ((_flexThetaRunningAverage - FLEX_BOUNDARY) / PI) * 90.0f; // glm::sign(flexTheta) * pow((flexTheta - FLEX_BOUNDARY) / PI, POWER) * 180.0f; + flexCorrection = ((_flexThetaRunningAverage - FLEX_BOUNDARY) / PI) * 180.0f; } else if (_flexThetaRunningAverage < EXTEND_BOUNDARY) { - flexCorrection = ((_flexThetaRunningAverage - EXTEND_BOUNDARY) / PI) * 90.0f; // glm::sign(flexTheta) * pow((flexTheta - EXTEND_BOUNDARY) / PI, POWER) * 180.0f; + flexCorrection = ((_flexThetaRunningAverage - EXTEND_BOUNDARY) / PI) * 180.0f; } if (fabs(flexCorrection) > 30.0f) { flexCorrection = glm::sign(flexCorrection) * 30.0f; @@ -384,9 +377,9 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim fred += flexCorrection; } else { if (_flexThetaRunningAverage > FLEX_BOUNDARY) { - flexCorrection = ((_flexThetaRunningAverage - FLEX_BOUNDARY) / PI) * 180.0f; // glm::sign(flexTheta) * pow((flexTheta - FLEX_BOUNDARY) / PI, POWER) * 180.0f; + flexCorrection = ((_flexThetaRunningAverage - FLEX_BOUNDARY) / PI) * 180.0f; } else if (_flexThetaRunningAverage < EXTEND_BOUNDARY) { - flexCorrection = ((_flexThetaRunningAverage - EXTEND_BOUNDARY) / PI) * 180.0f; // glm::sign(flexTheta) * pow((flexTheta - EXTEND_BOUNDARY) / PI, POWER) * 180.0f; + flexCorrection = ((_flexThetaRunningAverage - EXTEND_BOUNDARY) / PI) * 180.0f; } if (fabs(flexCorrection) > 30.0f) { flexCorrection = glm::sign(flexCorrection) * 30.0f; @@ -452,22 +445,7 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim _lastTheta = 0.5f * _lastTheta + 0.5f * fred; - qCDebug(animation) << "twist correction: " << twistCorrection << " flex correction: " << flexCorrection << " ulnar correction " << ulnarCorrection; - - //} - - /*else { - if (trueTwistTheta < -TWIST_DEADZONE) { - fred -= glm::sign(trueTwistTheta) * pow((fabsf(trueTwistTheta) - TWIST_DEADZONE) / PI, POWER) * 90.0f; - } else { - if (trueTwistTheta > TWIST_DEADZONE) { - fred -= glm::sign(trueTwistTheta) * pow((fabsf(trueTwistTheta) - TWIST_DEADZONE) / PI, POWER) * 90.0f; - } - } - - - } - */ + //qCDebug(animation) << "twist correction: " << twistCorrection << " flex correction: " << flexCorrection << " ulnar correction " << ulnarCorrection; if (fabsf(_lastTheta) < 50.0f) { if (fabsf(_lastTheta) < 50.0f) { @@ -478,10 +456,9 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim _lastTheta = glm::sign(_lastTheta) * 175.0f; } - + float poleVectorTheta = theta; theta = ((180.0f - _lastTheta) / 180.0f)*PI; - - //qCDebug(animation) << "the wrist correction theta is -----> " << isLeft << " theta: " << deltaTheta; + qCDebug(animation) << "fake theta " << poleVectorTheta << " newly computed theta " << theta << " dot " << dot << " last dot "<< lastDot; } From 20d7e00ea8d0b66d48e18853711ff88ca994f0ce Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Thu, 14 Feb 2019 08:48:37 -0800 Subject: [PATCH 090/474] Standalone Tags - Checkpoint --- interface/resources/qml/hifi/Card.qml | 20 ++++ .../hifi/commerce/marketplace/Marketplace.qml | 5 +- .../commerce/marketplace/MarketplaceItem.qml | 97 +++++++++++++++---- .../marketplace/MarketplaceListItem.qml | 33 ++++++- .../hifi/commerce/purchases/PurchasedItem.qml | 21 ++++ .../qml/hifi/commerce/purchases/Purchases.qml | 2 + 6 files changed, 158 insertions(+), 20 deletions(-) diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml index 238c26236f..67abc1c3f9 100644 --- a/interface/resources/qml/hifi/Card.qml +++ b/interface/resources/qml/hifi/Card.qml @@ -39,6 +39,7 @@ Item { property bool isConcurrency: action === 'concurrency'; property bool isAnnouncement: action === 'announcement'; property bool isStacked: !isConcurrency && drillDownToPlace; + property bool standaloneOptimized: true; property int textPadding: 10; property int smallMargin: 4; @@ -281,7 +282,26 @@ Item { right: parent.right; margins: smallMargin; } + } + HiFiGlyphs { + id: standaloneOptomizedBadge + + anchors { + right: actionIcon.left + verticalCenter: parent.verticalCenter + bottom: parent.bottom; + } + height: root.standaloneOptimized ? 34 : 0 + + visible: standaloneOptimized + + text: hifi.glyphs.hmd + size: 34 + horizontalAlignment: Text.AlignHCenter + color: hifi.colors.blueHighlight + } + function go() { Tablet.playSound(TabletEnums.ButtonClick); goFunction(drillDownToPlace ? ("/places/" + placeName) : ("/user_stories/" + storyId)); diff --git a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml index 0d42cb599e..ba83b0b61f 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml @@ -122,6 +122,8 @@ Rectangle { marketplaceItem.license = result.data.license; marketplaceItem.available = result.data.availability === "available"; marketplaceItem.created_at = result.data.created_at; + marketplaceItem.standaloneOptimized = result.data.standalone_optimized; + marketplaceItem.standaloneVisible = result.data.standalone_optimized || result.data.standalone_incompatible; marketplaceItemScrollView.contentHeight = marketplaceItemContent.height; itemsList.visible = false; marketplaceItemView.visible = true; @@ -534,7 +536,8 @@ Rectangle { price: model.cost available: model.availability === "available" isLoggedIn: root.isLoggedIn; - + standaloneOptimized: model.standalone_optimized + onShowItem: { MarketplaceScriptingInterface.getMarketplaceItem(item_id); } diff --git a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml index 0a57e56099..9784e422f1 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml @@ -39,9 +39,11 @@ Rectangle { property string posted: "" property bool available: false property string created_at: "" - property bool isLoggedIn: false; - property int edition: -1; - property bool supports3DHTML: false; + property bool isLoggedIn: false + property int edition: -1 + property bool supports3DHTML: false + property bool standaloneVisible: false + property bool standaloneOptimized: false onCategoriesChanged: { @@ -50,16 +52,6 @@ Rectangle { categoriesListModel.append({"category":category}); }); } - - onDescriptionChanged: { - - if(root.supports3DHTML) { - descriptionTextModel.clear(); - descriptionTextModel.append({text: description}); - } else { - descriptionText.text = description; - } - } onAttributionsChanged: { attributionsModel.clear(); @@ -246,11 +238,38 @@ Rectangle { right: parent.right; top: itemImage.bottom; } - height: categoriesList.y - buyButton.y + categoriesList.height + height: categoriesList.y - badges.y + categoriesList.height function evalHeight() { - height = categoriesList.y - buyButton.y + categoriesList.height; - console.log("HEIGHT: " + height); + height = categoriesList.y - badges.y + categoriesList.height; + } + + Item { + id: badges + + anchors { + left: parent.left + top: parent.top + right: parent.right + } + height: childrenRect.height + + HiFiGlyphs { + id: standaloneOptomizedBadge + + anchors { + left: parent.left + verticalCenter: parent.verticalCenter + } + height: root.standaloneOptimized ? 34 : 0 + + visible: root.standaloneOptimized + + text: hifi.glyphs.hmd + size: 34 + horizontalAlignment: Text.AlignHCenter + color: hifi.colors.blueHighlight + } } HifiControlsUit.Button { @@ -258,7 +277,7 @@ Rectangle { anchors { right: parent.right - top: parent.top + top: badges.bottom left: parent.left topMargin: 15 } @@ -529,13 +548,55 @@ Rectangle { } } } + + Item { + id: standaloneItem + + anchors { + top: licenseItem.bottom + topMargin: 15 + left: parent.left + right: parent.right + } + height: root.standaloneVisible ? childrenRect.height : 0 + + visible: root.standaloneVisible + + RalewaySemiBold { + id: standaloneLabel + + anchors.top: parent.top + anchors.left: parent.left + width: paintedWidth + height: 20 + + text: root.standaloneOptimized ? "STAND-ALONE OPTIMIZED:" : "STAND-ALONE INCOMPATIBLE:" + size: 14 + color: hifi.colors.lightGrayText + verticalAlignment: Text.AlignVCenter + } + + RalewaySemiBold { + id: standaloneOptimizedText + + anchors.top: standaloneLabel.bottom + anchors.left: parent.left + anchors.topMargin: 5 + width: paintedWidth + + text: root.standaloneOptimized ? "This item is stand-alone optimized" : "This item is incompatible with stand-alone devices" + size: 14 + color: hifi.colors.lightGray + verticalAlignment: Text.AlignVCenter + } + } Item { id: descriptionItem property string text: "" anchors { - top: licenseItem.bottom + top: standaloneItem.bottom topMargin: 15 left: parent.left right: parent.right diff --git a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml index 2f37637e40..9da6d8552e 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml @@ -35,7 +35,8 @@ Rectangle { property string category: "" property int price: 0 property bool available: false - property bool isLoggedIn: false; + property bool isLoggedIn: false + property bool standaloneOptimized: false signal buy() signal showItem() @@ -288,8 +289,38 @@ Rectangle { onClicked: root.categoryClicked(root.category); } } + Item { + id: badges + + anchors { + left: parent.left + top: categoryLabel.bottom + right: buyButton.left + } + height: childrenRect.height + + HiFiGlyphs { + id: standaloneOptomizedBadge + + anchors { + left: parent.left + leftMargin: 15 + verticalCenter: parent.verticalCenter + + } + height: 24 + + visible: root.standaloneOptimized + + text: hifi.glyphs.hmd + size: 24 + horizontalAlignment: Text.AlignHCenter + color: hifi.colors.blueHighlight + } + } HifiControlsUit.Button { + id: buyButton anchors { right: parent.right top: parent.top diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index df6e216b32..ec49b596bc 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -50,6 +50,8 @@ Item { property string upgradeTitle; property bool updateAvailable: root.updateItemId && root.updateItemId !== ""; property bool valid; + property bool standaloneOptimized; + property bool standaloneIncompatible; property string originalStatusText; property string originalStatusColor; @@ -838,6 +840,25 @@ Item { root.sendToPurchases({ method: 'flipCard' }); } } + } + + HiFiGlyphs { + id: standaloneOptomizedBadge + + anchors { + right: parent.right + bottom: parent.bottom + rightMargin: 15 + bottomMargin:12 + } + height: root.standaloneOptimized ? 34 : 0 + + visible: root.standaloneOptimized + + text: hifi.glyphs.hmd + size: 34 + horizontalAlignment: Text.AlignHCenter + color: hifi.colors.blueHighlight } } diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index dc892e6640..a5446202a8 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -553,6 +553,8 @@ Rectangle { upgradeTitle: model.upgrade_title; itemType: model.item_type; valid: model.valid; + standaloneOptimized: model.standalone_optimized + standaloneIncompatible: model.standalone_incompatible anchors.topMargin: 10; anchors.bottomMargin: 10; From 3e66bce11262807205f8f2506233707c6abd61ef Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Thu, 14 Feb 2019 18:30:37 -0700 Subject: [PATCH 091/474] set useFlow function --- interface/src/avatar/MyAvatar.cpp | 55 ++++++++++ interface/src/avatar/MyAvatar.h | 10 ++ libraries/animation/src/Flow.cpp | 102 ++++++++++++++---- libraries/animation/src/Flow.h | 20 ++-- libraries/animation/src/Rig.cpp | 4 +- .../src/avatars-renderer/Avatar.cpp | 1 - 6 files changed, 161 insertions(+), 31 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index d5f8f8a3ec..ce751add0b 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -5320,3 +5320,58 @@ void MyAvatar::addAvatarHandsToFlow(const std::shared_ptr& otherAvatar) flow.setOthersCollision(otherAvatar->getID(), jointIndex, position); } } + +void MyAvatar::useFlow(const QVariantMap& physicsConfig, const QVariantMap& collisionsConfig) { + if (_skeletonModel->isLoaded()) { + auto &flow = _skeletonModel->getRig().getFlow(); + flow.init(); + auto physicsGroups = physicsConfig.keys(); + if (physicsGroups.size() > 0) { + for (auto &groupName : physicsGroups) { + auto &settings = physicsConfig[groupName].toMap(); + FlowPhysicsSettings physicsSettings = flow.getPhysicsSettingsForGroup(groupName); + if (settings.contains("active")) { + physicsSettings._active = settings["active"].toBool(); + } + if (settings.contains("damping")) { + physicsSettings._damping = settings["damping"].toFloat(); + } + if (settings.contains("delta")) { + physicsSettings._delta = settings["delta"].toFloat(); + } + if (settings.contains("gravity")) { + physicsSettings._gravity = settings["gravity"].toFloat(); + } + if (settings.contains("inertia")) { + physicsSettings._inertia = settings["inertia"].toFloat(); + } + if (settings.contains("radius")) { + physicsSettings._radius = settings["radius"].toFloat(); + } + if (settings.contains("stiffness")) { + physicsSettings._stiffness = settings["stiffness"].toFloat(); + } + flow.setPhysicsSettingsForGroup(groupName, physicsSettings); + } + } + auto collisionJoints = collisionsConfig.keys(); + if (collisionJoints.size() > 0) { + auto &collisionSystem = flow.getCollisionSystem(); + collisionSystem.resetCollisions(); + for (auto &jointName : collisionJoints) { + int jointIndex = getJointIndex(jointName); + FlowCollisionSettings collisionsSettings; + auto &settings = collisionsConfig[jointName].toMap(); + collisionsSettings._entityID = getID(); + if (settings.contains("radius")) { + collisionsSettings._radius = settings["radius"].toFloat(); + } + if (settings.contains("offset")) { + collisionsSettings._offset = vec3FromVariant(settings["offset"]); + } + collisionSystem.addCollisionSphere(jointIndex, collisionsSettings); + } + + } + } +} \ No newline at end of file diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 996abcabf2..f753da4fa0 100755 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1185,6 +1185,16 @@ public: void addAvatarHandsToFlow(const std::shared_ptr& otherAvatar); + /**jsdoc + * Init flow simulation on avatar. + * @function MyAvatar.useFlow + * @param {Object} physicsConfig - object with the customized physic parameters + * i.e. {"hair": {"active": true, "stiffness": 0.0, "radius": 0.04, "gravity": -0.035, "damping": 0.8, "inertia": 0.8, "delta": 0.35}} + * @param {Object} collisionsConfig - object with the customized collision parameters + * i.e. {"Spine2": {"type": "sphere", "radius": 0.14, "offset": {"x": 0.0, "y": 0.2, "z": 0.0}}} + */ + Q_INVOKABLE void useFlow(const QVariantMap& flowConfig = QVariantMap(), const QVariantMap& collisionsConfig = QVariantMap()); + public slots: /**jsdoc diff --git a/libraries/animation/src/Flow.cpp b/libraries/animation/src/Flow.cpp index ab22e25326..3db0068434 100644 --- a/libraries/animation/src/Flow.cpp +++ b/libraries/animation/src/Flow.cpp @@ -176,7 +176,7 @@ void FlowCollisionSystem::modifySelfCollisionRadius(int jointIndex, float radius int collisionIndex = findSelfCollisionWithJoint(jointIndex); if (collisionIndex > -1) { _selfCollisions[collisionIndex]._initialRadius = radius; - _selfCollisions[collisionIndex]._radius = _scale * radius; + //_selfCollisions[collisionIndex]._radius = _scale * radius; } }; @@ -185,7 +185,7 @@ void FlowCollisionSystem::modifySelfCollisionYOffset(int jointIndex, float offse if (collisionIndex > -1) { auto currentOffset = _selfCollisions[collisionIndex]._offset; _selfCollisions[collisionIndex]._initialOffset = glm::vec3(currentOffset.x, offset, currentOffset.z); - _selfCollisions[collisionIndex]._offset = _selfCollisions[collisionIndex]._initialOffset * _scale; + //_selfCollisions[collisionIndex]._offset = _selfCollisions[collisionIndex]._initialOffset * _scale; } }; @@ -193,11 +193,27 @@ void FlowCollisionSystem::modifySelfCollisionOffset(int jointIndex, const glm::v int collisionIndex = findSelfCollisionWithJoint(jointIndex); if (collisionIndex > -1) { _selfCollisions[collisionIndex]._initialOffset = offset; - _selfCollisions[collisionIndex]._offset = _selfCollisions[collisionIndex]._initialOffset * _scale; + //_selfCollisions[collisionIndex]._offset = _selfCollisions[collisionIndex]._initialOffset * _scale; } }; - - +FlowCollisionSettings FlowCollisionSystem::getCollisionSettingsByJoint(int jointIndex) { + for (auto &collision : _selfCollisions) { + if (collision._jointIndex == jointIndex) { + return FlowCollisionSettings(collision._entityID, FlowCollisionType::CollisionSphere, collision._initialOffset, collision._initialRadius); + } + } + return FlowCollisionSettings(); +} +void FlowCollisionSystem::setCollisionSettingsByJoint(int jointIndex, const FlowCollisionSettings& settings) { + for (auto &collision : _selfCollisions) { + if (collision._jointIndex == jointIndex) { + collision._initialRadius = settings._radius; + collision._initialOffset = settings._offset; + collision._radius = _scale * settings._radius; + collision._offset = _scale * settings._offset; + } + } +} void FlowCollisionSystem::prepareCollisions() { _allCollisions.clear(); _allCollisions.resize(_selfCollisions.size() + _othersCollisions.size()); @@ -206,6 +222,11 @@ void FlowCollisionSystem::prepareCollisions() { _othersCollisions.clear(); } +FlowNode::FlowNode(const glm::vec3& initialPosition, FlowPhysicsSettings settings) { + _initialPosition = _previousPosition = _currentPosition = initialPosition; + _initialRadius = settings._radius; +} + void FlowNode::update(float deltaTime, const glm::vec3& accelerationOffset) { _acceleration = glm::vec3(0.0f, _settings._gravity, 0.0f); _previousVelocity = _currentVelocity; @@ -215,15 +236,16 @@ void FlowNode::update(float deltaTime, const glm::vec3& accelerationOffset) { // Add inertia const float FPS = 60.0f; float timeRatio = (FPS * deltaTime); - + float invertedTimeRatio = timeRatio > 0.0f ? 1.0f / timeRatio : 1.0f; auto deltaVelocity = _previousVelocity - _currentVelocity; auto centrifugeVector = glm::length(deltaVelocity) != 0.0f ? glm::normalize(deltaVelocity) : glm::vec3(); - _acceleration = _acceleration + centrifugeVector * _settings._inertia * glm::length(_currentVelocity) * (1 / timeRatio); + _acceleration = _acceleration + centrifugeVector * _settings._inertia * glm::length(_currentVelocity) * invertedTimeRatio; // Add offset _acceleration += accelerationOffset; - glm::vec3 deltaAcceleration = timeRatio * _acceleration * glm::pow(_settings._delta * _scale, 2); + //glm::vec3 deltaAcceleration = timeRatio * _acceleration * glm::pow(_settings._delta * _scale, 2); + glm::vec3 deltaAcceleration = timeRatio * _acceleration * glm::pow(_settings._delta, 2); // Calculate new position _currentPosition = (_currentPosition + _currentVelocity * _settings._damping) + deltaAcceleration; } @@ -257,11 +279,10 @@ void FlowNode::solveCollisions(const FlowCollisionResult& collision) { } }; -FlowJoint::FlowJoint(int jointIndex, int parentIndex, int childIndex, const QString& name, const QString& group, float scale, const FlowPhysicsSettings& settings) { +FlowJoint::FlowJoint(int jointIndex, int parentIndex, int childIndex, const QString& name, const QString& group, const FlowPhysicsSettings& settings) { _index = jointIndex; _name = name; _group = group; - _scale = scale; _childIndex = childIndex; _parentIndex = parentIndex; _node = FlowNode(glm::vec3(), settings); @@ -276,8 +297,7 @@ void FlowJoint::setInitialData(const glm::vec3& initialPosition, const glm::vec3 _initialRotation = initialRotation; _translationDirection = glm::normalize(_initialTranslation); _parentPosition = parentPosition; - _length = glm::length(_initialPosition - parentPosition); - _originalLength = _length / _scale; + _initialLength = _length = glm::length(_initialPosition - parentPosition); } void FlowJoint::setUpdatedData(const glm::vec3& updatedPosition, const glm::vec3& updatedTranslation, const glm::quat& updatedRotation, const glm::vec3& parentPosition, const glm::quat& parentWorldRotation) { @@ -313,8 +333,8 @@ void FlowJoint::solve(const FlowCollisionResult& collision) { _node.solve(_parentPosition, _length, collision); }; -FlowDummyJoint::FlowDummyJoint(const glm::vec3& initialPosition, int index, int parentIndex, int childIndex, float scale, FlowPhysicsSettings settings) : - FlowJoint(index, parentIndex, childIndex, DUMMY_KEYWORD + "_" + index, DUMMY_KEYWORD, scale, settings) { +FlowDummyJoint::FlowDummyJoint(const glm::vec3& initialPosition, int index, int parentIndex, int childIndex, FlowPhysicsSettings settings) : + FlowJoint(index, parentIndex, childIndex, DUMMY_KEYWORD + "_" + index, DUMMY_KEYWORD, settings) { _isDummy = true; _initialPosition = initialPosition; _node = FlowNode(_initialPosition, settings); @@ -451,6 +471,12 @@ bool FlowThread::getActive() { return _jointsPointer->at(_joints[0])._node._active; }; +void Flow::init() { + if (!_initialized) { + calculateConstraints(); + } +} + void Flow::calculateConstraints() { cleanUp(); if (!_rig) { @@ -507,7 +533,7 @@ void Flow::calculateConstraints() { jointSettings = DEFAULT_JOINT_SETTINGS; } if (_flowJointData.find(i) == _flowJointData.end()) { - auto flowJoint = FlowJoint(i, parentIndex, -1, name, group, _scale, jointSettings); + auto flowJoint = FlowJoint(i, parentIndex, -1, name, group, jointSettings); _flowJointData.insert(std::pair(i, flowJoint)); } } @@ -553,7 +579,7 @@ void Flow::calculateConstraints() { auto newSettings = FlowPhysicsSettings(joint._node._settings); newSettings._stiffness = ISOLATED_JOINT_STIFFNESS; int extraIndex = (int)_flowJointData.size(); - auto newJoint = FlowDummyJoint(jointPosition, extraIndex, jointIndex, -1, _scale, newSettings); + auto newJoint = FlowDummyJoint(jointPosition, extraIndex, jointIndex, -1, newSettings); newJoint._isDummy = false; newJoint._length = ISOLATED_JOINT_LENGTH; newJoint._childIndex = extraIndex; @@ -575,7 +601,7 @@ void Flow::calculateConstraints() { int parentIndex = rightHandIndex; for (int i = 0; i < DUMMY_JOINT_COUNT; i++) { int childIndex = (i == (DUMMY_JOINT_COUNT - 1)) ? -1 : extraIndex + 1; - auto newJoint = FlowDummyJoint(rightHandPosition, extraIndex, parentIndex, childIndex, _scale, DEFAULT_JOINT_SETTINGS); + auto newJoint = FlowDummyJoint(rightHandPosition, extraIndex, parentIndex, childIndex, DEFAULT_JOINT_SETTINGS); _flowJointData.insert(std::pair(extraIndex, newJoint)); parentIndex = extraIndex; extraIndex++; @@ -601,24 +627,39 @@ void Flow::cleanUp() { _flowJointKeywords.clear(); _collisionSystem.resetCollisions(); _initialized = false; + _isScaleSet = false; _active = false; } void Flow::setTransform(float scale, const glm::vec3& position, const glm::quat& rotation) { _scale = scale; - for (auto &joint : _flowJointData) { - joint.second._scale = scale; - joint.second._node._scale = scale; - } _entityPosition = position; _entityRotation = rotation; - _active = true; + _active = _initialized; } void Flow::update(float deltaTime) { if (_initialized && _active) { QElapsedTimer _timer; _timer.start(); + if (_scale != _lastScale) { + if (!_isScaleSet) { + for (auto &joint: _flowJointData) { + joint.second._initialLength = joint.second._length / _scale; + } + _isScaleSet = true; + } + _lastScale = _scale; + _collisionSystem.setScale(_scale); + for (int i = 0; i < _jointThreads.size(); i++) { + for (int j = 0; j < _jointThreads[i]._joints.size(); j++) { + auto &joint = _flowJointData[_jointThreads[i]._joints[j]]; + joint._node._settings._radius = joint._node._initialRadius * _scale; + joint._length = joint._initialLength * _scale; + } + _jointThreads[i].resetLength(); + } + } updateJoints(); for (size_t i = 0; i < _jointThreads.size(); i++) { size_t index = _invertThreadLoop ? _jointThreads.size() - 1 - i : i; @@ -710,4 +751,21 @@ void Flow::setOthersCollision(const QUuid& otherId, int jointIndex, const glm::v settings._entityID = otherId; settings._radius = HAND_COLLISION_RADIUS; _collisionSystem.addCollisionSphere(jointIndex, settings, position, false, true); +} + +FlowPhysicsSettings Flow::getPhysicsSettingsForGroup(const QString& group) { + for (auto &joint : _flowJointData) { + if (joint.second._group.toUpper() == group.toUpper()) { + return joint.second._node._settings; + } + } + return FlowPhysicsSettings(); +} + +void Flow::setPhysicsSettingsForGroup(const QString& group, const FlowPhysicsSettings& settings) { + for (auto &joint : _flowJointData) { + if (joint.second._group.toUpper() == group.toUpper()) { + joint.second._node._settings = settings; + } + } } \ No newline at end of file diff --git a/libraries/animation/src/Flow.h b/libraries/animation/src/Flow.h index a06f785da2..7f77df0beb 100644 --- a/libraries/animation/src/Flow.h +++ b/libraries/animation/src/Flow.h @@ -155,7 +155,6 @@ public: FlowCollisionSystem() {}; void addCollisionSphere(int jointIndex, const FlowCollisionSettings& settings, const glm::vec3& position = { 0.0f, 0.0f, 0.0f }, bool isSelfCollision = true, bool isTouch = false); FlowCollisionResult computeCollision(const std::vector collisions); - void setScale(float scale); std::vector checkFlowThreadCollisions(FlowThread* flowThread); @@ -169,6 +168,9 @@ public: void prepareCollisions(); void resetCollisions(); void resetOthersCollisions() { _othersCollisions.clear(); } + void setScale(float scale); + FlowCollisionSettings getCollisionSettingsByJoint(int jointIndex); + void setCollisionSettingsByJoint(int jointIndex, const FlowCollisionSettings& settings); protected: std::vector _selfCollisions; std::vector _othersCollisions; @@ -179,8 +181,7 @@ protected: class FlowNode { public: FlowNode() {}; - FlowNode(const glm::vec3& initialPosition, FlowPhysicsSettings settings) : - _initialPosition(initialPosition), _previousPosition(initialPosition), _currentPosition(initialPosition){}; + FlowNode(const glm::vec3& initialPosition, FlowPhysicsSettings settings); FlowPhysicsSettings _settings; glm::vec3 _initialPosition; @@ -195,7 +196,6 @@ public: FlowCollisionResult _previousCollision; float _initialRadius { 0.0f }; - float _scale{ 1.0f }; bool _anchored { false }; bool _colliding { false }; @@ -210,7 +210,7 @@ public: class FlowJoint { public: FlowJoint() {}; - FlowJoint(int jointIndex, int parentIndex, int childIndex, const QString& name, const QString& group, float scale, const FlowPhysicsSettings& settings); + FlowJoint(int jointIndex, int parentIndex, int childIndex, const QString& name, const QString& group, const FlowPhysicsSettings& settings); void setInitialData(const glm::vec3& initialPosition, const glm::vec3& initialTranslation, const glm::quat& initialRotation, const glm::vec3& parentPosition); void setUpdatedData(const glm::vec3& updatedPosition, const glm::vec3& updatedTranslation, const glm::quat& updatedRotation, const glm::vec3& parentPosition, const glm::quat& parentWorldRotation); void setRecoveryPosition(const glm::vec3& recoveryPosition); @@ -241,13 +241,13 @@ public: glm::vec3 _translationDirection; float _scale { 1.0f }; float _length { 0.0f }; - float _originalLength { 0.0f }; + float _initialLength { 0.0f }; bool _applyRecovery { false }; }; class FlowDummyJoint : public FlowJoint { public: - FlowDummyJoint(const glm::vec3& initialPosition, int index, int parentIndex, int childIndex, float scale, FlowPhysicsSettings settings); + FlowDummyJoint(const glm::vec3& initialPosition, int index, int parentIndex, int childIndex, FlowPhysicsSettings settings); }; @@ -277,6 +277,8 @@ public: class Flow { public: Flow(Rig* rig) { _rig = rig; }; + void init(); + bool isActive() { return _active; } void calculateConstraints(); void update(float deltaTime); void setTransform(float scale, const glm::vec3& position, const glm::quat& rotation); @@ -284,6 +286,8 @@ public: const std::vector& getThreads() const { return _jointThreads; } void setOthersCollision(const QUuid& otherId, int jointIndex, const glm::vec3& position); FlowCollisionSystem& getCollisionSystem() { return _collisionSystem; } + FlowPhysicsSettings getPhysicsSettingsForGroup(const QString& group); + void setPhysicsSettingsForGroup(const QString& group, const FlowPhysicsSettings& settings); private: void setJoints(); void cleanUp(); @@ -292,6 +296,7 @@ private: bool worldToJointPoint(const glm::vec3& position, const int jointIndex, glm::vec3& jointSpacePosition) const; Rig* _rig; float _scale { 1.0f }; + float _lastScale{ 1.0f }; glm::vec3 _entityPosition; glm::quat _entityRotation; std::map _flowJointData; @@ -300,6 +305,7 @@ private: FlowCollisionSystem _collisionSystem; bool _initialized { false }; bool _active { false }; + bool _isScaleSet { false }; int _deltaTime { 0 }; int _deltaTimeLimit { 4000000 }; int _updates { 0 }; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index e6e4f74c11..9b6d97d919 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1905,7 +1905,9 @@ void Rig::initAnimGraph(const QUrl& url) { qCritical(animation) << "Error loading: code = " << error << "str =" << str; }); connect(this, &Rig::onLoadComplete, [&]() { - _flow.calculateConstraints(); + if (_flow.isActive()) { + _flow.calculateConstraints(); + } }); } } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 223813eecb..50cb813c3a 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -736,7 +736,6 @@ void Avatar::postUpdate(float deltaTime, const render::ScenePointer& scene) { // glm::vec3 pos2 = joints.find(joint.second._parentIndex) != joints.end() ? joints[joint.second._parentIndex]._node._currentPosition : getJointPosition(joint.second._parentIndex); // DebugDraw::getInstance().drawRay(pos1, pos2, glm::vec4(1.0f, 1.0f, 1.0f, 1.0f)); DebugDraw::getInstance().drawRay(joints[index1]._node._currentPosition, joints[index2]._node._currentPosition, glm::vec4(1.0f, 1.0f, 1.0f, 1.0f)); - } } } From 88eae0f2ec7586fae9d286d5af9cacaf9b3634b7 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Thu, 14 Feb 2019 18:49:49 -0700 Subject: [PATCH 092/474] Remove test functions --- interface/src/avatar/AvatarManager.cpp | 103 ------------------ interface/src/avatar/AvatarManager.h | 9 -- .../src/avatars-renderer/Avatar.h | 3 - libraries/entities/src/EntityTree.cpp | 6 - libraries/entities/src/EntityTree.h | 1 - 5 files changed, 122 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index d2ba7149f2..685619aed8 100755 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -763,109 +763,6 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic return result; } -RayToAvatarIntersectionResult AvatarManager::findRayIntersectionOld(const PickRay& ray, - const QScriptValue& avatarIdsToInclude, - const QScriptValue& avatarIdsToDiscard) { - QVector avatarsToInclude = qVectorEntityItemIDFromScriptValue(avatarIdsToInclude); - QVector avatarsToDiscard = qVectorEntityItemIDFromScriptValue(avatarIdsToDiscard); - - return findRayIntersectionVectorOld(ray, avatarsToInclude, avatarsToDiscard); -} - -RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVectorOld(const PickRay& ray, - const QVector& avatarsToInclude, - const QVector& avatarsToDiscard) { - RayToAvatarIntersectionResult result; - if (QThread::currentThread() != thread()) { - BLOCKING_INVOKE_METHOD(const_cast(this), "findRayIntersectionVector", - Q_RETURN_ARG(RayToAvatarIntersectionResult, result), - Q_ARG(const PickRay&, ray), - Q_ARG(const QVector&, avatarsToInclude), - Q_ARG(const QVector&, avatarsToDiscard)); - return result; - } - - // It's better to intersect the ray against the avatar's actual mesh, but this is currently difficult to - // do, because the transformed mesh data only exists over in GPU-land. As a compromise, this code - // intersects against the avatars capsule and then against the (T-pose) mesh. The end effect is that picking - // against the avatar is sort-of right, but you likely wont be able to pick against the arms. - - // TODO -- find a way to extract transformed avatar mesh data from the rendering engine. - - std::vector sortedAvatars; - auto avatarHashCopy = getHashCopy(); - for (auto avatarData : avatarHashCopy) { - auto avatar = std::static_pointer_cast(avatarData); - if ((avatarsToInclude.size() > 0 && !avatarsToInclude.contains(avatar->getID())) || - (avatarsToDiscard.size() > 0 && avatarsToDiscard.contains(avatar->getID()))) { - continue; - } - - float distance = FLT_MAX; -#if 0 - // if we weren't picking against the capsule, we would want to pick against the avatarBounds... - SkeletonModelPointer avatarModel = avatar->getSkeletonModel(); - AABox avatarBounds = avatarModel->getRenderableMeshBound(); - if (avatarBounds.contains(ray.origin)) { - distance = 0.0f; - } - else { - float boundDistance = FLT_MAX; - BoxFace face; - glm::vec3 surfaceNormal; - if (avatarBounds.findRayIntersection(ray.origin, ray.direction, boundDistance, face, surfaceNormal)) { - distance = boundDistance; - } - } -#else - glm::vec3 start; - glm::vec3 end; - float radius; - avatar->getCapsule(start, end, radius); - findRayCapsuleIntersection(ray.origin, ray.direction, start, end, radius, distance); -#endif - - if (distance < FLT_MAX) { - sortedAvatars.emplace_back(distance, avatar); - } - } - - if (sortedAvatars.size() > 1) { - static auto comparator = [](const SortedAvatar& left, const SortedAvatar& right) { return left.first < right.first; }; - std::sort(sortedAvatars.begin(), sortedAvatars.end(), comparator); - } - - for (auto it = sortedAvatars.begin(); it != sortedAvatars.end(); ++it) { - const SortedAvatar& sortedAvatar = *it; - // We can exit once avatarCapsuleDistance > bestDistance - if (sortedAvatar.first > result.distance) { - break; - } - - float distance = FLT_MAX; - BoxFace face; - glm::vec3 surfaceNormal; - QVariantMap extraInfo; - SkeletonModelPointer avatarModel = sortedAvatar.second->getSkeletonModel(); - if (avatarModel->findRayIntersectionAgainstSubMeshes(ray.origin, ray.direction, distance, face, surfaceNormal, extraInfo, true)) { - if (distance < result.distance) { - result.intersects = true; - result.avatarID = sortedAvatar.second->getID(); - result.distance = distance; - result.face = face; - result.surfaceNormal = surfaceNormal; - result.extraInfo = extraInfo; - } - } - } - - if (result.intersects) { - result.intersection = ray.origin + ray.direction * result.distance; - } - - return result; -} - ParabolaToAvatarIntersectionResult AvatarManager::findParabolaIntersectionVector(const PickParabola& pick, const QVector& avatarsToInclude, const QVector& avatarsToDiscard) { diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 66f28206e0..50d9e80e8b 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -157,15 +157,6 @@ public: const QVector& avatarsToDiscard, bool pickAgainstMesh); - Q_INVOKABLE RayToAvatarIntersectionResult findRayIntersectionOld(const PickRay& ray, - const QScriptValue& avatarIdsToInclude = QScriptValue(), - const QScriptValue& avatarIdsToDiscard = QScriptValue()); - - Q_INVOKABLE RayToAvatarIntersectionResult findRayIntersectionVectorOld(const PickRay& ray, - const QVector& avatarsToInclude, - const QVector& avatarsToDiscard); - - /**jsdoc * @function AvatarManager.findParabolaIntersectionVector * @param {PickParabola} pick diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 4ba2ffc07d..b43fe012b7 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -35,7 +35,6 @@ #include "MetaModelPayload.h" #include "MultiSphereShape.h" -#include "Flow.h" namespace render { template <> const ItemKey payloadGetKey(const AvatarSharedPointer& avatar); @@ -286,8 +285,6 @@ public: */ Q_INVOKABLE glm::quat jointToWorldRotation(const glm::quat& rotation, const int jointIndex = -1) const; - Q_INVOKABLE void callFlow() { _skeletonModel->getRig().computeFlowSkeleton(); }; - virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override; virtual void setAttachmentData(const QVector& attachmentData) override; diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 47064bdbd3..954462a9f2 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -2943,12 +2943,6 @@ int EntityTree::getJointIndex(const QUuid& entityID, const QString& name) const } return entity->getJointIndex(name); } -void EntityTree::callFlowSkeleton(const QUuid& entityID) { - /* - EntityTree* nonConstThis = const_cast(this); - EntityItemPointer entity = nonConstThis->findEntityByEntityItemID(entityID); - */ -} QStringList EntityTree::getJointNames(const QUuid& entityID) const { EntityTree* nonConstThis = const_cast(this); diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index cc80083e0d..f9b7b8d67f 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -240,7 +240,6 @@ public: // these are used to call through to EntityItems Q_INVOKABLE int getJointIndex(const QUuid& entityID, const QString& name) const; Q_INVOKABLE QStringList getJointNames(const QUuid& entityID) const; - Q_INVOKABLE void callFlowSkeleton(const QUuid& entityID); void knowAvatarID(QUuid avatarID) { _avatarIDs += avatarID; } void forgetAvatarID(QUuid avatarID) { _avatarIDs -= avatarID; } From 5c26bbec5ca90e651fad42476ae6fc8a3737ad5d Mon Sep 17 00:00:00 2001 From: amantley Date: Thu, 14 Feb 2019 18:06:20 -0800 Subject: [PATCH 093/474] moving the theta calculation to rig --- .../src/AnimPoleVectorConstraint.cpp | 36 +- libraries/animation/src/Rig.cpp | 373 +++++++++++++++++- libraries/animation/src/Rig.h | 9 + 3 files changed, 410 insertions(+), 8 deletions(-) diff --git a/libraries/animation/src/AnimPoleVectorConstraint.cpp b/libraries/animation/src/AnimPoleVectorConstraint.cpp index f230cfdad8..fa78793dd2 100644 --- a/libraries/animation/src/AnimPoleVectorConstraint.cpp +++ b/libraries/animation/src/AnimPoleVectorConstraint.cpp @@ -177,8 +177,16 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim _poses = underPoses; } + + + // Look up poleVector from animVars, make sure to convert into geom space. glm::vec3 poleVector = animVars.lookupRigToGeometryVector(_poleVectorVar, Vectors::UNIT_Z); + if (_skeleton->nameToJointIndex("LeftHand") == _tipJointIndex) { + + float thetaFromRig = animVars.lookup("thetaRight", 0.0f); + qCDebug(animation) << " anim pole vector theta from rig " << thetaFromRig; + } // determine if we should interpolate bool enabled = animVars.lookup(_enabledVar, _enabled); @@ -238,11 +246,14 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim glm::vec3 pretendPoleVector; if (refVectorLength > MIN_LENGTH && sideVectorLength > MIN_LENGTH) { poleVector = lastDot * (refVectorProj / refVectorProjLength) + glm::sign(_lastTheta) * lastSideDot * (sideVector / sideVectorLength); + if (_skeleton->nameToJointIndex("LeftHand") == _tipJointIndex) { + qCDebug(animation) << " anim pole vector computed: " << poleVector; + } } else { poleVector = glm::vec3(1.0f, 0.0f, 0.0f); } } - + // project poleVector on plane formed by axis. glm::vec3 poleVectorProj = poleVector - glm::dot(poleVector, unitAxis) * unitAxis; float poleVectorProjLength = glm::length(poleVectorProj); @@ -262,7 +273,18 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim if (_skeleton->nameToJointIndex("LeftHand") == _tipJointIndex) { isLeft = true; } - fred = findThetaNewWay(tipPose.trans(), basePose.trans(), isLeft); + //qCDebug(animation) << "hand pose anim pole vector: " << isLeft << " isLeft " << tipPose; + //fred = findThetaNewWay(tipPose.trans(), basePose.trans(), isLeft); + if (isLeft) { + float thetaFromRig = animVars.lookup("thetaLeft", 0.0f); + qCDebug(animation) << " anim pole vector theta from rig left" << thetaFromRig; + fred = thetaFromRig; + + } else { + float thetaFromRig = animVars.lookup("thetaRight", 0.0f); + qCDebug(animation) << " anim pole vector theta from rig right" << thetaFromRig; + fred = thetaFromRig; + } glm::quat relativeHandRotation = (midPose.inverse() * tipPose).rot(); @@ -354,11 +376,11 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim if (!isLeft) { deltaThetaUlnar = correctElbowForHandUlnarRadialDeviation(tipPose, midPose); } - + if (isLeft) { - fred *= -1.0f; + // fred *= -1.0f; } - + // make the dead zone PI/6.0 const float POWER = 2.0f; @@ -386,6 +408,7 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim } fred -= flexCorrection; } + //qCDebug(animation) << "flexCorrection anim" << flexCorrection; const float TWIST_ULNAR_DEADZONE = 0.0f; const float ULNAR_BOUNDARY_MINUS = -PI / 12.0f; @@ -425,6 +448,7 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim fred += ulnarCorrection; } } + //qCDebug(animation) << "ulnarCorrection anim" << ulnarCorrection; // remember direction of travel. const float TWIST_DEADZONE = PI / 2.0f; @@ -458,7 +482,7 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim float poleVectorTheta = theta; theta = ((180.0f - _lastTheta) / 180.0f)*PI; - qCDebug(animation) << "fake theta " << poleVectorTheta << " newly computed theta " << theta << " dot " << dot << " last dot "<< lastDot; + } diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index faddfdebb3..e476c8e5fd 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1465,7 +1465,8 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab int oppositeArmJointIndex = _animSkeleton->nameToJointIndex("RightArm"); if (ENABLE_POLE_VECTORS && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0 && oppositeArmJointIndex >= 0) { glm::vec3 poleVector; - bool usePoleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, oppositeArmJointIndex, poleVector); + //bool usePoleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, oppositeArmJointIndex, poleVector); + bool usePoleVector = calculateElbowPoleVectorOptimized(handJointIndex, elbowJointIndex, armJointIndex, true, poleVector); if (usePoleVector) { glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector); _animVars.set("leftHandPoleVectorEnabled", true); @@ -1520,7 +1521,8 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab if (ENABLE_POLE_VECTORS && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0 && oppositeArmJointIndex >= 0) { glm::vec3 poleVector; - bool usePoleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, oppositeArmJointIndex, poleVector); + //bool usePoleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, oppositeArmJointIndex, poleVector); + bool usePoleVector = calculateElbowPoleVectorOptimized(handJointIndex, elbowJointIndex, armJointIndex, false, poleVector); if (usePoleVector) { glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector); _animVars.set("rightHandPoleVectorEnabled", true); @@ -1686,6 +1688,373 @@ void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm } } +bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int shoulderIndex, bool left, glm::vec3& poleVector) { + // get the default poses for the upper and lower arm + // then use this length to judge how far the hand is away from the shoulder. + // then create weights that make the elbow angle less when the x value is large in either direction. + // make the angle less when z is small. + // lower y with x center lower angle + // lower y with x out higher angle + //AnimPose oppositeArmPose = _externalPoseSet._absolutePoses[oppositeArmIndex]; + glm::vec3 referenceVector; + if (left) { + referenceVector = Vectors::UNIT_X; + } else { + referenceVector = -Vectors::UNIT_X; + } + + AnimPose handPose = _externalPoseSet._absolutePoses[handIndex]; + AnimPose shoulderPose = _externalPoseSet._absolutePoses[shoulderIndex]; + AnimPose elbowPose = _externalPoseSet._absolutePoses[elbowIndex]; + + //qCDebug(animation) << "handPose Rig " << left << "isleft" << handPose; + + AnimPose absoluteShoulderPose = getAbsoluteDefaultPose(shoulderIndex); + AnimPose absoluteHandPose = getAbsoluteDefaultPose(handIndex); + // subtract 10 centimeters from the arm length for some reason actual arm position is clamped to length - 10cm. + float defaultArmLength = glm::length(absoluteHandPose.trans() - absoluteShoulderPose.trans()); + + // calculate the reference axis and the side axis. + // Look up refVector from animVars, make sure to convert into geom space. + glm::vec3 refVector = glmExtractRotation(_rigToGeometryTransform) * elbowPose.rot() * Vectors::UNIT_X; + float refVectorLength = glm::length(refVector); + glm::vec3 unitRef = refVector / refVectorLength; + + glm::vec3 axis = shoulderPose.trans() - handPose.trans(); + float axisLength = glm::length(axis); + glm::vec3 unitAxis = axis / axisLength; + + glm::vec3 sideVector = glm::cross(unitAxis, unitRef); + float sideVectorLength = glm::length(sideVector); + + // project refVector onto axis plane + glm::vec3 refVectorProj = unitRef - glm::dot(unitRef, unitAxis) * unitAxis; + float refVectorProjLength = glm::length(refVectorProj); + + if (left) { + + //qCDebug(animation) << "rig ref proj " << refVectorProj/refVectorProjLength; // "rig reference vector: " << refVector / refVectorLength; + } + + //qCDebug(animation) << "rig reference vector projected: " << refVectorProj << " left is " << left; + + + // qCDebug(animation) << "default arm length " << defaultArmLength; + + // phi_0 is the lowest angle we can have + const float phi_0 = 15.0f; + const float zStart = 0.6f; + const float xStart = 0.1f; + // biases + //const glm::vec3 biases(30.0f, 120.0f, -30.0f); + const glm::vec3 biases(0.0f, 135.0f, 0.0f); + // weights + const float zWeightBottom = -100.0f; + //const glm::vec3 weights(-30.0f, 30.0f, 210.0f); + const glm::vec3 weights(-50.0f, 60.0f, 260.0f); + glm::vec3 armToHand = handPose.trans() - shoulderPose.trans(); + // qCDebug(animation) << "current arm length " << glm::length(armToHand); + float initial_valuesY = (fabsf(armToHand[1] / defaultArmLength) * weights[1]) + biases[1]; + float initial_valuesZ; + if (armToHand[1] > 0.0f) { + initial_valuesZ = weights[2] * glm::max(zStart - (armToHand[2] / defaultArmLength), 0.0f) * fabs(armToHand[1] / defaultArmLength); + } else { + initial_valuesZ = zWeightBottom * glm::max(zStart - (armToHand[2] / defaultArmLength), 0.0f) * fabs(armToHand[1] / defaultArmLength); + } + + //1.0f + armToHand[1]/defaultArmLength + + float initial_valuesX; + if (left) { + initial_valuesX = weights[0] * glm::max(-1.0f * (armToHand[0] / defaultArmLength) + xStart, 0.0f); + } else { + initial_valuesX = weights[0] * (((armToHand[0] / defaultArmLength) + xStart) - (0.3f) * ((1.0f + (armToHand[1] / defaultArmLength)) / 2.0f)); + } + + float theta = initial_valuesX + initial_valuesY + initial_valuesZ; + + if (theta < 13.0f) { + theta = 13.0f; + } + if (theta > 175.0f) { + theta = 175.0f; + } + + if (left) { + theta *= -1.0f; + } + + float halfTheta = theta; + + glm::quat relativeHandRotation = (elbowPose.inverse() * handPose).rot(); + + relativeHandRotation = glm::normalize(relativeHandRotation); + if (relativeHandRotation.w < 0.0f) { + relativeHandRotation.x *= -1.0f; + relativeHandRotation.y *= -1.0f; + relativeHandRotation.z *= -1.0f; + relativeHandRotation.w *= -1.0f; + } + + glm::quat twist; + glm::quat ulnarDeviation; + //swingTwistDecomposition(twistUlnarSwing, Vectors::UNIT_Z, twist, ulnarDeviation); + swingTwistDecomposition(relativeHandRotation, Vectors::UNIT_Z, twist, ulnarDeviation); + + ulnarDeviation = glm::normalize(ulnarDeviation); + if (ulnarDeviation.w < 0.0f) { + ulnarDeviation.x *= -1.0f; + ulnarDeviation.y *= -1.0f; + ulnarDeviation.z *= -1.0f; + ulnarDeviation.w *= -1.0f; + } + + glm::vec3 ulnarAxis = glm::axis(ulnarDeviation); + float ulnarDeviationTheta = glm::sign(ulnarAxis[2]) * glm::angle(ulnarDeviation); + if (left) { + if (glm::sign(ulnarDeviationTheta) != glm::sign(_ulnarRadialThetaRunningAverageLeft) && fabsf(ulnarDeviationTheta) > (5.0f * PI) / 6.0f) { + // don't allow the theta to cross the 180 degree limit. + ulnarDeviationTheta = -1.0f * ulnarDeviationTheta; + } + _ulnarRadialThetaRunningAverageLeft = 0.5f * _ulnarRadialThetaRunningAverageLeft + 0.5f * ulnarDeviationTheta; + } else { + if (glm::sign(ulnarDeviationTheta) != glm::sign(_ulnarRadialThetaRunningAverageRight) && fabsf(ulnarDeviationTheta) > (5.0f * PI) / 6.0f) { + // don't allow the theta to cross the 180 degree limit. + ulnarDeviationTheta = -1.0f * ulnarDeviationTheta; + } + _ulnarRadialThetaRunningAverageRight = 0.5f * _ulnarRadialThetaRunningAverageRight + 0.5f * ulnarDeviationTheta; + + } + //get the swingTwist of the hand to lower arm + glm::quat flex; + glm::quat twistUlnarSwing; + swingTwistDecomposition(relativeHandRotation, Vectors::UNIT_X, twistUlnarSwing, flex); + flex = glm::normalize(flex); + if (flex.w < 0.0f) { + flex.x *= -1.0f; + flex.y *= -1.0f; + flex.z *= -1.0f; + flex.w *= -1.0f; + } + + glm::vec3 flexAxis = glm::axis(flex); + + //float swingTheta = glm::angle(twistUlnarSwing); + float flexTheta = glm::sign(flexAxis[0]) * glm::angle(flex); + if (left) { + if (glm::sign(flexTheta) != glm::sign(_flexThetaRunningAverageLeft) && fabsf(flexTheta) > (5.0f * PI) / 6.0f) { + // don't allow the theta to cross the 180 degree limit. + flexTheta = -1.0f * flexTheta; + } + _flexThetaRunningAverageLeft = 0.5f * _flexThetaRunningAverageLeft + 0.5f * flexTheta; + } else { + if (glm::sign(flexTheta) != glm::sign(_flexThetaRunningAverageRight) && fabsf(flexTheta) > (5.0f * PI) / 6.0f) { + // don't allow the theta to cross the 180 degree limit. + flexTheta = -1.0f * flexTheta; + } + _flexThetaRunningAverageRight = 0.5f * _flexThetaRunningAverageRight + 0.5f * flexTheta; + + } + + + glm::quat trueTwist; + glm::quat nonTwist; + swingTwistDecomposition(relativeHandRotation, Vectors::UNIT_Y, nonTwist, trueTwist); + trueTwist = glm::normalize(trueTwist); + if (trueTwist.w < 0.0f) { + trueTwist.x *= -1.0f; + trueTwist.y *= -1.0f; + trueTwist.z *= -1.0f; + trueTwist.w *= -1.0f; + } + glm::vec3 trueTwistAxis = glm::axis(trueTwist); + float trueTwistTheta; + trueTwistTheta = glm::sign(trueTwistAxis[1]) * glm::angle(trueTwist); + if (left) { + if (glm::sign(trueTwistTheta) != glm::sign(_twistThetaRunningAverageLeft) && fabsf(trueTwistTheta) > (5.0f * PI) / 6.0f) { + // don't allow the theta to cross the 180 degree limit. + trueTwistTheta = -1.0f * trueTwistTheta; + } + _twistThetaRunningAverageLeft = 0.5f * _twistThetaRunningAverageLeft + 0.5f * trueTwistTheta; + } else { + if (glm::sign(trueTwistTheta) != glm::sign(_twistThetaRunningAverageRight) && fabsf(trueTwistTheta) > (5.0f * PI) / 6.0f) { + // don't allow the theta to cross the 180 degree limit. + trueTwistTheta = -1.0f * trueTwistTheta; + } + _twistThetaRunningAverageRight = 0.5f * _twistThetaRunningAverageRight + 0.5f * trueTwistTheta; + + } + + if (!left) { + qCDebug(animation) << "flex ave: " << (_flexThetaRunningAverageRight / PI) * 180.0f << " twist ave: " << (_twistThetaRunningAverageRight / PI) * 180.0f << " ulnar deviation ave: " << (_ulnarRadialThetaRunningAverageRight / PI) * 180.0f; + //qCDebug(animation) << "flex: " << (flexTheta / PI) * 180.0f << " twist: " << (trueTwistTheta / PI) * 180.0f << " ulnar deviation: " << (ulnarDeviationTheta / PI) * 180.0f; + + } + + // make the dead zone PI/6.0 + const float POWER = 2.0f; + const float FLEX_BOUNDARY = PI / 4.0f; + const float EXTEND_BOUNDARY = -PI / 5.0f; + float flexCorrection = 0.0f; + if (left) { + if (_flexThetaRunningAverageLeft > FLEX_BOUNDARY) { + flexCorrection = ((_flexThetaRunningAverageLeft - FLEX_BOUNDARY) / PI) * 180.0f; + } else if (_flexThetaRunningAverageLeft < EXTEND_BOUNDARY) { + flexCorrection = ((_flexThetaRunningAverageLeft - EXTEND_BOUNDARY) / PI) * 180.0f; + } + if (fabs(flexCorrection) > 30.0f) { + flexCorrection = glm::sign(flexCorrection) * 30.0f; + } + theta += flexCorrection; + } else { + if (_flexThetaRunningAverageRight > FLEX_BOUNDARY) { + flexCorrection = ((_flexThetaRunningAverageRight - FLEX_BOUNDARY) / PI) * 180.0f; + } else if (_flexThetaRunningAverageRight < EXTEND_BOUNDARY) { + flexCorrection = ((_flexThetaRunningAverageRight - EXTEND_BOUNDARY) / PI) * 180.0f; + } + if (fabs(flexCorrection) > 30.0f) { + flexCorrection = glm::sign(flexCorrection) * 30.0f; + } + theta -= flexCorrection; + } + //qCDebug(animation) << "flexCorrection rig" << flexCorrection; + + const float TWIST_ULNAR_DEADZONE = 0.0f; + const float ULNAR_BOUNDARY_MINUS = -PI / 12.0f; + const float ULNAR_BOUNDARY_PLUS = PI / 24.0f; + float ulnarDiff = 0.0f; + float ulnarCorrection = 0.0f; + if (left) { + if (_ulnarRadialThetaRunningAverageLeft > ULNAR_BOUNDARY_PLUS) { + ulnarDiff = _ulnarRadialThetaRunningAverageLeft - ULNAR_BOUNDARY_PLUS; + } else if (_ulnarRadialThetaRunningAverageLeft < ULNAR_BOUNDARY_MINUS) { + ulnarDiff = _ulnarRadialThetaRunningAverageLeft - ULNAR_BOUNDARY_MINUS; + } + if (fabs(ulnarDiff) > 0.0f) { + if (fabs(_twistThetaRunningAverageLeft) > TWIST_ULNAR_DEADZONE) { + float twistCoefficient = (fabs(_twistThetaRunningAverageLeft) / (PI / 20.0f)); + if (twistCoefficient > 1.0f) { + twistCoefficient = 1.0f; + } + + if (left) { + if (trueTwistTheta < 0.0f) { + ulnarCorrection -= glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 90.0f * twistCoefficient; + } else { + ulnarCorrection += glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 90.0f * twistCoefficient; + } + } else { + // right hand + if (trueTwistTheta > 0.0f) { + ulnarCorrection -= glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 90.0f * twistCoefficient; + } else { + ulnarCorrection += glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 90.0f * twistCoefficient; + } + + } + if (fabsf(ulnarCorrection) > 20.0f) { + ulnarCorrection = glm::sign(ulnarCorrection) * 20.0f; + } + theta += ulnarCorrection; + } + } + } else { + if (_ulnarRadialThetaRunningAverageRight > ULNAR_BOUNDARY_PLUS) { + ulnarDiff = _ulnarRadialThetaRunningAverageRight - ULNAR_BOUNDARY_PLUS; + } else if (_ulnarRadialThetaRunningAverageRight < ULNAR_BOUNDARY_MINUS) { + ulnarDiff = _ulnarRadialThetaRunningAverageRight - ULNAR_BOUNDARY_MINUS; + } + if (fabs(ulnarDiff) > 0.0f) { + if (fabs(_twistThetaRunningAverageRight) > TWIST_ULNAR_DEADZONE) { + float twistCoefficient = (fabs(_twistThetaRunningAverageRight) / (PI / 20.0f)); + if (twistCoefficient > 1.0f) { + twistCoefficient = 1.0f; + } + + if (left) { + if (trueTwistTheta < 0.0f) { + ulnarCorrection -= glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 90.0f * twistCoefficient; + } else { + ulnarCorrection += glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 90.0f * twistCoefficient; + } + } else { + // right hand + if (trueTwistTheta > 0.0f) { + ulnarCorrection -= glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 90.0f * twistCoefficient; + } else { + ulnarCorrection += glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 90.0f * twistCoefficient; + } + + } + if (fabsf(ulnarCorrection) > 20.0f) { + ulnarCorrection = glm::sign(ulnarCorrection) * 20.0f; + } + theta += ulnarCorrection; + } + } + + } + // qCDebug(animation) << "ulnarCorrection rig" << ulnarCorrection; + + // remember direction of travel. + const float TWIST_DEADZONE = PI / 2.0f; + //if (!isLeft) { + float twistCorrection = 0.0f; + if (left) { + if (_twistThetaRunningAverageLeft < -TWIST_DEADZONE) { + twistCorrection = glm::sign(_twistThetaRunningAverageLeft) * ((fabsf(_twistThetaRunningAverageLeft) - TWIST_DEADZONE) / PI) * 60.0f; + } else { + if (_twistThetaRunningAverageLeft > TWIST_DEADZONE) { + twistCorrection = glm::sign(_twistThetaRunningAverageLeft) * ((fabsf(_twistThetaRunningAverageLeft) - TWIST_DEADZONE) / PI) * 60.0f; + } + } + } else { + if (_twistThetaRunningAverageRight < -TWIST_DEADZONE) { + twistCorrection = glm::sign(_twistThetaRunningAverageRight) * ((fabsf(_twistThetaRunningAverageRight) - TWIST_DEADZONE) / PI) * 60.0f; + } else { + if (_twistThetaRunningAverageRight > TWIST_DEADZONE) { + twistCorrection = glm::sign(_twistThetaRunningAverageRight) * ((fabsf(_twistThetaRunningAverageRight) - TWIST_DEADZONE) / PI) * 60.0f; + } + } + + } + if (fabsf(twistCorrection) > 30.0f) { + theta += glm::sign(twistCorrection) * 30.0f; + } else { + theta += twistCorrection; + } + //qCDebug(animation) << "twistCorrection rig" << twistCorrection; + + //qCDebug(animation) << "theta in rig " << left << " isLeft " << theta; + //return theta; + if (left) { + _animVars.set("thetaLeft", halfTheta); + } else { + _animVars.set("thetaRight", halfTheta); + } + // convert theta back to pole vector + float lastDot = cosf(((180.0f - theta) / 180.0f)*PI); + float lastSideDot = sqrt(1.0f - (lastDot*lastDot)); + + const float MIN_LENGTH = 1.0e-4f; + if (refVectorLength > MIN_LENGTH && sideVectorLength > MIN_LENGTH) { + poleVector = lastDot * (refVectorProj / refVectorProjLength) + glm::sign(theta) * lastSideDot * (sideVector / sideVectorLength); + if (left) { + + //qCDebug(animation) << "pole vector in rig " << poleVector; + } + // + } else { + poleVector = glm::vec3(1.0f, 0.0f, 0.0f); + return false; + } + + + return true; + + +} + bool Rig::calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex, int oppositeArmIndex, glm::vec3& poleVector) const { // The resulting Pole Vector is calculated as the sum of a three vectors. // The first is the vector with direction shoulder-hand. The module of this vector is inversely proportional to the strength of the resulting Pole Vector. diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 7468e9f6f9..52f2c142a8 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -258,6 +258,7 @@ protected: void calcAnimAlpha(float speed, const std::vector& referenceSpeeds, float* alphaOut) const; bool calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex, int oppositeArmIndex, glm::vec3& poleVector) const; + bool calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int shoulderIndex, bool left, glm::vec3& poleVector); glm::vec3 calculateKneePoleVector(int footJointIndex, int kneeJoint, int upLegIndex, int hipsIndex, const AnimPose& targetFootPose) const; glm::vec3 deflectHandFromTorso(const glm::vec3& handPosition, const HFMJointShapeInfo& hipsShapeInfo, const HFMJointShapeInfo& spineShapeInfo, const HFMJointShapeInfo& spine1ShapeInfo, const HFMJointShapeInfo& spine2ShapeInfo) const; @@ -425,6 +426,14 @@ protected: bool _computeNetworkAnimation { false }; bool _sendNetworkNode { false }; + float _twistThetaRunningAverageLeft { 0.0f }; + float _flexThetaRunningAverageLeft { 0.0f }; + float _ulnarRadialThetaRunningAverageLeft { 0.0f }; + float _twistThetaRunningAverageRight{ 0.0f }; + float _flexThetaRunningAverageRight { 0.0f }; + float _ulnarRadialThetaRunningAverageRight { 0.0f }; + float _lastTheta { 0.0f }; + AnimContext _lastContext; AnimVariantMap _lastAnimVars; From 40196dcb409d26620183f928251713805353c913 Mon Sep 17 00:00:00 2001 From: amantley Date: Thu, 14 Feb 2019 18:17:31 -0800 Subject: [PATCH 094/474] wrist action works in rig now --- libraries/animation/src/AnimPoleVectorConstraint.cpp | 4 ++-- libraries/animation/src/Rig.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/animation/src/AnimPoleVectorConstraint.cpp b/libraries/animation/src/AnimPoleVectorConstraint.cpp index fa78793dd2..68e561e187 100644 --- a/libraries/animation/src/AnimPoleVectorConstraint.cpp +++ b/libraries/animation/src/AnimPoleVectorConstraint.cpp @@ -285,7 +285,7 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim qCDebug(animation) << " anim pole vector theta from rig right" << thetaFromRig; fred = thetaFromRig; } - + /* glm::quat relativeHandRotation = (midPose.inverse() * tipPose).rot(); relativeHandRotation = glm::normalize(relativeHandRotation); @@ -466,7 +466,7 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim } else { fred += twistCorrection; } - + */ _lastTheta = 0.5f * _lastTheta + 0.5f * fred; //qCDebug(animation) << "twist correction: " << twistCorrection << " flex correction: " << flexCorrection << " ulnar correction " << ulnarCorrection; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index e476c8e5fd..3c72721816 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -2028,9 +2028,9 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s //qCDebug(animation) << "theta in rig " << left << " isLeft " << theta; //return theta; if (left) { - _animVars.set("thetaLeft", halfTheta); + _animVars.set("thetaLeft", theta); } else { - _animVars.set("thetaRight", halfTheta); + _animVars.set("thetaRight", theta); } // convert theta back to pole vector float lastDot = cosf(((180.0f - theta) / 180.0f)*PI); From f8554d10a806f2c9772e949112809a599cd244c4 Mon Sep 17 00:00:00 2001 From: Angus Antley Date: Thu, 14 Feb 2019 23:45:45 -0800 Subject: [PATCH 095/474] starting cleanup --- .../src/AnimPoleVectorConstraint.cpp | 262 +----------------- .../animation/src/AnimPoleVectorConstraint.h | 3 - libraries/animation/src/Rig.cpp | 184 +++++------- 3 files changed, 80 insertions(+), 369 deletions(-) diff --git a/libraries/animation/src/AnimPoleVectorConstraint.cpp b/libraries/animation/src/AnimPoleVectorConstraint.cpp index 68e561e187..ae4496e8e9 100644 --- a/libraries/animation/src/AnimPoleVectorConstraint.cpp +++ b/libraries/animation/src/AnimPoleVectorConstraint.cpp @@ -34,69 +34,6 @@ AnimPoleVectorConstraint::~AnimPoleVectorConstraint() { } -static float correctElbowForHandFlexionExtension(const AnimPose& hand, const AnimPose& lowerArm) { - - // first calculate the ulnar/radial deviation - // use the lower arm x-axis and the hand x-axis - glm::vec3 xAxisLowerArm = lowerArm.rot() * glm::vec3(1.0f, 0.0f, 0.0f); - glm::vec3 yAxisLowerArm = lowerArm.rot() * glm::vec3(0.0f, 1.0f, 0.0f); - glm::vec3 zAxisLowerArm = lowerArm.rot() * glm::vec3(0.0f, 0.0f, 1.0f); - glm::vec3 xAxisHand = hand.rot() * glm::vec3(1.0f, 0.0f, 0.0f); - glm::vec3 yAxisHand = hand.rot() * glm::vec3(0.0f, 1.0f, 0.0f); - - //float ulnarRadialDeviation = atan2(glm::dot(xAxisHand, xAxisLowerArm), glm::dot(xAxisHand, yAxisLowerArm)); - float flexionExtension = atan2(glm::dot(yAxisHand, zAxisLowerArm), glm::dot(yAxisHand, yAxisLowerArm)); - - //qCDebug(animation) << "flexion angle " << flexionExtension; - - - float deltaInDegrees = (flexionExtension / PI) * 180.0f; - - //qCDebug(animation) << "delta in degrees " << deltaInDegrees; - - float deltaFinal = glm::sign(deltaInDegrees) * powf(fabsf(deltaInDegrees/180.0f), 1.5f) * 180.0f * -0.3f; - return deltaInDegrees;// deltaFinal; -} - -static float correctElbowForHandUlnarRadialDeviation(const AnimPose& hand, const AnimPose& lowerArm) { - - const float DEAD_ZONE = 0.3f; - const float FILTER_EXPONENT = 2.0f; - // first calculate the ulnar/radial deviation - // use the lower arm x-axis and the hand x-axis - glm::vec3 xAxisLowerArm = lowerArm.rot() * glm::vec3(1.0f, 0.0f, 0.0f); - glm::vec3 yAxisLowerArm = lowerArm.rot() * glm::vec3(0.0f, 1.0f, 0.0f); - glm::vec3 zAxisLowerArm = lowerArm.rot() * glm::vec3(0.0f, 0.0f, 1.0f); - glm::vec3 xAxisHand = hand.rot() * glm::vec3(1.0f, 0.0f, 0.0f); - glm::vec3 yAxisHand = hand.rot() * glm::vec3(0.0f, 1.0f, 0.0f); - - float ulnarRadialDeviation = atan2(glm::dot(xAxisHand, xAxisLowerArm), glm::dot(xAxisHand, yAxisLowerArm)); - //float flexionExtension = atan2(glm::dot(yAxisHand, zAxisLowerArm), glm::dot(yAxisHand, yAxisLowerArm)); - - - - float makeForwardZeroRadians = ulnarRadialDeviation - (PI / 2.0f); - - //qCDebug(animation) << "calibrated ulnar " << makeForwardZeroRadians; - - float deltaFractionOfPi = (makeForwardZeroRadians / PI); - float deltaUlnarRadial; - if (fabsf(deltaFractionOfPi) < DEAD_ZONE) { - deltaUlnarRadial = 0.0f; - } else { - deltaUlnarRadial = (deltaFractionOfPi - glm::sign(deltaFractionOfPi) * DEAD_ZONE) / (1.0f - DEAD_ZONE); - } - - float deltaUlnarRadialDegrees = glm::sign(deltaUlnarRadial) * powf(fabsf(deltaUlnarRadial), FILTER_EXPONENT) * 180.0f; - - - - //qCDebug(animation) << "ulnar delta in degrees " << deltaUlnarRadialDegrees; - - float deltaFinal = deltaUlnarRadialDegrees; - return deltaFractionOfPi * 180.0f; // deltaFinal; -} - float AnimPoleVectorConstraint::findThetaNewWay(const glm::vec3& hand, const glm::vec3& shoulder, bool left) const { // get the default poses for the upper and lower arm // then use this length to judge how far the hand is away from the shoulder. @@ -184,8 +121,8 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim glm::vec3 poleVector = animVars.lookupRigToGeometryVector(_poleVectorVar, Vectors::UNIT_Z); if (_skeleton->nameToJointIndex("LeftHand") == _tipJointIndex) { - float thetaFromRig = animVars.lookup("thetaRight", 0.0f); - qCDebug(animation) << " anim pole vector theta from rig " << thetaFromRig; + //float thetaFromRig = animVars.lookup("thetaRight", 0.0f); + //qCDebug(animation) << " anim pole vector theta from rig " << thetaFromRig; } // determine if we should interpolate @@ -227,6 +164,11 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim glm::vec3 refVector = midPose.xformVectorFast(_referenceVector); float refVectorLength = glm::length(refVector); + if (_skeleton->nameToJointIndex("LeftHand") == _tipJointIndex) { + //qCDebug(animation) << "mid pose anim " << midPose; + //qCDebug(animation) << "ref vector anim " << refVector; + } + glm::vec3 axis = basePose.trans() - tipPose.trans(); float axisLength = glm::length(axis); glm::vec3 unitAxis = axis / axisLength; @@ -247,7 +189,7 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim if (refVectorLength > MIN_LENGTH && sideVectorLength > MIN_LENGTH) { poleVector = lastDot * (refVectorProj / refVectorProjLength) + glm::sign(_lastTheta) * lastSideDot * (sideVector / sideVectorLength); if (_skeleton->nameToJointIndex("LeftHand") == _tipJointIndex) { - qCDebug(animation) << " anim pole vector computed: " << poleVector; + //qCDebug(animation) << "pole vector anim: " << poleVector; } } else { poleVector = glm::vec3(1.0f, 0.0f, 0.0f); @@ -277,196 +219,15 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim //fred = findThetaNewWay(tipPose.trans(), basePose.trans(), isLeft); if (isLeft) { float thetaFromRig = animVars.lookup("thetaLeft", 0.0f); - qCDebug(animation) << " anim pole vector theta from rig left" << thetaFromRig; + //qCDebug(animation) << " anim pole vector theta from rig left" << thetaFromRig; fred = thetaFromRig; } else { float thetaFromRig = animVars.lookup("thetaRight", 0.0f); - qCDebug(animation) << " anim pole vector theta from rig right" << thetaFromRig; + //qCDebug(animation) << " anim pole vector theta from rig right" << thetaFromRig; fred = thetaFromRig; } - /* - glm::quat relativeHandRotation = (midPose.inverse() * tipPose).rot(); - - relativeHandRotation = glm::normalize(relativeHandRotation); - if (relativeHandRotation.w < 0.0f) { - relativeHandRotation.x *= -1.0f; - relativeHandRotation.y *= -1.0f; - relativeHandRotation.z *= -1.0f; - relativeHandRotation.w *= -1.0f; - } - - glm::quat twist; - glm::quat ulnarDeviation; - //swingTwistDecomposition(twistUlnarSwing, Vectors::UNIT_Z, twist, ulnarDeviation); - swingTwistDecomposition(relativeHandRotation, Vectors::UNIT_Z, twist, ulnarDeviation); - - ulnarDeviation = glm::normalize(ulnarDeviation); - if (ulnarDeviation.w < 0.0f) { - ulnarDeviation.x *= -1.0f; - ulnarDeviation.y *= -1.0f; - ulnarDeviation.z *= -1.0f; - ulnarDeviation.w *= -1.0f; - } - //glm::vec3 twistAxis = glm::axis(twist); - glm::vec3 ulnarAxis = glm::axis(ulnarDeviation); - //float twistTheta = glm::sign(twistAxis[1]) * glm::angle(twist); - float ulnarDeviationTheta = glm::sign(ulnarAxis[2]) * glm::angle(ulnarDeviation); - if (glm::sign(ulnarDeviationTheta) != glm::sign(_ulnarRadialThetaRunningAverage) && fabsf(ulnarDeviationTheta) >(5.0f * PI) / 6.0f) { - // don't allow the theta to cross the 180 degree limit. - ulnarDeviationTheta = -1.0f * ulnarDeviationTheta; - } - _ulnarRadialThetaRunningAverage = 0.5f * _ulnarRadialThetaRunningAverage + 0.5f * ulnarDeviationTheta; - - //get the swingTwist of the hand to lower arm - glm::quat flex; - glm::quat twistUlnarSwing; - - swingTwistDecomposition(relativeHandRotation, Vectors::UNIT_X, twistUlnarSwing, flex); - - flex = glm::normalize(flex); - if (flex.w < 0.0f) { - flex.x *= -1.0f; - flex.y *= -1.0f; - flex.z *= -1.0f; - flex.w *= -1.0f; - } - - glm::vec3 flexAxis = glm::axis(flex); - - //float swingTheta = glm::angle(twistUlnarSwing); - float flexTheta = glm::sign(flexAxis[0]) * glm::angle(flex); - if (glm::sign(flexTheta) != glm::sign(_flexThetaRunningAverage) && fabsf(flexTheta) > (5.0f * PI) / 6.0f) { - // don't allow the theta to cross the 180 degree limit. - flexTheta = -1.0f * flexTheta; - } - _flexThetaRunningAverage = 0.5f * _flexThetaRunningAverage + 0.5f * flexTheta; - - - glm::quat trueTwist; - glm::quat nonTwist; - swingTwistDecomposition(relativeHandRotation, Vectors::UNIT_Y, nonTwist, trueTwist); - trueTwist = glm::normalize(trueTwist); - if (trueTwist.w < 0.0f) { - trueTwist.x *= -1.0f; - trueTwist.y *= -1.0f; - trueTwist.z *= -1.0f; - trueTwist.w *= -1.0f; - } - glm::vec3 trueTwistAxis = glm::axis(trueTwist); - float trueTwistTheta; - trueTwistTheta = glm::sign(trueTwistAxis[1]) * glm::angle(trueTwist); - if (glm::sign(trueTwistTheta) != glm::sign(_twistThetaRunningAverage) && fabsf(trueTwistTheta) >(5.0f * PI) / 6.0f) { - // don't allow the theta to cross the 180 degree limit. - trueTwistTheta = -1.0f * trueTwistTheta; - } - - _twistThetaRunningAverage = 0.5f * _twistThetaRunningAverage + 0.5f * trueTwistTheta; - - - if (!isLeft) { - //qCDebug(animation) << "flex ave: " << (_flexThetaRunningAverage / PI) * 180.0f << " twist ave: " << (_twistThetaRunningAverage / PI) * 180.0f << " ulnar deviation ave: " << (_ulnarRadialThetaRunningAverage / PI) * 180.0f; - //qCDebug(animation) << "flex: " << (flexTheta / PI) * 180.0f << " twist: " << (trueTwistTheta / PI) * 180.0f << " ulnar deviation: " << (ulnarDeviationTheta / PI) * 180.0f; - - } - - // here is where we would do the wrist correction. - float deltaTheta = correctElbowForHandFlexionExtension(tipPose, midPose); - float deltaThetaUlnar; - if (!isLeft) { - deltaThetaUlnar = correctElbowForHandUlnarRadialDeviation(tipPose, midPose); - } - if (isLeft) { - // fred *= -1.0f; - } - - // make the dead zone PI/6.0 - - const float POWER = 2.0f; - const float FLEX_BOUNDARY = PI / 4.0f; - const float EXTEND_BOUNDARY = -PI / 5.0f; - float flexCorrection = 0.0f; - if (isLeft) { - if (_flexThetaRunningAverage > FLEX_BOUNDARY) { - flexCorrection = ((_flexThetaRunningAverage - FLEX_BOUNDARY) / PI) * 180.0f; - } else if (_flexThetaRunningAverage < EXTEND_BOUNDARY) { - flexCorrection = ((_flexThetaRunningAverage - EXTEND_BOUNDARY) / PI) * 180.0f; - } - if (fabs(flexCorrection) > 30.0f) { - flexCorrection = glm::sign(flexCorrection) * 30.0f; - } - fred += flexCorrection; - } else { - if (_flexThetaRunningAverage > FLEX_BOUNDARY) { - flexCorrection = ((_flexThetaRunningAverage - FLEX_BOUNDARY) / PI) * 180.0f; - } else if (_flexThetaRunningAverage < EXTEND_BOUNDARY) { - flexCorrection = ((_flexThetaRunningAverage - EXTEND_BOUNDARY) / PI) * 180.0f; - } - if (fabs(flexCorrection) > 30.0f) { - flexCorrection = glm::sign(flexCorrection) * 30.0f; - } - fred -= flexCorrection; - } - //qCDebug(animation) << "flexCorrection anim" << flexCorrection; - - const float TWIST_ULNAR_DEADZONE = 0.0f; - const float ULNAR_BOUNDARY_MINUS = -PI / 12.0f; - const float ULNAR_BOUNDARY_PLUS = PI / 24.0f; - float ulnarDiff = 0.0f; - float ulnarCorrection = 0.0f; - if (_ulnarRadialThetaRunningAverage > ULNAR_BOUNDARY_PLUS) { - ulnarDiff = _ulnarRadialThetaRunningAverage - ULNAR_BOUNDARY_PLUS; - } else if (_ulnarRadialThetaRunningAverage < ULNAR_BOUNDARY_MINUS) { - ulnarDiff = _ulnarRadialThetaRunningAverage - ULNAR_BOUNDARY_MINUS; - } - if(fabs(ulnarDiff) > 0.0f){ - if (fabs(_twistThetaRunningAverage) > TWIST_ULNAR_DEADZONE) { - float twistCoefficient = (fabs(_twistThetaRunningAverage) / (PI / 20.0f)); - if (twistCoefficient > 1.0f) { - twistCoefficient = 1.0f; - } - - if (isLeft) { - if (trueTwistTheta < 0.0f) { - ulnarCorrection -= glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 90.0f * twistCoefficient; - } else { - ulnarCorrection += glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 90.0f * twistCoefficient; - } - } else { - // right hand - if (trueTwistTheta > 0.0f) { - ulnarCorrection -= glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 90.0f * twistCoefficient; - } else { - ulnarCorrection += glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 90.0f * twistCoefficient; - } - - } - if (fabsf(ulnarCorrection) > 20.0f) { - ulnarCorrection = glm::sign(ulnarCorrection) * 20.0f; - } - fred += ulnarCorrection; - } - } - //qCDebug(animation) << "ulnarCorrection anim" << ulnarCorrection; - - // remember direction of travel. - const float TWIST_DEADZONE = PI / 2.0f; - //if (!isLeft) { - float twistCorrection = 0.0f; - if (_twistThetaRunningAverage < -TWIST_DEADZONE) { - twistCorrection = glm::sign(_twistThetaRunningAverage) * ((fabsf(_twistThetaRunningAverage) - TWIST_DEADZONE) / PI) * 60.0f; - } else { - if (_twistThetaRunningAverage > TWIST_DEADZONE) { - twistCorrection = glm::sign(_twistThetaRunningAverage) * ((fabsf(_twistThetaRunningAverage) - TWIST_DEADZONE) / PI) * 60.0f; - } - } - if (fabsf(twistCorrection) > 30.0f) { - fred += glm::sign(twistCorrection) * 30.0f; - } else { - fred += twistCorrection; - } - */ _lastTheta = 0.5f * _lastTheta + 0.5f * fred; //qCDebug(animation) << "twist correction: " << twistCorrection << " flex correction: " << flexCorrection << " ulnar correction " << ulnarCorrection; @@ -482,12 +243,9 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim float poleVectorTheta = theta; theta = ((180.0f - _lastTheta) / 180.0f)*PI; - } - - //glm::quat deltaRot = glm::angleAxis(theta, unitAxis); glm::quat deltaRot = glm::angleAxis(theta, unitAxis); diff --git a/libraries/animation/src/AnimPoleVectorConstraint.h b/libraries/animation/src/AnimPoleVectorConstraint.h index 078676d4f9..d0c80a393b 100644 --- a/libraries/animation/src/AnimPoleVectorConstraint.h +++ b/libraries/animation/src/AnimPoleVectorConstraint.h @@ -66,9 +66,6 @@ protected: float _interpAlphaVel { 0.0f }; float _interpAlpha { 0.0f }; - float _twistThetaRunningAverage { 0.0f }; - float _flexThetaRunningAverage { 0.0f }; - float _ulnarRadialThetaRunningAverage { 0.0f }; float _lastTheta { 0.0f }; AnimChain _snapshotChain; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 3c72721816..3ca1411ec0 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1695,7 +1695,7 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s // make the angle less when z is small. // lower y with x center lower angle // lower y with x out higher angle - //AnimPose oppositeArmPose = _externalPoseSet._absolutePoses[oppositeArmIndex]; + glm::vec3 referenceVector; if (left) { referenceVector = Vectors::UNIT_X; @@ -1707,40 +1707,38 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s AnimPose shoulderPose = _externalPoseSet._absolutePoses[shoulderIndex]; AnimPose elbowPose = _externalPoseSet._absolutePoses[elbowIndex]; - //qCDebug(animation) << "handPose Rig " << left << "isleft" << handPose; - AnimPose absoluteShoulderPose = getAbsoluteDefaultPose(shoulderIndex); AnimPose absoluteHandPose = getAbsoluteDefaultPose(handIndex); - // subtract 10 centimeters from the arm length for some reason actual arm position is clamped to length - 10cm. float defaultArmLength = glm::length(absoluteHandPose.trans() - absoluteShoulderPose.trans()); // calculate the reference axis and the side axis. // Look up refVector from animVars, make sure to convert into geom space. - glm::vec3 refVector = glmExtractRotation(_rigToGeometryTransform) * elbowPose.rot() * Vectors::UNIT_X; + glm::vec3 refVector = (AnimPose(_rigToGeometryTransform) * elbowPose).xformVectorFast(Vectors::UNIT_X); float refVectorLength = glm::length(refVector); - glm::vec3 unitRef = refVector / refVectorLength; - glm::vec3 axis = shoulderPose.trans() - handPose.trans(); + if (left) { + //AnimPose temp(_rigToGeometryTransform); + //glm::mat4 elbowMat(elbowPose); + //AnimPose result3(_rigToGeometryTransform * elbowMat); + //AnimPose geomElbow2 = temp * elbowPose; + //qCDebug(animation) << "mid pose geom2 rig" << geomElbow2; + //qCDebug(animation) << "mid pose result rig" << result3; + //qCDebug(animation) << "ref vector rig" << refVector; + } + + AnimPose geomShoulder = AnimPose(_rigToGeometryTransform) * shoulderPose; + AnimPose geomHand = AnimPose(_rigToGeometryTransform) * handPose; + glm::vec3 axis = geomShoulder.trans() - geomHand.trans(); float axisLength = glm::length(axis); glm::vec3 unitAxis = axis / axisLength; - glm::vec3 sideVector = glm::cross(unitAxis, unitRef); + glm::vec3 sideVector = glm::cross(unitAxis, refVector); float sideVectorLength = glm::length(sideVector); // project refVector onto axis plane - glm::vec3 refVectorProj = unitRef - glm::dot(unitRef, unitAxis) * unitAxis; + glm::vec3 refVectorProj = refVector - glm::dot(refVector, unitAxis) * unitAxis; float refVectorProjLength = glm::length(refVectorProj); - if (left) { - - //qCDebug(animation) << "rig ref proj " << refVectorProj/refVectorProjLength; // "rig reference vector: " << refVector / refVectorLength; - } - - //qCDebug(animation) << "rig reference vector projected: " << refVectorProj << " left is " << left; - - - // qCDebug(animation) << "default arm length " << defaultArmLength; - // phi_0 is the lowest angle we can have const float phi_0 = 15.0f; const float zStart = 0.6f; @@ -1750,28 +1748,25 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s const glm::vec3 biases(0.0f, 135.0f, 0.0f); // weights const float zWeightBottom = -100.0f; - //const glm::vec3 weights(-30.0f, 30.0f, 210.0f); const glm::vec3 weights(-50.0f, 60.0f, 260.0f); glm::vec3 armToHand = handPose.trans() - shoulderPose.trans(); - // qCDebug(animation) << "current arm length " << glm::length(armToHand); - float initial_valuesY = (fabsf(armToHand[1] / defaultArmLength) * weights[1]) + biases[1]; - float initial_valuesZ; + float yFactor = (fabsf(armToHand[1] / defaultArmLength) * weights[1]) + biases[1]; + + float zFactor; if (armToHand[1] > 0.0f) { - initial_valuesZ = weights[2] * glm::max(zStart - (armToHand[2] / defaultArmLength), 0.0f) * fabs(armToHand[1] / defaultArmLength); + zFactor = weights[2] * glm::max(zStart - (armToHand[2] / defaultArmLength), 0.0f) * fabs(armToHand[1] / defaultArmLength); } else { - initial_valuesZ = zWeightBottom * glm::max(zStart - (armToHand[2] / defaultArmLength), 0.0f) * fabs(armToHand[1] / defaultArmLength); + zFactor = zWeightBottom * glm::max(zStart - (armToHand[2] / defaultArmLength), 0.0f) * fabs(armToHand[1] / defaultArmLength); } - //1.0f + armToHand[1]/defaultArmLength - - float initial_valuesX; + float xFactor; if (left) { - initial_valuesX = weights[0] * glm::max(-1.0f * (armToHand[0] / defaultArmLength) + xStart, 0.0f); + xFactor = weights[0] * glm::max(-1.0f * (armToHand[0] / defaultArmLength) + xStart, 0.0f); } else { - initial_valuesX = weights[0] * (((armToHand[0] / defaultArmLength) + xStart) - (0.3f) * ((1.0f + (armToHand[1] / defaultArmLength)) / 2.0f)); + xFactor = weights[0] * (((armToHand[0] / defaultArmLength) + xStart) - (0.3f) * ((1.0f + (armToHand[1] / defaultArmLength)) / 2.0f)); } - float theta = initial_valuesX + initial_valuesY + initial_valuesZ; + float theta = xFactor + yFactor + zFactor; if (theta < 13.0f) { theta = 13.0f; @@ -1784,117 +1779,93 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s theta *= -1.0f; } - float halfTheta = theta; - + // now we calculate the contribution of the hand glm::quat relativeHandRotation = (elbowPose.inverse() * handPose).rot(); - - relativeHandRotation = glm::normalize(relativeHandRotation); if (relativeHandRotation.w < 0.0f) { - relativeHandRotation.x *= -1.0f; - relativeHandRotation.y *= -1.0f; - relativeHandRotation.z *= -1.0f; - relativeHandRotation.w *= -1.0f; + relativeHandRotation *= -1.0f; } - glm::quat twist; glm::quat ulnarDeviation; - //swingTwistDecomposition(twistUlnarSwing, Vectors::UNIT_Z, twist, ulnarDeviation); - swingTwistDecomposition(relativeHandRotation, Vectors::UNIT_Z, twist, ulnarDeviation); - - ulnarDeviation = glm::normalize(ulnarDeviation); + glm::quat nonUlnarDeviation; + swingTwistDecomposition(relativeHandRotation, Vectors::UNIT_Z, nonUlnarDeviation, ulnarDeviation); if (ulnarDeviation.w < 0.0f) { - ulnarDeviation.x *= -1.0f; - ulnarDeviation.y *= -1.0f; - ulnarDeviation.z *= -1.0f; - ulnarDeviation.w *= -1.0f; + ulnarDeviation *= 1.0f; } - glm::vec3 ulnarAxis = glm::axis(ulnarDeviation); float ulnarDeviationTheta = glm::sign(ulnarAxis[2]) * glm::angle(ulnarDeviation); if (left) { - if (glm::sign(ulnarDeviationTheta) != glm::sign(_ulnarRadialThetaRunningAverageLeft) && fabsf(ulnarDeviationTheta) > (5.0f * PI) / 6.0f) { + if (glm::sign(ulnarDeviationTheta) != glm::sign(_ulnarRadialThetaRunningAverageLeft) && fabsf(ulnarDeviationTheta) > (PI / 2.0f)) { // don't allow the theta to cross the 180 degree limit. ulnarDeviationTheta = -1.0f * ulnarDeviationTheta; } + // put some smoothing on the theta _ulnarRadialThetaRunningAverageLeft = 0.5f * _ulnarRadialThetaRunningAverageLeft + 0.5f * ulnarDeviationTheta; } else { - if (glm::sign(ulnarDeviationTheta) != glm::sign(_ulnarRadialThetaRunningAverageRight) && fabsf(ulnarDeviationTheta) > (5.0f * PI) / 6.0f) { + if (glm::sign(ulnarDeviationTheta) != glm::sign(_ulnarRadialThetaRunningAverageRight) && fabsf(ulnarDeviationTheta) > (PI / 2.0f)) { // don't allow the theta to cross the 180 degree limit. ulnarDeviationTheta = -1.0f * ulnarDeviationTheta; } + // put some smoothing on the theta _ulnarRadialThetaRunningAverageRight = 0.5f * _ulnarRadialThetaRunningAverageRight + 0.5f * ulnarDeviationTheta; - } - //get the swingTwist of the hand to lower arm + + //get the flex/extension of the wrist rotation glm::quat flex; - glm::quat twistUlnarSwing; - swingTwistDecomposition(relativeHandRotation, Vectors::UNIT_X, twistUlnarSwing, flex); - flex = glm::normalize(flex); + glm::quat nonFlex; + swingTwistDecomposition(relativeHandRotation, Vectors::UNIT_X, nonFlex, flex); if (flex.w < 0.0f) { - flex.x *= -1.0f; - flex.y *= -1.0f; - flex.z *= -1.0f; - flex.w *= -1.0f; + flex *= 1.0f; } - glm::vec3 flexAxis = glm::axis(flex); - - //float swingTheta = glm::angle(twistUlnarSwing); float flexTheta = glm::sign(flexAxis[0]) * glm::angle(flex); + if (left) { - if (glm::sign(flexTheta) != glm::sign(_flexThetaRunningAverageLeft) && fabsf(flexTheta) > (5.0f * PI) / 6.0f) { + if (glm::sign(flexTheta) != glm::sign(_flexThetaRunningAverageLeft) && fabsf(flexTheta) > (PI / 2.0f)) { // don't allow the theta to cross the 180 degree limit. flexTheta = -1.0f * flexTheta; } + // put some smoothing on the theta _flexThetaRunningAverageLeft = 0.5f * _flexThetaRunningAverageLeft + 0.5f * flexTheta; } else { - if (glm::sign(flexTheta) != glm::sign(_flexThetaRunningAverageRight) && fabsf(flexTheta) > (5.0f * PI) / 6.0f) { + if (glm::sign(flexTheta) != glm::sign(_flexThetaRunningAverageRight) && fabsf(flexTheta) > (PI / 2.0f)) { // don't allow the theta to cross the 180 degree limit. flexTheta = -1.0f * flexTheta; } + // put some smoothing on the theta _flexThetaRunningAverageRight = 0.5f * _flexThetaRunningAverageRight + 0.5f * flexTheta; - } - - glm::quat trueTwist; + glm::quat twist; glm::quat nonTwist; - swingTwistDecomposition(relativeHandRotation, Vectors::UNIT_Y, nonTwist, trueTwist); - trueTwist = glm::normalize(trueTwist); - if (trueTwist.w < 0.0f) { - trueTwist.x *= -1.0f; - trueTwist.y *= -1.0f; - trueTwist.z *= -1.0f; - trueTwist.w *= -1.0f; + swingTwistDecomposition(relativeHandRotation, Vectors::UNIT_Y, nonTwist, twist); + if (twist.w < 0.0f) { + twist *= 1.0f; } - glm::vec3 trueTwistAxis = glm::axis(trueTwist); - float trueTwistTheta; - trueTwistTheta = glm::sign(trueTwistAxis[1]) * glm::angle(trueTwist); + glm::vec3 trueTwistAxis = glm::axis(twist); + float trueTwistTheta = glm::sign(trueTwistAxis[1]) * glm::angle(twist); if (left) { - if (glm::sign(trueTwistTheta) != glm::sign(_twistThetaRunningAverageLeft) && fabsf(trueTwistTheta) > (5.0f * PI) / 6.0f) { + if (glm::sign(trueTwistTheta) != glm::sign(_twistThetaRunningAverageLeft) && fabsf(trueTwistTheta) > (PI / 2.0f)) { // don't allow the theta to cross the 180 degree limit. trueTwistTheta = -1.0f * trueTwistTheta; } + // put some smoothing on the theta _twistThetaRunningAverageLeft = 0.5f * _twistThetaRunningAverageLeft + 0.5f * trueTwistTheta; } else { - if (glm::sign(trueTwistTheta) != glm::sign(_twistThetaRunningAverageRight) && fabsf(trueTwistTheta) > (5.0f * PI) / 6.0f) { + if (glm::sign(trueTwistTheta) != glm::sign(_twistThetaRunningAverageRight) && fabsf(trueTwistTheta) > (PI / 2.0f)) { // don't allow the theta to cross the 180 degree limit. trueTwistTheta = -1.0f * trueTwistTheta; } + // put some smoothing on the theta _twistThetaRunningAverageRight = 0.5f * _twistThetaRunningAverageRight + 0.5f * trueTwistTheta; - } if (!left) { qCDebug(animation) << "flex ave: " << (_flexThetaRunningAverageRight / PI) * 180.0f << " twist ave: " << (_twistThetaRunningAverageRight / PI) * 180.0f << " ulnar deviation ave: " << (_ulnarRadialThetaRunningAverageRight / PI) * 180.0f; - //qCDebug(animation) << "flex: " << (flexTheta / PI) * 180.0f << " twist: " << (trueTwistTheta / PI) * 180.0f << " ulnar deviation: " << (ulnarDeviationTheta / PI) * 180.0f; - } - - // make the dead zone PI/6.0 + const float POWER = 2.0f; - const float FLEX_BOUNDARY = PI / 4.0f; - const float EXTEND_BOUNDARY = -PI / 5.0f; + const float FLEX_BOUNDARY = PI / 5.0f; + const float EXTEND_BOUNDARY = -PI / 6.0f; float flexCorrection = 0.0f; if (left) { if (_flexThetaRunningAverageLeft > FLEX_BOUNDARY) { @@ -1917,7 +1888,7 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s } theta -= flexCorrection; } - //qCDebug(animation) << "flexCorrection rig" << flexCorrection; + qCDebug(animation) << "flexCorrection rig" << flexCorrection; const float TWIST_ULNAR_DEADZONE = 0.0f; const float ULNAR_BOUNDARY_MINUS = -PI / 12.0f; @@ -1936,7 +1907,6 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s if (twistCoefficient > 1.0f) { twistCoefficient = 1.0f; } - if (left) { if (trueTwistTheta < 0.0f) { ulnarCorrection -= glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 90.0f * twistCoefficient; @@ -1950,7 +1920,6 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s } else { ulnarCorrection += glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 90.0f * twistCoefficient; } - } if (fabsf(ulnarCorrection) > 20.0f) { ulnarCorrection = glm::sign(ulnarCorrection) * 20.0f; @@ -1970,7 +1939,6 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s if (twistCoefficient > 1.0f) { twistCoefficient = 1.0f; } - if (left) { if (trueTwistTheta < 0.0f) { ulnarCorrection -= glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 90.0f * twistCoefficient; @@ -1984,7 +1952,6 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s } else { ulnarCorrection += glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 90.0f * twistCoefficient; } - } if (fabsf(ulnarCorrection) > 20.0f) { ulnarCorrection = glm::sign(ulnarCorrection) * 20.0f; @@ -1992,41 +1959,31 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s theta += ulnarCorrection; } } - } - // qCDebug(animation) << "ulnarCorrection rig" << ulnarCorrection; + qCDebug(animation) << "ulnarCorrection rig" << ulnarCorrection; // remember direction of travel. - const float TWIST_DEADZONE = PI / 2.0f; + const float TWIST_DEADZONE = (4 * PI) / 9.0f; //if (!isLeft) { float twistCorrection = 0.0f; if (left) { - if (_twistThetaRunningAverageLeft < -TWIST_DEADZONE) { - twistCorrection = glm::sign(_twistThetaRunningAverageLeft) * ((fabsf(_twistThetaRunningAverageLeft) - TWIST_DEADZONE) / PI) * 60.0f; - } else { - if (_twistThetaRunningAverageLeft > TWIST_DEADZONE) { - twistCorrection = glm::sign(_twistThetaRunningAverageLeft) * ((fabsf(_twistThetaRunningAverageLeft) - TWIST_DEADZONE) / PI) * 60.0f; - } - } + if (fabsf(_twistThetaRunningAverageLeft) > TWIST_DEADZONE) { + twistCorrection = glm::sign(_twistThetaRunningAverageLeft) * ((fabsf(_twistThetaRunningAverageLeft) - TWIST_DEADZONE) / PI) * 80.0f; + } } else { - if (_twistThetaRunningAverageRight < -TWIST_DEADZONE) { - twistCorrection = glm::sign(_twistThetaRunningAverageRight) * ((fabsf(_twistThetaRunningAverageRight) - TWIST_DEADZONE) / PI) * 60.0f; - } else { - if (_twistThetaRunningAverageRight > TWIST_DEADZONE) { - twistCorrection = glm::sign(_twistThetaRunningAverageRight) * ((fabsf(_twistThetaRunningAverageRight) - TWIST_DEADZONE) / PI) * 60.0f; - } - } - + if (fabsf(_twistThetaRunningAverageRight) > TWIST_DEADZONE) { + twistCorrection = glm::sign(_twistThetaRunningAverageRight) * ((fabsf(_twistThetaRunningAverageRight) - TWIST_DEADZONE) / PI) * 80.0f; + } } if (fabsf(twistCorrection) > 30.0f) { theta += glm::sign(twistCorrection) * 30.0f; } else { theta += twistCorrection; } - //qCDebug(animation) << "twistCorrection rig" << twistCorrection; + qCDebug(animation) << "twistCorrection rig" << twistCorrection; + + // put final global limiter here....... - //qCDebug(animation) << "theta in rig " << left << " isLeft " << theta; - //return theta; if (left) { _animVars.set("thetaLeft", theta); } else { @@ -2040,7 +1997,6 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s if (refVectorLength > MIN_LENGTH && sideVectorLength > MIN_LENGTH) { poleVector = lastDot * (refVectorProj / refVectorProjLength) + glm::sign(theta) * lastSideDot * (sideVector / sideVectorLength); if (left) { - //qCDebug(animation) << "pole vector in rig " << poleVector; } // From 382a03929e77ef1d5ce8ff9ef60e601f1007ee64 Mon Sep 17 00:00:00 2001 From: Angus Antley Date: Fri, 15 Feb 2019 07:06:43 -0800 Subject: [PATCH 096/474] changed back to regular 2 bone Ik for arms in json and cleaned up rig.cpp and polevector constraint --- .../avatar-animation_withSplineIKNode.json | 4 +- .../src/AnimPoleVectorConstraint.cpp | 124 +----------------- libraries/animation/src/Rig.cpp | 48 +++++-- libraries/animation/src/Rig.h | 3 +- 4 files changed, 46 insertions(+), 133 deletions(-) diff --git a/interface/resources/avatar/avatar-animation_withSplineIKNode.json b/interface/resources/avatar/avatar-animation_withSplineIKNode.json index 9fd64860bd..b1f198c52c 100644 --- a/interface/resources/avatar/avatar-animation_withSplineIKNode.json +++ b/interface/resources/avatar/avatar-animation_withSplineIKNode.json @@ -129,7 +129,7 @@ "children": [ { "id": "rightHandIK", - "type": "armIK", + "type": "twoBoneIK", "data": { "alpha": 1.0, "enabled": false, @@ -159,7 +159,7 @@ "children": [ { "id": "leftHandIK", - "type": "armIK", + "type": "twoBoneIK", "data": { "alpha": 1.0, "enabled": false, diff --git a/libraries/animation/src/AnimPoleVectorConstraint.cpp b/libraries/animation/src/AnimPoleVectorConstraint.cpp index ae4496e8e9..d575ddabe4 100644 --- a/libraries/animation/src/AnimPoleVectorConstraint.cpp +++ b/libraries/animation/src/AnimPoleVectorConstraint.cpp @@ -34,64 +34,6 @@ AnimPoleVectorConstraint::~AnimPoleVectorConstraint() { } -float AnimPoleVectorConstraint::findThetaNewWay(const glm::vec3& hand, const glm::vec3& shoulder, bool left) const { - // get the default poses for the upper and lower arm - // then use this length to judge how far the hand is away from the shoulder. - // then create weights that make the elbow angle less when the x value is large in either direction. - // make the angle less when z is small. - // lower y with x center lower angle - // lower y with x out higher angle - AnimPose shoulderPose = _skeleton->getAbsoluteDefaultPose(_skeleton->nameToJointIndex("RightShoulder")); - AnimPose handPose = _skeleton->getAbsoluteDefaultPose(_skeleton->nameToJointIndex("RightHand")); - // subtract 10 centimeters from the arm length for some reason actual arm position is clamped to length - 10cm. - float defaultArmLength = glm::length( handPose.trans() - shoulderPose.trans() ) - 10.0f; - // qCDebug(animation) << "default arm length " << defaultArmLength; - - // phi_0 is the lowest angle we can have - const float phi_0 = 15.0f; - const float zStart = 0.6f; - const float xStart = 0.1f; - // biases - //const glm::vec3 biases(30.0f, 120.0f, -30.0f); - const glm::vec3 biases(0.0f, 135.0f, 0.0f); - // weights - const float zWeightBottom = -100.0f; - //const glm::vec3 weights(-30.0f, 30.0f, 210.0f); - const glm::vec3 weights(-50.0f, 60.0f, 260.0f); - glm::vec3 armToHand = hand - shoulder; - // qCDebug(animation) << "current arm length " << glm::length(armToHand); - float initial_valuesY = (fabsf(armToHand[1] / defaultArmLength) * weights[1]) + biases[1]; - float initial_valuesZ; - if (armToHand[1] > 0.0f) { - initial_valuesZ = weights[2] * glm::max(zStart - (armToHand[2] / defaultArmLength), 0.0f) * fabs(armToHand[1] / defaultArmLength); - } else { - initial_valuesZ = zWeightBottom * glm::max(zStart - (armToHand[2] / defaultArmLength), 0.0f) * fabs(armToHand[1] / defaultArmLength); - } - - //1.0f + armToHand[1]/defaultArmLength - - float initial_valuesX; - if (left) { - initial_valuesX = weights[0] * glm::max(-1.0f * (armToHand[0] / defaultArmLength) + xStart, 0.0f); - } else { - initial_valuesX = weights[0] * (((armToHand[0] / defaultArmLength) + xStart) - (0.3f) * ((1.0f + (armToHand[1] / defaultArmLength))/2.0f)); - } - - float theta = initial_valuesX + initial_valuesY + initial_valuesZ; - - if (theta < 13.0f) { - theta = 13.0f; - } - if (theta > 175.0f) { - theta = 175.0f; - } - - if (!left) { - //qCDebug(animation) << "relative hand x " << initial_valuesX << " y " << initial_valuesY << " z " << initial_valuesZ << "theta value for pole vector is " << theta; - } - return theta; -} - const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { assert(_children.size() == 1); @@ -114,13 +56,9 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim _poses = underPoses; } - - - // Look up poleVector from animVars, make sure to convert into geom space. glm::vec3 poleVector = animVars.lookupRigToGeometryVector(_poleVectorVar, Vectors::UNIT_Z); if (_skeleton->nameToJointIndex("LeftHand") == _tipJointIndex) { - //float thetaFromRig = animVars.lookup("thetaRight", 0.0f); //qCDebug(animation) << " anim pole vector theta from rig " << thetaFromRig; } @@ -164,11 +102,6 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim glm::vec3 refVector = midPose.xformVectorFast(_referenceVector); float refVectorLength = glm::length(refVector); - if (_skeleton->nameToJointIndex("LeftHand") == _tipJointIndex) { - //qCDebug(animation) << "mid pose anim " << midPose; - //qCDebug(animation) << "ref vector anim " << refVector; - } - glm::vec3 axis = basePose.trans() - tipPose.trans(); float axisLength = glm::length(axis); glm::vec3 unitAxis = axis / axisLength; @@ -180,22 +113,6 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim glm::vec3 refVectorProj = refVector - glm::dot(refVector, unitAxis) * unitAxis; float refVectorProjLength = glm::length(refVectorProj); - float lastDot; - if ((_skeleton->nameToJointIndex("RightHand") == _tipJointIndex) || (_skeleton->nameToJointIndex("LeftHand") == _tipJointIndex)) { - //fake pole vector computation. - lastDot = cosf(((180.0f - _lastTheta) / 180.0f)*PI); - float lastSideDot = sqrt(1.0f - (lastDot*lastDot)); - glm::vec3 pretendPoleVector; - if (refVectorLength > MIN_LENGTH && sideVectorLength > MIN_LENGTH) { - poleVector = lastDot * (refVectorProj / refVectorProjLength) + glm::sign(_lastTheta) * lastSideDot * (sideVector / sideVectorLength); - if (_skeleton->nameToJointIndex("LeftHand") == _tipJointIndex) { - //qCDebug(animation) << "pole vector anim: " << poleVector; - } - } else { - poleVector = glm::vec3(1.0f, 0.0f, 0.0f); - } - } - // project poleVector on plane formed by axis. glm::vec3 poleVectorProj = poleVector - glm::dot(poleVector, unitAxis) * unitAxis; float poleVectorProjLength = glm::length(poleVectorProj); @@ -208,45 +125,16 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim float sideDot = glm::dot(poleVector, sideVector); float theta = copysignf(1.0f, sideDot) * acosf(dot); - float fred; + // overwrite theta if we are using optimized code if ((_skeleton->nameToJointIndex("RightHand") == _tipJointIndex) || (_skeleton->nameToJointIndex("LeftHand") == _tipJointIndex)) { - //qCDebug(animation) << "theta from the old code " << theta; - bool isLeft = false; + if (_skeleton->nameToJointIndex("LeftHand") == _tipJointIndex) { - isLeft = true; + theta = animVars.lookup("thetaLeftElbow", 0.0f); + } else if (_skeleton->nameToJointIndex("RightHand") == _tipJointIndex) { + theta = animVars.lookup("thetaRightElbow", 0.0f); } - //qCDebug(animation) << "hand pose anim pole vector: " << isLeft << " isLeft " << tipPose; - //fred = findThetaNewWay(tipPose.trans(), basePose.trans(), isLeft); - if (isLeft) { - float thetaFromRig = animVars.lookup("thetaLeft", 0.0f); - //qCDebug(animation) << " anim pole vector theta from rig left" << thetaFromRig; - fred = thetaFromRig; - - } else { - float thetaFromRig = animVars.lookup("thetaRight", 0.0f); - //qCDebug(animation) << " anim pole vector theta from rig right" << thetaFromRig; - fred = thetaFromRig; - } - - _lastTheta = 0.5f * _lastTheta + 0.5f * fred; - - //qCDebug(animation) << "twist correction: " << twistCorrection << " flex correction: " << flexCorrection << " ulnar correction " << ulnarCorrection; - - if (fabsf(_lastTheta) < 50.0f) { - if (fabsf(_lastTheta) < 50.0f) { - _lastTheta = glm::sign(_lastTheta) * 50.0f; - } - } - if (fabsf(_lastTheta) > 175.0f) { - _lastTheta = glm::sign(_lastTheta) * 175.0f; - } - - float poleVectorTheta = theta; - theta = ((180.0f - _lastTheta) / 180.0f)*PI; - } - //glm::quat deltaRot = glm::angleAxis(theta, unitAxis); glm::quat deltaRot = glm::angleAxis(theta, unitAxis); // transform result back into parent relative frame. @@ -334,8 +222,6 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim return _poses; } - - // for AnimDebugDraw rendering const AnimPoseVec& AnimPoleVectorConstraint::getPosesInternal() const { return _poses; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 3ca1411ec0..d1b39bf049 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1692,7 +1692,7 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s // get the default poses for the upper and lower arm // then use this length to judge how far the hand is away from the shoulder. // then create weights that make the elbow angle less when the x value is large in either direction. - // make the angle less when z is small. + // make the angle less when z is small. // lower y with x center lower angle // lower y with x out higher angle @@ -1744,14 +1744,13 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s const float zStart = 0.6f; const float xStart = 0.1f; // biases - //const glm::vec3 biases(30.0f, 120.0f, -30.0f); const glm::vec3 biases(0.0f, 135.0f, 0.0f); // weights const float zWeightBottom = -100.0f; const glm::vec3 weights(-50.0f, 60.0f, 260.0f); glm::vec3 armToHand = handPose.trans() - shoulderPose.trans(); float yFactor = (fabsf(armToHand[1] / defaultArmLength) * weights[1]) + biases[1]; - + float zFactor; if (armToHand[1] > 0.0f) { zFactor = weights[2] * glm::max(zStart - (armToHand[2] / defaultArmLength), 0.0f) * fabs(armToHand[1] / defaultArmLength); @@ -1888,7 +1887,6 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s } theta -= flexCorrection; } - qCDebug(animation) << "flexCorrection rig" << flexCorrection; const float TWIST_ULNAR_DEADZONE = 0.0f; const float ULNAR_BOUNDARY_MINUS = -PI / 12.0f; @@ -1960,7 +1958,6 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s } } } - qCDebug(animation) << "ulnarCorrection rig" << ulnarCorrection; // remember direction of travel. const float TWIST_DEADZONE = (4 * PI) / 9.0f; @@ -1969,26 +1966,55 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s if (left) { if (fabsf(_twistThetaRunningAverageLeft) > TWIST_DEADZONE) { twistCorrection = glm::sign(_twistThetaRunningAverageLeft) * ((fabsf(_twistThetaRunningAverageLeft) - TWIST_DEADZONE) / PI) * 80.0f; - } + } } else { if (fabsf(_twistThetaRunningAverageRight) > TWIST_DEADZONE) { twistCorrection = glm::sign(_twistThetaRunningAverageRight) * ((fabsf(_twistThetaRunningAverageRight) - TWIST_DEADZONE) / PI) * 80.0f; - } + } } if (fabsf(twistCorrection) > 30.0f) { theta += glm::sign(twistCorrection) * 30.0f; } else { theta += twistCorrection; } - qCDebug(animation) << "twistCorrection rig" << twistCorrection; - // put final global limiter here....... + //qCDebug(animation) << "twist correction: " << twistCorrection << " flex correction: " << flexCorrection << " ulnar correction " << ulnarCorrection; + // global limiting if (left) { - _animVars.set("thetaLeft", theta); + // final global smoothing + _lastThetaLeft = 0.5f * _lastThetaLeft + 0.5f * theta; + + if (fabsf(_lastThetaLeft) < 50.0f) { + if (fabsf(_lastThetaLeft) < 50.0f) { + _lastThetaLeft = glm::sign(_lastThetaLeft) * 50.0f; + } + } + if (fabsf(_lastThetaLeft) > 175.0f) { + _lastThetaLeft = glm::sign(_lastThetaLeft) * 175.0f; + } + // convert to radians and make 180 0 to match pole vector theta + float thetaRadians = ((180.0f - _lastThetaLeft) / 180.0f)*PI; + _animVars.set("thetaLeftElbow", thetaRadians); + } else { - _animVars.set("thetaRight", theta); + // final global smoothing + _lastThetaRight = 0.5f * _lastThetaRight + 0.5f * theta; + + if (fabsf(_lastThetaRight) < 50.0f) { + if (fabsf(_lastThetaRight) < 50.0f) { + _lastThetaRight = glm::sign(_lastThetaRight) * 50.0f; + } + } + if (fabsf(_lastThetaRight) > 175.0f) { + _lastThetaRight = glm::sign(_lastThetaRight) * 175.0f; + } + // convert to radians and make 180 0 to match pole vector theta + float thetaRadians = ((180.0f - _lastThetaRight) / 180.0f)*PI; + _animVars.set("thetaRightElbow", thetaRadians); } + + // remove this if inaccurate // convert theta back to pole vector float lastDot = cosf(((180.0f - theta) / 180.0f)*PI); float lastSideDot = sqrt(1.0f - (lastDot*lastDot)); diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 52f2c142a8..693aa732fa 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -432,7 +432,8 @@ protected: float _twistThetaRunningAverageRight{ 0.0f }; float _flexThetaRunningAverageRight { 0.0f }; float _ulnarRadialThetaRunningAverageRight { 0.0f }; - float _lastTheta { 0.0f }; + float _lastThetaLeft { 0.0f }; + float _lastThetaRight { 0.0f }; AnimContext _lastContext; AnimVariantMap _lastAnimVars; From 07af2c525e359abb87bb2861996cda701c2c80d0 Mon Sep 17 00:00:00 2001 From: Angus Antley Date: Fri, 15 Feb 2019 07:31:28 -0800 Subject: [PATCH 097/474] tweaked ulnar radial limit and extend limit --- libraries/animation/src/Rig.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index d1b39bf049..6c2851ec17 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1863,14 +1863,14 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s } const float POWER = 2.0f; - const float FLEX_BOUNDARY = PI / 5.0f; - const float EXTEND_BOUNDARY = -PI / 6.0f; + const float FLEX_BOUNDARY = PI / 6.0f; + const float EXTEND_BOUNDARY = -PI / 4.0f; float flexCorrection = 0.0f; if (left) { if (_flexThetaRunningAverageLeft > FLEX_BOUNDARY) { - flexCorrection = ((_flexThetaRunningAverageLeft - FLEX_BOUNDARY) / PI) * 180.0f; + flexCorrection = ((_flexThetaRunningAverageLeft - FLEX_BOUNDARY) / PI) * 90.0f; } else if (_flexThetaRunningAverageLeft < EXTEND_BOUNDARY) { - flexCorrection = ((_flexThetaRunningAverageLeft - EXTEND_BOUNDARY) / PI) * 180.0f; + flexCorrection = ((_flexThetaRunningAverageLeft - EXTEND_BOUNDARY) / PI) * 90.0f; } if (fabs(flexCorrection) > 30.0f) { flexCorrection = glm::sign(flexCorrection) * 30.0f; @@ -1889,8 +1889,8 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s } const float TWIST_ULNAR_DEADZONE = 0.0f; - const float ULNAR_BOUNDARY_MINUS = -PI / 12.0f; - const float ULNAR_BOUNDARY_PLUS = PI / 24.0f; + const float ULNAR_BOUNDARY_MINUS = -PI / 6.0f; + const float ULNAR_BOUNDARY_PLUS = PI / 6.0f; float ulnarDiff = 0.0f; float ulnarCorrection = 0.0f; if (left) { @@ -1907,16 +1907,16 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s } if (left) { if (trueTwistTheta < 0.0f) { - ulnarCorrection -= glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 90.0f * twistCoefficient; + ulnarCorrection -= glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 40.0f * twistCoefficient; } else { - ulnarCorrection += glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 90.0f * twistCoefficient; + ulnarCorrection += glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 40.0f * twistCoefficient; } } else { // right hand if (trueTwistTheta > 0.0f) { - ulnarCorrection -= glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 90.0f * twistCoefficient; + ulnarCorrection -= glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 40.0f * twistCoefficient; } else { - ulnarCorrection += glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 90.0f * twistCoefficient; + ulnarCorrection += glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 40.0f * twistCoefficient; } } if (fabsf(ulnarCorrection) > 20.0f) { From 98c321c71863ddf1e2c1ca6703c41834458c683c Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Fri, 15 Feb 2019 09:40:49 -0700 Subject: [PATCH 098/474] Fix warnings --- interface/src/avatar/MyAvatar.cpp | 10 +- interface/src/avatar/MyAvatar.h | 4 +- libraries/animation/src/Flow.cpp | 164 ++++++++++++------------------ libraries/animation/src/Flow.h | 28 ++--- 4 files changed, 81 insertions(+), 125 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 5880f4ee3c..96fdd6a9e1 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -5322,15 +5322,17 @@ void MyAvatar::addAvatarHandsToFlow(const std::shared_ptr& otherAvatar) } } -void MyAvatar::useFlow(const QVariantMap& physicsConfig, const QVariantMap& collisionsConfig) { +void MyAvatar::useFlow(bool isActive, bool isCollidable, const QVariantMap& physicsConfig, const QVariantMap& collisionsConfig) { if (_skeletonModel->isLoaded()) { auto &flow = _skeletonModel->getRig().getFlow(); - flow.init(); + flow.init(isActive, isCollidable); + auto &collisionSystem = flow.getCollisionSystem(); + collisionSystem.setActive(isCollidable); auto physicsGroups = physicsConfig.keys(); if (physicsGroups.size() > 0) { for (auto &groupName : physicsGroups) { auto &settings = physicsConfig[groupName].toMap(); - FlowPhysicsSettings physicsSettings = flow.getPhysicsSettingsForGroup(groupName); + FlowPhysicsSettings physicsSettings; if (settings.contains("active")) { physicsSettings._active = settings["active"].toBool(); } @@ -5357,7 +5359,6 @@ void MyAvatar::useFlow(const QVariantMap& physicsConfig, const QVariantMap& coll } auto collisionJoints = collisionsConfig.keys(); if (collisionJoints.size() > 0) { - auto &collisionSystem = flow.getCollisionSystem(); collisionSystem.resetCollisions(); for (auto &jointName : collisionJoints) { int jointIndex = getJointIndex(jointName); @@ -5372,7 +5373,6 @@ void MyAvatar::useFlow(const QVariantMap& physicsConfig, const QVariantMap& coll } collisionSystem.addCollisionSphere(jointIndex, collisionsSettings); } - } } } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index f98ae9208a..2c8dedd430 100755 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1189,12 +1189,14 @@ public: /**jsdoc * Init flow simulation on avatar. * @function MyAvatar.useFlow + * @param {boolean} - Set to true to activate flow simulation. + * @param {boolean} - Set to true to activate collisions. * @param {Object} physicsConfig - object with the customized physic parameters * i.e. {"hair": {"active": true, "stiffness": 0.0, "radius": 0.04, "gravity": -0.035, "damping": 0.8, "inertia": 0.8, "delta": 0.35}} * @param {Object} collisionsConfig - object with the customized collision parameters * i.e. {"Spine2": {"type": "sphere", "radius": 0.14, "offset": {"x": 0.0, "y": 0.2, "z": 0.0}}} */ - Q_INVOKABLE void useFlow(const QVariantMap& flowConfig = QVariantMap(), const QVariantMap& collisionsConfig = QVariantMap()); + Q_INVOKABLE void useFlow(bool isActive, bool isCollidable, const QVariantMap& physicsConfig = QVariantMap(), const QVariantMap& collisionsConfig = QVariantMap()); public slots: diff --git a/libraries/animation/src/Flow.cpp b/libraries/animation/src/Flow.cpp index 3db0068434..607b2775e0 100644 --- a/libraries/animation/src/Flow.cpp +++ b/libraries/animation/src/Flow.cpp @@ -93,8 +93,7 @@ FlowCollisionResult FlowCollisionSystem::computeCollision(const std::vector FlowCollisionSystem::checkFlowThreadCollisions( return results; }; -int FlowCollisionSystem::findSelfCollisionWithJoint(int jointIndex) { - for (size_t i = 0; i < _selfCollisions.size(); i++) { - if (_selfCollisions[i]._jointIndex == jointIndex) { - return (int)i; - } - } - return -1; -}; - -void FlowCollisionSystem::modifySelfCollisionRadius(int jointIndex, float radius) { - int collisionIndex = findSelfCollisionWithJoint(jointIndex); - if (collisionIndex > -1) { - _selfCollisions[collisionIndex]._initialRadius = radius; - //_selfCollisions[collisionIndex]._radius = _scale * radius; - } -}; - -void FlowCollisionSystem::modifySelfCollisionYOffset(int jointIndex, float offset) { - int collisionIndex = findSelfCollisionWithJoint(jointIndex); - if (collisionIndex > -1) { - auto currentOffset = _selfCollisions[collisionIndex]._offset; - _selfCollisions[collisionIndex]._initialOffset = glm::vec3(currentOffset.x, offset, currentOffset.z); - //_selfCollisions[collisionIndex]._offset = _selfCollisions[collisionIndex]._initialOffset * _scale; - } -}; - -void FlowCollisionSystem::modifySelfCollisionOffset(int jointIndex, const glm::vec3& offset) { - int collisionIndex = findSelfCollisionWithJoint(jointIndex); - if (collisionIndex > -1) { - _selfCollisions[collisionIndex]._initialOffset = offset; - //_selfCollisions[collisionIndex]._offset = _selfCollisions[collisionIndex]._initialOffset * _scale; - } -}; FlowCollisionSettings FlowCollisionSystem::getCollisionSettingsByJoint(int jointIndex) { for (auto &collision : _selfCollisions) { if (collision._jointIndex == jointIndex) { @@ -243,13 +209,11 @@ void FlowNode::update(float deltaTime, const glm::vec3& accelerationOffset) { // Add offset _acceleration += accelerationOffset; - - //glm::vec3 deltaAcceleration = timeRatio * _acceleration * glm::pow(_settings._delta * _scale, 2); - glm::vec3 deltaAcceleration = timeRatio * _acceleration * glm::pow(_settings._delta, 2); + float accelerationFactor = glm::pow(_settings._delta, 2) * timeRatio; + glm::vec3 deltaAcceleration = _acceleration * accelerationFactor; // Calculate new position - _currentPosition = (_currentPosition + _currentVelocity * _settings._damping) + deltaAcceleration; - } - else { + _currentPosition = _currentPosition + (_currentVelocity * _settings._damping) + deltaAcceleration; + } else { _acceleration = glm::vec3(0.0f); _currentVelocity = glm::vec3(0.0f); } @@ -273,8 +237,7 @@ void FlowNode::solveCollisions(const FlowCollisionResult& collision) { if (_colliding) { _currentPosition = _currentPosition + collision._normal * collision._offset; _previousCollision = collision; - } - else { + } else { _previousCollision = FlowCollisionResult(); } }; @@ -317,7 +280,8 @@ void FlowJoint::update(float deltaTime) { glm::vec3 accelerationOffset = glm::vec3(0.0f); if (_node._settings._stiffness > 0.0f) { glm::vec3 recoveryVector = _recoveryPosition - _node._currentPosition; - accelerationOffset = recoveryVector * glm::pow(_node._settings._stiffness, 3); + float recoveryFactor = glm::pow(_node._settings._stiffness, 3); + accelerationOffset = recoveryVector * recoveryFactor; } _node.update(deltaTime, accelerationOffset); if (_node._anchored) { @@ -386,7 +350,8 @@ void FlowThread::computeRecovery() { glm::quat parentRotation = parentJoint._parentWorldRotation * parentJoint._initialRotation; for (size_t i = 1; i < _joints.size(); i++) { auto joint = _jointsPointer->at(_joints[i]); - glm::quat rotation = i == 1 ? parentRotation : rotation * parentJoint._initialRotation; + glm::quat rotation; + rotation = (i == 1) ? parentRotation : parentJoint._initialRotation; _jointsPointer->at(_joints[i])._recoveryPosition = joint._recoveryPosition = parentJoint._recoveryPosition + (rotation * (joint._initialTranslation * 0.01f)); parentJoint = joint; } @@ -395,8 +360,11 @@ void FlowThread::computeRecovery() { void FlowThread::update(float deltaTime) { if (getActive()) { _positions.clear(); - _radius = _jointsPointer->at(_joints[0])._node._settings._radius; - computeRecovery(); + auto &firstJoint = _jointsPointer->at(_joints[0]); + _radius = firstJoint._node._settings._radius; + if (firstJoint._node._settings._stiffness > 0.0f) { + computeRecovery(); + } for (size_t i = 0; i < _joints.size(); i++) { auto &joint = _jointsPointer->at(_joints[i]); joint.update(deltaTime); @@ -405,11 +373,10 @@ void FlowThread::update(float deltaTime) { } }; -void FlowThread::solve(bool useCollisions, FlowCollisionSystem& collisionSystem) { +void FlowThread::solve(FlowCollisionSystem& collisionSystem) { if (getActive()) { - if (useCollisions) { + if (collisionSystem.getActive()) { auto bodyCollisions = collisionSystem.checkFlowThreadCollisions(this); - int handTouchedJoint = -1; for (size_t i = 0; i < _joints.size(); i++) { int index = _joints[i]; _jointsPointer->at(index).solve(bodyCollisions[i]); @@ -471,10 +438,15 @@ bool FlowThread::getActive() { return _jointsPointer->at(_joints[0])._node._active; }; -void Flow::init() { - if (!_initialized) { - calculateConstraints(); +void Flow::init(bool isActive, bool isCollidable) { + if (isActive) { + if (!_initialized) { + calculateConstraints(); + } + } else { + cleanUp(); } + } void Flow::calculateConstraints() { @@ -509,18 +481,18 @@ void Flow::calculateConstraints() { if (isFlowJoint || isSimJoint) { group = ""; if (isSimJoint) { - qDebug() << "FLOW is sim: " << name; - for (size_t j = 1; j < name.size() - 1; j++) { + for (int j = 1; j < name.size() - 1; j++) { bool toFloatSuccess; - auto subname = (QStringRef(&name, (int)(name.size() - j), 1)).toString().toFloat(&toFloatSuccess); - if (!toFloatSuccess && (name.size() - j) > simPrefix.size()) { - group = QStringRef(&name, simPrefix.size(), (int)(name.size() - j + 1)).toString(); + QStringRef(&name, (int)(name.size() - j), 1).toString().toFloat(&toFloatSuccess); + if (!toFloatSuccess && (name.size() - j) > (int)simPrefix.size()) { + group = QStringRef(&name, (int)simPrefix.size(), (int)(name.size() - j + 1)).toString(); break; } } if (group.isEmpty()) { - group = QStringRef(&name, simPrefix.size(), name.size() - 1).toString(); + group = QStringRef(&name, (int)simPrefix.size(), name.size() - 1).toString(); } + qCDebug(animation) << "Sim joint added to flow: " << name; } else { group = split[1]; } @@ -559,12 +531,12 @@ void Flow::calculateConstraints() { std::vector roots; - for (auto& itr = _flowJointData.begin(); itr != _flowJointData.end(); itr++) { - if (_flowJointData.find(itr->second._parentIndex) == _flowJointData.end()) { - itr->second._node._anchored = true; - roots.push_back(itr->first); + for (auto &joint :_flowJointData) { + if (_flowJointData.find(joint.second._parentIndex) == _flowJointData.end()) { + joint.second._node._anchored = true; + roots.push_back(joint.first); } else { - _flowJointData[itr->second._parentIndex]._childIndex = itr->first; + _flowJointData[joint.second._parentIndex]._childIndex = joint.first; } } @@ -629,6 +601,10 @@ void Flow::cleanUp() { _initialized = false; _isScaleSet = false; _active = false; + if (_rig) { + _rig->clearJointStates(); + } + } void Flow::setTransform(float scale, const glm::vec3& position, const glm::quat& rotation) { @@ -638,52 +614,49 @@ void Flow::setTransform(float scale, const glm::vec3& position, const glm::quat& _active = _initialized; } +void Flow::setScale(float scale) { + if (!_isScaleSet) { + for (auto &joint : _flowJointData) { + joint.second._initialLength = joint.second._length / _scale; + } + _isScaleSet = true; + } + _lastScale = _scale; + _collisionSystem.setScale(_scale); + for (size_t i = 0; i < _jointThreads.size(); i++) { + for (size_t j = 0; j < _jointThreads[i]._joints.size(); j++) { + auto &joint = _flowJointData[_jointThreads[i]._joints[j]]; + joint._node._settings._radius = joint._node._initialRadius * _scale; + joint._length = joint._initialLength * _scale; + } + _jointThreads[i].resetLength(); + } +} + void Flow::update(float deltaTime) { if (_initialized && _active) { - QElapsedTimer _timer; - _timer.start(); + uint64_t startTime = usecTimestampNow(); + uint64_t updateExpiry = startTime + MAX_UPDATE_FLOW_TIME_BUDGET; if (_scale != _lastScale) { - if (!_isScaleSet) { - for (auto &joint: _flowJointData) { - joint.second._initialLength = joint.second._length / _scale; - } - _isScaleSet = true; - } - _lastScale = _scale; - _collisionSystem.setScale(_scale); - for (int i = 0; i < _jointThreads.size(); i++) { - for (int j = 0; j < _jointThreads[i]._joints.size(); j++) { - auto &joint = _flowJointData[_jointThreads[i]._joints[j]]; - joint._node._settings._radius = joint._node._initialRadius * _scale; - joint._length = joint._initialLength * _scale; - } - _jointThreads[i].resetLength(); - } + setScale(_scale); } updateJoints(); for (size_t i = 0; i < _jointThreads.size(); i++) { size_t index = _invertThreadLoop ? _jointThreads.size() - 1 - i : i; auto &thread = _jointThreads[index]; thread.update(deltaTime); - thread.solve(USE_COLLISIONS, _collisionSystem); + thread.solve(_collisionSystem); if (!updateRootFramePositions(index)) { return; } thread.apply(); - if (_timer.elapsed() > MAX_UPDATE_FLOW_TIME_BUDGET) { + if (usecTimestampNow() > updateExpiry) { break; qWarning(animation) << "Flow Bones ran out of time updating threads"; } } setJoints(); _invertThreadLoop = !_invertThreadLoop; - _deltaTime += _timer.nsecsElapsed(); - _updates++; - if (_deltaTime > _deltaTimeLimit) { - qDebug() << "Flow C++ update " << _deltaTime / _updates << " nanoSeconds "; - _deltaTime = 0; - _updates = 0; - } } } @@ -753,15 +726,6 @@ void Flow::setOthersCollision(const QUuid& otherId, int jointIndex, const glm::v _collisionSystem.addCollisionSphere(jointIndex, settings, position, false, true); } -FlowPhysicsSettings Flow::getPhysicsSettingsForGroup(const QString& group) { - for (auto &joint : _flowJointData) { - if (joint.second._group.toUpper() == group.toUpper()) { - return joint.second._node._settings; - } - } - return FlowPhysicsSettings(); -} - void Flow::setPhysicsSettingsForGroup(const QString& group, const FlowPhysicsSettings& settings) { for (auto &joint : _flowJointData) { if (joint.second._group.toUpper() == group.toUpper()) { diff --git a/libraries/animation/src/Flow.h b/libraries/animation/src/Flow.h index 7f77df0beb..bb15846b5e 100644 --- a/libraries/animation/src/Flow.h +++ b/libraries/animation/src/Flow.h @@ -11,7 +11,6 @@ #ifndef hifi_Flow_h #define hifi_Flow_h -#include #include #include #include @@ -22,11 +21,7 @@ class Rig; class AnimSkeleton; -const bool SHOW_AVATAR = true; -const bool SHOW_DEBUG_SHAPES = false; -const bool SHOW_SOLID_SHAPES = false; const bool SHOW_DUMMY_JOINTS = false; -const bool USE_COLLISIONS = true; const int LEFT_HAND = 0; const int RIGHT_HAND = 1; @@ -55,7 +50,7 @@ const float DUMMY_JOINT_DISTANCE = 0.05f; const float ISOLATED_JOINT_STIFFNESS = 0.0f; const float ISOLATED_JOINT_LENGTH = 0.05f; -const float DEFAULT_STIFFNESS = 0.8f; +const float DEFAULT_STIFFNESS = 0.0f; const float DEFAULT_GRAVITY = -0.0096f; const float DEFAULT_DAMPING = 0.85f; const float DEFAULT_INERTIA = 0.8f; @@ -76,7 +71,7 @@ struct FlowPhysicsSettings { _radius = radius; } bool _active{ true }; - float _stiffness{ 0.0f }; + float _stiffness{ DEFAULT_STIFFNESS }; float _gravity{ DEFAULT_GRAVITY }; float _damping{ DEFAULT_DAMPING }; float _inertia{ DEFAULT_INERTIA }; @@ -158,11 +153,6 @@ public: std::vector checkFlowThreadCollisions(FlowThread* flowThread); - int findSelfCollisionWithJoint(int jointIndex); - void modifySelfCollisionRadius(int jointIndex, float radius); - void modifySelfCollisionYOffset(int jointIndex, float offset); - void modifySelfCollisionOffset(int jointIndex, const glm::vec3& offset); - std::vector& getSelfCollisions() { return _selfCollisions; }; void setOthersCollisions(const std::vector& othersCollisions) { _othersCollisions = othersCollisions; } void prepareCollisions(); @@ -171,11 +161,14 @@ public: void setScale(float scale); FlowCollisionSettings getCollisionSettingsByJoint(int jointIndex); void setCollisionSettingsByJoint(int jointIndex, const FlowCollisionSettings& settings); + void setActive(bool active) { _active = active; } + bool getActive() const { return _active; } protected: std::vector _selfCollisions; std::vector _othersCollisions; std::vector _allCollisions; - float _scale{ 1.0f }; + float _scale { 1.0f }; + bool _active { false }; }; class FlowNode { @@ -260,7 +253,7 @@ public: void computeFlowThread(int rootIndex); void computeRecovery(); void update(float deltaTime); - void solve(bool useCollisions, FlowCollisionSystem& collisionSystem); + void solve(FlowCollisionSystem& collisionSystem); void computeJointRotations(); void apply(); bool getActive(); @@ -277,7 +270,7 @@ public: class Flow { public: Flow(Rig* rig) { _rig = rig; }; - void init(); + void init(bool isActive, bool isCollidable); bool isActive() { return _active; } void calculateConstraints(); void update(float deltaTime); @@ -286,7 +279,6 @@ public: const std::vector& getThreads() const { return _jointThreads; } void setOthersCollision(const QUuid& otherId, int jointIndex, const glm::vec3& position); FlowCollisionSystem& getCollisionSystem() { return _collisionSystem; } - FlowPhysicsSettings getPhysicsSettingsForGroup(const QString& group); void setPhysicsSettingsForGroup(const QString& group, const FlowPhysicsSettings& settings); private: void setJoints(); @@ -294,6 +286,7 @@ private: void updateJoints(); bool updateRootFramePositions(size_t threadIndex); bool worldToJointPoint(const glm::vec3& position, const int jointIndex, glm::vec3& jointSpacePosition) const; + void setScale(float scale); Rig* _rig; float _scale { 1.0f }; float _lastScale{ 1.0f }; @@ -306,9 +299,6 @@ private: bool _initialized { false }; bool _active { false }; bool _isScaleSet { false }; - int _deltaTime { 0 }; - int _deltaTimeLimit { 4000000 }; - int _updates { 0 }; bool _invertThreadLoop { false }; }; From c966f71cb1e040bde1a87a439dabb96c7e9e5cd9 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Fri, 15 Feb 2019 10:17:37 -0700 Subject: [PATCH 099/474] More fixes --- 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 96fdd6a9e1..f2e69ab5d3 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -5331,7 +5331,7 @@ void MyAvatar::useFlow(bool isActive, bool isCollidable, const QVariantMap& phys auto physicsGroups = physicsConfig.keys(); if (physicsGroups.size() > 0) { for (auto &groupName : physicsGroups) { - auto &settings = physicsConfig[groupName].toMap(); + auto settings = physicsConfig[groupName].toMap(); FlowPhysicsSettings physicsSettings; if (settings.contains("active")) { physicsSettings._active = settings["active"].toBool(); @@ -5363,7 +5363,7 @@ void MyAvatar::useFlow(bool isActive, bool isCollidable, const QVariantMap& phys for (auto &jointName : collisionJoints) { int jointIndex = getJointIndex(jointName); FlowCollisionSettings collisionsSettings; - auto &settings = collisionsConfig[jointName].toMap(); + auto settings = collisionsConfig[jointName].toMap(); collisionsSettings._entityID = getID(); if (settings.contains("radius")) { collisionsSettings._radius = settings["radius"].toFloat(); From d6dfaacf6fd4eca7b4e71c84757c80aa8eae2a42 Mon Sep 17 00:00:00 2001 From: amantley Date: Fri, 15 Feb 2019 10:35:25 -0800 Subject: [PATCH 100/474] adding ifdef for android os --- interface/src/avatar/MyAvatar.cpp | 3 +++ libraries/animation/src/AnimSplineIK.cpp | 8 ++++---- libraries/animation/src/Rig.cpp | 11 ++++++++--- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index b5a938faba..aa47c63572 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2951,6 +2951,9 @@ void MyAvatar::initAnimGraph() { graphUrl = _fstAnimGraphOverrideUrl; } else { graphUrl = PathUtils::resourcesUrl("avatar/avatar-animation.json"); + #ifdef Q_OS_ANDROID + graphUrl = PathUtils::resourcesUrl("avatar/avatar-animation_withSplineIKNode.json"); + #endif } emit animGraphUrlChanged(graphUrl); diff --git a/libraries/animation/src/AnimSplineIK.cpp b/libraries/animation/src/AnimSplineIK.cpp index 72dcbfc5e7..c91bd3bae2 100644 --- a/libraries/animation/src/AnimSplineIK.cpp +++ b/libraries/animation/src/AnimSplineIK.cpp @@ -131,7 +131,7 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const baseParentAbsPose = _skeleton->getAbsolutePose(baseParentIndex, _poses); } _poses[_baseJointIndex] = baseParentAbsPose.inverse() * baseTargetAbsolutePose; - _poses[_baseJointIndex].scale() = glm::vec3(1.0f); + _poses[_baseJointIndex].scale() = 1.0f; // initialize the middle joint target IKTarget midTarget; @@ -290,7 +290,7 @@ void AnimSplineIK::setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) { void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, int base, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug, AnimChain& chainInfoOut) const { // build spline from tip to base - AnimPose tipPose = AnimPose(glm::vec3(1.0f), target.getRotation(), target.getTranslation()); + AnimPose tipPose = AnimPose(1.0f, target.getRotation(), target.getTranslation()); AnimPose basePose = absolutePoses[base]; CubicHermiteSplineFunctorWithArcLength spline; @@ -338,7 +338,7 @@ void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, int base, c glm::mat3 m(u, v, glm::cross(u, v)); glm::quat rot = glm::normalize(glm::quat_cast(m)); - AnimPose desiredAbsPose = AnimPose(glm::vec3(1.0f), rot, trans) * splineJointInfo.offsetPose; + AnimPose desiredAbsPose = AnimPose(1.0f, rot, trans) * splineJointInfo.offsetPose; // apply flex coefficent AnimPose flexedAbsPose; @@ -457,7 +457,7 @@ void AnimSplineIK::computeAndCacheSplineJointInfosForIKTarget(const AnimContext& glm::mat3 m(u, v, glm::cross(u, v)); glm::quat rot = glm::normalize(glm::quat_cast(m)); - AnimPose pose(glm::vec3(1.0f), rot, spline(t)); + AnimPose pose(1.0f, rot, spline(t)); AnimPose offsetPose = pose.inverse() * defaultPose; SplineJointInfo splineJointInfo = { index, ratio, offsetPose }; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index d25ea4669c..e3b997e8cc 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1516,8 +1516,13 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab if (ENABLE_POLE_VECTORS && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0 && oppositeArmJointIndex >= 0) { glm::vec3 poleVector; - //bool usePoleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, oppositeArmJointIndex, poleVector); - bool usePoleVector = calculateElbowPoleVectorOptimized(handJointIndex, elbowJointIndex, armJointIndex, false, poleVector); + bool usePoleVector = false; + #ifdef Q_OS_ANDROID + usePoleVector = calculateElbowPoleVectorOptimized(handJointIndex, elbowJointIndex, armJointIndex, false, poleVector); + #else + usePoleVector = calculateElbowPoleVectorOptimized(handJointIndex, elbowJointIndex, armJointIndex, false, poleVector); + // bool usePoleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, oppositeArmJointIndex, poleVector); + #endif if (usePoleVector) { glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector); _animVars.set("rightHandPoleVectorEnabled", true); @@ -1708,7 +1713,7 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s // calculate the reference axis and the side axis. // Look up refVector from animVars, make sure to convert into geom space. - glm::vec3 refVector = (AnimPose(_rigToGeometryTransform) * elbowPose).xformVectorFast(Vectors::UNIT_X); + glm::vec3 refVector = (AnimPose(_rigToGeometryTransform) * elbowPose).xformVector(Vectors::UNIT_X); float refVectorLength = glm::length(refVector); if (left) { From 7119bc597243c4e7e881d65fb627f16c51103600 Mon Sep 17 00:00:00 2001 From: amantley Date: Fri, 15 Feb 2019 10:54:02 -0800 Subject: [PATCH 101/474] reverted the scale optimization in animspline.cpp --- libraries/animation/src/AnimSplineIK.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/animation/src/AnimSplineIK.cpp b/libraries/animation/src/AnimSplineIK.cpp index c91bd3bae2..72dcbfc5e7 100644 --- a/libraries/animation/src/AnimSplineIK.cpp +++ b/libraries/animation/src/AnimSplineIK.cpp @@ -131,7 +131,7 @@ const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const baseParentAbsPose = _skeleton->getAbsolutePose(baseParentIndex, _poses); } _poses[_baseJointIndex] = baseParentAbsPose.inverse() * baseTargetAbsolutePose; - _poses[_baseJointIndex].scale() = 1.0f; + _poses[_baseJointIndex].scale() = glm::vec3(1.0f); // initialize the middle joint target IKTarget midTarget; @@ -290,7 +290,7 @@ void AnimSplineIK::setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) { void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, int base, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug, AnimChain& chainInfoOut) const { // build spline from tip to base - AnimPose tipPose = AnimPose(1.0f, target.getRotation(), target.getTranslation()); + AnimPose tipPose = AnimPose(glm::vec3(1.0f), target.getRotation(), target.getTranslation()); AnimPose basePose = absolutePoses[base]; CubicHermiteSplineFunctorWithArcLength spline; @@ -338,7 +338,7 @@ void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, int base, c glm::mat3 m(u, v, glm::cross(u, v)); glm::quat rot = glm::normalize(glm::quat_cast(m)); - AnimPose desiredAbsPose = AnimPose(1.0f, rot, trans) * splineJointInfo.offsetPose; + AnimPose desiredAbsPose = AnimPose(glm::vec3(1.0f), rot, trans) * splineJointInfo.offsetPose; // apply flex coefficent AnimPose flexedAbsPose; @@ -457,7 +457,7 @@ void AnimSplineIK::computeAndCacheSplineJointInfosForIKTarget(const AnimContext& glm::mat3 m(u, v, glm::cross(u, v)); glm::quat rot = glm::normalize(glm::quat_cast(m)); - AnimPose pose(1.0f, rot, spline(t)); + AnimPose pose(glm::vec3(1.0f), rot, spline(t)); AnimPose offsetPose = pose.inverse() * defaultPose; SplineJointInfo splineJointInfo = { index, ratio, offsetPose }; From be98fb1ac763b5ad305ab0819cebb38f17afb9b7 Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Fri, 15 Feb 2019 11:21:40 -0800 Subject: [PATCH 102/474] Standalone Tags - checkpoint --- .../InspectionCertificate.qml | 63 +++++++++++++++++-- .../hifi/commerce/purchases/PurchasedItem.qml | 13 +++- .../qml/hifi/commerce/purchases/Purchases.qml | 19 +++++- interface/src/Application.cpp | 8 +++ .../PlatformInfoScriptingInterface.cpp | 12 +++- .../PlatformInfoScriptingInterface.h | 8 ++- .../shared/src/shared/GlobalAppProperties.cpp | 1 + .../shared/src/shared/GlobalAppProperties.h | 1 + 8 files changed, 113 insertions(+), 12 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml index 8ca34af28a..16faf2feb7 100644 --- a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml +++ b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml @@ -30,6 +30,8 @@ Rectangle { property string dateAcquired: "--"; property string itemCost: "--"; property string marketplace_item_id: ""; + property bool standaloneOptimized: false; + property bool standaloneIncompatible: false; property string certTitleTextColor: hifi.colors.darkGray; property string certTextColor: hifi.colors.white; property string infoTextColor: hifi.colors.blueAccent; @@ -71,6 +73,8 @@ Rectangle { } else { root.marketplace_item_id = result.data.marketplace_item_id; root.isMyCert = result.isMyCert ? result.isMyCert : false; + root.standaloneOptimized = result.data.standalone_optimized; + root.standaloneIncompatible = result.data.standalone_incompatible; if (root.certInfoReplaceMode > 3) { root.itemName = result.data.marketplace_item_name; @@ -421,6 +425,24 @@ Rectangle { anchors.rightMargin: 24; height: root.useGoldCert ? 220 : 372; + HiFiGlyphs { + id: standaloneOptomizedBadge + + anchors { + right: parent.right + top: ownedByHeader.top + rightMargin: 15 + topMargin: 28 + } + + visible: root.standaloneOptimized + + text: hifi.glyphs.hmd + size: 34 + horizontalAlignment: Text.AlignHCenter + color: hifi.colors.blueHighlight + } + RalewayRegular { id: errorText; visible: !root.useGoldCert; @@ -467,6 +489,7 @@ Rectangle { color: root.infoTextColor; elide: Text.ElideRight; } + AnonymousProRegular { id: isMyCertText; visible: root.isMyCert && ownedBy.text !== "--" && ownedBy.text !== ""; @@ -485,14 +508,46 @@ Rectangle { verticalAlignment: Text.AlignVCenter; } + RalewayRegular { + id: standaloneHeader; + text: root.standaloneOptimized ? "STAND-ALONE OPTIMIZED" : "STAND-ALONE INCOMPATIBLE"; + // Text size + size: 16; + // Anchors + anchors.top: ownedBy.bottom; + anchors.topMargin: 15; + anchors.left: parent.left; + anchors.right: parent.right; + visible: root.standaloneOptimized || root.standaloneIncompatible; + height: visible ? paintedHeight : 0; + // Style + color: hifi.colors.darkGray; + } + + RalewayRegular { + id: standaloneText; + text: root.standaloneOptimized ? "This item is stand-alone optimized" : "This item is incompatible with stand-alone devices"; + // Text size + size: 18; + // Anchors + anchors.top: standaloneHeader.bottom; + anchors.topMargin: 8; + anchors.left: standaloneHeader.left; + visible: root.standaloneOptimized || root.standaloneIncompatible; + height: visible ? paintedHeight : 0; + // Style + color: root.infoTextColor; + elide: Text.ElideRight; + } + RalewayRegular { id: dateAcquiredHeader; text: "ACQUISITION DATE"; // Text size size: 16; // Anchors - anchors.top: ownedBy.bottom; - anchors.topMargin: 28; + anchors.top: standaloneText.bottom; + anchors.topMargin: 20; anchors.left: parent.left; anchors.right: parent.horizontalCenter; anchors.rightMargin: 8; @@ -521,8 +576,8 @@ Rectangle { // Text size size: 16; // Anchors - anchors.top: ownedBy.bottom; - anchors.topMargin: 28; + anchors.top: standaloneText.bottom; + anchors.topMargin: 20; anchors.left: parent.horizontalCenter; anchors.right: parent.right; height: paintedHeight; diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index ec49b596bc..22792e5727 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -405,7 +405,9 @@ Item { id: permissionExplanationText; anchors.fill: parent; text: { - if (root.itemType === "contentSet") { + if (root.standaloneIncompatible) { + "This item is incompatible with stand-alone devices. Learn more"; + } else if (root.itemType === "contentSet") { "You do not have 'Replace Content' permissions in this domain. Learn more"; } else if (root.itemType === "entity") { "You do not have 'Rez Certified' permissions in this domain. Learn more"; @@ -419,7 +421,11 @@ Item { verticalAlignment: Text.AlignVCenter; onLinkActivated: { - sendToPurchases({method: 'showPermissionsExplanation', itemType: root.itemType}); + if(link == "#standaloneIncompatible") { + sendToPurchases({method: 'showStandaloneIncompatibleExplanation'}); + } else { + sendToPurchases({method: 'showPermissionsExplanation', itemType: root.itemType}); + } } } } @@ -701,7 +707,8 @@ Item { anchors.bottomMargin: 8; width: 160; height: 40; - enabled: root.hasPermissionToRezThis && + enabled: !root.standaloneIncompatible && + root.hasPermissionToRezThis && MyAvatar.skeletonModelURL !== root.itemHref && !root.wornEntityID && root.valid; diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index a5446202a8..4b285e5402 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -12,7 +12,7 @@ // import Hifi 1.0 as Hifi -import QtQuick 2.5 +import QtQuick 2.9 import stylesUit 1.0 import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls @@ -33,6 +33,7 @@ Rectangle { property bool purchasesReceived: false; property bool punctuationMode: false; property bool isDebuggingFirstUseTutorial: false; + property bool isStandalone: false; property string installedApps; property bool keyboardRaised: false; property int numUpdatesAvailable: 0; @@ -44,6 +45,7 @@ Rectangle { purchasesModel.getFirstPage(); Commerce.getAvailableUpdates(); } + Connections { target: Commerce; @@ -110,6 +112,11 @@ Rectangle { } } + Component.onCompleted: { + isStandalone = PlatformInfo.isStandalone(); + console.log(isStandalone ? "IS STANDALONE" : "ISN'T STANDALONE"); + } + HifiCommerceCommon.CommerceLightbox { id: lightboxPopup; z: 999; @@ -554,7 +561,7 @@ Rectangle { itemType: model.item_type; valid: model.valid; standaloneOptimized: model.standalone_optimized - standaloneIncompatible: model.standalone_incompatible + standaloneIncompatible: root.isStandalone && model.standalone_incompatible anchors.topMargin: 10; anchors.bottomMargin: 10; @@ -675,6 +682,14 @@ Rectangle { lightboxPopup.visible = false; } lightboxPopup.visible = true; + } else if (msg.method === "showStandaloneIncompatibleExplanation") { + lightboxPopup.titleText = "Stand-alone Incompatible"; + lightboxPopup.bodyText = "The item is incompatible with stand-alone devices."; + lightboxPopup.button1text = "CLOSE"; + lightboxPopup.button1method = function() { + lightboxPopup.visible = false; + } + lightboxPopup.visible = true; } else if (msg.method === "setFilterText") { filterBar.text = msg.filterText; } else if (msg.method === "flipCard") { diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 1029398794..bdb19b7c90 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -768,6 +768,11 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { bool isStore = cmdOptionExists(argc, const_cast(argv), OCULUS_STORE_ARG); qApp->setProperty(hifi::properties::OCULUS_STORE, isStore); + // emulate standalone device + static const auto STANDALONE_ARG = "--standalone"; + bool isStandalone = cmdOptionExists(argc, const_cast(argv), STANDALONE_ARG); + qApp->setProperty(hifi::properties::STANDALONE, isStandalone); + // Ignore any previous crashes if running from command line with a test script. bool inTestMode { false }; for (int i = 0; i < argc; ++i) { @@ -3029,6 +3034,9 @@ void Application::initializeUi() { }; OffscreenQmlSurface::addWhitelistContextHandler({ QUrl{ "hifi/commerce/marketplace/Marketplace.qml" }, + QUrl{ "hifi/commerce/purchases/Purchases.qml" }, + QUrl{ "hifi/commerce/wallet/Wallet.qml" }, + QUrl{ "hifi/commerce/wallet/WalletHome.qml" }, }, platformInfoCallback); QmlContextCallback ttsCallback = [](QQmlContext* context) { diff --git a/interface/src/scripting/PlatformInfoScriptingInterface.cpp b/interface/src/scripting/PlatformInfoScriptingInterface.cpp index b390ab7119..89d609810c 100644 --- a/interface/src/scripting/PlatformInfoScriptingInterface.cpp +++ b/interface/src/scripting/PlatformInfoScriptingInterface.cpp @@ -7,7 +7,7 @@ // #include "PlatformInfoScriptingInterface.h" #include "Application.h" - +#include #include #ifdef Q_OS_WIN @@ -138,6 +138,14 @@ bool PlatformInfoScriptingInterface::has3DHTML() { #if defined(Q_OS_ANDROID) return false; #else - return true; + return !qApp->property(hifi::properties::STANDALONE).toBool(); +#endif +} + +bool PlatformInfoScriptingInterface::isStandalone() { +#if defined(Q_OS_ANDROID) + return false; +#else + return qApp->property(hifi::properties::STANDALONE).toBool(); #endif } diff --git a/interface/src/scripting/PlatformInfoScriptingInterface.h b/interface/src/scripting/PlatformInfoScriptingInterface.h index aece09b008..31f0058585 100644 --- a/interface/src/scripting/PlatformInfoScriptingInterface.h +++ b/interface/src/scripting/PlatformInfoScriptingInterface.h @@ -68,9 +68,15 @@ public slots: /**jsdoc * Returns true if device supports 3d HTML - * @function Window.hasRift + * @function Window.has3DHTML * @returns {boolean} true if device supports 3d HTML, otherwise false.*/ bool has3DHTML(); + + /**jsdoc + * Returns true if device is standalone + * @function Window.hasRift + * @returns {boolean} true if device is a standalone device, otherwise false.*/ + bool isStandalone(); }; #endif // hifi_PlatformInfoScriptingInterface_h diff --git a/libraries/shared/src/shared/GlobalAppProperties.cpp b/libraries/shared/src/shared/GlobalAppProperties.cpp index 54e50da3ea..1fd6c191b2 100644 --- a/libraries/shared/src/shared/GlobalAppProperties.cpp +++ b/libraries/shared/src/shared/GlobalAppProperties.cpp @@ -14,6 +14,7 @@ namespace hifi { namespace properties { const char* STEAM = "com.highfidelity.launchedFromSteam"; const char* LOGGER = "com.highfidelity.logger"; const char* OCULUS_STORE = "com.highfidelity.oculusStore"; + const char* STANDALONE = "com.highfidelity.standalone"; const char* TEST = "com.highfidelity.test"; const char* TRACING = "com.highfidelity.tracing"; const char* HMD = "com.highfidelity.hmd"; diff --git a/libraries/shared/src/shared/GlobalAppProperties.h b/libraries/shared/src/shared/GlobalAppProperties.h index 174be61939..6809d5530a 100644 --- a/libraries/shared/src/shared/GlobalAppProperties.h +++ b/libraries/shared/src/shared/GlobalAppProperties.h @@ -16,6 +16,7 @@ namespace hifi { namespace properties { extern const char* STEAM; extern const char* LOGGER; extern const char* OCULUS_STORE; + extern const char* STANDALONE; extern const char* TEST; extern const char* TRACING; extern const char* HMD; From b670f72e8422f966d58a684831e93c30cfbf2063 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Fri, 15 Feb 2019 12:35:17 -0700 Subject: [PATCH 103/474] fix warning on linux --- libraries/animation/src/Flow.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/libraries/animation/src/Flow.cpp b/libraries/animation/src/Flow.cpp index 607b2775e0..974dd8dc54 100644 --- a/libraries/animation/src/Flow.cpp +++ b/libraries/animation/src/Flow.cpp @@ -46,7 +46,7 @@ FlowCollisionResult FlowCollisionSphere::checkSegmentCollision(const glm::vec3& FlowCollisionResult result; auto segment = point2 - point1; auto segmentLength = glm::length(segment); - auto maxDistance = glm::sqrt(glm::pow(collisionResult1._radius, 2) + glm::pow(segmentLength, 2)); + auto maxDistance = glm::sqrt(powf(collisionResult1._radius, 2.0f) + powf(segmentLength, 2.0f)); if (collisionResult1._distance < maxDistance && collisionResult2._distance < maxDistance) { float segmentPercentage = collisionResult1._distance / (collisionResult1._distance + collisionResult2._distance); glm::vec3 collisionPoint = point1 + segment * segmentPercentage; @@ -123,21 +123,17 @@ std::vector FlowCollisionSystem::checkFlowThreadCollisions( auto prevCollision = collisionData[i - 1]; nextCollision = _allCollisions[j].computeSphereCollision(flowThread->_positions[i], flowThread->_radius); collisionData.push_back(nextCollision); - bool isTouching = false; if (prevCollision._offset > 0.0f) { if (i == 1) { FlowThreadResults[i - 1].push_back(prevCollision); - isTouching = true; } } else if (nextCollision._offset > 0.0f) { FlowThreadResults[i].push_back(nextCollision); - isTouching = true; } else { FlowCollisionResult segmentCollision = _allCollisions[j].checkSegmentCollision(flowThread->_positions[i - 1], flowThread->_positions[i], prevCollision, nextCollision); if (segmentCollision._offset > 0) { FlowThreadResults[i - 1].push_back(segmentCollision); FlowThreadResults[i].push_back(segmentCollision); - isTouching = true; } } } @@ -209,7 +205,7 @@ void FlowNode::update(float deltaTime, const glm::vec3& accelerationOffset) { // Add offset _acceleration += accelerationOffset; - float accelerationFactor = glm::pow(_settings._delta, 2) * timeRatio; + float accelerationFactor = powf(_settings._delta, 2.0f) * timeRatio; glm::vec3 deltaAcceleration = _acceleration * accelerationFactor; // Calculate new position _currentPosition = _currentPosition + (_currentVelocity * _settings._damping) + deltaAcceleration; @@ -280,7 +276,7 @@ void FlowJoint::update(float deltaTime) { glm::vec3 accelerationOffset = glm::vec3(0.0f); if (_node._settings._stiffness > 0.0f) { glm::vec3 recoveryVector = _recoveryPosition - _node._currentPosition; - float recoveryFactor = glm::pow(_node._settings._stiffness, 3); + float recoveryFactor = powf(_node._settings._stiffness, 3.0f); accelerationOffset = recoveryVector * recoveryFactor; } _node.update(deltaTime, accelerationOffset); From 3f9b761e426a10cf1b28eef497d1cf1f62fb1f5a Mon Sep 17 00:00:00 2001 From: amantley Date: Fri, 15 Feb 2019 14:05:42 -0800 Subject: [PATCH 104/474] updating the android only if defs --- .../src/AnimPoleVectorConstraint.cpp | 52 +++++++---- libraries/animation/src/Rig.cpp | 88 ++++++------------- libraries/animation/src/Rig.h | 2 +- 3 files changed, 63 insertions(+), 79 deletions(-) diff --git a/libraries/animation/src/AnimPoleVectorConstraint.cpp b/libraries/animation/src/AnimPoleVectorConstraint.cpp index d575ddabe4..67fbefdf6f 100644 --- a/libraries/animation/src/AnimPoleVectorConstraint.cpp +++ b/libraries/animation/src/AnimPoleVectorConstraint.cpp @@ -58,10 +58,6 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim // Look up poleVector from animVars, make sure to convert into geom space. glm::vec3 poleVector = animVars.lookupRigToGeometryVector(_poleVectorVar, Vectors::UNIT_Z); - if (_skeleton->nameToJointIndex("LeftHand") == _tipJointIndex) { - //float thetaFromRig = animVars.lookup("thetaRight", 0.0f); - //qCDebug(animation) << " anim pole vector theta from rig " << thetaFromRig; - } // determine if we should interpolate bool enabled = animVars.lookup(_enabledVar, _enabled); @@ -116,25 +112,20 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim // project poleVector on plane formed by axis. glm::vec3 poleVectorProj = poleVector - glm::dot(poleVector, unitAxis) * unitAxis; float poleVectorProjLength = glm::length(poleVectorProj); + +#ifdef Q_OS_ANDROID - // double check for zero length vectors or vectors parallel to rotaiton axis. - if (axisLength > MIN_LENGTH && refVectorLength > MIN_LENGTH && sideVectorLength > MIN_LENGTH && - refVectorProjLength > MIN_LENGTH && poleVectorProjLength > MIN_LENGTH) { + // get theta set by optimized ik for Quest + if ((_skeleton->nameToJointIndex("RightHand") == _tipJointIndex) || (_skeleton->nameToJointIndex("LeftHand") == _tipJointIndex)) { - float dot = glm::clamp(glm::dot(refVectorProj / refVectorProjLength, poleVectorProj / poleVectorProjLength), 0.0f, 1.0f); - float sideDot = glm::dot(poleVector, sideVector); - float theta = copysignf(1.0f, sideDot) * acosf(dot); - - // overwrite theta if we are using optimized code - if ((_skeleton->nameToJointIndex("RightHand") == _tipJointIndex) || (_skeleton->nameToJointIndex("LeftHand") == _tipJointIndex)) { - - if (_skeleton->nameToJointIndex("LeftHand") == _tipJointIndex) { - theta = animVars.lookup("thetaLeftElbow", 0.0f); - } else if (_skeleton->nameToJointIndex("RightHand") == _tipJointIndex) { - theta = animVars.lookup("thetaRightElbow", 0.0f); - } + float theta; + if (_skeleton->nameToJointIndex("LeftHand") == _tipJointIndex) { + theta = animVars.lookup("thetaLeftElbow", 0.0f); + } else if (_skeleton->nameToJointIndex("RightHand") == _tipJointIndex) { + theta = animVars.lookup("thetaRightElbow", 0.0f); } + glm::quat deltaRot = glm::angleAxis(theta, unitAxis); // transform result back into parent relative frame. @@ -145,6 +136,29 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim ikChain.setRelativePoseAtJointIndex(_tipJointIndex, AnimPose(relTipRot, underPoses[_tipJointIndex].trans())); } +#else + // double check for zero length vectors or vectors parallel to rotaiton axis. + if (axisLength > MIN_LENGTH && refVectorLength > MIN_LENGTH && sideVectorLength > MIN_LENGTH && + refVectorProjLength > MIN_LENGTH && poleVectorProjLength > MIN_LENGTH) { + + float dot = glm::clamp(glm::dot(refVectorProj / refVectorProjLength, poleVectorProj / poleVectorProjLength), 0.0f, 1.0f); + float sideDot = glm::dot(poleVector, sideVector); + float theta = copysignf(1.0f, sideDot) * acosf(dot); + + + + glm::quat deltaRot = glm::angleAxis(theta, unitAxis); + + // transform result back into parent relative frame. + glm::quat relBaseRot = glm::inverse(baseParentPose.rot()) * deltaRot * basePose.rot(); + ikChain.setRelativePoseAtJointIndex(_baseJointIndex, AnimPose(relBaseRot, underPoses[_baseJointIndex].trans())); + + glm::quat relTipRot = glm::inverse(midPose.rot()) * glm::inverse(deltaRot) * tipPose.rot(); + ikChain.setRelativePoseAtJointIndex(_tipJointIndex, AnimPose(relTipRot, underPoses[_tipJointIndex].trans())); + } + +#endif + // start off by initializing output poses with the underPoses _poses = underPoses; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 27ae8858d7..3cef6e677d 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1459,9 +1459,19 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab int elbowJointIndex = _animSkeleton->nameToJointIndex("LeftForeArm"); int oppositeArmJointIndex = _animSkeleton->nameToJointIndex("RightArm"); if (ENABLE_POLE_VECTORS && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0 && oppositeArmJointIndex >= 0) { + #ifdef Q_OS_ANDROID + float poleTheta; + bool usePoleTheta = calculateElbowPoleVectorOptimized(handJointIndex, elbowJointIndex, armJointIndex, true, poleTheta); + if (usePoleTheta) { + _animVars.set("leftHandPoleVectorEnabled", true); + _animVars.set("leftHandPoleReferenceVector", Vectors::UNIT_X); + _animVars.set("thetaLeftElbow", transformVectorFast(sensorToRigMatrix, sensorPoleVector)); + } else { + _animVars.set("leftHandPoleVectorEnabled", false); + } + #else glm::vec3 poleVector; - //bool usePoleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, oppositeArmJointIndex, poleVector); - bool usePoleVector = calculateElbowPoleVectorOptimized(handJointIndex, elbowJointIndex, armJointIndex, true, poleVector); + bool usePoleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, oppositeArmJointIndex, poleVector); if (usePoleVector) { glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector); _animVars.set("leftHandPoleVectorEnabled", true); @@ -1470,6 +1480,8 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab } else { _animVars.set("leftHandPoleVectorEnabled", false); } + #endif + } else { _animVars.set("leftHandPoleVectorEnabled", false); } @@ -1515,14 +1527,19 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab int oppositeArmJointIndex = _animSkeleton->nameToJointIndex("LeftArm"); if (ENABLE_POLE_VECTORS && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0 && oppositeArmJointIndex >= 0) { - glm::vec3 poleVector; - bool usePoleVector = false; #ifdef Q_OS_ANDROID - usePoleVector = calculateElbowPoleVectorOptimized(handJointIndex, elbowJointIndex, armJointIndex, false, poleVector); + float poleTheta; + bool usePoleTheta = calculateElbowPoleVectorOptimized(handJointIndex, elbowJointIndex, armJointIndex, true, poleTheta); + if (usePoleTheta) { + _animVars.set("rightHandPoleVectorEnabled", true); + _animVars.set("rightHandPoleReferenceVector", Vectors::UNIT_X); + _animVars.set("thetaRightElbow", poleTheta); + } else { + _animVars.set("rightHandPoleVectorEnabled", false); + } #else - usePoleVector = calculateElbowPoleVectorOptimized(handJointIndex, elbowJointIndex, armJointIndex, false, poleVector); - // bool usePoleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, oppositeArmJointIndex, poleVector); - #endif + glm::vec3 poleVector; + bool usePoleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, oppositeArmJointIndex, poleVector); if (usePoleVector) { glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector); _animVars.set("rightHandPoleVectorEnabled", true); @@ -1531,6 +1548,8 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab } else { _animVars.set("rightHandPoleVectorEnabled", false); } + + #endif } else { _animVars.set("rightHandPoleVectorEnabled", false); } @@ -1688,7 +1707,7 @@ void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm } } -bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int shoulderIndex, bool left, glm::vec3& poleVector) { +bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int shoulderIndex, bool left, float& poleTheta) { // get the default poses for the upper and lower arm // then use this length to judge how far the hand is away from the shoulder. // then create weights that make the elbow angle less when the x value is large in either direction. @@ -1711,36 +1730,7 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s AnimPose absoluteHandPose = getAbsoluteDefaultPose(handIndex); float defaultArmLength = glm::length(absoluteHandPose.trans() - absoluteShoulderPose.trans()); - // calculate the reference axis and the side axis. - // Look up refVector from animVars, make sure to convert into geom space. - glm::vec3 refVector = (AnimPose(_rigToGeometryTransform) * elbowPose).xformVector(Vectors::UNIT_X); - float refVectorLength = glm::length(refVector); - - if (left) { - //AnimPose temp(_rigToGeometryTransform); - //glm::mat4 elbowMat(elbowPose); - //AnimPose result3(_rigToGeometryTransform * elbowMat); - //AnimPose geomElbow2 = temp * elbowPose; - //qCDebug(animation) << "mid pose geom2 rig" << geomElbow2; - //qCDebug(animation) << "mid pose result rig" << result3; - //qCDebug(animation) << "ref vector rig" << refVector; - } - - AnimPose geomShoulder = AnimPose(_rigToGeometryTransform) * shoulderPose; - AnimPose geomHand = AnimPose(_rigToGeometryTransform) * handPose; - glm::vec3 axis = geomShoulder.trans() - geomHand.trans(); - float axisLength = glm::length(axis); - glm::vec3 unitAxis = axis / axisLength; - - glm::vec3 sideVector = glm::cross(unitAxis, refVector); - float sideVectorLength = glm::length(sideVector); - - // project refVector onto axis plane - glm::vec3 refVectorProj = refVector - glm::dot(refVector, unitAxis) * unitAxis; - float refVectorProjLength = glm::length(refVectorProj); - - // phi_0 is the lowest angle we can have - const float phi_0 = 15.0f; + //calculate the hand position influence on theta const float zStart = 0.6f; const float xStart = 0.1f; // biases @@ -2014,27 +2004,7 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s _animVars.set("thetaRightElbow", thetaRadians); } - // remove this if inaccurate - // convert theta back to pole vector - float lastDot = cosf(((180.0f - theta) / 180.0f)*PI); - float lastSideDot = sqrt(1.0f - (lastDot*lastDot)); - - const float MIN_LENGTH = 1.0e-4f; - if (refVectorLength > MIN_LENGTH && sideVectorLength > MIN_LENGTH) { - poleVector = lastDot * (refVectorProj / refVectorProjLength) + glm::sign(theta) * lastSideDot * (sideVector / sideVectorLength); - if (left) { - //qCDebug(animation) << "pole vector in rig " << poleVector; - } - // - } else { - poleVector = glm::vec3(1.0f, 0.0f, 0.0f); - return false; - } - - return true; - - } bool Rig::calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex, int oppositeArmIndex, glm::vec3& poleVector) const { diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 693aa732fa..4dce28f7ae 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -258,7 +258,7 @@ protected: void calcAnimAlpha(float speed, const std::vector& referenceSpeeds, float* alphaOut) const; bool calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex, int oppositeArmIndex, glm::vec3& poleVector) const; - bool calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int shoulderIndex, bool left, glm::vec3& poleVector); + bool calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int shoulderIndex, bool left, float& poleTheta); glm::vec3 calculateKneePoleVector(int footJointIndex, int kneeJoint, int upLegIndex, int hipsIndex, const AnimPose& targetFootPose) const; glm::vec3 deflectHandFromTorso(const glm::vec3& handPosition, const HFMJointShapeInfo& hipsShapeInfo, const HFMJointShapeInfo& spineShapeInfo, const HFMJointShapeInfo& spine1ShapeInfo, const HFMJointShapeInfo& spine2ShapeInfo) const; From 7446fa9e7eb81ae3476b70baea885518b6cc54a9 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 15 Feb 2019 11:21:06 -0800 Subject: [PATCH 105/474] allow mesh shapes to use MOTION_TYPE_KINEMATIC --- libraries/physics/src/EntityMotionState.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index ce9cb20c21..91c4c43c1d 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -205,6 +205,16 @@ PhysicsMotionType EntityMotionState::computePhysicsMotionType() const { if (_entity->getShapeType() == SHAPE_TYPE_STATIC_MESH || (_body && _body->getCollisionShape()->getShapeType() == TRIANGLE_MESH_SHAPE_PROXYTYPE)) { + if (_entity->isMoving()) { + // DANGER: Bullet doesn't support non-zero velocity for these shapes --> collision details may be wrong + // in ways that allow other DYNAMIC objects to tunnel/penetrate/snag. + // However, in practice low-velocity collisions work OK most of the time, and if we enforce these objects + // to be MOTION_TYPE_STATIC then some other bugs can be worse (e.g. when Grabbing --> Grab Action fails) + // so we're making a tradeoff here. + // TODO: The Correct Solution is to NOT use btBvhTriangleMesh shape for moving objects and instead compute the convex + // decomposition and build a btCompoundShape with convex sub-shapes. + return MOTION_TYPE_KINEMATIC; + } return MOTION_TYPE_STATIC; } From 36093926d06cdce3da7ea5dc774a700d08353186 Mon Sep 17 00:00:00 2001 From: amantley Date: Fri, 15 Feb 2019 15:00:39 -0800 Subject: [PATCH 106/474] added fake android defines for running the new ik on pc --- interface/src/avatar/MyAvatar.cpp | 3 +++ .../src/AnimPoleVectorConstraint.cpp | 4 +++- libraries/animation/src/Rig.cpp | 19 +++++++++------ tools/unity-avatar-exporter/Assets/README.txt | 23 +++++++++++++++++++ 4 files changed, 41 insertions(+), 8 deletions(-) create mode 100644 tools/unity-avatar-exporter/Assets/README.txt diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 817c870244..ea5f2163fe 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2943,6 +2943,8 @@ void MyAvatar::setAnimGraphUrl(const QUrl& url) { connect(&(_skeletonModel->getRig()), SIGNAL(onLoadComplete()), this, SLOT(animGraphLoaded())); } +#define FAKE_Q_OS_ANDROID + void MyAvatar::initAnimGraph() { QUrl graphUrl; if (!_prefOverrideAnimGraphUrl.get().isEmpty()) { @@ -2951,6 +2953,7 @@ void MyAvatar::initAnimGraph() { graphUrl = _fstAnimGraphOverrideUrl; } else { graphUrl = PathUtils::resourcesUrl("avatar/avatar-animation.json"); + //#ifdef FAKE_Q_OS_ANDROID #ifdef Q_OS_ANDROID graphUrl = PathUtils::resourcesUrl("avatar/avatar-animation_withSplineIKNode.json"); #endif diff --git a/libraries/animation/src/AnimPoleVectorConstraint.cpp b/libraries/animation/src/AnimPoleVectorConstraint.cpp index 67fbefdf6f..998368a0c6 100644 --- a/libraries/animation/src/AnimPoleVectorConstraint.cpp +++ b/libraries/animation/src/AnimPoleVectorConstraint.cpp @@ -13,6 +13,7 @@ #include "AnimUtil.h" #include "GLMHelpers.h" +#define FAKE_Q_OS_ANDROID true; const float FRAMES_PER_SECOND = 30.0f; const float INTERP_DURATION = 6.0f; @@ -112,7 +113,8 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim // project poleVector on plane formed by axis. glm::vec3 poleVectorProj = poleVector - glm::dot(poleVector, unitAxis) * unitAxis; float poleVectorProjLength = glm::length(poleVectorProj); - + +//#ifdef FAKE_Q_OS_ANDROID #ifdef Q_OS_ANDROID // get theta set by optimized ik for Quest diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 3cef6e677d..188f2aa790 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -34,6 +34,7 @@ #include "IKTarget.h" #include "PathUtils.h" +#define FAKE_Q_OS_ANDROID true; static int nextRigId = 1; static std::map rigRegistry; @@ -1459,14 +1460,15 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab int elbowJointIndex = _animSkeleton->nameToJointIndex("LeftForeArm"); int oppositeArmJointIndex = _animSkeleton->nameToJointIndex("RightArm"); if (ENABLE_POLE_VECTORS && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0 && oppositeArmJointIndex >= 0) { + //#ifdef FAKE_Q_OS_ANDROID #ifdef Q_OS_ANDROID float poleTheta; bool usePoleTheta = calculateElbowPoleVectorOptimized(handJointIndex, elbowJointIndex, armJointIndex, true, poleTheta); if (usePoleTheta) { _animVars.set("leftHandPoleVectorEnabled", true); _animVars.set("leftHandPoleReferenceVector", Vectors::UNIT_X); - _animVars.set("thetaLeftElbow", transformVectorFast(sensorToRigMatrix, sensorPoleVector)); - } else { + _animVars.set("thetaLeftElbow", poleTheta); + } else { _animVars.set("leftHandPoleVectorEnabled", false); } #else @@ -1527,12 +1529,14 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab int oppositeArmJointIndex = _animSkeleton->nameToJointIndex("LeftArm"); if (ENABLE_POLE_VECTORS && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0 && oppositeArmJointIndex >= 0) { + + //#ifdef FAKE_Q_OS_ANDROID #ifdef Q_OS_ANDROID float poleTheta; - bool usePoleTheta = calculateElbowPoleVectorOptimized(handJointIndex, elbowJointIndex, armJointIndex, true, poleTheta); + bool usePoleTheta = calculateElbowPoleVectorOptimized(handJointIndex, elbowJointIndex, armJointIndex, false, poleTheta); if (usePoleTheta) { _animVars.set("rightHandPoleVectorEnabled", true); - _animVars.set("rightHandPoleReferenceVector", Vectors::UNIT_X); + _animVars.set("rightHandPoleReferenceVector", -Vectors::UNIT_X); _animVars.set("thetaRightElbow", poleTheta); } else { _animVars.set("rightHandPoleVectorEnabled", false); @@ -1951,7 +1955,6 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s // remember direction of travel. const float TWIST_DEADZONE = (4 * PI) / 9.0f; - //if (!isLeft) { float twistCorrection = 0.0f; if (left) { if (fabsf(_twistThetaRunningAverageLeft) > TWIST_DEADZONE) { @@ -1985,7 +1988,8 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s } // convert to radians and make 180 0 to match pole vector theta float thetaRadians = ((180.0f - _lastThetaLeft) / 180.0f)*PI; - _animVars.set("thetaLeftElbow", thetaRadians); + //_animVars.set("thetaLeftElbow", thetaRadians); + poleTheta = thetaRadians; } else { // final global smoothing @@ -2001,7 +2005,8 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s } // convert to radians and make 180 0 to match pole vector theta float thetaRadians = ((180.0f - _lastThetaRight) / 180.0f)*PI; - _animVars.set("thetaRightElbow", thetaRadians); + //_animVars.set("thetaRightElbow", thetaRadians); + poleTheta = thetaRadians; } return true; diff --git a/tools/unity-avatar-exporter/Assets/README.txt b/tools/unity-avatar-exporter/Assets/README.txt new file mode 100644 index 0000000000..f02bc688ae --- /dev/null +++ b/tools/unity-avatar-exporter/Assets/README.txt @@ -0,0 +1,23 @@ +High Fidelity, Inc. +Avatar Exporter +Version 0.1 + +Note: It is recommended to use Unity versions between 2017.4.17f1 and 2018.2.12f1 for this Avatar Exporter. + +To create a new avatar project: +1. Import your .fbx avatar model into your Unity project's Assets by either dragging and dropping the file into the Assets window or by using Assets menu > Import New Assets. +2. Select the .fbx avatar that you imported in step 1 in the Assets window, and in the Rig section of the Inspector window set the Animation Type to Humanoid and choose Apply. +3. With the .fbx avatar still selected in the Assets window, choose High Fidelity menu > Export New Avatar. +4. Select a name for your avatar project (this will be used to create a directory with that name), as well as the target location for your project folder. +5. Once it is exported, your project directory will open in File Explorer. + +To update an existing avatar project: +1. Select the existing .fbx avatar in the Assets window that you would like to re-export. +2. Choose High Fidelity menu > Update Existing Avatar and browse to the .fst file you would like to update. +3. If the .fbx file in your Unity Assets folder is newer than the existing .fbx file in your selected avatar project or vice-versa, you will be prompted if you wish to replace the older file with the newer file before performing the update. +4. Once it is updated, your project directory will open in File Explorer. + +* WARNING * +If you are using any external textures as part of your .fbx model, be sure they are copied into the textures folder that is created in the project folder after exporting a new avatar. + +For further details including troubleshooting tips, see the full documentation at https://docs.highfidelity.com/create-and-explore/avatars/create-avatars/unity-extension From e46b9479131bd6ffa1840ced1de938918b14797d Mon Sep 17 00:00:00 2001 From: David Back Date: Fri, 15 Feb 2019 15:31:11 -0800 Subject: [PATCH 107/474] properly disable draggable numbers, fix disable script and collision sound --- scripts/system/html/js/entityProperties.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index c1a8f363b5..32b576c1f5 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -1485,6 +1485,8 @@ const ENTITY_SCRIPT_STATUS = { unloaded: "Unloaded" }; +const ENABLE_DISABLE_SELECTOR = "input, textarea, span, .dropdown dl, .color-picker"; + const PROPERTY_NAME_DIVISION = { GROUP: 0, PROPERTY: 1, @@ -1584,8 +1586,7 @@ function disableChildren(el, selector) { } function enableProperties() { - enableChildren(document.getElementById("properties-list"), - "input, textarea, checkbox, .dropdown dl, .color-picker , .draggable-number.text"); + enableChildren(document.getElementById("properties-list"), ENABLE_DISABLE_SELECTOR); enableChildren(document, ".colpick"); let elLocked = getPropertyInputElement("locked"); @@ -1596,8 +1597,7 @@ function enableProperties() { } function disableProperties() { - disableChildren(document.getElementById("properties-list"), - "input, textarea, checkbox, .dropdown dl, .color-picker, .draggable-number.text"); + disableChildren(document.getElementById("properties-list"), ENABLE_DISABLE_SELECTOR); disableChildren(document, ".colpick"); for (let pickKey in colorPickers) { colorPickers[pickKey].colpickHide(); @@ -3349,8 +3349,8 @@ function loaded() { let shouldHide = selectedEntityProperties.certificateID !== ""; if (shouldHide) { propertyValue = "** Certified **"; + property.elInput.disabled = true; } - property.elInput.disabled = shouldHide; } let isPropertyNotNumber = false; From 95530e6ba53096c8412eccb29f9f7eca8e6e4ecc Mon Sep 17 00:00:00 2001 From: amantley Date: Fri, 15 Feb 2019 15:41:23 -0800 Subject: [PATCH 108/474] removed the unnecessary animArmIK.h and .cpp --- libraries/animation/src/AnimArmIK.cpp | 42 -------- libraries/animation/src/AnimArmIK.h | 34 ------ libraries/animation/src/AnimContext.h | 1 - libraries/animation/src/AnimNodeLoader.cpp | 24 ----- libraries/animation/src/Rig.cpp | 114 +++++++++------------ 5 files changed, 50 insertions(+), 165 deletions(-) delete mode 100644 libraries/animation/src/AnimArmIK.cpp delete mode 100644 libraries/animation/src/AnimArmIK.h diff --git a/libraries/animation/src/AnimArmIK.cpp b/libraries/animation/src/AnimArmIK.cpp deleted file mode 100644 index 87606fe5d2..0000000000 --- a/libraries/animation/src/AnimArmIK.cpp +++ /dev/null @@ -1,42 +0,0 @@ -// -// AnimArmIK.cpp -// -// Created by Angus Antley on 1/9/19. -// Copyright (c) 2019 High Fidelity, Inc. All rights reserved. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "AnimArmIK.h" - -#include - -#include "AnimationLogging.h" -#include "AnimUtil.h" - -AnimArmIK::AnimArmIK(const QString& id, float alpha, bool enabled, float interpDuration, - const QString& baseJointName, const QString& midJointName, - const QString& tipJointName, const glm::vec3& midHingeAxis, - const QString& alphaVar, const QString& enabledVar, - const QString& endEffectorRotationVarVar, const QString& endEffectorPositionVarVar) : - AnimTwoBoneIK(id, alpha, enabled, interpDuration, baseJointName, midJointName, tipJointName, midHingeAxis, alphaVar, enabledVar, endEffectorRotationVarVar, endEffectorPositionVarVar) -{ - -} - -AnimArmIK::~AnimArmIK() { - -} - -const AnimPoseVec& AnimArmIK::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { - - //qCDebug(animation) << "evaluating the arm IK"; - _poses = AnimTwoBoneIK::evaluate(animVars, context, dt, triggersOut); - - //assert(_children.size() == 1); - //if (_children.size() != 1) { - // return _poses; - //} - -} \ No newline at end of file diff --git a/libraries/animation/src/AnimArmIK.h b/libraries/animation/src/AnimArmIK.h deleted file mode 100644 index 26f79a1b9c..0000000000 --- a/libraries/animation/src/AnimArmIK.h +++ /dev/null @@ -1,34 +0,0 @@ -// -// AnimArmIK.h -// -// Created by Angus Antley on 1/9/19. -// Copyright (c) 2019 High Fidelity, Inc. All rights reserved. -// -// 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_AnimArmIK_h -#define hifi_AnimArmIK_h - -//#include "AnimNode.h" -#include "AnimTwoBoneIK.h" -//#include "AnimChain.h" - -// Simple two bone IK chain -class AnimArmIK : public AnimTwoBoneIK { -public: - AnimArmIK(const QString& id, float alpha, bool enabled, float interpDuration, - const QString& baseJointName, const QString& midJointName, - const QString& tipJointName, const glm::vec3& midHingeAxis, - const QString& alphaVar, const QString& enabledVar, - const QString& endEffectorRotationVarVar, const QString& endEffectorPositionVarVar); - virtual ~AnimArmIK(); - virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override; - -protected: - -}; - -#endif // hifi_AnimArmIK_h - diff --git a/libraries/animation/src/AnimContext.h b/libraries/animation/src/AnimContext.h index 2c5b010f1b..e3ab5d9788 100644 --- a/libraries/animation/src/AnimContext.h +++ b/libraries/animation/src/AnimContext.h @@ -28,7 +28,6 @@ enum class AnimNodeType { InverseKinematics, DefaultPose, TwoBoneIK, - ArmIK, SplineIK, PoleVectorConstraint, NumTypes diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 707bf7b976..3518fe14e5 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -26,7 +26,6 @@ #include "AnimInverseKinematics.h" #include "AnimDefaultPose.h" #include "AnimTwoBoneIK.h" -#include "AnimArmIK.h" #include "AnimSplineIK.h" #include "AnimPoleVectorConstraint.h" @@ -65,7 +64,6 @@ static const char* animNodeTypeToString(AnimNode::Type type) { case AnimNode::Type::InverseKinematics: return "inverseKinematics"; case AnimNode::Type::DefaultPose: return "defaultPose"; case AnimNode::Type::TwoBoneIK: return "twoBoneIK"; - case AnimNode::Type::ArmIK: return "armIK"; case AnimNode::Type::SplineIK: return "splineIK"; case AnimNode::Type::PoleVectorConstraint: return "poleVectorConstraint"; case AnimNode::Type::NumTypes: return nullptr; @@ -129,7 +127,6 @@ static NodeLoaderFunc animNodeTypeToLoaderFunc(AnimNode::Type type) { case AnimNode::Type::InverseKinematics: return loadInverseKinematicsNode; case AnimNode::Type::DefaultPose: return loadDefaultPoseNode; case AnimNode::Type::TwoBoneIK: return loadTwoBoneIKNode; - case AnimNode::Type::ArmIK: return loadArmIKNode; case AnimNode::Type::SplineIK: return loadSplineIKNode; case AnimNode::Type::PoleVectorConstraint: return loadPoleVectorConstraintNode; case AnimNode::Type::NumTypes: return nullptr; @@ -148,7 +145,6 @@ static NodeProcessFunc animNodeTypeToProcessFunc(AnimNode::Type type) { case AnimNode::Type::InverseKinematics: return processDoNothing; case AnimNode::Type::DefaultPose: return processDoNothing; case AnimNode::Type::TwoBoneIK: return processDoNothing; - case AnimNode::Type::ArmIK: return processDoNothing; case AnimNode::Type::SplineIK: return processDoNothing; case AnimNode::Type::PoleVectorConstraint: return processDoNothing; case AnimNode::Type::NumTypes: return nullptr; @@ -630,26 +626,6 @@ static AnimNode::Pointer loadSplineIKNode(const QJsonObject& jsonObj, const QStr return node; } -static AnimNode::Pointer loadArmIKNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { - READ_FLOAT(alpha, jsonObj, id, jsonUrl, nullptr); - READ_BOOL(enabled, jsonObj, id, jsonUrl, nullptr); - READ_FLOAT(interpDuration, jsonObj, id, jsonUrl, nullptr); - READ_STRING(baseJointName, jsonObj, id, jsonUrl, nullptr); - READ_STRING(midJointName, jsonObj, id, jsonUrl, nullptr); - READ_STRING(tipJointName, jsonObj, id, jsonUrl, nullptr); - READ_VEC3(midHingeAxis, jsonObj, id, jsonUrl, nullptr); - READ_STRING(alphaVar, jsonObj, id, jsonUrl, nullptr); - READ_STRING(enabledVar, jsonObj, id, jsonUrl, nullptr); - READ_STRING(endEffectorRotationVarVar, jsonObj, id, jsonUrl, nullptr); - READ_STRING(endEffectorPositionVarVar, jsonObj, id, jsonUrl, nullptr); - - auto node = std::make_shared(id, alpha, enabled, interpDuration, - baseJointName, midJointName, tipJointName, midHingeAxis, - alphaVar, enabledVar, - endEffectorRotationVarVar, endEffectorPositionVarVar); - return node; -} - static AnimNode::Pointer loadTwoBoneIKNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { READ_FLOAT(alpha, jsonObj, id, jsonUrl, nullptr); READ_BOOL(enabled, jsonObj, id, jsonUrl, nullptr); diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 188f2aa790..e224c4f571 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1462,8 +1462,9 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab if (ENABLE_POLE_VECTORS && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0 && oppositeArmJointIndex >= 0) { //#ifdef FAKE_Q_OS_ANDROID #ifdef Q_OS_ANDROID + bool isLeft = true; float poleTheta; - bool usePoleTheta = calculateElbowPoleVectorOptimized(handJointIndex, elbowJointIndex, armJointIndex, true, poleTheta); + bool usePoleTheta = calculateElbowPoleVectorOptimized(handJointIndex, elbowJointIndex, armJointIndex, isLeft, poleTheta); if (usePoleTheta) { _animVars.set("leftHandPoleVectorEnabled", true); _animVars.set("leftHandPoleReferenceVector", Vectors::UNIT_X); @@ -1532,8 +1533,9 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab //#ifdef FAKE_Q_OS_ANDROID #ifdef Q_OS_ANDROID + isLeft = false; float poleTheta; - bool usePoleTheta = calculateElbowPoleVectorOptimized(handJointIndex, elbowJointIndex, armJointIndex, false, poleTheta); + bool usePoleTheta = calculateElbowPoleVectorOptimized(handJointIndex, elbowJointIndex, armJointIndex, isLeft, poleTheta); if (usePoleTheta) { _animVars.set("rightHandPoleVectorEnabled", true); _animVars.set("rightHandPoleReferenceVector", -Vectors::UNIT_X); @@ -1719,13 +1721,6 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s // lower y with x center lower angle // lower y with x out higher angle - glm::vec3 referenceVector; - if (left) { - referenceVector = Vectors::UNIT_X; - } else { - referenceVector = -Vectors::UNIT_X; - } - AnimPose handPose = _externalPoseSet._absolutePoses[handIndex]; AnimPose shoulderPose = _externalPoseSet._absolutePoses[shoulderIndex]; AnimPose elbowPose = _externalPoseSet._absolutePoses[elbowIndex]; @@ -1772,7 +1767,7 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s theta *= -1.0f; } - // now we calculate the contribution of the hand + // now we calculate the contribution of the hand rotation relative to the arm glm::quat relativeHandRotation = (elbowPose.inverse() * handPose).rot(); if (relativeHandRotation.w < 0.0f) { relativeHandRotation *= -1.0f; @@ -1862,9 +1857,9 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s float flexCorrection = 0.0f; if (left) { if (_flexThetaRunningAverageLeft > FLEX_BOUNDARY) { - flexCorrection = ((_flexThetaRunningAverageLeft - FLEX_BOUNDARY) / PI) * 90.0f; + flexCorrection = ((_flexThetaRunningAverageLeft - FLEX_BOUNDARY) / PI) * 180.0f; } else if (_flexThetaRunningAverageLeft < EXTEND_BOUNDARY) { - flexCorrection = ((_flexThetaRunningAverageLeft - EXTEND_BOUNDARY) / PI) * 90.0f; + flexCorrection = ((_flexThetaRunningAverageLeft - EXTEND_BOUNDARY) / PI) * 180.0f; } if (fabs(flexCorrection) > 30.0f) { flexCorrection = glm::sign(flexCorrection) * 30.0f; @@ -1882,7 +1877,6 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s theta -= flexCorrection; } - const float TWIST_ULNAR_DEADZONE = 0.0f; const float ULNAR_BOUNDARY_MINUS = -PI / 6.0f; const float ULNAR_BOUNDARY_PLUS = PI / 6.0f; float ulnarDiff = 0.0f; @@ -1894,30 +1888,28 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s ulnarDiff = _ulnarRadialThetaRunningAverageLeft - ULNAR_BOUNDARY_MINUS; } if (fabs(ulnarDiff) > 0.0f) { - if (fabs(_twistThetaRunningAverageLeft) > TWIST_ULNAR_DEADZONE) { - float twistCoefficient = (fabs(_twistThetaRunningAverageLeft) / (PI / 20.0f)); - if (twistCoefficient > 1.0f) { - twistCoefficient = 1.0f; - } - if (left) { - if (trueTwistTheta < 0.0f) { - ulnarCorrection -= glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 40.0f * twistCoefficient; - } else { - ulnarCorrection += glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 40.0f * twistCoefficient; - } - } else { - // right hand - if (trueTwistTheta > 0.0f) { - ulnarCorrection -= glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 40.0f * twistCoefficient; - } else { - ulnarCorrection += glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 40.0f * twistCoefficient; - } - } - if (fabsf(ulnarCorrection) > 20.0f) { - ulnarCorrection = glm::sign(ulnarCorrection) * 20.0f; - } - theta += ulnarCorrection; + float twistCoefficient = (fabs(_twistThetaRunningAverageLeft) / (PI / 20.0f)); + if (twistCoefficient > 1.0f) { + twistCoefficient = 1.0f; } + if (left) { + if (trueTwistTheta < 0.0f) { + ulnarCorrection -= glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 40.0f * twistCoefficient; + } else { + ulnarCorrection += glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 40.0f * twistCoefficient; + } + } else { + // right hand + if (trueTwistTheta > 0.0f) { + ulnarCorrection -= glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 40.0f * twistCoefficient; + } else { + ulnarCorrection += glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 40.0f * twistCoefficient; + } + } + if (fabsf(ulnarCorrection) > 20.0f) { + ulnarCorrection = glm::sign(ulnarCorrection) * 20.0f; + } + theta += ulnarCorrection; } } else { if (_ulnarRadialThetaRunningAverageRight > ULNAR_BOUNDARY_PLUS) { @@ -1926,34 +1918,31 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s ulnarDiff = _ulnarRadialThetaRunningAverageRight - ULNAR_BOUNDARY_MINUS; } if (fabs(ulnarDiff) > 0.0f) { - if (fabs(_twistThetaRunningAverageRight) > TWIST_ULNAR_DEADZONE) { - float twistCoefficient = (fabs(_twistThetaRunningAverageRight) / (PI / 20.0f)); - if (twistCoefficient > 1.0f) { - twistCoefficient = 1.0f; - } - if (left) { - if (trueTwistTheta < 0.0f) { - ulnarCorrection -= glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 90.0f * twistCoefficient; - } else { - ulnarCorrection += glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 90.0f * twistCoefficient; - } - } else { - // right hand - if (trueTwistTheta > 0.0f) { - ulnarCorrection -= glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 90.0f * twistCoefficient; - } else { - ulnarCorrection += glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 90.0f * twistCoefficient; - } - } - if (fabsf(ulnarCorrection) > 20.0f) { - ulnarCorrection = glm::sign(ulnarCorrection) * 20.0f; - } - theta += ulnarCorrection; + float twistCoefficient = (fabs(_twistThetaRunningAverageRight) / (PI / 20.0f)); + if (twistCoefficient > 1.0f) { + twistCoefficient = 1.0f; } + if (left) { + if (trueTwistTheta < 0.0f) { + ulnarCorrection -= glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 40.0f * twistCoefficient; + } else { + ulnarCorrection += glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 40.0f * twistCoefficient; + } + } else { + // right hand + if (trueTwistTheta > 0.0f) { + ulnarCorrection -= glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 40.0f * twistCoefficient; + } else { + ulnarCorrection += glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 40.0f * twistCoefficient; + } + } + if (fabsf(ulnarCorrection) > 20.0f) { + ulnarCorrection = glm::sign(ulnarCorrection) * 20.0f; + } + theta += ulnarCorrection; } } - // remember direction of travel. const float TWIST_DEADZONE = (4 * PI) / 9.0f; float twistCorrection = 0.0f; if (left) { @@ -1965,14 +1954,13 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s twistCorrection = glm::sign(_twistThetaRunningAverageRight) * ((fabsf(_twistThetaRunningAverageRight) - TWIST_DEADZONE) / PI) * 80.0f; } } + // limit the twist correction if (fabsf(twistCorrection) > 30.0f) { theta += glm::sign(twistCorrection) * 30.0f; } else { theta += twistCorrection; } - //qCDebug(animation) << "twist correction: " << twistCorrection << " flex correction: " << flexCorrection << " ulnar correction " << ulnarCorrection; - // global limiting if (left) { // final global smoothing @@ -1988,7 +1976,6 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s } // convert to radians and make 180 0 to match pole vector theta float thetaRadians = ((180.0f - _lastThetaLeft) / 180.0f)*PI; - //_animVars.set("thetaLeftElbow", thetaRadians); poleTheta = thetaRadians; } else { @@ -2005,7 +1992,6 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s } // convert to radians and make 180 0 to match pole vector theta float thetaRadians = ((180.0f - _lastThetaRight) / 180.0f)*PI; - //_animVars.set("thetaRightElbow", thetaRadians); poleTheta = thetaRadians; } From 46768c2a6e516d6be213839939f4663cc9bd1f36 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Sat, 16 Feb 2019 00:42:19 +0100 Subject: [PATCH 109/474] remove the code that prevented the crash from happening, since it is not reproducible anymore --- scripts/system/modules/createWindow.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/scripts/system/modules/createWindow.js b/scripts/system/modules/createWindow.js index 0c4412abfb..7369cf91f8 100644 --- a/scripts/system/modules/createWindow.js +++ b/scripts/system/modules/createWindow.js @@ -125,9 +125,6 @@ module.exports = (function() { Script.scriptEnding.connect(this, function() { this.window.close(); - // FIXME: temp solution for reload crash (MS18269), - // we should decide on proper object ownership strategy for InteractiveWindow API - this.window = null; }); }, setVisible: function(visible) { From 59a1e92dd8641aa630c0dc7106449ca66eeae573 Mon Sep 17 00:00:00 2001 From: David Back Date: Fri, 15 Feb 2019 16:25:04 -0800 Subject: [PATCH 110/474] fix git ignore for avatar exporter --- .gitignore | 9 +++------ tools/unity-avatar-exporter/Assets/Editor.meta | 8 -------- .../Assets/Editor/AvatarExporter.cs.meta | 11 ----------- tools/unity-avatar-exporter/Assets/README.txt.meta | 7 ------- 4 files changed, 3 insertions(+), 32 deletions(-) delete mode 100644 tools/unity-avatar-exporter/Assets/Editor.meta delete mode 100644 tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs.meta delete mode 100644 tools/unity-avatar-exporter/Assets/README.txt.meta diff --git a/.gitignore b/.gitignore index 5a965b494c..e8527bbaf4 100644 --- a/.gitignore +++ b/.gitignore @@ -98,12 +98,9 @@ tools/jsdoc/package-lock.json # Python compile artifacts **/__pycache__ -# ignore unneeded unity project files for avatar exporter -tools/unity-avatar-exporter/Library -tools/unity-avatar-exporter/Logs -tools/unity-avatar-exporter/Packages -tools/unity-avatar-exporter/ProjectSettings -tools/unity-avatar-exporter/Temp +# ignore local unity project files for avatar exporter +tools/unity-avatar-exporter + server-console/package-lock.json vcpkg/ /tools/nitpick/compiledResources diff --git a/tools/unity-avatar-exporter/Assets/Editor.meta b/tools/unity-avatar-exporter/Assets/Editor.meta deleted file mode 100644 index cf7dcf12dd..0000000000 --- a/tools/unity-avatar-exporter/Assets/Editor.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 02111c50e71dd664da8ad5c6a6eca767 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs.meta b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs.meta deleted file mode 100644 index 373aecc6a8..0000000000 --- a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 00403fdc52187214c8418bc0a7f387e2 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/tools/unity-avatar-exporter/Assets/README.txt.meta b/tools/unity-avatar-exporter/Assets/README.txt.meta deleted file mode 100644 index 148fd21fdd..0000000000 --- a/tools/unity-avatar-exporter/Assets/README.txt.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 30b2b6221fd08234eb07c4d6d525d32e -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: From 304f993391b9f187e7b079b837fb570175485a62 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 15 Feb 2019 16:50:45 -0800 Subject: [PATCH 111/474] remove enforcement of MOTION_TYPE_STATIC for mesh shapes --- libraries/physics/src/EntityMotionState.cpp | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 91c4c43c1d..4d210c96c5 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -203,21 +203,6 @@ PhysicsMotionType EntityMotionState::computePhysicsMotionType() const { } assert(entityTreeIsLocked()); - if (_entity->getShapeType() == SHAPE_TYPE_STATIC_MESH - || (_body && _body->getCollisionShape()->getShapeType() == TRIANGLE_MESH_SHAPE_PROXYTYPE)) { - if (_entity->isMoving()) { - // DANGER: Bullet doesn't support non-zero velocity for these shapes --> collision details may be wrong - // in ways that allow other DYNAMIC objects to tunnel/penetrate/snag. - // However, in practice low-velocity collisions work OK most of the time, and if we enforce these objects - // to be MOTION_TYPE_STATIC then some other bugs can be worse (e.g. when Grabbing --> Grab Action fails) - // so we're making a tradeoff here. - // TODO: The Correct Solution is to NOT use btBvhTriangleMesh shape for moving objects and instead compute the convex - // decomposition and build a btCompoundShape with convex sub-shapes. - return MOTION_TYPE_KINEMATIC; - } - return MOTION_TYPE_STATIC; - } - if (_entity->getLocked()) { if (_entity->isMoving()) { return MOTION_TYPE_KINEMATIC; From 2fdc9bce7734ff32a424576bac580a3638b4d8c9 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 15 Feb 2019 16:59:18 -0800 Subject: [PATCH 112/474] remove velocity restrictions on SHAPE_TYPE_STATIC_MESH --- libraries/entities/src/EntityItem.cpp | 78 +++++++++++---------------- 1 file changed, 32 insertions(+), 46 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 2c6d679b46..049b46ec7e 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1912,25 +1912,19 @@ void EntityItem::setRotation(glm::quat rotation) { void EntityItem::setVelocity(const glm::vec3& value) { glm::vec3 velocity = getLocalVelocity(); if (velocity != value) { - if (getShapeType() == SHAPE_TYPE_STATIC_MESH) { - if (velocity != Vectors::ZERO) { - setLocalVelocity(Vectors::ZERO); - } - } else { - float speed = glm::length(value); - 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) { - velocity = ENTITY_ITEM_ZERO_VEC3; - } else if (speed > MAX_LINEAR_SPEED) { - velocity = (MAX_LINEAR_SPEED / speed) * value; - } else { - velocity = value; - } - setLocalVelocity(velocity); - _flags |= Simulation::DIRTY_LINEAR_VELOCITY; + float speed = glm::length(value); + 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) { + velocity = ENTITY_ITEM_ZERO_VEC3; + } else if (speed > MAX_LINEAR_SPEED) { + velocity = (MAX_LINEAR_SPEED / speed) * value; + } else { + velocity = value; } + setLocalVelocity(velocity); + _flags |= Simulation::DIRTY_LINEAR_VELOCITY; } } } @@ -1948,19 +1942,15 @@ void EntityItem::setDamping(float value) { void EntityItem::setGravity(const glm::vec3& value) { withWriteLock([&] { if (_gravity != value) { - if (getShapeType() == SHAPE_TYPE_STATIC_MESH) { - _gravity = Vectors::ZERO; - } else { - float magnitude = glm::length(value); - 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; - } else { - _gravity = value; - } - _flags |= Simulation::DIRTY_LINEAR_VELOCITY; + float magnitude = glm::length(value); + 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; + } else { + _gravity = value; } + _flags |= Simulation::DIRTY_LINEAR_VELOCITY; } } }); @@ -1969,23 +1959,19 @@ void EntityItem::setGravity(const glm::vec3& value) { void EntityItem::setAngularVelocity(const glm::vec3& value) { glm::vec3 angularVelocity = getLocalAngularVelocity(); if (angularVelocity != value) { - if (getShapeType() == SHAPE_TYPE_STATIC_MESH) { - setLocalAngularVelocity(Vectors::ZERO); - } else { - float speed = glm::length(value); - 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) { - angularVelocity = ENTITY_ITEM_ZERO_VEC3; - } else if (speed > MAX_ANGULAR_SPEED) { - angularVelocity = (MAX_ANGULAR_SPEED / speed) * value; - } else { - angularVelocity = value; - } - setLocalAngularVelocity(angularVelocity); - _flags |= Simulation::DIRTY_ANGULAR_VELOCITY; + float speed = glm::length(value); + 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) { + angularVelocity = ENTITY_ITEM_ZERO_VEC3; + } else if (speed > MAX_ANGULAR_SPEED) { + angularVelocity = (MAX_ANGULAR_SPEED / speed) * value; + } else { + angularVelocity = value; } + setLocalAngularVelocity(angularVelocity); + _flags |= Simulation::DIRTY_ANGULAR_VELOCITY; } } } From d78f253d24063a0c3d4cea95ff8e92bb28a04e60 Mon Sep 17 00:00:00 2001 From: amantley Date: Fri, 15 Feb 2019 17:43:53 -0800 Subject: [PATCH 113/474] code to generate pole vector from theta --- interface/src/avatar/MyAvatar.cpp | 7 ++--- .../src/AnimPoleVectorConstraint.cpp | 5 ++-- libraries/animation/src/Rig.cpp | 30 ++++++++++++++----- 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index ea5f2163fe..c2e7292a0a 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2943,8 +2943,7 @@ void MyAvatar::setAnimGraphUrl(const QUrl& url) { connect(&(_skeletonModel->getRig()), SIGNAL(onLoadComplete()), this, SLOT(animGraphLoaded())); } -#define FAKE_Q_OS_ANDROID - +#define USE_Q_OS_ANDROID void MyAvatar::initAnimGraph() { QUrl graphUrl; if (!_prefOverrideAnimGraphUrl.get().isEmpty()) { @@ -2953,8 +2952,8 @@ void MyAvatar::initAnimGraph() { graphUrl = _fstAnimGraphOverrideUrl; } else { graphUrl = PathUtils::resourcesUrl("avatar/avatar-animation.json"); - //#ifdef FAKE_Q_OS_ANDROID - #ifdef Q_OS_ANDROID + + #if defined(Q_OS_ANDROID) || defined(USE_Q_OS_ANDROID) graphUrl = PathUtils::resourcesUrl("avatar/avatar-animation_withSplineIKNode.json"); #endif } diff --git a/libraries/animation/src/AnimPoleVectorConstraint.cpp b/libraries/animation/src/AnimPoleVectorConstraint.cpp index 998368a0c6..505471efdd 100644 --- a/libraries/animation/src/AnimPoleVectorConstraint.cpp +++ b/libraries/animation/src/AnimPoleVectorConstraint.cpp @@ -13,7 +13,7 @@ #include "AnimUtil.h" #include "GLMHelpers.h" -#define FAKE_Q_OS_ANDROID true; +#define USE_Q_OS_ANDROID const float FRAMES_PER_SECOND = 30.0f; const float INTERP_DURATION = 6.0f; @@ -114,8 +114,7 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim glm::vec3 poleVectorProj = poleVector - glm::dot(poleVector, unitAxis) * unitAxis; float poleVectorProjLength = glm::length(poleVectorProj); -//#ifdef FAKE_Q_OS_ANDROID -#ifdef Q_OS_ANDROID +#if defined(Q_OS_ANDROID) || defined(USE_Q_OS_ANDROID) // get theta set by optimized ik for Quest if ((_skeleton->nameToJointIndex("RightHand") == _tipJointIndex) || (_skeleton->nameToJointIndex("LeftHand") == _tipJointIndex)) { diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index e224c4f571..ab35186bfd 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -34,8 +34,7 @@ #include "IKTarget.h" #include "PathUtils.h" -#define FAKE_Q_OS_ANDROID true; - +#define USE_Q_OS_ANDROID static int nextRigId = 1; static std::map rigRegistry; static std::mutex rigRegistryMutex; @@ -1460,8 +1459,7 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab int elbowJointIndex = _animSkeleton->nameToJointIndex("LeftForeArm"); int oppositeArmJointIndex = _animSkeleton->nameToJointIndex("RightArm"); if (ENABLE_POLE_VECTORS && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0 && oppositeArmJointIndex >= 0) { - //#ifdef FAKE_Q_OS_ANDROID - #ifdef Q_OS_ANDROID + #if defined(Q_OS_ANDROID) || defined(USE_Q_OS_ANDROID) bool isLeft = true; float poleTheta; bool usePoleTheta = calculateElbowPoleVectorOptimized(handJointIndex, elbowJointIndex, armJointIndex, isLeft, poleTheta); @@ -1531,9 +1529,8 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab if (ENABLE_POLE_VECTORS && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0 && oppositeArmJointIndex >= 0) { - //#ifdef FAKE_Q_OS_ANDROID - #ifdef Q_OS_ANDROID - isLeft = false; + #if defined(Q_OS_ANDROID) || defined(USE_Q_OS_ANDROID) + bool isLeft = false; float poleTheta; bool usePoleTheta = calculateElbowPoleVectorOptimized(handJointIndex, elbowJointIndex, armJointIndex, isLeft, poleTheta); if (usePoleTheta) { @@ -1848,7 +1845,7 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s } if (!left) { - qCDebug(animation) << "flex ave: " << (_flexThetaRunningAverageRight / PI) * 180.0f << " twist ave: " << (_twistThetaRunningAverageRight / PI) * 180.0f << " ulnar deviation ave: " << (_ulnarRadialThetaRunningAverageRight / PI) * 180.0f; + //qCDebug(animation) << "flex ave: " << (_flexThetaRunningAverageRight / PI) * 180.0f << " twist ave: " << (_twistThetaRunningAverageRight / PI) * 180.0f << " ulnar deviation ave: " << (_ulnarRadialThetaRunningAverageRight / PI) * 180.0f; } const float POWER = 2.0f; @@ -1995,6 +1992,23 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s poleTheta = thetaRadians; } + float xValue = sin(poleTheta); + float yValue = cos(poleTheta); + float zValue = 0.0f; + glm::vec3 thetaVector(xValue, yValue, zValue); + glm::vec3 xAxis = glm::cross(Vectors::UNIT_Y, armToHand); + glm::vec3 up = glm::cross(armToHand, xAxis); + glm::quat armAxisRotation; + glm::vec3 u, v, w; + glm::vec3 fwd = armToHand/glm::length(armToHand); + + generateBasisVectors(Vectors::UNIT_Y, fwd, u, v, w); + AnimPose armAxisPose(glm::mat4(glm::vec4(w, 0.0f), glm::vec4(u, 0.0f), glm::vec4(v, 0.0f), glm::vec4(glm::vec3(0.0f, 0.0f, 0.0f), 1.0f))); + glm::vec3 pole = armAxisPose * thetaVector; + + qCDebug(animation) << "the pole from theta is " << pole; + + return true; } From 0982c37c5e0d089524c30d14b2d6fa1c1f836bb3 Mon Sep 17 00:00:00 2001 From: Angus Antley Date: Sat, 16 Feb 2019 14:50:47 -0800 Subject: [PATCH 114/474] took out the theta animvar and just use theta converted to pole vector --- .../src/AnimPoleVectorConstraint.cpp | 40 ++------ libraries/animation/src/Rig.cpp | 99 +++++++++++-------- libraries/animation/src/Rig.h | 2 +- 3 files changed, 64 insertions(+), 77 deletions(-) diff --git a/libraries/animation/src/AnimPoleVectorConstraint.cpp b/libraries/animation/src/AnimPoleVectorConstraint.cpp index 505471efdd..7b60d6984b 100644 --- a/libraries/animation/src/AnimPoleVectorConstraint.cpp +++ b/libraries/animation/src/AnimPoleVectorConstraint.cpp @@ -114,17 +114,14 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim glm::vec3 poleVectorProj = poleVector - glm::dot(poleVector, unitAxis) * unitAxis; float poleVectorProjLength = glm::length(poleVectorProj); -#if defined(Q_OS_ANDROID) || defined(USE_Q_OS_ANDROID) + // double check for zero length vectors or vectors parallel to rotaiton axis. + if (axisLength > MIN_LENGTH && refVectorLength > MIN_LENGTH && sideVectorLength > MIN_LENGTH && + refVectorProjLength > MIN_LENGTH && poleVectorProjLength > MIN_LENGTH) { - // get theta set by optimized ik for Quest - if ((_skeleton->nameToJointIndex("RightHand") == _tipJointIndex) || (_skeleton->nameToJointIndex("LeftHand") == _tipJointIndex)) { + float dot = glm::clamp(glm::dot(refVectorProj / refVectorProjLength, poleVectorProj / poleVectorProjLength), 0.0f, 1.0f); + float sideDot = glm::dot(poleVector, sideVector); + float theta = copysignf(1.0f, sideDot) * acosf(dot); - float theta; - if (_skeleton->nameToJointIndex("LeftHand") == _tipJointIndex) { - theta = animVars.lookup("thetaLeftElbow", 0.0f); - } else if (_skeleton->nameToJointIndex("RightHand") == _tipJointIndex) { - theta = animVars.lookup("thetaRightElbow", 0.0f); - } glm::quat deltaRot = glm::angleAxis(theta, unitAxis); @@ -136,30 +133,7 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim glm::quat relTipRot = glm::inverse(midPose.rot()) * glm::inverse(deltaRot) * tipPose.rot(); ikChain.setRelativePoseAtJointIndex(_tipJointIndex, AnimPose(relTipRot, underPoses[_tipJointIndex].trans())); } - -#else - // double check for zero length vectors or vectors parallel to rotaiton axis. - if (axisLength > MIN_LENGTH && refVectorLength > MIN_LENGTH && sideVectorLength > MIN_LENGTH && - refVectorProjLength > MIN_LENGTH && poleVectorProjLength > MIN_LENGTH) { - - float dot = glm::clamp(glm::dot(refVectorProj / refVectorProjLength, poleVectorProj / poleVectorProjLength), 0.0f, 1.0f); - float sideDot = glm::dot(poleVector, sideVector); - float theta = copysignf(1.0f, sideDot) * acosf(dot); - - - - glm::quat deltaRot = glm::angleAxis(theta, unitAxis); - - // transform result back into parent relative frame. - glm::quat relBaseRot = glm::inverse(baseParentPose.rot()) * deltaRot * basePose.rot(); - ikChain.setRelativePoseAtJointIndex(_baseJointIndex, AnimPose(relBaseRot, underPoses[_baseJointIndex].trans())); - - glm::quat relTipRot = glm::inverse(midPose.rot()) * glm::inverse(deltaRot) * tipPose.rot(); - ikChain.setRelativePoseAtJointIndex(_tipJointIndex, AnimPose(relTipRot, underPoses[_tipJointIndex].trans())); - } - -#endif - + // start off by initializing output poses with the underPoses _poses = underPoses; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index ab35186bfd..bd990470e2 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1459,20 +1459,13 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab int elbowJointIndex = _animSkeleton->nameToJointIndex("LeftForeArm"); int oppositeArmJointIndex = _animSkeleton->nameToJointIndex("RightArm"); if (ENABLE_POLE_VECTORS && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0 && oppositeArmJointIndex >= 0) { - #if defined(Q_OS_ANDROID) || defined(USE_Q_OS_ANDROID) - bool isLeft = true; - float poleTheta; - bool usePoleTheta = calculateElbowPoleVectorOptimized(handJointIndex, elbowJointIndex, armJointIndex, isLeft, poleTheta); - if (usePoleTheta) { - _animVars.set("leftHandPoleVectorEnabled", true); - _animVars.set("leftHandPoleReferenceVector", Vectors::UNIT_X); - _animVars.set("thetaLeftElbow", poleTheta); - } else { - _animVars.set("leftHandPoleVectorEnabled", false); - } - #else glm::vec3 poleVector; +#if defined(Q_OS_ANDROID) || defined(USE_Q_OS_ANDROID) + bool isLeft = true; + bool usePoleVector = calculateElbowPoleVectorOptimized(handJointIndex, elbowJointIndex, armJointIndex, isLeft, poleVector); +#else bool usePoleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, oppositeArmJointIndex, poleVector); +#endif if (usePoleVector) { glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector); _animVars.set("leftHandPoleVectorEnabled", true); @@ -1481,8 +1474,6 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab } else { _animVars.set("leftHandPoleVectorEnabled", false); } - #endif - } else { _animVars.set("leftHandPoleVectorEnabled", false); } @@ -1528,21 +1519,13 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab int oppositeArmJointIndex = _animSkeleton->nameToJointIndex("LeftArm"); if (ENABLE_POLE_VECTORS && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0 && oppositeArmJointIndex >= 0) { - - #if defined(Q_OS_ANDROID) || defined(USE_Q_OS_ANDROID) - bool isLeft = false; - float poleTheta; - bool usePoleTheta = calculateElbowPoleVectorOptimized(handJointIndex, elbowJointIndex, armJointIndex, isLeft, poleTheta); - if (usePoleTheta) { - _animVars.set("rightHandPoleVectorEnabled", true); - _animVars.set("rightHandPoleReferenceVector", -Vectors::UNIT_X); - _animVars.set("thetaRightElbow", poleTheta); - } else { - _animVars.set("rightHandPoleVectorEnabled", false); - } - #else glm::vec3 poleVector; +#if defined(Q_OS_ANDROID) || defined(USE_Q_OS_ANDROID) + bool isLeft = false; + bool usePoleVector = calculateElbowPoleVectorOptimized(handJointIndex, elbowJointIndex, armJointIndex, isLeft, poleVector); +#else bool usePoleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, oppositeArmJointIndex, poleVector); +#endif if (usePoleVector) { glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector); _animVars.set("rightHandPoleVectorEnabled", true); @@ -1551,8 +1534,6 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab } else { _animVars.set("rightHandPoleVectorEnabled", false); } - - #endif } else { _animVars.set("rightHandPoleVectorEnabled", false); } @@ -1710,7 +1691,7 @@ void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm } } -bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int shoulderIndex, bool left, float& poleTheta) { +bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int shoulderIndex, bool left, glm::vec3& poleVector) { // get the default poses for the upper and lower arm // then use this length to judge how far the hand is away from the shoulder. // then create weights that make the elbow angle less when the x value is large in either direction. @@ -1735,6 +1716,13 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s const float zWeightBottom = -100.0f; const glm::vec3 weights(-50.0f, 60.0f, 260.0f); glm::vec3 armToHand = handPose.trans() - shoulderPose.trans(); + glm::vec3 unitAxis; + float axisLength = glm::length(armToHand); + if (axisLength > 0.0f) { + unitAxis = armToHand / axisLength; + } else { + unitAxis = Vectors::UNIT_Y; + } float yFactor = (fabsf(armToHand[1] / defaultArmLength) * weights[1]) + biases[1]; float zFactor; @@ -1764,8 +1752,24 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s theta *= -1.0f; } + float deltaTheta = 0.0f; + if (left) { + deltaTheta = theta - _lastThetaLeft; + } else { + deltaTheta = theta - _lastThetaRight; + } + float deltaThetaRadians = (deltaTheta / 180.0f)*PI; + AnimPose deltaRot(glm::angleAxis(deltaThetaRadians, unitAxis), glm::vec3()); + AnimPose relMid = shoulderPose.inverse() * elbowPose; + AnimPose updatedBase = shoulderPose * deltaRot; + AnimPose newAbsMid = updatedBase * relMid; + + // now we calculate the contribution of the hand rotation relative to the arm - glm::quat relativeHandRotation = (elbowPose.inverse() * handPose).rot(); + // we are adding in the delta rotation so that we have the hand correction relative to the + // latest theta for hand position + glm::quat relativeHandRotation = (newAbsMid.inverse() * handPose).rot(); + //glm::quat relativeHandRotation = (elbowPose.inverse() * handPose).rot(); if (relativeHandRotation.w < 0.0f) { relativeHandRotation *= -1.0f; } @@ -1959,6 +1963,7 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s } // global limiting + float thetaRadians = 0.0f; if (left) { // final global smoothing _lastThetaLeft = 0.5f * _lastThetaLeft + 0.5f * theta; @@ -1972,9 +1977,7 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s _lastThetaLeft = glm::sign(_lastThetaLeft) * 175.0f; } // convert to radians and make 180 0 to match pole vector theta - float thetaRadians = ((180.0f - _lastThetaLeft) / 180.0f)*PI; - poleTheta = thetaRadians; - + thetaRadians = ((180.0f - _lastThetaLeft) / 180.0f)*PI; } else { // final global smoothing _lastThetaRight = 0.5f * _lastThetaRight + 0.5f * theta; @@ -1988,26 +1991,32 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s _lastThetaRight = glm::sign(_lastThetaRight) * 175.0f; } // convert to radians and make 180 0 to match pole vector theta - float thetaRadians = ((180.0f - _lastThetaRight) / 180.0f)*PI; - poleTheta = thetaRadians; + thetaRadians = ((180.0f - _lastThetaRight) / 180.0f)*PI; } - float xValue = sin(poleTheta); - float yValue = cos(poleTheta); + float xValue = -1.0f * sin(thetaRadians); + float yValue = -1.0f * cos(thetaRadians); float zValue = 0.0f; glm::vec3 thetaVector(xValue, yValue, zValue); + //glm::vec3 thetaVector(1.0f, 0.0f, 0.0f); + glm::vec3 xAxis = glm::cross(Vectors::UNIT_Y, armToHand); glm::vec3 up = glm::cross(armToHand, xAxis); glm::quat armAxisRotation; glm::vec3 u, v, w; glm::vec3 fwd = armToHand/glm::length(armToHand); - generateBasisVectors(Vectors::UNIT_Y, fwd, u, v, w); - AnimPose armAxisPose(glm::mat4(glm::vec4(w, 0.0f), glm::vec4(u, 0.0f), glm::vec4(v, 0.0f), glm::vec4(glm::vec3(0.0f, 0.0f, 0.0f), 1.0f))); - glm::vec3 pole = armAxisPose * thetaVector; - - qCDebug(animation) << "the pole from theta is " << pole; + generateBasisVectors(fwd, Vectors::UNIT_Y, u, v, w); + AnimPose armAxisPose(glm::mat4(glm::vec4(-w, 0.0f), glm::vec4(v, 0.0f), glm::vec4(u, 0.0f), glm::vec4(glm::vec3(0.0f, 0.0f, 0.0f), 1.0f))); + poleVector = armAxisPose * thetaVector; + if (left) { + qCDebug(animation) << "theta vector " << thetaVector; + //qCDebug(animation) << "fwd " << fwd; + //qCDebug(animation) << "the x is " << w; + //qCDebug(animation) << "the y is " << v; + //qCDebug(animation) << "the z is " << u; + } return true; } @@ -2082,6 +2091,10 @@ bool Rig::calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex, correctionVector = forwardAmount * frontVector; } poleVector = glm::normalize(attenuationVector + fullPoleVector + correctionVector); + + if (handIndex == _animSkeleton->nameToJointIndex("LeftHand")) { + qCDebug(animation) << "the pole vector is " << poleVector; + } return true; } diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 4dce28f7ae..693aa732fa 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -258,7 +258,7 @@ protected: void calcAnimAlpha(float speed, const std::vector& referenceSpeeds, float* alphaOut) const; bool calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex, int oppositeArmIndex, glm::vec3& poleVector) const; - bool calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int shoulderIndex, bool left, float& poleTheta); + bool calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int shoulderIndex, bool left, glm::vec3& poleVector); glm::vec3 calculateKneePoleVector(int footJointIndex, int kneeJoint, int upLegIndex, int hipsIndex, const AnimPose& targetFootPose) const; glm::vec3 deflectHandFromTorso(const glm::vec3& handPosition, const HFMJointShapeInfo& hipsShapeInfo, const HFMJointShapeInfo& spineShapeInfo, const HFMJointShapeInfo& spine1ShapeInfo, const HFMJointShapeInfo& spine2ShapeInfo) const; From e1dfd7d28889ce5c6d18e0424aef56a1f0f333ba Mon Sep 17 00:00:00 2001 From: Angus Antley Date: Sat, 16 Feb 2019 23:40:16 -0800 Subject: [PATCH 115/474] cleanup white space --- libraries/animation/src/AnimNodeLoader.cpp | 1 - .../src/AnimPoleVectorConstraint.cpp | 5 +- libraries/animation/src/AnimSplineIK.cpp | 1 - libraries/animation/src/AnimSplineIK.h | 2 +- libraries/animation/src/Rig.cpp | 51 ++++++++----------- libraries/animation/src/Rig.h | 8 +-- 6 files changed, 25 insertions(+), 43 deletions(-) diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 3518fe14e5..b637d131f8 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -42,7 +42,6 @@ static AnimNode::Pointer loadManipulatorNode(const QJsonObject& jsonObj, const Q static AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer loadDefaultPoseNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer loadTwoBoneIKNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); -static AnimNode::Pointer loadArmIKNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer loadSplineIKNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer loadPoleVectorConstraintNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); diff --git a/libraries/animation/src/AnimPoleVectorConstraint.cpp b/libraries/animation/src/AnimPoleVectorConstraint.cpp index 7b60d6984b..f017fe2348 100644 --- a/libraries/animation/src/AnimPoleVectorConstraint.cpp +++ b/libraries/animation/src/AnimPoleVectorConstraint.cpp @@ -13,7 +13,6 @@ #include "AnimUtil.h" #include "GLMHelpers.h" -#define USE_Q_OS_ANDROID const float FRAMES_PER_SECOND = 30.0f; const float INTERP_DURATION = 6.0f; @@ -122,8 +121,6 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim float sideDot = glm::dot(poleVector, sideVector); float theta = copysignf(1.0f, sideDot) * acosf(dot); - - glm::quat deltaRot = glm::angleAxis(theta, unitAxis); // transform result back into parent relative frame. @@ -133,7 +130,7 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim glm::quat relTipRot = glm::inverse(midPose.rot()) * glm::inverse(deltaRot) * tipPose.rot(); ikChain.setRelativePoseAtJointIndex(_tipJointIndex, AnimPose(relTipRot, underPoses[_tipJointIndex].trans())); } - + // start off by initializing output poses with the underPoses _poses = underPoses; diff --git a/libraries/animation/src/AnimSplineIK.cpp b/libraries/animation/src/AnimSplineIK.cpp index 72dcbfc5e7..30e0a42e65 100644 --- a/libraries/animation/src/AnimSplineIK.cpp +++ b/libraries/animation/src/AnimSplineIK.cpp @@ -14,7 +14,6 @@ #include #include "AnimUtil.h" -static const float JOINT_CHAIN_INTERP_TIME = 0.5f; static const float FRAMES_PER_SECOND = 30.0f; AnimSplineIK::AnimSplineIK(const QString& id, float alpha, bool enabled, float interpDuration, diff --git a/libraries/animation/src/AnimSplineIK.h b/libraries/animation/src/AnimSplineIK.h index bca0f7fe77..a4d8da37ca 100644 --- a/libraries/animation/src/AnimSplineIK.h +++ b/libraries/animation/src/AnimSplineIK.h @@ -57,8 +57,8 @@ protected: bool _enabled; float _interpDuration; QString _baseJointName; - QString _tipJointName; QString _midJointName; + QString _tipJointName; QString _basePositionVar; QString _baseRotationVar; QString _midPositionVar; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index bd990470e2..ee8daef668 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1462,7 +1462,7 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab glm::vec3 poleVector; #if defined(Q_OS_ANDROID) || defined(USE_Q_OS_ANDROID) bool isLeft = true; - bool usePoleVector = calculateElbowPoleVectorOptimized(handJointIndex, elbowJointIndex, armJointIndex, isLeft, poleVector); + bool usePoleVector = calculateElbowPoleVectorOptimized(handJointIndex, elbowJointIndex, armJointIndex, isLeft, poleVector); #else bool usePoleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, oppositeArmJointIndex, poleVector); #endif @@ -1727,9 +1727,9 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s float zFactor; if (armToHand[1] > 0.0f) { - zFactor = weights[2] * glm::max(zStart - (armToHand[2] / defaultArmLength), 0.0f) * fabs(armToHand[1] / defaultArmLength); + zFactor = weights[2] * glm::max(zStart - (armToHand[2] / defaultArmLength), 0.0f) * fabsf(armToHand[1] / defaultArmLength); } else { - zFactor = zWeightBottom * glm::max(zStart - (armToHand[2] / defaultArmLength), 0.0f) * fabs(armToHand[1] / defaultArmLength); + zFactor = zWeightBottom * glm::max(zStart - (armToHand[2] / defaultArmLength), 0.0f) * fabsf(armToHand[1] / defaultArmLength); } float xFactor; @@ -1766,8 +1766,8 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s // now we calculate the contribution of the hand rotation relative to the arm - // we are adding in the delta rotation so that we have the hand correction relative to the - // latest theta for hand position + // we are adding in the delta rotation so that we have the hand correction relative to the + // latest theta for hand position glm::quat relativeHandRotation = (newAbsMid.inverse() * handPose).rot(); //glm::quat relativeHandRotation = (elbowPose.inverse() * handPose).rot(); if (relativeHandRotation.w < 0.0f) { @@ -1852,7 +1852,6 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s //qCDebug(animation) << "flex ave: " << (_flexThetaRunningAverageRight / PI) * 180.0f << " twist ave: " << (_twistThetaRunningAverageRight / PI) * 180.0f << " ulnar deviation ave: " << (_ulnarRadialThetaRunningAverageRight / PI) * 180.0f; } - const float POWER = 2.0f; const float FLEX_BOUNDARY = PI / 6.0f; const float EXTEND_BOUNDARY = -PI / 4.0f; float flexCorrection = 0.0f; @@ -1862,7 +1861,7 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s } else if (_flexThetaRunningAverageLeft < EXTEND_BOUNDARY) { flexCorrection = ((_flexThetaRunningAverageLeft - EXTEND_BOUNDARY) / PI) * 180.0f; } - if (fabs(flexCorrection) > 30.0f) { + if (fabsf(flexCorrection) > 30.0f) { flexCorrection = glm::sign(flexCorrection) * 30.0f; } theta += flexCorrection; @@ -1872,7 +1871,7 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s } else if (_flexThetaRunningAverageRight < EXTEND_BOUNDARY) { flexCorrection = ((_flexThetaRunningAverageRight - EXTEND_BOUNDARY) / PI) * 180.0f; } - if (fabs(flexCorrection) > 30.0f) { + if (fabsf(flexCorrection) > 30.0f) { flexCorrection = glm::sign(flexCorrection) * 30.0f; } theta -= flexCorrection; @@ -1888,23 +1887,23 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s } else if (_ulnarRadialThetaRunningAverageLeft < ULNAR_BOUNDARY_MINUS) { ulnarDiff = _ulnarRadialThetaRunningAverageLeft - ULNAR_BOUNDARY_MINUS; } - if (fabs(ulnarDiff) > 0.0f) { - float twistCoefficient = (fabs(_twistThetaRunningAverageLeft) / (PI / 20.0f)); + if (fabsf(ulnarDiff) > 0.0f) { + float twistCoefficient = (fabsf(_twistThetaRunningAverageLeft) / (PI / 20.0f)); if (twistCoefficient > 1.0f) { twistCoefficient = 1.0f; } if (left) { if (trueTwistTheta < 0.0f) { - ulnarCorrection -= glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 40.0f * twistCoefficient; + ulnarCorrection -= glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 40.0f * twistCoefficient; } else { - ulnarCorrection += glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 40.0f * twistCoefficient; + ulnarCorrection += glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 40.0f * twistCoefficient; } } else { // right hand if (trueTwistTheta > 0.0f) { - ulnarCorrection -= glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 40.0f * twistCoefficient; + ulnarCorrection -= glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 40.0f * twistCoefficient; } else { - ulnarCorrection += glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 40.0f * twistCoefficient; + ulnarCorrection += glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 40.0f * twistCoefficient; } } if (fabsf(ulnarCorrection) > 20.0f) { @@ -1918,23 +1917,23 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s } else if (_ulnarRadialThetaRunningAverageRight < ULNAR_BOUNDARY_MINUS) { ulnarDiff = _ulnarRadialThetaRunningAverageRight - ULNAR_BOUNDARY_MINUS; } - if (fabs(ulnarDiff) > 0.0f) { - float twistCoefficient = (fabs(_twistThetaRunningAverageRight) / (PI / 20.0f)); + if (fabsf(ulnarDiff) > 0.0f) { + float twistCoefficient = (fabsf(_twistThetaRunningAverageRight) / (PI / 20.0f)); if (twistCoefficient > 1.0f) { twistCoefficient = 1.0f; } if (left) { if (trueTwistTheta < 0.0f) { - ulnarCorrection -= glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 40.0f * twistCoefficient; + ulnarCorrection -= glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 40.0f * twistCoefficient; } else { - ulnarCorrection += glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 40.0f * twistCoefficient; + ulnarCorrection += glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 40.0f * twistCoefficient; } } else { // right hand if (trueTwistTheta > 0.0f) { - ulnarCorrection -= glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 40.0f * twistCoefficient; + ulnarCorrection -= glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 40.0f * twistCoefficient; } else { - ulnarCorrection += glm::sign(ulnarDiff) * (fabs(ulnarDiff) / PI) * 40.0f * twistCoefficient; + ulnarCorrection += glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 40.0f * twistCoefficient; } } if (fabsf(ulnarCorrection) > 20.0f) { @@ -1998,15 +1997,12 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s float yValue = -1.0f * cos(thetaRadians); float zValue = 0.0f; glm::vec3 thetaVector(xValue, yValue, zValue); - //glm::vec3 thetaVector(1.0f, 0.0f, 0.0f); - glm::vec3 xAxis = glm::cross(Vectors::UNIT_Y, armToHand); - glm::vec3 up = glm::cross(armToHand, xAxis); - glm::quat armAxisRotation; - glm::vec3 u, v, w; + glm::vec3 up = Vectors::UNIT_Y; glm::vec3 fwd = armToHand/glm::length(armToHand); + glm::vec3 u, v, w; - generateBasisVectors(fwd, Vectors::UNIT_Y, u, v, w); + generateBasisVectors(fwd, up, u, v, w); AnimPose armAxisPose(glm::mat4(glm::vec4(-w, 0.0f), glm::vec4(v, 0.0f), glm::vec4(u, 0.0f), glm::vec4(glm::vec3(0.0f, 0.0f, 0.0f), 1.0f))); poleVector = armAxisPose * thetaVector; @@ -2092,9 +2088,6 @@ bool Rig::calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex, } poleVector = glm::normalize(attenuationVector + fullPoleVector + correctionVector); - if (handIndex == _animSkeleton->nameToJointIndex("LeftHand")) { - qCDebug(animation) << "the pole vector is " << poleVector; - } return true; } diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 693aa732fa..94f18d789a 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -415,12 +415,6 @@ protected: glm::vec3 _prevLeftFootPoleVector { Vectors::UNIT_Z }; // sensor space bool _prevLeftFootPoleVectorValid { false }; - glm::vec3 _prevRightHandPoleVector { Vectors::UNIT_Z }; // sensor space - bool _prevRightHandPoleVectorValid { false }; - - glm::vec3 _prevLeftHandPoleVector { Vectors::UNIT_Z }; // sensor space - bool _prevLeftHandPoleVectorValid { false }; - int _rigId; bool _headEnabled { false }; bool _computeNetworkAnimation { false }; @@ -429,7 +423,7 @@ protected: float _twistThetaRunningAverageLeft { 0.0f }; float _flexThetaRunningAverageLeft { 0.0f }; float _ulnarRadialThetaRunningAverageLeft { 0.0f }; - float _twistThetaRunningAverageRight{ 0.0f }; + float _twistThetaRunningAverageRight { 0.0f }; float _flexThetaRunningAverageRight { 0.0f }; float _ulnarRadialThetaRunningAverageRight { 0.0f }; float _lastThetaLeft { 0.0f }; From 748368bfdac72aac4d900ff430eb686d2419d483 Mon Sep 17 00:00:00 2001 From: Angus Antley Date: Sun, 17 Feb 2019 23:32:52 -0800 Subject: [PATCH 116/474] mid tweak on the wrist and position coeffs --- interface/src/avatar/MySkeletonModel.cpp | 7 +- .../src/AnimPoleVectorConstraint.cpp | 2 + libraries/animation/src/Rig.cpp | 71 ++++++++++--------- 3 files changed, 46 insertions(+), 34 deletions(-) diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 51a2c3767b..32a8e1e38d 100755 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -15,6 +15,8 @@ #include "InterfaceLogging.h" #include "AnimUtil.h" +#define USE_Q_OS_ANDROID + MySkeletonModel::MySkeletonModel(Avatar* owningAvatar, QObject* parent) : SkeletonModel(owningAvatar, parent) { } @@ -250,8 +252,9 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { AnimPose headAvatarSpace(avatarHeadPose.getRotation(), avatarHeadPose.getTranslation()); AnimPose headRigSpace = avatarToRigPose * headAvatarSpace; AnimPose hipsRigSpace = sensorToRigPose * sensorHips; +#if defined(Q_OS_ANDROID) || defined(USE_Q_OS_ANDROID) glm::vec3 spine2TargetTranslation = computeSpine2WithHeadHipsSpline(myAvatar, hipsRigSpace, headRigSpace); - +#endif const float SPINE2_ROTATION_FILTER = 0.5f; AnimPose currentSpine2Pose; AnimPose currentHeadPose; @@ -273,7 +276,9 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { } generateBasisVectors(up, fwd, u, v, w); AnimPose newSpinePose(glm::mat4(glm::vec4(w, 0.0f), glm::vec4(u, 0.0f), glm::vec4(v, 0.0f), glm::vec4(glm::vec3(0.0f, 0.0f, 0.0f), 1.0f))); +#if defined(Q_OS_ANDROID) || defined(USE_Q_OS_ANDROID) currentSpine2Pose.trans() = spine2TargetTranslation; +#endif currentSpine2Pose.rot() = safeLerp(currentSpine2Pose.rot(), newSpinePose.rot(), SPINE2_ROTATION_FILTER); params.primaryControllerPoses[Rig::PrimaryControllerType_Spine2] = currentSpine2Pose; params.primaryControllerFlags[Rig::PrimaryControllerType_Spine2] = (uint8_t)Rig::ControllerFlags::Enabled | (uint8_t)Rig::ControllerFlags::Estimated; diff --git a/libraries/animation/src/AnimPoleVectorConstraint.cpp b/libraries/animation/src/AnimPoleVectorConstraint.cpp index f017fe2348..3745b1ab1f 100644 --- a/libraries/animation/src/AnimPoleVectorConstraint.cpp +++ b/libraries/animation/src/AnimPoleVectorConstraint.cpp @@ -123,6 +123,8 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim glm::quat deltaRot = glm::angleAxis(theta, unitAxis); + //qCDebug(animation) << "anim ik theta " << (theta/PI)*180.0f; + // transform result back into parent relative frame. glm::quat relBaseRot = glm::inverse(baseParentPose.rot()) * deltaRot * basePose.rot(); ikChain.setRelativePoseAtJointIndex(_baseJointIndex, AnimPose(relBaseRot, underPoses[_baseJointIndex].trans())); diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index ee8daef668..a7c0e60700 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1714,7 +1714,7 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s const glm::vec3 biases(0.0f, 135.0f, 0.0f); // weights const float zWeightBottom = -100.0f; - const glm::vec3 weights(-50.0f, 60.0f, 260.0f); + const glm::vec3 weights(-50.0f, 60.0f, 90.0f); glm::vec3 armToHand = handPose.trans() - shoulderPose.trans(); glm::vec3 unitAxis; float axisLength = glm::length(armToHand); @@ -1724,22 +1724,24 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s unitAxis = Vectors::UNIT_Y; } float yFactor = (fabsf(armToHand[1] / defaultArmLength) * weights[1]) + biases[1]; - + float zFactor; if (armToHand[1] > 0.0f) { - zFactor = weights[2] * glm::max(zStart - (armToHand[2] / defaultArmLength), 0.0f) * fabsf(armToHand[1] / defaultArmLength); + zFactor = weights[2] * glm::max(zStart - (armToHand[2] / defaultArmLength), 0.0f) * glm::max(fabsf((armToHand[1] - 0.1f) / defaultArmLength), 0.0f); } else { zFactor = zWeightBottom * glm::max(zStart - (armToHand[2] / defaultArmLength), 0.0f) * fabsf(armToHand[1] / defaultArmLength); } float xFactor; if (left) { - xFactor = weights[0] * glm::max(-1.0f * (armToHand[0] / defaultArmLength) + xStart, 0.0f); + //xFactor = weights[0] * glm::max(-1.0f * (armToHand[0] / defaultArmLength) + xStart, 0.0f); + xFactor = weights[0] * ((-1.0f * (armToHand[0] / defaultArmLength) + xStart) - (0.2f) * ((1.0f + (armToHand[1] / defaultArmLength)) / 2.0f)); } else { - xFactor = weights[0] * (((armToHand[0] / defaultArmLength) + xStart) - (0.3f) * ((1.0f + (armToHand[1] / defaultArmLength)) / 2.0f)); + xFactor = weights[0] * (((armToHand[0] / defaultArmLength) + xStart) - (0.2f) * ((1.0f + (armToHand[1] / defaultArmLength)) / 2.0f)); } - + float theta = xFactor + yFactor + zFactor; + //float theta = yFactor; if (theta < 13.0f) { theta = 13.0f; @@ -1764,12 +1766,12 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s AnimPose updatedBase = shoulderPose * deltaRot; AnimPose newAbsMid = updatedBase * relMid; - + /* // now we calculate the contribution of the hand rotation relative to the arm // we are adding in the delta rotation so that we have the hand correction relative to the // latest theta for hand position - glm::quat relativeHandRotation = (newAbsMid.inverse() * handPose).rot(); - //glm::quat relativeHandRotation = (elbowPose.inverse() * handPose).rot(); + //glm::quat relativeHandRotation = (newAbsMid.inverse() * handPose).rot(); + glm::quat relativeHandRotation = (elbowPose.inverse() * handPose).rot(); if (relativeHandRotation.w < 0.0f) { relativeHandRotation *= -1.0f; } @@ -1795,9 +1797,9 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s ulnarDeviationTheta = -1.0f * ulnarDeviationTheta; } // put some smoothing on the theta - _ulnarRadialThetaRunningAverageRight = 0.5f * _ulnarRadialThetaRunningAverageRight + 0.5f * ulnarDeviationTheta; + _ulnarRadialThetaRunningAverageRight = 0.75f * _ulnarRadialThetaRunningAverageRight + 0.25f * ulnarDeviationTheta; } - + //get the flex/extension of the wrist rotation glm::quat flex; glm::quat nonFlex; @@ -1823,7 +1825,7 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s // put some smoothing on the theta _flexThetaRunningAverageRight = 0.5f * _flexThetaRunningAverageRight + 0.5f * flexTheta; } - + glm::quat twist; glm::quat nonTwist; swingTwistDecomposition(relativeHandRotation, Vectors::UNIT_Y, nonTwist, twist); @@ -1847,11 +1849,11 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s // put some smoothing on the theta _twistThetaRunningAverageRight = 0.5f * _twistThetaRunningAverageRight + 0.5f * trueTwistTheta; } - + if (!left) { - //qCDebug(animation) << "flex ave: " << (_flexThetaRunningAverageRight / PI) * 180.0f << " twist ave: " << (_twistThetaRunningAverageRight / PI) * 180.0f << " ulnar deviation ave: " << (_ulnarRadialThetaRunningAverageRight / PI) * 180.0f; + qCDebug(animation) << "flex ave: " << (_flexThetaRunningAverageRight / PI) * 180.0f << " twist ave: " << (_twistThetaRunningAverageRight / PI) * 180.0f << " ulnar deviation ave: " << (_ulnarRadialThetaRunningAverageRight / PI) * 180.0f; } - + const float FLEX_BOUNDARY = PI / 6.0f; const float EXTEND_BOUNDARY = -PI / 4.0f; float flexCorrection = 0.0f; @@ -1876,16 +1878,17 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s } theta -= flexCorrection; } + const float ULNAR_BOUNDARY_MINUS = -PI / 6.0f; - const float ULNAR_BOUNDARY_PLUS = PI / 6.0f; + const float ULNAR_BOUNDARY_PLUS = PI / 4.0f; float ulnarDiff = 0.0f; float ulnarCorrection = 0.0f; if (left) { - if (_ulnarRadialThetaRunningAverageLeft > ULNAR_BOUNDARY_PLUS) { - ulnarDiff = _ulnarRadialThetaRunningAverageLeft - ULNAR_BOUNDARY_PLUS; - } else if (_ulnarRadialThetaRunningAverageLeft < ULNAR_BOUNDARY_MINUS) { - ulnarDiff = _ulnarRadialThetaRunningAverageLeft - ULNAR_BOUNDARY_MINUS; + if (_ulnarRadialThetaRunningAverageLeft > -ULNAR_BOUNDARY_MINUS) { + ulnarDiff = _ulnarRadialThetaRunningAverageLeft + ULNAR_BOUNDARY_MINUS; + } else if (_ulnarRadialThetaRunningAverageLeft < -ULNAR_BOUNDARY_PLUS) { + ulnarDiff = _ulnarRadialThetaRunningAverageLeft + ULNAR_BOUNDARY_PLUS; } if (fabsf(ulnarDiff) > 0.0f) { float twistCoefficient = (fabsf(_twistThetaRunningAverageLeft) / (PI / 20.0f)); @@ -1893,17 +1896,17 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s twistCoefficient = 1.0f; } if (left) { - if (trueTwistTheta < 0.0f) { - ulnarCorrection -= glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 40.0f * twistCoefficient; + if (_twistThetaRunningAverageLeft < 0.0f) { + ulnarCorrection -= glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 80.0f * twistCoefficient; } else { - ulnarCorrection += glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 40.0f * twistCoefficient; + ulnarCorrection += glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 80.0f * twistCoefficient; } } else { // right hand - if (trueTwistTheta > 0.0f) { - ulnarCorrection -= glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 40.0f * twistCoefficient; + if (_twistThetaRunningAverageLeft > 0.0f) { + ulnarCorrection -= glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 80.0f * twistCoefficient; } else { - ulnarCorrection += glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 40.0f * twistCoefficient; + ulnarCorrection += glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 80.0f * twistCoefficient; } } if (fabsf(ulnarCorrection) > 20.0f) { @@ -1923,17 +1926,17 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s twistCoefficient = 1.0f; } if (left) { - if (trueTwistTheta < 0.0f) { - ulnarCorrection -= glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 40.0f * twistCoefficient; + if (_twistThetaRunningAverageRight < 0.0f) { + ulnarCorrection -= glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 80.0f * twistCoefficient; } else { - ulnarCorrection += glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 40.0f * twistCoefficient; + ulnarCorrection += glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 80.0f * twistCoefficient; } } else { // right hand - if (trueTwistTheta > 0.0f) { - ulnarCorrection -= glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 40.0f * twistCoefficient; + if (_twistThetaRunningAverageRight > 0.0f) { + ulnarCorrection -= glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 80.0f * twistCoefficient; } else { - ulnarCorrection += glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 40.0f * twistCoefficient; + ulnarCorrection += glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 80.0f * twistCoefficient; } } if (fabsf(ulnarCorrection) > 20.0f) { @@ -1943,6 +1946,7 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s } } + const float TWIST_DEADZONE = (4 * PI) / 9.0f; float twistCorrection = 0.0f; if (left) { @@ -1960,6 +1964,7 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s } else { theta += twistCorrection; } + */ // global limiting float thetaRadians = 0.0f; @@ -2007,7 +2012,7 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s poleVector = armAxisPose * thetaVector; if (left) { - qCDebug(animation) << "theta vector " << thetaVector; + //qCDebug(animation) << "theta vector " << thetaVector; //qCDebug(animation) << "fwd " << fwd; //qCDebug(animation) << "the x is " << w; //qCDebug(animation) << "the y is " << v; From f2301e7dac060eab7ade9fe59c813acca5911295 Mon Sep 17 00:00:00 2001 From: Angus Antley Date: Tue, 19 Feb 2019 07:35:13 -0800 Subject: [PATCH 117/474] fixed relative wrist correction problem --- .../src/AnimPoleVectorConstraint.cpp | 6 +- libraries/animation/src/Rig.cpp | 93 ++++++++----------- libraries/animation/src/Rig.h | 2 + 3 files changed, 45 insertions(+), 56 deletions(-) diff --git a/libraries/animation/src/AnimPoleVectorConstraint.cpp b/libraries/animation/src/AnimPoleVectorConstraint.cpp index 3745b1ab1f..afcf9b29ee 100644 --- a/libraries/animation/src/AnimPoleVectorConstraint.cpp +++ b/libraries/animation/src/AnimPoleVectorConstraint.cpp @@ -117,13 +117,15 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim if (axisLength > MIN_LENGTH && refVectorLength > MIN_LENGTH && sideVectorLength > MIN_LENGTH && refVectorProjLength > MIN_LENGTH && poleVectorProjLength > MIN_LENGTH) { - float dot = glm::clamp(glm::dot(refVectorProj / refVectorProjLength, poleVectorProj / poleVectorProjLength), 0.0f, 1.0f); + float dot = glm::clamp(glm::dot(refVectorProj / refVectorProjLength, poleVectorProj / poleVectorProjLength), -1.0f, 1.0f); float sideDot = glm::dot(poleVector, sideVector); float theta = copysignf(1.0f, sideDot) * acosf(dot); glm::quat deltaRot = glm::angleAxis(theta, unitAxis); - //qCDebug(animation) << "anim ik theta " << (theta/PI)*180.0f; + if (_tipJointName == "RightHand") { + qCDebug(animation) << "anim ik theta " << (theta / PI)*180.0f; + } // transform result back into parent relative frame. glm::quat relBaseRot = glm::inverse(baseParentPose.rot()) * deltaRot * basePose.rot(); diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index a7c0e60700..48ffdb4970 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1723,8 +1723,9 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s } else { unitAxis = Vectors::UNIT_Y; } + float yFactor = (fabsf(armToHand[1] / defaultArmLength) * weights[1]) + biases[1]; - + float zFactor; if (armToHand[1] > 0.0f) { zFactor = weights[2] * glm::max(zStart - (armToHand[2] / defaultArmLength), 0.0f) * glm::max(fabsf((armToHand[1] - 0.1f) / defaultArmLength), 0.0f); @@ -1739,7 +1740,7 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s } else { xFactor = weights[0] * (((armToHand[0] / defaultArmLength) + xStart) - (0.2f) * ((1.0f + (armToHand[1] / defaultArmLength)) / 2.0f)); } - + float theta = xFactor + yFactor + zFactor; //float theta = yFactor; @@ -1766,7 +1767,7 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s AnimPose updatedBase = shoulderPose * deltaRot; AnimPose newAbsMid = updatedBase * relMid; - /* + // now we calculate the contribution of the hand rotation relative to the arm // we are adding in the delta rotation so that we have the hand correction relative to the // latest theta for hand position @@ -1799,7 +1800,7 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s // put some smoothing on the theta _ulnarRadialThetaRunningAverageRight = 0.75f * _ulnarRadialThetaRunningAverageRight + 0.25f * ulnarDeviationTheta; } - + //get the flex/extension of the wrist rotation glm::quat flex; glm::quat nonFlex; @@ -1825,7 +1826,7 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s // put some smoothing on the theta _flexThetaRunningAverageRight = 0.5f * _flexThetaRunningAverageRight + 0.5f * flexTheta; } - + glm::quat twist; glm::quat nonTwist; swingTwistDecomposition(relativeHandRotation, Vectors::UNIT_Y, nonTwist, twist); @@ -1849,13 +1850,11 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s // put some smoothing on the theta _twistThetaRunningAverageRight = 0.5f * _twistThetaRunningAverageRight + 0.5f * trueTwistTheta; } - - if (!left) { - qCDebug(animation) << "flex ave: " << (_flexThetaRunningAverageRight / PI) * 180.0f << " twist ave: " << (_twistThetaRunningAverageRight / PI) * 180.0f << " ulnar deviation ave: " << (_ulnarRadialThetaRunningAverageRight / PI) * 180.0f; - } - - const float FLEX_BOUNDARY = PI / 6.0f; - const float EXTEND_BOUNDARY = -PI / 4.0f; + + + float currentWristCoefficient = 0.0f; + const float FLEX_BOUNDARY = 0.0f; // PI / 6.0f; + const float EXTEND_BOUNDARY = 0.0f; //-PI / 4.0f; float flexCorrection = 0.0f; if (left) { if (_flexThetaRunningAverageLeft > FLEX_BOUNDARY) { @@ -1863,22 +1862,21 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s } else if (_flexThetaRunningAverageLeft < EXTEND_BOUNDARY) { flexCorrection = ((_flexThetaRunningAverageLeft - EXTEND_BOUNDARY) / PI) * 180.0f; } - if (fabsf(flexCorrection) > 30.0f) { - flexCorrection = glm::sign(flexCorrection) * 30.0f; + if (fabsf(flexCorrection) > 175.0f) { + flexCorrection = glm::sign(flexCorrection) * 175.0f; } - theta += flexCorrection; + currentWristCoefficient += flexCorrection; } else { if (_flexThetaRunningAverageRight > FLEX_BOUNDARY) { flexCorrection = ((_flexThetaRunningAverageRight - FLEX_BOUNDARY) / PI) * 180.0f; } else if (_flexThetaRunningAverageRight < EXTEND_BOUNDARY) { flexCorrection = ((_flexThetaRunningAverageRight - EXTEND_BOUNDARY) / PI) * 180.0f; } - if (fabsf(flexCorrection) > 30.0f) { - flexCorrection = glm::sign(flexCorrection) * 30.0f; + if (fabsf(flexCorrection) > 175.0f) { + flexCorrection = glm::sign(flexCorrection) * 175.0f; } - theta -= flexCorrection; + currentWristCoefficient -= flexCorrection; } - const float ULNAR_BOUNDARY_MINUS = -PI / 6.0f; const float ULNAR_BOUNDARY_PLUS = PI / 4.0f; @@ -1901,18 +1899,11 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s } else { ulnarCorrection += glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 80.0f * twistCoefficient; } - } else { - // right hand - if (_twistThetaRunningAverageLeft > 0.0f) { - ulnarCorrection -= glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 80.0f * twistCoefficient; - } else { - ulnarCorrection += glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 80.0f * twistCoefficient; - } } if (fabsf(ulnarCorrection) > 20.0f) { ulnarCorrection = glm::sign(ulnarCorrection) * 20.0f; } - theta += ulnarCorrection; + currentWristCoefficient += ulnarCorrection; } } else { if (_ulnarRadialThetaRunningAverageRight > ULNAR_BOUNDARY_PLUS) { @@ -1931,22 +1922,14 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s } else { ulnarCorrection += glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 80.0f * twistCoefficient; } - } else { - // right hand - if (_twistThetaRunningAverageRight > 0.0f) { - ulnarCorrection -= glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 80.0f * twistCoefficient; - } else { - ulnarCorrection += glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 80.0f * twistCoefficient; - } } if (fabsf(ulnarCorrection) > 20.0f) { ulnarCorrection = glm::sign(ulnarCorrection) * 20.0f; } - theta += ulnarCorrection; + currentWristCoefficient += ulnarCorrection; } } - const float TWIST_DEADZONE = (4 * PI) / 9.0f; float twistCorrection = 0.0f; if (left) { @@ -1960,11 +1943,24 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s } // limit the twist correction if (fabsf(twistCorrection) > 30.0f) { - theta += glm::sign(twistCorrection) * 30.0f; + currentWristCoefficient += glm::sign(twistCorrection) * 30.0f; } else { - theta += twistCorrection; + currentWristCoefficient += twistCorrection; + } + + if (left) { + _lastWristCoefficientLeft = _lastThetaLeft - theta; + _lastWristCoefficientLeft += currentWristCoefficient; + theta += _lastWristCoefficientLeft; + } else { + _lastWristCoefficientRight = _lastThetaRight - theta; + _lastWristCoefficientRight += currentWristCoefficient; + theta += _lastWristCoefficientRight; + } + + if (left) { + qCDebug(animation) << "theta " << theta << "Last wrist" << _lastWristCoefficientLeft << " flex ave: " << (_flexThetaRunningAverageLeft / PI) * 180.0f << " twist ave: " << (_twistThetaRunningAverageLeft / PI) * 180.0f << " ulnar deviation ave: " << (_ulnarRadialThetaRunningAverageLeft / PI) * 180.0f; } - */ // global limiting float thetaRadians = 0.0f; @@ -1973,9 +1969,7 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s _lastThetaLeft = 0.5f * _lastThetaLeft + 0.5f * theta; if (fabsf(_lastThetaLeft) < 50.0f) { - if (fabsf(_lastThetaLeft) < 50.0f) { - _lastThetaLeft = glm::sign(_lastThetaLeft) * 50.0f; - } + _lastThetaLeft = glm::sign(_lastThetaLeft) * 50.0f; } if (fabsf(_lastThetaLeft) > 175.0f) { _lastThetaLeft = glm::sign(_lastThetaLeft) * 175.0f; @@ -1986,10 +1980,9 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s // final global smoothing _lastThetaRight = 0.5f * _lastThetaRight + 0.5f * theta; - if (fabsf(_lastThetaRight) < 50.0f) { - if (fabsf(_lastThetaRight) < 50.0f) { - _lastThetaRight = glm::sign(_lastThetaRight) * 50.0f; - } + + if (fabsf(_lastThetaRight) < 10.0f) { + _lastThetaRight = glm::sign(_lastThetaRight) * 10.0f; } if (fabsf(_lastThetaRight) > 175.0f) { _lastThetaRight = glm::sign(_lastThetaRight) * 175.0f; @@ -2011,14 +2004,6 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s AnimPose armAxisPose(glm::mat4(glm::vec4(-w, 0.0f), glm::vec4(v, 0.0f), glm::vec4(u, 0.0f), glm::vec4(glm::vec3(0.0f, 0.0f, 0.0f), 1.0f))); poleVector = armAxisPose * thetaVector; - if (left) { - //qCDebug(animation) << "theta vector " << thetaVector; - //qCDebug(animation) << "fwd " << fwd; - //qCDebug(animation) << "the x is " << w; - //qCDebug(animation) << "the y is " << v; - //qCDebug(animation) << "the z is " << u; - } - return true; } diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 94f18d789a..7b9c3d238d 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -428,6 +428,8 @@ protected: float _ulnarRadialThetaRunningAverageRight { 0.0f }; float _lastThetaLeft { 0.0f }; float _lastThetaRight { 0.0f }; + float _lastWristCoefficientRight { 0.0f }; + float _lastWristCoefficientLeft { 0.0f }; AnimContext _lastContext; AnimVariantMap _lastAnimVars; From 04e57d0dd1af9a90836c2b549f62f0bf8e0b10f7 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Tue, 19 Feb 2019 17:45:46 -0700 Subject: [PATCH 118/474] No Rig pointer on Flow class, solve network animations and fixed bug --- interface/src/avatar/MyAvatar.cpp | 8 +- libraries/animation/src/Flow.cpp | 419 ++++++++++-------- libraries/animation/src/Flow.h | 87 +++- libraries/animation/src/Rig.cpp | 43 +- libraries/animation/src/Rig.h | 8 +- .../src/avatars-renderer/Avatar.cpp | 11 +- 6 files changed, 355 insertions(+), 221 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index f2e69ab5d3..f7c1052354 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -5317,15 +5317,17 @@ void MyAvatar::addAvatarHandsToFlow(const std::shared_ptr& otherAvatar) auto &flow = _skeletonModel->getRig().getFlow(); for (auto &handJointName : HAND_COLLISION_JOINTS) { int jointIndex = otherAvatar->getJointIndex(handJointName); - glm::vec3 position = otherAvatar->getJointPosition(jointIndex); - flow.setOthersCollision(otherAvatar->getID(), jointIndex, position); + if (jointIndex != -1) { + glm::vec3 position = otherAvatar->getJointPosition(jointIndex); + flow.setOthersCollision(otherAvatar->getID(), jointIndex, position); + } } } void MyAvatar::useFlow(bool isActive, bool isCollidable, const QVariantMap& physicsConfig, const QVariantMap& collisionsConfig) { if (_skeletonModel->isLoaded()) { + _skeletonModel->getRig().initFlow(isActive); auto &flow = _skeletonModel->getRig().getFlow(); - flow.init(isActive, isCollidable); auto &collisionSystem = flow.getCollisionSystem(); collisionSystem.setActive(isCollidable); auto physicsGroups = physicsConfig.keys(); diff --git a/libraries/animation/src/Flow.cpp b/libraries/animation/src/Flow.cpp index 974dd8dc54..86252b698d 100644 --- a/libraries/animation/src/Flow.cpp +++ b/libraries/animation/src/Flow.cpp @@ -197,7 +197,7 @@ void FlowNode::update(float deltaTime, const glm::vec3& accelerationOffset) { if (!_anchored) { // Add inertia const float FPS = 60.0f; - float timeRatio = (FPS * deltaTime); + float timeRatio = _scale * (FPS * deltaTime); float invertedTimeRatio = timeRatio > 0.0f ? 1.0f / timeRatio : 1.0f; auto deltaVelocity = _previousVelocity - _currentVelocity; auto centrifugeVector = glm::length(deltaVelocity) != 0.0f ? glm::normalize(deltaVelocity) : glm::vec3(); @@ -224,7 +224,7 @@ void FlowNode::solve(const glm::vec3& constrainPoint, float maxDistance, const F void FlowNode::solveConstraints(const glm::vec3& constrainPoint, float maxDistance) { glm::vec3 constrainVector = _currentPosition - constrainPoint; float difference = maxDistance / glm::length(constrainVector); - _currentPosition = difference < 1.0 ? constrainPoint + constrainVector * difference : _currentPosition; + _currentPosition = difference < 1.0f ? constrainPoint + constrainVector * difference : _currentPosition; }; void FlowNode::solveCollisions(const FlowCollisionResult& collision) { @@ -244,14 +244,13 @@ FlowJoint::FlowJoint(int jointIndex, int parentIndex, int childIndex, const QStr _group = group; _childIndex = childIndex; _parentIndex = parentIndex; - _node = FlowNode(glm::vec3(), settings); + FlowNode(glm::vec3(), settings); }; void FlowJoint::setInitialData(const glm::vec3& initialPosition, const glm::vec3& initialTranslation, const glm::quat& initialRotation, const glm::vec3& parentPosition) { _initialPosition = initialPosition; - _node._initialPosition = initialPosition; - _node._previousPosition = initialPosition; - _node._currentPosition = initialPosition; + _previousPosition = initialPosition; + _currentPosition = initialPosition; _initialTranslation = initialTranslation; _initialRotation = initialRotation; _translationDirection = glm::normalize(_initialTranslation); @@ -274,33 +273,48 @@ void FlowJoint::setRecoveryPosition(const glm::vec3& recoveryPosition) { void FlowJoint::update(float deltaTime) { glm::vec3 accelerationOffset = glm::vec3(0.0f); - if (_node._settings._stiffness > 0.0f) { - glm::vec3 recoveryVector = _recoveryPosition - _node._currentPosition; - float recoveryFactor = powf(_node._settings._stiffness, 3.0f); + if (_settings._stiffness > 0.0f) { + glm::vec3 recoveryVector = _recoveryPosition - _currentPosition; + float recoveryFactor = powf(_settings._stiffness, 3.0f); accelerationOffset = recoveryVector * recoveryFactor; } - _node.update(deltaTime, accelerationOffset); - if (_node._anchored) { + FlowNode::update(deltaTime, accelerationOffset); + if (_anchored) { if (!_isDummy) { - _node._currentPosition = _updatedPosition; + _currentPosition = _updatedPosition; } else { - _node._currentPosition = _parentPosition; + _currentPosition = _parentPosition; } } }; +void FlowJoint::setScale(float scale, bool initScale) { + if (initScale) { + _initialLength = _length / scale; + } + _settings._radius = _initialRadius * scale; + _length = _initialLength * scale; + _scale = scale; +} + void FlowJoint::solve(const FlowCollisionResult& collision) { - _node.solve(_parentPosition, _length, collision); + FlowNode::solve(_parentPosition, _length, collision); }; FlowDummyJoint::FlowDummyJoint(const glm::vec3& initialPosition, int index, int parentIndex, int childIndex, FlowPhysicsSettings settings) : FlowJoint(index, parentIndex, childIndex, DUMMY_KEYWORD + "_" + index, DUMMY_KEYWORD, settings) { _isDummy = true; _initialPosition = initialPosition; - _node = FlowNode(_initialPosition, settings); _length = DUMMY_JOINT_DISTANCE; } +void FlowDummyJoint::toIsolatedJoint(float length, int childIndex, const QString& group) { + _isDummy = false; + _length = length; + _childIndex = childIndex; + _group = group; + +} FlowThread::FlowThread(int rootIndex, std::map* joints) { _jointsPointer = joints; @@ -342,46 +356,38 @@ void FlowThread::computeFlowThread(int rootIndex) { void FlowThread::computeRecovery() { int parentIndex = _joints[0]; auto parentJoint = _jointsPointer->at(parentIndex); - _jointsPointer->at(parentIndex)._recoveryPosition = parentJoint._recoveryPosition = parentJoint._node._currentPosition; + _jointsPointer->at(parentIndex)._recoveryPosition = parentJoint._recoveryPosition = parentJoint._currentPosition; glm::quat parentRotation = parentJoint._parentWorldRotation * parentJoint._initialRotation; for (size_t i = 1; i < _joints.size(); i++) { auto joint = _jointsPointer->at(_joints[i]); - glm::quat rotation; - rotation = (i == 1) ? parentRotation : parentJoint._initialRotation; - _jointsPointer->at(_joints[i])._recoveryPosition = joint._recoveryPosition = parentJoint._recoveryPosition + (rotation * (joint._initialTranslation * 0.01f)); + _jointsPointer->at(_joints[i])._recoveryPosition = joint._recoveryPosition = parentJoint._recoveryPosition + (parentRotation * (joint._initialTranslation * 0.01f)); parentJoint = joint; } }; void FlowThread::update(float deltaTime) { - if (getActive()) { - _positions.clear(); - auto &firstJoint = _jointsPointer->at(_joints[0]); - _radius = firstJoint._node._settings._radius; - if (firstJoint._node._settings._stiffness > 0.0f) { - computeRecovery(); - } - for (size_t i = 0; i < _joints.size(); i++) { - auto &joint = _jointsPointer->at(_joints[i]); - joint.update(deltaTime); - _positions.push_back(joint._node._currentPosition); - } + _positions.clear(); + auto &firstJoint = _jointsPointer->at(_joints[0]); + _radius = firstJoint._settings._radius; + computeRecovery(); + for (size_t i = 0; i < _joints.size(); i++) { + auto &joint = _jointsPointer->at(_joints[i]); + joint.update(deltaTime); + _positions.push_back(joint._currentPosition); } }; void FlowThread::solve(FlowCollisionSystem& collisionSystem) { - if (getActive()) { - if (collisionSystem.getActive()) { - auto bodyCollisions = collisionSystem.checkFlowThreadCollisions(this); - for (size_t i = 0; i < _joints.size(); i++) { - int index = _joints[i]; - _jointsPointer->at(index).solve(bodyCollisions[i]); - } - } else { - for (size_t i = 0; i < _joints.size(); i++) { - int index = _joints[i]; - _jointsPointer->at(index).solve(FlowCollisionResult()); - } + if (collisionSystem.getActive()) { + auto bodyCollisions = collisionSystem.checkFlowThreadCollisions(this); + for (size_t i = 0; i < _joints.size(); i++) { + int index = _joints[i]; + _jointsPointer->at(index).solve(bodyCollisions[i]); + } + } else { + for (size_t i = 0; i < _joints.size(); i++) { + int index = _joints[i]; + _jointsPointer->at(index).solve(FlowCollisionResult()); } } }; @@ -421,94 +427,102 @@ void FlowThread::computeJointRotations() { joint0 = joint1; joint1 = nextJoint; } -}; - -void FlowThread::apply() { - if (!getActive()) { - return; - } - computeJointRotations(); -}; - -bool FlowThread::getActive() { - return _jointsPointer->at(_joints[0])._node._active; -}; - -void Flow::init(bool isActive, bool isCollidable) { - if (isActive) { - if (!_initialized) { - calculateConstraints(); - } - } else { - cleanUp(); - } - + } -void Flow::calculateConstraints() { +void FlowThread::setScale(float scale, bool initScale) { + for (size_t i = 0; i < _joints.size(); i++) { + auto &joint = _jointsPointer->at(_joints[i]); + joint.setScale(scale, initScale); + } + resetLength(); +} + +FlowThread& FlowThread::operator=(const FlowThread& otherFlowThread) { + for (int jointIndex: otherFlowThread._joints) { + auto& joint = otherFlowThread._jointsPointer->at(jointIndex); + auto& myJoint = _jointsPointer->at(jointIndex); + myJoint._acceleration = joint._acceleration; + myJoint._currentPosition = joint._currentPosition; + myJoint._currentRotation = joint._currentRotation; + myJoint._currentVelocity = joint._currentVelocity; + myJoint._length = joint._length; + myJoint._parentPosition = joint._parentPosition; + myJoint._parentWorldRotation = joint._parentWorldRotation; + myJoint._previousPosition = joint._previousPosition; + myJoint._previousVelocity = joint._previousVelocity; + myJoint._scale = joint._scale; + myJoint._translationDirection = joint._translationDirection; + myJoint._updatedPosition = joint._updatedPosition; + myJoint._updatedRotation = joint._updatedRotation; + myJoint._updatedTranslation = joint._updatedTranslation; + } + return *this; +} + +void Flow::calculateConstraints(const std::shared_ptr& skeleton, + AnimPoseVec& relativePoses, AnimPoseVec& absolutePoses) { cleanUp(); - if (!_rig) { + if (!skeleton) { return; } int rightHandIndex = -1; auto flowPrefix = FLOW_JOINT_PREFIX.toUpper(); auto simPrefix = SIM_JOINT_PREFIX.toUpper(); - auto skeleton = _rig->getAnimSkeleton(); std::vector handsIndices; - if (skeleton) { - for (int i = 0; i < skeleton->getNumJoints(); i++) { - auto name = skeleton->getJointName(i); - if (name == "RightHand") { - rightHandIndex = i; - } - if (std::find(HAND_COLLISION_JOINTS.begin(), HAND_COLLISION_JOINTS.end(), name) != HAND_COLLISION_JOINTS.end()) { - handsIndices.push_back(i); - } - auto parentIndex = skeleton->getParentIndex(i); - if (parentIndex == -1) { - continue; - } - auto jointChildren = skeleton->getChildrenOfJoint(i); - // auto childIndex = jointChildren.size() > 0 ? jointChildren[0] : -1; - auto group = QStringRef(&name, 0, 3).toString().toUpper(); - auto split = name.split("_"); - bool isSimJoint = (group == simPrefix); - bool isFlowJoint = split.size() > 2 && split[0].toUpper() == flowPrefix; - if (isFlowJoint || isSimJoint) { - group = ""; - if (isSimJoint) { - for (int j = 1; j < name.size() - 1; j++) { - bool toFloatSuccess; - QStringRef(&name, (int)(name.size() - j), 1).toString().toFloat(&toFloatSuccess); - if (!toFloatSuccess && (name.size() - j) > (int)simPrefix.size()) { - group = QStringRef(&name, (int)simPrefix.size(), (int)(name.size() - j + 1)).toString(); - break; - } - } - if (group.isEmpty()) { - group = QStringRef(&name, (int)simPrefix.size(), name.size() - 1).toString(); - } - qCDebug(animation) << "Sim joint added to flow: " << name; - } else { - group = split[1]; - } - if (!group.isEmpty()) { - _flowJointKeywords.push_back(group); - FlowPhysicsSettings jointSettings; - if (PRESET_FLOW_DATA.find(group) != PRESET_FLOW_DATA.end()) { - jointSettings = PRESET_FLOW_DATA.at(group); - } else { - jointSettings = DEFAULT_JOINT_SETTINGS; - } - if (_flowJointData.find(i) == _flowJointData.end()) { - auto flowJoint = FlowJoint(i, parentIndex, -1, name, group, jointSettings); - _flowJointData.insert(std::pair(i, flowJoint)); + + for (int i = 0; i < skeleton->getNumJoints(); i++) { + auto name = skeleton->getJointName(i); + if (name == "RightHand") { + rightHandIndex = i; + } + if (std::find(HAND_COLLISION_JOINTS.begin(), HAND_COLLISION_JOINTS.end(), name) != HAND_COLLISION_JOINTS.end()) { + handsIndices.push_back(i); + } + auto parentIndex = skeleton->getParentIndex(i); + if (parentIndex == -1) { + continue; + } + auto jointChildren = skeleton->getChildrenOfJoint(i); + // auto childIndex = jointChildren.size() > 0 ? jointChildren[0] : -1; + auto group = QStringRef(&name, 0, 3).toString().toUpper(); + auto split = name.split("_"); + bool isSimJoint = (group == simPrefix); + bool isFlowJoint = split.size() > 2 && split[0].toUpper() == flowPrefix; + if (isFlowJoint || isSimJoint) { + group = ""; + if (isSimJoint) { + for (int j = 1; j < name.size() - 1; j++) { + bool toFloatSuccess; + QStringRef(&name, (int)(name.size() - j), 1).toString().toFloat(&toFloatSuccess); + if (!toFloatSuccess && (name.size() - j) > (int)simPrefix.size()) { + group = QStringRef(&name, (int)simPrefix.size(), (int)(name.size() - j + 1)).toString(); + break; } } + if (group.isEmpty()) { + group = QStringRef(&name, (int)simPrefix.size(), name.size() - 1).toString(); + } + qCDebug(animation) << "Sim joint added to flow: " << name; } else { - if (PRESET_COLLISION_DATA.find(name) != PRESET_COLLISION_DATA.end()) { - _collisionSystem.addCollisionSphere(i, PRESET_COLLISION_DATA.at(name)); + group = split[1]; + } + if (!group.isEmpty()) { + _flowJointKeywords.push_back(group); + FlowPhysicsSettings jointSettings; + if (PRESET_FLOW_DATA.find(group) != PRESET_FLOW_DATA.end()) { + jointSettings = PRESET_FLOW_DATA.at(group); + } else { + jointSettings = DEFAULT_JOINT_SETTINGS; } + if (_flowJointData.find(i) == _flowJointData.end()) { + auto flowJoint = FlowJoint(i, parentIndex, -1, name, group, jointSettings); + _flowJointData.insert(std::pair(i, flowJoint)); + } + } + } else { + if (PRESET_COLLISION_DATA.find(name) != PRESET_COLLISION_DATA.end()) { + _collisionSystem.addCollisionSphere(i, PRESET_COLLISION_DATA.at(name)); } } } @@ -517,10 +531,10 @@ void Flow::calculateConstraints() { int jointIndex = jointData.first; glm::vec3 jointPosition, parentPosition, jointTranslation; glm::quat jointRotation; - _rig->getJointPositionInWorldFrame(jointIndex, jointPosition, _entityPosition, _entityRotation); - _rig->getJointPositionInWorldFrame(jointData.second._parentIndex, parentPosition, _entityPosition, _entityRotation); - _rig->getJointTranslation(jointIndex, jointTranslation); - _rig->getJointRotation(jointIndex, jointRotation); + getJointPositionInWorldFrame(absolutePoses, jointIndex, jointPosition, _entityPosition, _entityRotation); + getJointPositionInWorldFrame(absolutePoses, jointData.second.getParentIndex(), parentPosition, _entityPosition, _entityRotation); + getJointTranslation(relativePoses, jointIndex, jointTranslation); + getJointRotation(relativePoses, jointIndex, jointRotation); jointData.second.setInitialData(jointPosition, jointTranslation, jointRotation, parentPosition); } @@ -528,11 +542,11 @@ void Flow::calculateConstraints() { std::vector roots; for (auto &joint :_flowJointData) { - if (_flowJointData.find(joint.second._parentIndex) == _flowJointData.end()) { - joint.second._node._anchored = true; + if (_flowJointData.find(joint.second.getParentIndex()) == _flowJointData.end()) { + joint.second.setAnchored(true); roots.push_back(joint.first); } else { - _flowJointData[joint.second._parentIndex]._childIndex = joint.first; + _flowJointData[joint.second.getParentIndex()].setChildIndex(joint.first); } } @@ -543,15 +557,12 @@ void Flow::calculateConstraints() { if (thread._joints.size() == 1) { int jointIndex = roots[i]; auto joint = _flowJointData[jointIndex]; - auto jointPosition = joint._updatedPosition; - auto newSettings = FlowPhysicsSettings(joint._node._settings); + auto jointPosition = joint.getUpdatedPosition(); + auto newSettings = FlowPhysicsSettings(joint.getSettings()); newSettings._stiffness = ISOLATED_JOINT_STIFFNESS; int extraIndex = (int)_flowJointData.size(); auto newJoint = FlowDummyJoint(jointPosition, extraIndex, jointIndex, -1, newSettings); - newJoint._isDummy = false; - newJoint._length = ISOLATED_JOINT_LENGTH; - newJoint._childIndex = extraIndex; - newJoint._group = _flowJointData[jointIndex]._group; + newJoint.toIsolatedJoint(ISOLATED_JOINT_LENGTH, extraIndex, _flowJointData[jointIndex].getGroup()); thread = FlowThread(jointIndex, &_flowJointData); } _jointThreads.push_back(thread); @@ -559,13 +570,13 @@ void Flow::calculateConstraints() { } if (_jointThreads.size() == 0) { - _rig->clearJointStates(); + onCleanup(); } if (SHOW_DUMMY_JOINTS && rightHandIndex > -1) { int jointCount = (int)_flowJointData.size(); int extraIndex = (int)_flowJointData.size(); glm::vec3 rightHandPosition; - _rig->getJointPositionInWorldFrame(rightHandIndex, rightHandPosition, _entityPosition, _entityRotation); + getJointPositionInWorldFrame(absolutePoses, rightHandIndex, rightHandPosition, _entityPosition, _entityRotation); int parentIndex = rightHandIndex; for (int i = 0; i < DUMMY_JOINT_COUNT; i++) { int childIndex = (i == (DUMMY_JOINT_COUNT - 1)) ? -1 : extraIndex + 1; @@ -574,7 +585,7 @@ void Flow::calculateConstraints() { parentIndex = extraIndex; extraIndex++; } - _flowJointData[jointCount]._node._anchored = true; + _flowJointData[jointCount].setAnchored(true); auto extraThread = FlowThread(jointCount, &_flowJointData); _jointThreads.push_back(extraThread); @@ -596,70 +607,70 @@ void Flow::cleanUp() { _collisionSystem.resetCollisions(); _initialized = false; _isScaleSet = false; - _active = false; - if (_rig) { - _rig->clearJointStates(); - } - + onCleanup(); } void Flow::setTransform(float scale, const glm::vec3& position, const glm::quat& rotation) { _scale = scale; _entityPosition = position; _entityRotation = rotation; - _active = _initialized; } void Flow::setScale(float scale) { - if (!_isScaleSet) { - for (auto &joint : _flowJointData) { - joint.second._initialLength = joint.second._length / _scale; - } - _isScaleSet = true; - } - _lastScale = _scale; _collisionSystem.setScale(_scale); for (size_t i = 0; i < _jointThreads.size(); i++) { - for (size_t j = 0; j < _jointThreads[i]._joints.size(); j++) { - auto &joint = _flowJointData[_jointThreads[i]._joints[j]]; - joint._node._settings._radius = joint._node._initialRadius * _scale; - joint._length = joint._initialLength * _scale; - } - _jointThreads[i].resetLength(); + _jointThreads[i].setScale(_scale, !_isScaleSet); } + if (_lastScale != _scale) { + _lastScale = _scale; + _isScaleSet = true; + } + } -void Flow::update(float deltaTime) { +void Flow::update(float deltaTime, AnimPoseVec& relativePoses, AnimPoseVec& absolutePoses, const std::vector& overrideFlags) { if (_initialized && _active) { uint64_t startTime = usecTimestampNow(); uint64_t updateExpiry = startTime + MAX_UPDATE_FLOW_TIME_BUDGET; if (_scale != _lastScale) { setScale(_scale); } - updateJoints(); for (size_t i = 0; i < _jointThreads.size(); i++) { size_t index = _invertThreadLoop ? _jointThreads.size() - 1 - i : i; auto &thread = _jointThreads[index]; thread.update(deltaTime); thread.solve(_collisionSystem); - if (!updateRootFramePositions(index)) { + if (!updateRootFramePositions(absolutePoses, index)) { return; } - thread.apply(); + thread.computeJointRotations(); if (usecTimestampNow() > updateExpiry) { break; - qWarning(animation) << "Flow Bones ran out of time updating threads"; + qWarning(animation) << "Flow Bones ran out of time while updating threads"; } } - setJoints(); + setJoints(relativePoses, overrideFlags); + updateJoints(relativePoses, absolutePoses); _invertThreadLoop = !_invertThreadLoop; } } -bool Flow::worldToJointPoint(const glm::vec3& position, const int jointIndex, glm::vec3& jointSpacePosition) const { +void Flow::updateAbsolutePoses(const AnimPoseVec& relativePoses, AnimPoseVec& absolutePoses) { + for (auto &joint : _flowJointData) { + int index = joint.second.getIndex(); + int parentIndex = joint.second.getParentIndex(); + if (index >= 0 && index < relativePoses.size() && + parentIndex >= 0 && parentIndex < absolutePoses.size()) { + absolutePoses[index] = absolutePoses[parentIndex] * relativePoses[index]; + } + } +} + +bool Flow::worldToJointPoint(const AnimPoseVec& absolutePoses, const glm::vec3& position, const int jointIndex, glm::vec3& jointSpacePosition) const { glm::vec3 jointPos; glm::quat jointRot; - if (_rig->getJointPositionInWorldFrame(jointIndex, jointPos, _entityPosition, _entityRotation) && _rig->getJointRotationInWorldFrame(jointIndex, jointRot, _entityRotation)) { + if (getJointPositionInWorldFrame(absolutePoses, jointIndex, jointPos, _entityPosition, _entityRotation) && + getJointRotationInWorldFrame(absolutePoses, jointIndex, jointRot, _entityRotation)) { glm::vec3 modelOffset = position - jointPos; jointSpacePosition = glm::inverse(jointRot) * modelOffset; return true; @@ -667,13 +678,13 @@ bool Flow::worldToJointPoint(const glm::vec3& position, const int jointIndex, gl return false; } -bool Flow::updateRootFramePositions(size_t threadIndex) { +bool Flow::updateRootFramePositions(const AnimPoseVec& absolutePoses, size_t threadIndex) { auto &joints = _jointThreads[threadIndex]._joints; - int rootIndex = _flowJointData[joints[0]]._parentIndex; + int rootIndex = _flowJointData[joints[0]].getParentIndex(); _jointThreads[threadIndex]._rootFramePositions.clear(); for (size_t j = 0; j < joints.size(); j++) { glm::vec3 jointPos; - if (worldToJointPoint(_flowJointData[joints[j]]._node._currentPosition, rootIndex, jointPos)) { + if (worldToJointPoint(absolutePoses, _flowJointData[joints[j]].getCurrentPosition(), rootIndex, jointPos)) { _jointThreads[threadIndex]._rootFramePositions.push_back(jointPos); } else { return false; @@ -682,35 +693,38 @@ bool Flow::updateRootFramePositions(size_t threadIndex) { return true; } -void Flow::updateJoints() { +void Flow::updateJoints(AnimPoseVec& relativePoses, AnimPoseVec& absolutePoses) { + updateAbsolutePoses(relativePoses, absolutePoses); for (auto &jointData : _flowJointData) { int jointIndex = jointData.first; glm::vec3 jointPosition, parentPosition, jointTranslation; glm::quat jointRotation, parentWorldRotation; - _rig->getJointPositionInWorldFrame(jointIndex, jointPosition, _entityPosition, _entityRotation); - _rig->getJointPositionInWorldFrame(jointData.second._parentIndex, parentPosition, _entityPosition, _entityRotation); - _rig->getJointTranslation(jointIndex, jointTranslation); - _rig->getJointRotation(jointIndex, jointRotation); - _rig->getJointRotationInWorldFrame(jointData.second._parentIndex, parentWorldRotation, _entityRotation); + getJointPositionInWorldFrame(absolutePoses, jointIndex, jointPosition, _entityPosition, _entityRotation); + getJointPositionInWorldFrame(absolutePoses, jointData.second.getParentIndex(), parentPosition, _entityPosition, _entityRotation); + getJointTranslation(relativePoses, jointIndex, jointTranslation); + getJointRotation(relativePoses, jointIndex, jointRotation); + getJointRotationInWorldFrame(absolutePoses, jointData.second.getParentIndex(), parentWorldRotation, _entityRotation); jointData.second.setUpdatedData(jointPosition, jointTranslation, jointRotation, parentPosition, parentWorldRotation); } auto &selfCollisions = _collisionSystem.getSelfCollisions(); for (auto &collision : selfCollisions) { glm::quat jointRotation; - _rig->getJointPositionInWorldFrame(collision._jointIndex, collision._position, _entityPosition, _entityRotation); - _rig->getJointRotationInWorldFrame(collision._jointIndex, jointRotation, _entityRotation); + getJointPositionInWorldFrame(absolutePoses, collision._jointIndex, collision._position, _entityPosition, _entityRotation); + getJointRotationInWorldFrame(absolutePoses, collision._jointIndex, jointRotation, _entityRotation); glm::vec3 worldOffset = jointRotation * collision._offset; collision._position = collision._position + worldOffset; } _collisionSystem.prepareCollisions(); } -void Flow::setJoints() { +void Flow::setJoints(AnimPoseVec& relativePoses, const std::vector& overrideFlags) { for (auto &thread : _jointThreads) { auto &joints = thread._joints; for (int jointIndex : joints) { auto &joint = _flowJointData[jointIndex]; - _rig->setJointRotation(jointIndex, true, joint._currentRotation, 2.0f); + if (jointIndex >= 0 && jointIndex < (int)relativePoses.size() && !overrideFlags[jointIndex]) { + relativePoses[jointIndex].rot() = joint.getCurrentRotation(); + } } } } @@ -724,8 +738,61 @@ void Flow::setOthersCollision(const QUuid& otherId, int jointIndex, const glm::v void Flow::setPhysicsSettingsForGroup(const QString& group, const FlowPhysicsSettings& settings) { for (auto &joint : _flowJointData) { - if (joint.second._group.toUpper() == group.toUpper()) { - joint.second._node._settings = settings; + if (joint.second.getGroup().toUpper() == group.toUpper()) { + joint.second.setSettings(settings); } } +} + +bool Flow::getJointPositionInWorldFrame(const AnimPoseVec& absolutePoses, int jointIndex, glm::vec3& position, glm::vec3 translation, glm::quat rotation) const { + if (jointIndex >= 0 && jointIndex < (int)absolutePoses.size()) { + glm::vec3 poseSetTrans = absolutePoses[jointIndex].trans(); + position = (rotation * poseSetTrans) + translation; + if (!isNaN(position)) { + return true; + } else { + position = glm::vec3(0.0f); + } + } + return false; +} + +bool Flow::getJointRotationInWorldFrame(const AnimPoseVec& absolutePoses, int jointIndex, glm::quat& result, const glm::quat& rotation) const { + if (jointIndex >= 0 && jointIndex < (int)absolutePoses.size()) { + result = rotation * absolutePoses[jointIndex].rot(); + return true; + } else { + return false; + } +} + +bool Flow::getJointRotation(const AnimPoseVec& relativePoses, int jointIndex, glm::quat& rotation) const { + if (jointIndex >= 0 && jointIndex < (int)relativePoses.size()) { + rotation = relativePoses[jointIndex].rot(); + return true; + } else { + return false; + } +} + +bool Flow::getJointTranslation(const AnimPoseVec& relativePoses, int jointIndex, glm::vec3& translation) const { + if (jointIndex >= 0 && jointIndex < (int)relativePoses.size()) { + translation = relativePoses[jointIndex].trans(); + return true; + } else { + return false; + } +} + +Flow& Flow::operator=(const Flow& otherFlow) { + _active = otherFlow.getActive(); + _scale = otherFlow.getScale(); + _isScaleSet = true; + auto &threads = otherFlow.getThreads(); + if (threads.size() == _jointThreads.size()) { + for (size_t i = 0; i < _jointThreads.size(); i++) { + _jointThreads[i] = threads[i]; + } + } + return *this; } \ No newline at end of file diff --git a/libraries/animation/src/Flow.h b/libraries/animation/src/Flow.h index bb15846b5e..e3e20e25f9 100644 --- a/libraries/animation/src/Flow.h +++ b/libraries/animation/src/Flow.h @@ -17,6 +17,7 @@ #include #include #include +#include "AnimPose.h" class Rig; class AnimSkeleton; @@ -175,6 +176,13 @@ class FlowNode { public: FlowNode() {}; FlowNode(const glm::vec3& initialPosition, FlowPhysicsSettings settings); + + void update(float deltaTime, const glm::vec3& accelerationOffset); + void solve(const glm::vec3& constrainPoint, float maxDistance, const FlowCollisionResult& collision); + void solveConstraints(const glm::vec3& constrainPoint, float maxDistance); + void solveCollisions(const FlowCollisionResult& collision); + +protected: FlowPhysicsSettings _settings; glm::vec3 _initialPosition; @@ -194,15 +202,14 @@ public: bool _colliding { false }; bool _active { true }; - void update(float deltaTime, const glm::vec3& accelerationOffset); - void solve(const glm::vec3& constrainPoint, float maxDistance, const FlowCollisionResult& collision); - void solveConstraints(const glm::vec3& constrainPoint, float maxDistance); - void solveCollisions(const FlowCollisionResult& collision); + float _scale{ 1.0f }; }; -class FlowJoint { +class FlowJoint : public FlowNode { public: - FlowJoint() {}; + friend class FlowThread; + + FlowJoint(): FlowNode() {}; FlowJoint(int jointIndex, int parentIndex, int childIndex, const QString& name, const QString& group, const FlowPhysicsSettings& settings); void setInitialData(const glm::vec3& initialPosition, const glm::vec3& initialTranslation, const glm::quat& initialRotation, const glm::vec3& parentPosition); void setUpdatedData(const glm::vec3& updatedPosition, const glm::vec3& updatedTranslation, const glm::quat& updatedRotation, const glm::vec3& parentPosition, const glm::quat& parentWorldRotation); @@ -210,13 +217,30 @@ public: void update(float deltaTime); void solve(const FlowCollisionResult& collision); + void setScale(float scale, bool initScale); + bool isAnchored() { return _anchored; } + void setAnchored(bool anchored) { _anchored = anchored; } + + const FlowPhysicsSettings& getSettings() { return _settings; } + void setSettings(const FlowPhysicsSettings& settings) { _settings = settings; } + + const glm::vec3& getCurrentPosition() { return _currentPosition; } + int getIndex() { return _index; } + int getParentIndex() { return _parentIndex; } + void setChildIndex(int index) { _childIndex = index; } + const glm::vec3& getUpdatedPosition() { return _updatedPosition; } + const QString& getGroup() { return _group; } + const glm::quat& getCurrentRotation() { return _currentRotation; } + +protected: + int _index{ -1 }; int _parentIndex{ -1 }; int _childIndex{ -1 }; QString _name; QString _group; bool _isDummy{ false }; - glm::vec3 _initialPosition; + glm::vec3 _initialTranslation; glm::quat _initialRotation; @@ -229,24 +253,26 @@ public: glm::vec3 _parentPosition; glm::quat _parentWorldRotation; - - FlowNode _node; glm::vec3 _translationDirection; - float _scale { 1.0f }; + float _length { 0.0f }; float _initialLength { 0.0f }; + bool _applyRecovery { false }; }; class FlowDummyJoint : public FlowJoint { public: FlowDummyJoint(const glm::vec3& initialPosition, int index, int parentIndex, int childIndex, FlowPhysicsSettings settings); + void toIsolatedJoint(float length, int childIndex, const QString& group); }; class FlowThread { public: FlowThread() {}; + FlowThread& operator=(const FlowThread& otherFlowThread); + FlowThread(int rootIndex, std::map* joints); void resetLength(); @@ -255,9 +281,8 @@ public: void update(float deltaTime); void solve(FlowCollisionSystem& collisionSystem); void computeJointRotations(); - void apply(); - bool getActive(); void setRootFramePositions(const std::vector& rootFramePositions) { _rootFramePositions = rootFramePositions; } + void setScale(float scale, bool initScale = false); std::vector _joints; std::vector _positions; @@ -267,27 +292,41 @@ public: std::vector _rootFramePositions; }; -class Flow { +class Flow : public QObject{ + Q_OBJECT public: - Flow(Rig* rig) { _rig = rig; }; - void init(bool isActive, bool isCollidable); - bool isActive() { return _active; } - void calculateConstraints(); - void update(float deltaTime); + Flow() { } + Flow& operator=(const Flow& otherFlow); + bool getActive() const { return _active; } + void setActive(bool active) { _active = active; } + bool isInitialized() const { return _initialized; } + float getScale() const { return _scale; } + void calculateConstraints(const std::shared_ptr& skeleton, AnimPoseVec& relativePoses, AnimPoseVec& absolutePoses); + void update(float deltaTime, AnimPoseVec& relativePoses, AnimPoseVec& absolutePoses, const std::vector& overrideFlags); void setTransform(float scale, const glm::vec3& position, const glm::quat& rotation); const std::map& getJoints() const { return _flowJointData; } const std::vector& getThreads() const { return _jointThreads; } void setOthersCollision(const QUuid& otherId, int jointIndex, const glm::vec3& position); FlowCollisionSystem& getCollisionSystem() { return _collisionSystem; } void setPhysicsSettingsForGroup(const QString& group, const FlowPhysicsSettings& settings); -private: - void setJoints(); void cleanUp(); - void updateJoints(); - bool updateRootFramePositions(size_t threadIndex); - bool worldToJointPoint(const glm::vec3& position, const int jointIndex, glm::vec3& jointSpacePosition) const; + +signals: + void onCleanup(); + +private: + void updateAbsolutePoses(const AnimPoseVec& relativePoses, AnimPoseVec& absolutePoses); + bool getJointPositionInWorldFrame(const AnimPoseVec& absolutePoses, int jointIndex, glm::vec3& position, glm::vec3 translation, glm::quat rotation) const; + bool getJointRotationInWorldFrame(const AnimPoseVec& absolutePoses, int jointIndex, glm::quat& result, const glm::quat& rotation) const; + bool getJointRotation(const AnimPoseVec& relativePoses, int jointIndex, glm::quat& rotation) const; + bool getJointTranslation(const AnimPoseVec& relativePoses, int jointIndex, glm::vec3& translation) const; + bool worldToJointPoint(const AnimPoseVec& absolutePoses, const glm::vec3& position, const int jointIndex, glm::vec3& jointSpacePosition) const; + + void setJoints(AnimPoseVec& relativePoses, const std::vector& overrideFlags); + void updateJoints(AnimPoseVec& relativePoses, AnimPoseVec& absolutePoses); + bool updateRootFramePositions(const AnimPoseVec& absolutePoses, size_t threadIndex); void setScale(float scale); - Rig* _rig; + float _scale { 1.0f }; float _lastScale{ 1.0f }; glm::vec3 _entityPosition; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 8cc5fcc337..eb02f07e62 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -74,7 +74,7 @@ static const QString MAIN_STATE_MACHINE_RIGHT_FOOT_ROTATION("mainStateMachineRig static const QString MAIN_STATE_MACHINE_RIGHT_FOOT_POSITION("mainStateMachineRightFootPosition"); -Rig::Rig() : _flow(this) { +Rig::Rig() { // Ensure thread-safe access to the rigRegistry. std::lock_guard guard(rigRegistryMutex); @@ -361,7 +361,6 @@ void Rig::reset(const HFMModel& hfmModel) { _animSkeleton = std::make_shared(hfmModel); - _internalPoseSet._relativePoses.clear(); _internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses(); @@ -745,7 +744,8 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos glm::vec3 forward = worldRotation * IDENTITY_FORWARD; glm::vec3 workingVelocity = worldVelocity; - _flow.setTransform(sensorToWorldScale, worldPosition, worldRotation * Quaternions::Y_180); + _internalFlow.setTransform(sensorToWorldScale, worldPosition, worldRotation * Quaternions::Y_180); + _networkFlow.setTransform(sensorToWorldScale, worldPosition, worldRotation * Quaternions::Y_180); { glm::vec3 localVel = glm::inverse(worldRotation) * workingVelocity; @@ -1209,10 +1209,21 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons } applyOverridePoses(); - buildAbsoluteRigPoses(_internalPoseSet._relativePoses, _internalPoseSet._absolutePoses); - buildAbsoluteRigPoses(_networkPoseSet._relativePoses, _networkPoseSet._absolutePoses); + + buildAbsoluteRigPoses(_internalPoseSet._relativePoses, _internalPoseSet._absolutePoses); + _internalFlow.update(deltaTime, _internalPoseSet._relativePoses, _internalPoseSet._absolutePoses, _internalPoseSet._overrideFlags); + + if (_sendNetworkNode) { + if (_internalFlow.getActive() && !_networkFlow.getActive()) { + _networkFlow = _internalFlow; + } + buildAbsoluteRigPoses(_networkPoseSet._relativePoses, _networkPoseSet._absolutePoses); + _networkFlow.update(deltaTime, _networkPoseSet._relativePoses, _networkPoseSet._absolutePoses, _internalPoseSet._overrideFlags); + } else if (_networkFlow.getActive()) { + _networkFlow.setActive(false); + } + // copy internal poses to external poses - _flow.update(deltaTime); { QWriteLocker writeLock(&_externalPoseSetLock); @@ -1899,11 +1910,15 @@ void Rig::initAnimGraph(const QUrl& url) { connect(_networkLoader.get(), &AnimNodeLoader::error, [networkUrl](int error, QString str) { qCritical(animation) << "Error loading: code = " << error << "str =" << str; }); + + /* connect(this, &Rig::onLoadComplete, [&]() { - if (_flow.isActive()) { - _flow.calculateConstraints(); + if (_internalFlow.getActive()) { + _internalFlow.calculateConstraints(_animSkeleton, _internalPoseSet._relativePoses, _internalPoseSet._absolutePoses); + _networkFlow.calculateConstraints(_animSkeleton, _internalPoseSet._relativePoses, _internalPoseSet._absolutePoses); } }); + */ } } @@ -2111,3 +2126,15 @@ void Rig::computeAvatarBoundingCapsule( glm::vec3 capsuleCenter = transformPoint(_geometryToRigTransform, (0.5f * (totalExtents.maximum + totalExtents.minimum))); localOffsetOut = capsuleCenter - hipsPosition; } + +void Rig::initFlow(bool isActive) { + _internalFlow.setActive(isActive); + if (isActive) { + if (!_internalFlow.isInitialized()) { + _internalFlow.calculateConstraints(_animSkeleton, _internalPoseSet._relativePoses, _internalPoseSet._absolutePoses); + _networkFlow.calculateConstraints(_animSkeleton, _internalPoseSet._relativePoses, _internalPoseSet._absolutePoses); + } + } else { + _internalFlow.cleanUp(); + } +} \ No newline at end of file diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 47c9697dac..2f0e2ad65b 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -234,8 +234,9 @@ public: const AnimContext::DebugAlphaMap& getDebugAlphaMap() const { return _lastContext.getDebugAlphaMap(); } const AnimVariantMap& getAnimVars() const { return _lastAnimVars; } const AnimContext::DebugStateMachineMap& getStateMachineMap() const { return _lastContext.getStateMachineMap(); } - void computeFlowSkeleton() { _flow.calculateConstraints(); } - Flow& getFlow() { return _flow; } + void initFlow(bool isActive); + Flow& getFlow() { return _internalFlow; } + signals: void onLoadComplete(); @@ -427,7 +428,8 @@ protected: SnapshotBlendPoseHelper _hipsBlendHelper; ControllerParameters _previousControllerParameters; - Flow _flow; + Flow _internalFlow; + Flow _networkFlow; }; #endif /* defined(__hifi__Rig__) */ diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 3b1065a3ca..be5eea5161 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -729,18 +729,15 @@ void Avatar::postUpdate(float deltaTime, const render::ScenePointer& scene) { } const bool DEBUG_FLOW = true; if (_skeletonModel->isLoaded() && DEBUG_FLOW) { - auto flow = _skeletonModel->getRig().getFlow(); - auto joints = flow.getJoints(); - auto threads = flow.getThreads(); + Flow* flow = _skeletonModel->getRig().getFlow(); + auto joints = flow->getJoints(); + auto threads = flow->getThreads(); for (auto &thread : threads) { auto& jointIndexes = thread._joints; for (size_t i = 1; i < jointIndexes.size(); i++) { auto index1 = jointIndexes[i - 1]; auto index2 = jointIndexes[i]; - // glm::vec3 pos1 = joint.second._node._currentPosition; - // glm::vec3 pos2 = joints.find(joint.second._parentIndex) != joints.end() ? joints[joint.second._parentIndex]._node._currentPosition : getJointPosition(joint.second._parentIndex); - // DebugDraw::getInstance().drawRay(pos1, pos2, glm::vec4(1.0f, 1.0f, 1.0f, 1.0f)); - DebugDraw::getInstance().drawRay(joints[index1]._node._currentPosition, joints[index2]._node._currentPosition, glm::vec4(1.0f, 1.0f, 1.0f, 1.0f)); + DebugDraw::getInstance().drawRay(joints[index1].getCurrentPosition(), joints[index2].getCurrentPosition(), glm::vec4(1.0f, 1.0f, 1.0f, 1.0f)); } } } From 8033e93eda84b482f9806d39dbb3d2e09e5fcaab Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Tue, 19 Feb 2019 17:38:37 -0800 Subject: [PATCH 119/474] Add avatar-priority boolean to zone entities RC-79 version --- libraries/entities/src/EntityItemProperties.cpp | 14 ++++++++++++++ libraries/entities/src/EntityItemProperties.h | 3 +++ libraries/entities/src/EntityPropertyFlags.h | 3 +++ libraries/entities/src/ZoneEntityItem.cpp | 5 +++++ libraries/entities/src/ZoneEntityItem.h | 7 +++++++ libraries/networking/src/udt/PacketHeaders.h | 1 + scripts/system/assets/data/createAppTooltips.json | 3 +++ scripts/system/edit.js | 3 ++- scripts/system/html/js/entityProperties.js | 6 ++++++ 9 files changed, 44 insertions(+), 1 deletion(-) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 7cafaece7a..103f5dbab7 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -616,6 +616,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_FLYING_ALLOWED, flyingAllowed); CHECK_PROPERTY_CHANGE(PROP_GHOSTING_ALLOWED, ghostingAllowed); CHECK_PROPERTY_CHANGE(PROP_FILTER_URL, filterURL); + CHECK_PROPERTY_CHANGE(PROP_AVATAR_PRIORITY, avatarPriority); CHECK_PROPERTY_CHANGE(PROP_KEY_LIGHT_MODE, keyLightMode); CHECK_PROPERTY_CHANGE(PROP_AMBIENT_LIGHT_MODE, ambientLightMode); CHECK_PROPERTY_CHANGE(PROP_SKYBOX_MODE, skyboxMode); @@ -1420,7 +1421,11 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {string} filterURL="" - The URL of a JavaScript file that filters changes to properties of entities within the * zone. It is periodically executed for each entity in the zone. It can, for example, be used to not allow changes to * certain properties.
+ * + * @property {boolean} avatarPriority=false - If true avatars within this zone will have their movements distributed to other + * clients with priority over other avatars. Use, for example, on a performance stage with a few presenters. *
+ *
  * function filter(properties) {
  *     // Test and edit properties object values,
  *     // e.g., properties.modelURL, as required.
@@ -1748,6 +1753,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
         COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_FLYING_ALLOWED, flyingAllowed);
         COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_GHOSTING_ALLOWED, ghostingAllowed);
         COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_FILTER_URL, filterURL);
+        COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_AVATAR_PRIORITY, avatarPriority);
 
         COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_KEY_LIGHT_MODE, keyLightMode, getKeyLightModeAsString());
         COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_AMBIENT_LIGHT_MODE, ambientLightMode, getAmbientLightModeAsString());
@@ -2108,6 +2114,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
     COPY_PROPERTY_FROM_QSCRIPTVALUE(flyingAllowed, bool, setFlyingAllowed);
     COPY_PROPERTY_FROM_QSCRIPTVALUE(ghostingAllowed, bool, setGhostingAllowed);
     COPY_PROPERTY_FROM_QSCRIPTVALUE(filterURL, QString, setFilterURL);
+    COPY_PROPERTY_FROM_QSCRIPTVALUE(avatarPriority, bool, setAvatarPriority);
     COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(keyLightMode, KeyLightMode);
     COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(ambientLightMode, AmbientLightMode);
     COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(skyboxMode, SkyboxMode);
@@ -2386,6 +2393,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) {
     COPY_PROPERTY_IF_CHANGED(flyingAllowed);
     COPY_PROPERTY_IF_CHANGED(ghostingAllowed);
     COPY_PROPERTY_IF_CHANGED(filterURL);
+    COPY_PROPERTY_IF_CHANGED(avatarPriority);
     COPY_PROPERTY_IF_CHANGED(keyLightMode);
     COPY_PROPERTY_IF_CHANGED(ambientLightMode);
     COPY_PROPERTY_IF_CHANGED(skyboxMode);
@@ -2770,6 +2778,7 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr
         ADD_PROPERTY_TO_MAP(PROP_FLYING_ALLOWED, FlyingAllowed, flyingAllowed, bool);
         ADD_PROPERTY_TO_MAP(PROP_GHOSTING_ALLOWED, GhostingAllowed, ghostingAllowed, bool);
         ADD_PROPERTY_TO_MAP(PROP_FILTER_URL, FilterURL, filterURL, QString);
+        ADD_PROPERTY_TO_MAP(PROP_AVATAR_PRIORITY, AvatarPriority, avatarPriority, bool);
         ADD_PROPERTY_TO_MAP(PROP_KEY_LIGHT_MODE, KeyLightMode, keyLightMode, uint32_t);
         ADD_PROPERTY_TO_MAP(PROP_AMBIENT_LIGHT_MODE, AmbientLightMode, ambientLightMode, uint32_t);
         ADD_PROPERTY_TO_MAP(PROP_SKYBOX_MODE, SkyboxMode, skyboxMode, uint32_t);
@@ -3169,6 +3178,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy
                 APPEND_ENTITY_PROPERTY(PROP_FLYING_ALLOWED, properties.getFlyingAllowed());
                 APPEND_ENTITY_PROPERTY(PROP_GHOSTING_ALLOWED, properties.getGhostingAllowed());
                 APPEND_ENTITY_PROPERTY(PROP_FILTER_URL, properties.getFilterURL());
+                APPEND_ENTITY_PROPERTY(PROP_AVATAR_PRIORITY, properties.getAvatarPriority());
 
                 APPEND_ENTITY_PROPERTY(PROP_KEY_LIGHT_MODE, (uint32_t)properties.getKeyLightMode());
                 APPEND_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_MODE, (uint32_t)properties.getAmbientLightMode());
@@ -3631,6 +3641,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
         READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_FLYING_ALLOWED, bool, setFlyingAllowed);
         READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_GHOSTING_ALLOWED, bool, setGhostingAllowed);
         READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_FILTER_URL, QString, setFilterURL);
+        READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_AVATAR_PRIORITY, bool, setAvatarPriority);
 
         READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_KEY_LIGHT_MODE, uint32_t, setKeyLightMode);
         READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_AMBIENT_LIGHT_MODE, uint32_t, setAmbientLightMode);
@@ -4596,6 +4607,9 @@ QList EntityItemProperties::listChangedProperties() {
     if (filterURLChanged()) {
         out += "filterURL";
     }
+    if (avatarPriorityChanged()) {
+        out += "avatarPriority";
+    }
     if (keyLightModeChanged()) {
         out += "keyLightMode";
     }
diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h
index dcba60b004..ff5204efe2 100644
--- a/libraries/entities/src/EntityItemProperties.h
+++ b/libraries/entities/src/EntityItemProperties.h
@@ -315,6 +315,7 @@ public:
     DEFINE_PROPERTY(PROP_FLYING_ALLOWED, FlyingAllowed, flyingAllowed, bool, ZoneEntityItem::DEFAULT_FLYING_ALLOWED);
     DEFINE_PROPERTY(PROP_GHOSTING_ALLOWED, GhostingAllowed, ghostingAllowed, bool, ZoneEntityItem::DEFAULT_GHOSTING_ALLOWED);
     DEFINE_PROPERTY(PROP_FILTER_URL, FilterURL, filterURL, QString, ZoneEntityItem::DEFAULT_FILTER_URL);
+    DEFINE_PROPERTY(PROP_AVATAR_PRIORITY, AvatarPriority, avatarPriority, bool, ZoneEntityItem::DEFAULT_AVATAR_PRIORITY);
     DEFINE_PROPERTY_REF_ENUM(PROP_KEY_LIGHT_MODE, KeyLightMode, keyLightMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT);
     DEFINE_PROPERTY_REF_ENUM(PROP_SKYBOX_MODE, SkyboxMode, skyboxMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT);
     DEFINE_PROPERTY_REF_ENUM(PROP_AMBIENT_LIGHT_MODE, AmbientLightMode, ambientLightMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT);
@@ -679,6 +680,8 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) {
     DEBUG_PROPERTY_IF_CHANGED(debug, properties, GhostingAllowed, ghostingAllowed, "");
     DEBUG_PROPERTY_IF_CHANGED(debug, properties, FilterURL, filterURL, "");
 
+    DEBUG_PROPERTY_IF_CHANGED(debug, properties, AvatarPriority, avatarPriority, "");
+
     DEBUG_PROPERTY_IF_CHANGED(debug, properties, EntityHostTypeAsString, entityHostType, "");
     DEBUG_PROPERTY_IF_CHANGED(debug, properties, OwningAvatarID, owningAvatarID, "");
 
diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h
index b11ecff5bb..79303e3d61 100644
--- a/libraries/entities/src/EntityPropertyFlags.h
+++ b/libraries/entities/src/EntityPropertyFlags.h
@@ -155,6 +155,7 @@ enum EntityPropertyList {
     PROP_DERIVED_28,
     PROP_DERIVED_29,
     PROP_DERIVED_30,
+    PROP_DERIVED_31,
 
     PROP_AFTER_LAST_ITEM,
 
@@ -275,6 +276,8 @@ enum EntityPropertyList {
     PROP_SKYBOX_MODE = PROP_DERIVED_28,
     PROP_HAZE_MODE = PROP_DERIVED_29,
     PROP_BLOOM_MODE = PROP_DERIVED_30,
+    // Avatar priority
+    PROP_AVATAR_PRIORITY = PROP_DERIVED_31,
 
     // Polyvox
     PROP_VOXEL_VOLUME_SIZE = PROP_DERIVED_0,
diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp
index 7f7f6170d4..13c7273d94 100644
--- a/libraries/entities/src/ZoneEntityItem.cpp
+++ b/libraries/entities/src/ZoneEntityItem.cpp
@@ -65,6 +65,7 @@ EntityItemProperties ZoneEntityItem::getProperties(const EntityPropertyFlags& de
     COPY_ENTITY_PROPERTY_TO_PROPERTIES(flyingAllowed, getFlyingAllowed);
     COPY_ENTITY_PROPERTY_TO_PROPERTIES(ghostingAllowed, getGhostingAllowed);
     COPY_ENTITY_PROPERTY_TO_PROPERTIES(filterURL, getFilterURL);
+    COPY_ENTITY_PROPERTY_TO_PROPERTIES(avatarPriority, getAvatarPriority);
 
     COPY_ENTITY_PROPERTY_TO_PROPERTIES(keyLightMode, getKeyLightMode);
     COPY_ENTITY_PROPERTY_TO_PROPERTIES(ambientLightMode, getAmbientLightMode);
@@ -111,6 +112,7 @@ bool ZoneEntityItem::setSubClassProperties(const EntityItemProperties& propertie
     SET_ENTITY_PROPERTY_FROM_PROPERTIES(flyingAllowed, setFlyingAllowed);
     SET_ENTITY_PROPERTY_FROM_PROPERTIES(ghostingAllowed, setGhostingAllowed);
     SET_ENTITY_PROPERTY_FROM_PROPERTIES(filterURL, setFilterURL);
+    SET_ENTITY_PROPERTY_FROM_PROPERTIES(avatarPriority, setAvatarPriority);
 
     SET_ENTITY_PROPERTY_FROM_PROPERTIES(keyLightMode, setKeyLightMode);
     SET_ENTITY_PROPERTY_FROM_PROPERTIES(ambientLightMode, setAmbientLightMode);
@@ -186,6 +188,7 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
     READ_ENTITY_PROPERTY(PROP_FLYING_ALLOWED, bool, setFlyingAllowed);
     READ_ENTITY_PROPERTY(PROP_GHOSTING_ALLOWED, bool, setGhostingAllowed);
     READ_ENTITY_PROPERTY(PROP_FILTER_URL, QString, setFilterURL);
+    READ_ENTITY_PROPERTY(PROP_AVATAR_PRIORITY, bool, setAvatarPriority);
 
     READ_ENTITY_PROPERTY(PROP_KEY_LIGHT_MODE, uint32_t, setKeyLightMode);
     READ_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_MODE, uint32_t, setAmbientLightMode);
@@ -211,6 +214,7 @@ EntityPropertyFlags ZoneEntityItem::getEntityProperties(EncodeBitstreamParams& p
     requestedProperties += PROP_FLYING_ALLOWED;
     requestedProperties += PROP_GHOSTING_ALLOWED;
     requestedProperties += PROP_FILTER_URL;
+    requestedProperties += PROP_AVATAR_PRIORITY;
 
     requestedProperties += PROP_KEY_LIGHT_MODE;
     requestedProperties += PROP_AMBIENT_LIGHT_MODE;
@@ -250,6 +254,7 @@ void ZoneEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits
     APPEND_ENTITY_PROPERTY(PROP_FLYING_ALLOWED, getFlyingAllowed());
     APPEND_ENTITY_PROPERTY(PROP_GHOSTING_ALLOWED, getGhostingAllowed());
     APPEND_ENTITY_PROPERTY(PROP_FILTER_URL, getFilterURL());
+    APPEND_ENTITY_PROPERTY(PROP_AVATAR_PRIORITY, getAvatarPriority());
 
     APPEND_ENTITY_PROPERTY(PROP_KEY_LIGHT_MODE, (uint32_t)getKeyLightMode());
     APPEND_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_MODE, (uint32_t)getAmbientLightMode());
diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h
index 813115add9..20bab7c710 100644
--- a/libraries/entities/src/ZoneEntityItem.h
+++ b/libraries/entities/src/ZoneEntityItem.h
@@ -96,6 +96,9 @@ public:
     QString getFilterURL() const;
     void setFilterURL(const QString url); 
 
+    bool getAvatarPriority() const { return _avatarPriority; }
+    void setAvatarPriority(bool value) { _avatarPriority = value; }
+
     bool keyLightPropertiesChanged() const { return _keyLightPropertiesChanged; }
     bool ambientLightPropertiesChanged() const { return _ambientLightPropertiesChanged; }
     bool skyboxPropertiesChanged() const { return _skyboxPropertiesChanged; }
@@ -125,6 +128,7 @@ public:
     static const bool DEFAULT_FLYING_ALLOWED;
     static const bool DEFAULT_GHOSTING_ALLOWED;
     static const QString DEFAULT_FILTER_URL;
+    static const bool DEFAULT_AVATAR_PRIORITY = false;
 
 protected:
     KeyLightPropertyGroup _keyLightProperties;
@@ -149,6 +153,9 @@ protected:
     bool _ghostingAllowed { DEFAULT_GHOSTING_ALLOWED };
     QString _filterURL { DEFAULT_FILTER_URL };
 
+    // Avatar-updates priority
+    bool _avatarPriority { DEFAULT_AVATAR_PRIORITY };
+
     // Dirty flags turn true when either keylight properties is changing values.
     bool _keyLightPropertiesChanged { false };
     bool _ambientLightPropertiesChanged { false };
diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h
index d898c03597..c0983f24db 100644
--- a/libraries/networking/src/udt/PacketHeaders.h
+++ b/libraries/networking/src/udt/PacketHeaders.h
@@ -260,6 +260,7 @@ enum class EntityVersion : PacketVersion {
     MissingWebEntityProperties,
     PulseProperties,
     RingGizmoEntities,
+    AvatarPriorityZone,
 
     // Add new versions above here
     NUM_PACKET_TYPE,
diff --git a/scripts/system/assets/data/createAppTooltips.json b/scripts/system/assets/data/createAppTooltips.json
index 4c78da7306..7e5f2c8659 100644
--- a/scripts/system/assets/data/createAppTooltips.json
+++ b/scripts/system/assets/data/createAppTooltips.json
@@ -134,6 +134,9 @@
     "bloom.bloomSize": {
         "tooltip": "The radius of bloom. The higher the value, the larger the bloom."
     },
+    "avatarPriority": {
+        "tooltip":  "Avatars in this zone will have a higher update priority."
+    },
     "modelURL": {
         "tooltip": "A mesh model from an FBX or OBJ file."
     },
diff --git a/scripts/system/edit.js b/scripts/system/edit.js
index 9d807264aa..67a789c266 100644
--- a/scripts/system/edit.js
+++ b/scripts/system/edit.js
@@ -382,7 +382,8 @@ const DEFAULT_ENTITY_PROPERTIES = {
             },
         },
         shapeType: "box",
-        bloomMode: "inherit"
+        bloomMode: "inherit",
+        avatarPriority: false
     },
     Model: {
         collisionShape: "none",
diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js
index ee95312fa4..404ded6ae2 100644
--- a/scripts/system/html/js/entityProperties.js
+++ b/scripts/system/html/js/entityProperties.js
@@ -428,6 +428,12 @@ const GROUPS = [
                 propertyID: "bloom.bloomSize",
                 showPropertyRule: { "bloomMode": "enabled" },
             },
+            {
+                label: "Avatar Priority",
+                type: "bool",
+                propertyID: "avatarPriority",
+            },
+
         ]
     },
     {

From 951380db15f8f36d3dc21a206a0cecbc51b6046e Mon Sep 17 00:00:00 2001
From: amantley 
Date: Tue, 19 Feb 2019 17:53:59 -0800
Subject: [PATCH 120/474] tweaked the constraints, to do: start conditions and
 possibly using base rotation on shoulder to determine hand offset

---
 .../src/AnimPoleVectorConstraint.cpp          |   2 +-
 libraries/animation/src/Rig.cpp               | 300 ++++++++++--------
 2 files changed, 172 insertions(+), 130 deletions(-)

diff --git a/libraries/animation/src/AnimPoleVectorConstraint.cpp b/libraries/animation/src/AnimPoleVectorConstraint.cpp
index afcf9b29ee..36c58ecb4e 100644
--- a/libraries/animation/src/AnimPoleVectorConstraint.cpp
+++ b/libraries/animation/src/AnimPoleVectorConstraint.cpp
@@ -124,7 +124,7 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim
         glm::quat deltaRot = glm::angleAxis(theta, unitAxis);
 
         if (_tipJointName == "RightHand") {
-            qCDebug(animation) << "anim ik theta " << (theta / PI)*180.0f;
+            //qCDebug(animation) << "anim ik theta " << (theta / PI)*180.0f;
         }
 
         // transform result back into parent relative frame.
diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp
index 48ffdb4970..02d9f66cf5 100644
--- a/libraries/animation/src/Rig.cpp
+++ b/libraries/animation/src/Rig.cpp
@@ -1691,22 +1691,8 @@ void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm
     }
 }
 
-bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int shoulderIndex, bool left, glm::vec3& poleVector) {
-    // get the default poses for the upper and lower arm
-    // then use this length to judge how far the hand is away from the shoulder.
-    // then create weights that make the elbow angle less when the x value is large in either direction.
-    // make the angle less when z is small.
-    // lower y with x center lower angle
-    // lower y with x out higher angle
-
-    AnimPose handPose = _externalPoseSet._absolutePoses[handIndex];
-    AnimPose shoulderPose = _externalPoseSet._absolutePoses[shoulderIndex];
-    AnimPose elbowPose = _externalPoseSet._absolutePoses[elbowIndex];
-
-    AnimPose absoluteShoulderPose = getAbsoluteDefaultPose(shoulderIndex);
-    AnimPose absoluteHandPose = getAbsoluteDefaultPose(handIndex);
-    float defaultArmLength = glm::length(absoluteHandPose.trans() - absoluteShoulderPose.trans());
-
+static float getHandPositionTheta(glm::vec3 armToHand, float defaultArmLength) {
+    float handPositionTheta = 0.0f;
     //calculate the hand position influence on theta
     const float zStart = 0.6f;
     const float xStart = 0.1f;
@@ -1715,14 +1701,7 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s
     // weights
     const float zWeightBottom = -100.0f;
     const glm::vec3 weights(-50.0f, 60.0f, 90.0f);
-    glm::vec3 armToHand = handPose.trans() - shoulderPose.trans();
-    glm::vec3 unitAxis;
-    float axisLength = glm::length(armToHand);
-    if (axisLength > 0.0f) {
-        unitAxis = armToHand / axisLength;
-    } else {
-        unitAxis = Vectors::UNIT_Y;
-    }
+
 
     float yFactor = (fabsf(armToHand[1] / defaultArmLength) * weights[1]) + biases[1];
 
@@ -1742,19 +1721,157 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s
     }
 
     float theta = xFactor + yFactor + zFactor;
-    //float theta = yFactor;
 
-    if (theta < 13.0f) {
-        theta = 13.0f;
+    if (handPositionTheta < 13.0f) {
+        handPositionTheta = 13.0f;
     }
-    if (theta > 175.0f) {
-        theta = 175.0f;
+    if (handPositionTheta > 175.0f) {
+        handPositionTheta = 175.0f;
     }
 
     if (left) {
-        theta *= -1.0f;
+        handPositionTheta *= -1.0f;
     }
 
+
+    return handPositionTheta;
+}
+
+static float computeUlnarRadialCompensation(float ulnarRadialTheta, float twistTheta, bool left) {
+    const float ULNAR_BOUNDARY_MINUS = PI / 6.0f;
+    const float ULNAR_BOUNDARY_PLUS = -PI / 4.0f;
+    float ulnarDiff = 0.0f;
+    float ulnarCorrection = 0.0f;
+    float currentWristCoefficient = 0.0f;
+    if (left) {
+        if (ulnarRadialTheta > ULNAR_BOUNDARY_MINUS) {
+            ulnarDiff = ulnarRadialTheta - ULNAR_BOUNDARY_MINUS;
+        } else if (ulnarRadialTheta < ULNAR_BOUNDARY_PLUS) {
+            ulnarDiff = ulnarRadialTheta - ULNAR_BOUNDARY_PLUS;
+        }
+        if (fabsf(ulnarDiff) > 0.0f) {
+            float twistCoefficient = (fabsf(twistTheta) / (PI / 20.0f));
+            if (twistCoefficient > 1.0f) {
+                twistCoefficient = 1.0f;
+            }
+            if (twistTheta < 0.0f) {
+                ulnarCorrection -= glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 90.0f * twistCoefficient;
+            } else {
+                ulnarCorrection += glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 90.0f * twistCoefficient;
+            }
+            if (fabsf(ulnarCorrection) > 20.0f) {
+                ulnarCorrection = glm::sign(ulnarCorrection) * 20.0f;
+            }
+            // return this --V
+            currentWristCoefficient += ulnarCorrection;
+        }
+    } else {
+        if (ulnarRadialTheta > ULNAR_BOUNDARY_MINUS) {
+            ulnarDiff = ulnarRadialTheta - ULNAR_BOUNDARY_MINUS;
+        } else if (ulnarRadialTheta < ULNAR_BOUNDARY_PLUS) {
+            ulnarDiff = ulnarRadialTheta - ULNAR_BOUNDARY_PLUS;
+        }
+        if (fabsf(ulnarDiff) > 0.0f) {
+            float twistCoefficient = (fabsf(twistTheta) / (PI / 20.0f));
+            if (twistCoefficient > 1.0f) {
+                twistCoefficient = 1.0f;
+            }
+            if (twistTheta < 0.0f) {
+                ulnarCorrection += glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 90.0f * twistCoefficient;
+            } else {
+                ulnarCorrection -= glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 90.0f * twistCoefficient;
+            }
+            if (fabsf(ulnarCorrection) > 20.0f) {
+                ulnarCorrection = glm::sign(ulnarCorrection) * 20.0f;
+            }
+            currentWristCoefficient += ulnarCorrection;
+        }
+    }
+
+    return currentWristCoefficient;
+
+
+}
+
+static float computeTwistCompensation(float twistTheta, bool left) {
+
+    const float TWIST_DEADZONE = (4 * PI) / 9.0f;
+    float twistCorrection = 0.0f;
+    if (left) {
+        if (fabsf(twistTheta) > TWIST_DEADZONE) {
+            twistCorrection = glm::sign(twistTheta) * ((fabsf(twistTheta) - TWIST_DEADZONE) / PI) * 100.0f;
+        }
+    } else {
+        if (fabsf(twistTheta) > TWIST_DEADZONE) {
+            twistCorrection = glm::sign(twistTheta) * ((fabsf(twistTheta) - TWIST_DEADZONE) / PI) * 100.0f;
+        }
+    }
+    // limit the twist correction
+    if (fabsf(twistCorrection) > 30.0f) {
+        twistCorrection = glm::sign(twistCorrection) * 30.0f;
+    }
+
+    return twistCorrection;
+}
+
+static float computeFlexCompensation(float flexTheta, bool left) {
+
+    const float FLEX_BOUNDARY = PI / 6.0f;
+    const float EXTEND_BOUNDARY = -PI / 4.0f;
+    float flexCorrection = 0.0f;
+    float currentWristCoefficient = 0.0f;
+    if (left) {
+        if (flexTheta > FLEX_BOUNDARY) {
+            flexCorrection = ((flexTheta - FLEX_BOUNDARY) / PI) * 180.0f;
+        } else if (flexTheta < EXTEND_BOUNDARY) {
+            flexCorrection = ((flexTheta - EXTEND_BOUNDARY) / PI) * 180.0f;
+        }
+        if (fabsf(flexCorrection) > 175.0f) {
+            flexCorrection = glm::sign(flexCorrection) * 175.0f;
+        }
+        currentWristCoefficient += flexCorrection;
+    } else {
+        if (flexTheta > FLEX_BOUNDARY) {
+            flexCorrection = ((flexTheta - FLEX_BOUNDARY) / PI) * 180.0f;
+        } else if (flexTheta < EXTEND_BOUNDARY) {
+            flexCorrection = ((flexTheta - EXTEND_BOUNDARY) / PI) * 180.0f;
+        }
+        if (fabsf(flexCorrection) > 175.0f) {
+            flexCorrection = glm::sign(flexCorrection) * 175.0f;
+        }
+        currentWristCoefficient -= flexCorrection;
+    }
+
+    return currentWristCoefficient;
+
+}
+
+bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int shoulderIndex, bool left, glm::vec3& poleVector) {
+    // get the default poses for the upper and lower arm
+    // then use this length to judge how far the hand is away from the shoulder.
+    // then create weights that make the elbow angle less when the x value is large in either direction.
+    // make the angle less when z is small.
+    // lower y with x center lower angle
+    // lower y with x out higher angle
+
+    AnimPose handPose = _externalPoseSet._absolutePoses[handIndex];
+    AnimPose shoulderPose = _externalPoseSet._absolutePoses[shoulderIndex];
+    AnimPose elbowPose = _externalPoseSet._absolutePoses[elbowIndex];
+
+    AnimPose absoluteShoulderPose = getAbsoluteDefaultPose(shoulderIndex);
+    AnimPose absoluteHandPose = getAbsoluteDefaultPose(handIndex);
+    float defaultArmLength = glm::length(absoluteHandPose.trans() - absoluteShoulderPose.trans());
+    glm::vec3 armToHand = handPose.trans() - shoulderPose.trans();
+    glm::vec3 unitAxis;
+    float axisLength = glm::length(armToHand);
+    if (axisLength > 0.0f) {
+        unitAxis = armToHand / axisLength;
+    } else {
+        unitAxis = Vectors::UNIT_Y;
+    }
+   
+    float theta = 175.0f;// getHandPositionTheta(armToHand, defaultArmLength);
+
     float deltaTheta = 0.0f;
     if (left) {
         deltaTheta = theta - _lastThetaLeft;
@@ -1767,6 +1884,10 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s
     AnimPose updatedBase = shoulderPose * deltaRot;
     AnimPose newAbsMid = updatedBase * relMid;
 
+    glm::quat axisRotation;
+    glm::quat nonAxisRotation;
+    swingTwistDecomposition(updatedBase.rot(), unitAxis, nonAxisRotation, axisRotation);
+    qCDebug(animation) << "the rotation about the axis of the arm " << (glm::sign(glm::axis(axisRotation)[2]) * glm::angle(axisRotation) / PI)*180.0f << " delta Rot theta " << deltaTheta;
 
     // now we calculate the contribution of the hand rotation relative to the arm
     // we are adding in the delta rotation so that we have the hand correction relative to the
@@ -1791,14 +1912,14 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s
             ulnarDeviationTheta = -1.0f * ulnarDeviationTheta;
         }
         // put some smoothing on the theta
-        _ulnarRadialThetaRunningAverageLeft = 0.5f * _ulnarRadialThetaRunningAverageLeft + 0.5f * ulnarDeviationTheta;
+        _ulnarRadialThetaRunningAverageLeft = ulnarDeviationTheta;
     } else {
         if (glm::sign(ulnarDeviationTheta) != glm::sign(_ulnarRadialThetaRunningAverageRight) && fabsf(ulnarDeviationTheta) > (PI / 2.0f)) {
             // don't allow the theta to cross the 180 degree limit.
             ulnarDeviationTheta = -1.0f * ulnarDeviationTheta;
         }
         // put some smoothing on the theta
-        _ulnarRadialThetaRunningAverageRight = 0.75f * _ulnarRadialThetaRunningAverageRight + 0.25f * ulnarDeviationTheta;
+        _ulnarRadialThetaRunningAverageRight = ulnarDeviationTheta;
     }
 
     //get the flex/extension of the wrist rotation
@@ -1851,115 +1972,36 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s
         _twistThetaRunningAverageRight = 0.5f * _twistThetaRunningAverageRight + 0.5f * trueTwistTheta;
     }
 
-
     float currentWristCoefficient = 0.0f;
-    const float FLEX_BOUNDARY = 0.0f; // PI / 6.0f;
-    const float EXTEND_BOUNDARY = 0.0f; //-PI / 4.0f;
-    float flexCorrection = 0.0f;
+    
     if (left) {
-        if (_flexThetaRunningAverageLeft > FLEX_BOUNDARY) {
-            flexCorrection = ((_flexThetaRunningAverageLeft - FLEX_BOUNDARY) / PI) * 180.0f;
-        } else if (_flexThetaRunningAverageLeft < EXTEND_BOUNDARY) {
-            flexCorrection = ((_flexThetaRunningAverageLeft - EXTEND_BOUNDARY) / PI) * 180.0f;
-        }
-        if (fabsf(flexCorrection) > 175.0f) {
-            flexCorrection = glm::sign(flexCorrection) * 175.0f;
-        }
-        currentWristCoefficient += flexCorrection;
+        currentWristCoefficient += computeUlnarRadialCompensation(_ulnarRadialThetaRunningAverageLeft, _twistThetaRunningAverageLeft, left);
+        currentWristCoefficient += computeTwistCompensation(_twistThetaRunningAverageLeft, left);
+        currentWristCoefficient += computeFlexCompensation(_flexThetaRunningAverageLeft, left);
     } else {
-        if (_flexThetaRunningAverageRight > FLEX_BOUNDARY) {
-            flexCorrection = ((_flexThetaRunningAverageRight - FLEX_BOUNDARY) / PI) * 180.0f;
-        } else if (_flexThetaRunningAverageRight < EXTEND_BOUNDARY) {
-            flexCorrection = ((_flexThetaRunningAverageRight - EXTEND_BOUNDARY) / PI) * 180.0f;
-        }
-        if (fabsf(flexCorrection) > 175.0f) {
-            flexCorrection = glm::sign(flexCorrection) * 175.0f;
-        }
-        currentWristCoefficient -= flexCorrection;
+        currentWristCoefficient += computeUlnarRadialCompensation(_ulnarRadialThetaRunningAverageRight, _twistThetaRunningAverageRight, left);
+        currentWristCoefficient += computeTwistCompensation(_twistThetaRunningAverageRight, left);
+        currentWristCoefficient += computeFlexCompensation(_flexThetaRunningAverageRight, left);
     }
-
-    const float ULNAR_BOUNDARY_MINUS = -PI / 6.0f;
-    const float ULNAR_BOUNDARY_PLUS = PI / 4.0f;
-    float ulnarDiff = 0.0f;
-    float ulnarCorrection = 0.0f;
-    if (left) {
-        if (_ulnarRadialThetaRunningAverageLeft > -ULNAR_BOUNDARY_MINUS) {
-            ulnarDiff = _ulnarRadialThetaRunningAverageLeft + ULNAR_BOUNDARY_MINUS;
-        } else if (_ulnarRadialThetaRunningAverageLeft < -ULNAR_BOUNDARY_PLUS) {
-            ulnarDiff = _ulnarRadialThetaRunningAverageLeft + ULNAR_BOUNDARY_PLUS;
-        }
-        if (fabsf(ulnarDiff) > 0.0f) {
-            float twistCoefficient = (fabsf(_twistThetaRunningAverageLeft) / (PI / 20.0f));
-            if (twistCoefficient > 1.0f) {
-                twistCoefficient = 1.0f;
-            }
-            if (left) {
-                if (_twistThetaRunningAverageLeft < 0.0f) {
-                    ulnarCorrection -= glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 80.0f * twistCoefficient;
-                } else {
-                    ulnarCorrection += glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 80.0f * twistCoefficient;
-                }
-            }
-            if (fabsf(ulnarCorrection) > 20.0f) {
-                ulnarCorrection = glm::sign(ulnarCorrection) * 20.0f;
-            }
-            currentWristCoefficient += ulnarCorrection;
-        }
-    } else {
-        if (_ulnarRadialThetaRunningAverageRight > ULNAR_BOUNDARY_PLUS) {
-            ulnarDiff = _ulnarRadialThetaRunningAverageRight - ULNAR_BOUNDARY_PLUS;
-        } else if (_ulnarRadialThetaRunningAverageRight < ULNAR_BOUNDARY_MINUS) {
-            ulnarDiff = _ulnarRadialThetaRunningAverageRight - ULNAR_BOUNDARY_MINUS;
-        }
-        if (fabsf(ulnarDiff) > 0.0f) {
-            float twistCoefficient = (fabsf(_twistThetaRunningAverageRight) / (PI / 20.0f));
-            if (twistCoefficient > 1.0f) {
-                twistCoefficient = 1.0f;
-            }
-            if (left) {
-                if (_twistThetaRunningAverageRight < 0.0f) {
-                    ulnarCorrection -= glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 80.0f * twistCoefficient;
-                } else {
-                    ulnarCorrection += glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 80.0f * twistCoefficient;
-                }
-            }
-            if (fabsf(ulnarCorrection) > 20.0f) {
-                ulnarCorrection = glm::sign(ulnarCorrection) * 20.0f;
-            }
-            currentWristCoefficient += ulnarCorrection;
-        }
-    }
-
-    const float TWIST_DEADZONE = (4 * PI) / 9.0f;
-    float twistCorrection = 0.0f;
-    if (left) {
-        if (fabsf(_twistThetaRunningAverageLeft) > TWIST_DEADZONE) {
-            twistCorrection = glm::sign(_twistThetaRunningAverageLeft) * ((fabsf(_twistThetaRunningAverageLeft) - TWIST_DEADZONE) / PI) * 80.0f;
-        }
-    } else {
-        if (fabsf(_twistThetaRunningAverageRight) > TWIST_DEADZONE) {
-            twistCorrection = glm::sign(_twistThetaRunningAverageRight) * ((fabsf(_twistThetaRunningAverageRight) - TWIST_DEADZONE) / PI) * 80.0f;
-        }
-    }
-    // limit the twist correction
-    if (fabsf(twistCorrection) > 30.0f) {
-        currentWristCoefficient += glm::sign(twistCorrection) * 30.0f;
-    } else {
-        currentWristCoefficient += twistCorrection;
-    }
-
+    
     if (left) {
         _lastWristCoefficientLeft = _lastThetaLeft - theta;
         _lastWristCoefficientLeft += currentWristCoefficient;
         theta += _lastWristCoefficientLeft;
+        if (theta > 0.0f) {
+            theta = 0.0f;
+        }
     } else {
         _lastWristCoefficientRight = _lastThetaRight - theta;
         _lastWristCoefficientRight += currentWristCoefficient;
         theta += _lastWristCoefficientRight;
+        if (theta < 0.0f) {
+           theta = 0.0f;
+        }
     }
-
-    if (left) {
-        qCDebug(animation) << "theta " << theta << "Last wrist" << _lastWristCoefficientLeft << " flex ave: " << (_flexThetaRunningAverageLeft / PI) * 180.0f << " twist ave: " << (_twistThetaRunningAverageLeft / PI) * 180.0f << " ulnar deviation ave: " << (_ulnarRadialThetaRunningAverageLeft / PI) * 180.0f;
+    
+    if (!left) {
+       // qCDebug(animation) << "theta " << theta << "Last wrist" << _lastWristCoefficientRight << " flex ave: " << (_flexThetaRunningAverageRight / PI) * 180.0f << " twist ave: " << (_twistThetaRunningAverageRight/ PI) * 180.0f << " ulnar deviation ave: " << (_ulnarRadialThetaRunningAverageRight / PI) * 180.0f;
     }
 
     // global limiting
@@ -1982,7 +2024,7 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s
 
 
         if (fabsf(_lastThetaRight) < 10.0f) {
-            _lastThetaRight = glm::sign(_lastThetaRight) * 10.0f;
+            _lastThetaRight = glm::sign(_lastThetaRight) * 50.0f;
         }
         if (fabsf(_lastThetaRight) > 175.0f) {
             _lastThetaRight = glm::sign(_lastThetaRight) * 175.0f;

From 1e73422b80e495de672e0d80942e58f4702c2dfe Mon Sep 17 00:00:00 2001
From: Angus Antley 
Date: Wed, 20 Feb 2019 06:25:44 -0800
Subject: [PATCH 121/474] added the wrist and position coeffs back in, 1.0
 works

---
 libraries/animation/src/Rig.cpp | 66 +++++++++++++++++++--------------
 libraries/animation/src/Rig.h   |  2 +
 2 files changed, 41 insertions(+), 27 deletions(-)

diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp
index 02d9f66cf5..2ef3617118 100644
--- a/libraries/animation/src/Rig.cpp
+++ b/libraries/animation/src/Rig.cpp
@@ -1691,7 +1691,7 @@ void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm
     }
 }
 
-static float getHandPositionTheta(glm::vec3 armToHand, float defaultArmLength) {
+static float getHandPositionTheta(glm::vec3 armToHand, float defaultArmLength, bool left) {
     float handPositionTheta = 0.0f;
     //calculate the hand position influence on theta
     const float zStart = 0.6f;
@@ -1720,10 +1720,10 @@ static float getHandPositionTheta(glm::vec3 armToHand, float defaultArmLength) {
         xFactor = weights[0] * (((armToHand[0] / defaultArmLength) + xStart) - (0.2f) * ((1.0f + (armToHand[1] / defaultArmLength)) / 2.0f));
     }
 
-    float theta = xFactor + yFactor + zFactor;
+    handPositionTheta = xFactor + yFactor + zFactor;
 
-    if (handPositionTheta < 13.0f) {
-        handPositionTheta = 13.0f;
+    if (handPositionTheta < 50.0f) {
+        handPositionTheta = 50.0f;
     }
     if (handPositionTheta > 175.0f) {
         handPositionTheta = 175.0f;
@@ -1755,9 +1755,9 @@ static float computeUlnarRadialCompensation(float ulnarRadialTheta, float twistT
                 twistCoefficient = 1.0f;
             }
             if (twistTheta < 0.0f) {
-                ulnarCorrection -= glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 90.0f * twistCoefficient;
+                ulnarCorrection -= glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 45.0f * twistCoefficient;
             } else {
-                ulnarCorrection += glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 90.0f * twistCoefficient;
+                ulnarCorrection += glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 45.0f * twistCoefficient;
             }
             if (fabsf(ulnarCorrection) > 20.0f) {
                 ulnarCorrection = glm::sign(ulnarCorrection) * 20.0f;
@@ -1777,9 +1777,9 @@ static float computeUlnarRadialCompensation(float ulnarRadialTheta, float twistT
                 twistCoefficient = 1.0f;
             }
             if (twistTheta < 0.0f) {
-                ulnarCorrection += glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 90.0f * twistCoefficient;
+                ulnarCorrection += glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 45.0f * twistCoefficient;
             } else {
-                ulnarCorrection -= glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 90.0f * twistCoefficient;
+                ulnarCorrection -= glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 45.0f * twistCoefficient;
             }
             if (fabsf(ulnarCorrection) > 20.0f) {
                 ulnarCorrection = glm::sign(ulnarCorrection) * 20.0f;
@@ -1795,7 +1795,7 @@ static float computeUlnarRadialCompensation(float ulnarRadialTheta, float twistT
 
 static float computeTwistCompensation(float twistTheta, bool left) {
 
-    const float TWIST_DEADZONE = (4 * PI) / 9.0f;
+    const float TWIST_DEADZONE = PI / 2.0f;
     float twistCorrection = 0.0f;
     if (left) {
         if (fabsf(twistTheta) > TWIST_DEADZONE) {
@@ -1822,9 +1822,9 @@ static float computeFlexCompensation(float flexTheta, bool left) {
     float currentWristCoefficient = 0.0f;
     if (left) {
         if (flexTheta > FLEX_BOUNDARY) {
-            flexCorrection = ((flexTheta - FLEX_BOUNDARY) / PI) * 180.0f;
+            flexCorrection = ((flexTheta - FLEX_BOUNDARY) / PI) * 60.0f;
         } else if (flexTheta < EXTEND_BOUNDARY) {
-            flexCorrection = ((flexTheta - EXTEND_BOUNDARY) / PI) * 180.0f;
+            flexCorrection = ((flexTheta - EXTEND_BOUNDARY) / PI) * 60.0f;
         }
         if (fabsf(flexCorrection) > 175.0f) {
             flexCorrection = glm::sign(flexCorrection) * 175.0f;
@@ -1832,9 +1832,9 @@ static float computeFlexCompensation(float flexTheta, bool left) {
         currentWristCoefficient += flexCorrection;
     } else {
         if (flexTheta > FLEX_BOUNDARY) {
-            flexCorrection = ((flexTheta - FLEX_BOUNDARY) / PI) * 180.0f;
+            flexCorrection = ((flexTheta - FLEX_BOUNDARY) / PI) * 60.0f;
         } else if (flexTheta < EXTEND_BOUNDARY) {
-            flexCorrection = ((flexTheta - EXTEND_BOUNDARY) / PI) * 180.0f;
+            flexCorrection = ((flexTheta - EXTEND_BOUNDARY) / PI) * 60.0f;
         }
         if (fabsf(flexCorrection) > 175.0f) {
             flexCorrection = glm::sign(flexCorrection) * 175.0f;
@@ -1870,7 +1870,8 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s
         unitAxis = Vectors::UNIT_Y;
     }
    
-    float theta = 175.0f;// getHandPositionTheta(armToHand, defaultArmLength);
+    float theta = getHandPositionTheta(armToHand, defaultArmLength, left);
+    qCDebug(animation) << "hand position theta " << left << " " << theta;
 
     float deltaTheta = 0.0f;
     if (left) {
@@ -1887,7 +1888,7 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s
     glm::quat axisRotation;
     glm::quat nonAxisRotation;
     swingTwistDecomposition(updatedBase.rot(), unitAxis, nonAxisRotation, axisRotation);
-    qCDebug(animation) << "the rotation about the axis of the arm " << (glm::sign(glm::axis(axisRotation)[2]) * glm::angle(axisRotation) / PI)*180.0f << " delta Rot theta " << deltaTheta;
+    //qCDebug(animation) << "the rotation about the axis of the arm " << (glm::sign(glm::axis(axisRotation)[2]) * glm::angle(axisRotation) / PI)*180.0f << " delta Rot theta " << deltaTheta;
 
     // now we calculate the contribution of the hand rotation relative to the arm
     // we are adding in the delta rotation so that we have the hand correction relative to the
@@ -1984,16 +1985,22 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s
         currentWristCoefficient += computeFlexCompensation(_flexThetaRunningAverageRight, left);
     }
     
+    // i think limit theta here so we don't subtract more than is possible from last theta.
+    // actually theta is limited.  to what though?
+    
     if (left) {
-        _lastWristCoefficientLeft = _lastThetaLeft - theta;
+        _lastWristCoefficientLeft = _lastThetaLeft - _lastPositionThetaLeft;
         _lastWristCoefficientLeft += currentWristCoefficient;
+        _lastPositionThetaLeft = theta;
         theta += _lastWristCoefficientLeft;
         if (theta > 0.0f) {
             theta = 0.0f;
         }
+        //qCDebug(animation) << "theta " << theta << " lastThetaLeft " << _lastThetaLeft << "last position theta left"<<_lastPositionThetaLeft  << "last wrist coeff " << _lastWristCoefficientLeft;
     } else {
-        _lastWristCoefficientRight = _lastThetaRight - theta;
+        _lastWristCoefficientRight = _lastThetaRight - _lastPositionThetaRight;
         _lastWristCoefficientRight += currentWristCoefficient;
+        _lastPositionThetaRight = theta;
         theta += _lastWristCoefficientRight;
         if (theta < 0.0f) {
            theta = 0.0f;
@@ -2008,26 +2015,31 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s
     float thetaRadians = 0.0f;
     if (left) {
         // final global smoothing
-        _lastThetaLeft = 0.5f * _lastThetaLeft + 0.5f * theta;
+        //_lastThetaLeft = 0.5f * _lastThetaLeft + 0.5f * theta;
+        _lastThetaLeft = theta;
 
-        if (fabsf(_lastThetaLeft) < 50.0f) {
-            _lastThetaLeft = glm::sign(_lastThetaLeft) * 50.0f;
+        if (_lastThetaLeft > -50.0f) {
+            _lastThetaLeft =  -50.0f;
         }
-        if (fabsf(_lastThetaLeft) > 175.0f) {
-            _lastThetaLeft = glm::sign(_lastThetaLeft) * 175.0f;
+        if (_lastThetaLeft < -175.0f) {
+            _lastThetaLeft = -175.0f;
+        }
+        const float MIN_VALUE = 0.0001f;
+        if (fabsf(_lastPositionThetaLeft - _lastThetaLeft) > MIN_VALUE) {
+            qCDebug(animation) << "theta " << theta << " lastThetaLeft " << _lastThetaLeft << "last position theta left" << _lastPositionThetaLeft << "last wrist coeff " << _lastWristCoefficientLeft;
         }
         // convert to radians and make 180 0 to match pole vector theta
         thetaRadians = ((180.0f - _lastThetaLeft) / 180.0f)*PI;
     } else {
         // final global smoothing
-        _lastThetaRight = 0.5f * _lastThetaRight + 0.5f * theta;
+        _lastThetaRight = theta; // 0.5f * _lastThetaRight + 0.5f * theta;
 
 
-        if (fabsf(_lastThetaRight) < 10.0f) {
-            _lastThetaRight = glm::sign(_lastThetaRight) * 50.0f;
+        if (_lastThetaRight < 50.0f) {
+            _lastThetaRight = 50.0f;
         }
-        if (fabsf(_lastThetaRight) > 175.0f) {
-            _lastThetaRight = glm::sign(_lastThetaRight) * 175.0f;
+        if (_lastThetaRight > 175.0f) {
+            _lastThetaRight = 175.0f;
         }
         // convert to radians and make 180 0 to match pole vector theta
         thetaRadians = ((180.0f - _lastThetaRight) / 180.0f)*PI;
diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h
index 7b9c3d238d..6f1a5906bb 100644
--- a/libraries/animation/src/Rig.h
+++ b/libraries/animation/src/Rig.h
@@ -430,6 +430,8 @@ protected:
     float _lastThetaRight { 0.0f };
     float _lastWristCoefficientRight { 0.0f };
     float _lastWristCoefficientLeft { 0.0f };
+    float _lastPositionThetaLeft { 0.0f };
+    float _lastPositionThetaRight { 0.0f };
 
     AnimContext _lastContext;
     AnimVariantMap _lastAnimVars;

From 7639eac3ad6f2a508ca9c0798d82b705486bd32b Mon Sep 17 00:00:00 2001
From: Angus Antley 
Date: Wed, 20 Feb 2019 07:18:14 -0800
Subject: [PATCH 122/474] cleaning up theta functions

---
 libraries/animation/src/Rig.cpp | 84 ++++++++++++++++++++-------------
 1 file changed, 51 insertions(+), 33 deletions(-)

diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp
index 2ef3617118..86106e0707 100644
--- a/libraries/animation/src/Rig.cpp
+++ b/libraries/animation/src/Rig.cpp
@@ -1743,6 +1743,7 @@ static float computeUlnarRadialCompensation(float ulnarRadialTheta, float twistT
     float ulnarDiff = 0.0f;
     float ulnarCorrection = 0.0f;
     float currentWristCoefficient = 0.0f;
+    /*
     if (left) {
         if (ulnarRadialTheta > ULNAR_BOUNDARY_MINUS) {
             ulnarDiff = ulnarRadialTheta - ULNAR_BOUNDARY_MINUS;
@@ -1787,6 +1788,35 @@ static float computeUlnarRadialCompensation(float ulnarRadialTheta, float twistT
             currentWristCoefficient += ulnarCorrection;
         }
     }
+    */
+    if (ulnarRadialTheta > ULNAR_BOUNDARY_MINUS) {
+        ulnarDiff = ulnarRadialTheta - ULNAR_BOUNDARY_MINUS;
+    } else if (ulnarRadialTheta < ULNAR_BOUNDARY_PLUS) {
+        ulnarDiff = ulnarRadialTheta - ULNAR_BOUNDARY_PLUS;
+    }
+    if (fabsf(ulnarDiff) > 0.0f) {
+        float twistCoefficient = (fabsf(twistTheta) / (PI / 20.0f));
+        if (twistCoefficient > 1.0f) {
+            twistCoefficient = 1.0f;
+        }
+        if (twistTheta < 0.0f) {
+            if (left) {
+                ulnarCorrection -= glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 45.0f * twistCoefficient;
+            } else {
+                ulnarCorrection += glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 45.0f * twistCoefficient;
+            }
+        } else {
+            if (left) {
+                ulnarCorrection += glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 45.0f * twistCoefficient;
+            } else {
+                ulnarCorrection -= glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 45.0f * twistCoefficient;
+            }
+        }
+        if (fabsf(ulnarCorrection) > 20.0f) {
+            ulnarCorrection = glm::sign(ulnarCorrection) * 20.0f;
+        }
+        currentWristCoefficient += ulnarCorrection;
+    }
 
     return currentWristCoefficient;
 
@@ -1797,14 +1827,9 @@ static float computeTwistCompensation(float twistTheta, bool left) {
 
     const float TWIST_DEADZONE = PI / 2.0f;
     float twistCorrection = 0.0f;
-    if (left) {
-        if (fabsf(twistTheta) > TWIST_DEADZONE) {
-            twistCorrection = glm::sign(twistTheta) * ((fabsf(twistTheta) - TWIST_DEADZONE) / PI) * 100.0f;
-        }
-    } else {
-        if (fabsf(twistTheta) > TWIST_DEADZONE) {
-            twistCorrection = glm::sign(twistTheta) * ((fabsf(twistTheta) - TWIST_DEADZONE) / PI) * 100.0f;
-        }
+    
+    if (fabsf(twistTheta) > TWIST_DEADZONE) {
+        twistCorrection = glm::sign(twistTheta) * ((fabsf(twistTheta) - TWIST_DEADZONE) / PI) * 100.0f;
     }
     // limit the twist correction
     if (fabsf(twistCorrection) > 30.0f) {
@@ -1820,25 +1845,18 @@ static float computeFlexCompensation(float flexTheta, bool left) {
     const float EXTEND_BOUNDARY = -PI / 4.0f;
     float flexCorrection = 0.0f;
     float currentWristCoefficient = 0.0f;
+  
+    if (flexTheta > FLEX_BOUNDARY) {
+        flexCorrection = ((flexTheta - FLEX_BOUNDARY) / PI) * 60.0f;
+    } else if (flexTheta < EXTEND_BOUNDARY) {
+        flexCorrection = ((flexTheta - EXTEND_BOUNDARY) / PI) * 60.0f;
+    }
+    if (fabsf(flexCorrection) > 175.0f) {
+        flexCorrection = glm::sign(flexCorrection) * 175.0f;
+    }
     if (left) {
-        if (flexTheta > FLEX_BOUNDARY) {
-            flexCorrection = ((flexTheta - FLEX_BOUNDARY) / PI) * 60.0f;
-        } else if (flexTheta < EXTEND_BOUNDARY) {
-            flexCorrection = ((flexTheta - EXTEND_BOUNDARY) / PI) * 60.0f;
-        }
-        if (fabsf(flexCorrection) > 175.0f) {
-            flexCorrection = glm::sign(flexCorrection) * 175.0f;
-        }
         currentWristCoefficient += flexCorrection;
     } else {
-        if (flexTheta > FLEX_BOUNDARY) {
-            flexCorrection = ((flexTheta - FLEX_BOUNDARY) / PI) * 60.0f;
-        } else if (flexTheta < EXTEND_BOUNDARY) {
-            flexCorrection = ((flexTheta - EXTEND_BOUNDARY) / PI) * 60.0f;
-        }
-        if (fabsf(flexCorrection) > 175.0f) {
-            flexCorrection = glm::sign(flexCorrection) * 175.0f;
-        }
         currentWristCoefficient -= flexCorrection;
     }
 
@@ -1870,14 +1888,14 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s
         unitAxis = Vectors::UNIT_Y;
     }
    
-    float theta = getHandPositionTheta(armToHand, defaultArmLength, left);
-    qCDebug(animation) << "hand position theta " << left << " " << theta;
+    float positionalTheta = getHandPositionTheta(armToHand, defaultArmLength, left);
+    qCDebug(animation) << "hand position theta " << left << " " << positionalTheta;
 
     float deltaTheta = 0.0f;
     if (left) {
-        deltaTheta = theta - _lastThetaLeft;
+        deltaTheta = positionalTheta - _lastThetaLeft;
     } else {
-        deltaTheta = theta - _lastThetaRight;
+        deltaTheta = positionalTheta - _lastThetaRight;
     }
     float deltaThetaRadians = (deltaTheta / 180.0f)*PI;
     AnimPose deltaRot(glm::angleAxis(deltaThetaRadians, unitAxis), glm::vec3());
@@ -1987,12 +2005,12 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s
     
     // i think limit theta here so we don't subtract more than is possible from last theta.
     // actually theta is limited.  to what though?
-    
+    float theta = 0.0f;
     if (left) {
         _lastWristCoefficientLeft = _lastThetaLeft - _lastPositionThetaLeft;
         _lastWristCoefficientLeft += currentWristCoefficient;
-        _lastPositionThetaLeft = theta;
-        theta += _lastWristCoefficientLeft;
+        _lastPositionThetaLeft = positionalTheta;
+        theta = positionalTheta + _lastWristCoefficientLeft;
         if (theta > 0.0f) {
             theta = 0.0f;
         }
@@ -2000,8 +2018,8 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s
     } else {
         _lastWristCoefficientRight = _lastThetaRight - _lastPositionThetaRight;
         _lastWristCoefficientRight += currentWristCoefficient;
-        _lastPositionThetaRight = theta;
-        theta += _lastWristCoefficientRight;
+        _lastPositionThetaRight = positionalTheta;
+        theta += positionalTheta + _lastWristCoefficientRight;
         if (theta < 0.0f) {
            theta = 0.0f;
         }

From fa44687de6caad2ee9242f504948174a9758db71 Mon Sep 17 00:00:00 2001
From: luiscuenca 
Date: Wed, 20 Feb 2019 09:22:39 -0700
Subject: [PATCH 123/474] fix errors and remove debug draw

---
 libraries/animation/src/Flow.cpp                   |  4 ++--
 .../src/avatars-renderer/Avatar.cpp                | 14 --------------
 2 files changed, 2 insertions(+), 16 deletions(-)

diff --git a/libraries/animation/src/Flow.cpp b/libraries/animation/src/Flow.cpp
index 86252b698d..480ded2002 100644
--- a/libraries/animation/src/Flow.cpp
+++ b/libraries/animation/src/Flow.cpp
@@ -659,8 +659,8 @@ void Flow::updateAbsolutePoses(const AnimPoseVec& relativePoses, AnimPoseVec& ab
     for (auto &joint : _flowJointData) {
         int index = joint.second.getIndex();
         int parentIndex = joint.second.getParentIndex();
-        if (index >= 0 && index < relativePoses.size() &&
-            parentIndex >= 0 && parentIndex < absolutePoses.size()) {
+        if (index >= 0 && index < (int)relativePoses.size() &&
+            parentIndex >= 0 && parentIndex < (int)absolutePoses.size()) {
             absolutePoses[index] = absolutePoses[parentIndex] * relativePoses[index];
         }
     }
diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp
index be5eea5161..e6881b0efe 100644
--- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp
+++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp
@@ -727,20 +727,6 @@ void Avatar::postUpdate(float deltaTime, const render::ScenePointer& scene) {
             DebugDraw::getInstance().drawRay(rightEyePosition, rightEyePosition + rightEyeRotation * Vectors::UNIT_Z * EYE_RAY_LENGTH, RED);
         }
     }
-    const bool DEBUG_FLOW = true;
-    if (_skeletonModel->isLoaded() && DEBUG_FLOW) {
-        Flow* flow = _skeletonModel->getRig().getFlow();
-        auto joints = flow->getJoints();
-        auto threads = flow->getThreads();
-        for (auto &thread : threads) {
-            auto& jointIndexes = thread._joints;
-            for (size_t i = 1; i < jointIndexes.size(); i++) {
-                auto index1 = jointIndexes[i - 1];
-                auto index2 = jointIndexes[i];
-                DebugDraw::getInstance().drawRay(joints[index1].getCurrentPosition(), joints[index2].getCurrentPosition(), glm::vec4(1.0f, 1.0f, 1.0f, 1.0f));
-            }
-        }
-    }
 
     fixupModelsInScene(scene);
     updateFitBoundingBox();

From bea7680864f4d7f39621701fce973f925169934c Mon Sep 17 00:00:00 2001
From: luiscuenca 
Date: Wed, 20 Feb 2019 10:57:11 -0700
Subject: [PATCH 124/474] Fix shared_ptr not part of std error

---
 libraries/animation/src/Flow.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/libraries/animation/src/Flow.h b/libraries/animation/src/Flow.h
index e3e20e25f9..5fe7b7acbc 100644
--- a/libraries/animation/src/Flow.h
+++ b/libraries/animation/src/Flow.h
@@ -11,6 +11,7 @@
 #ifndef hifi_Flow_h
 #define hifi_Flow_h
 
+#include 
 #include 
 #include 
 #include 

From d7c2231f336e7a4045332ed6900251f1c63cac2c Mon Sep 17 00:00:00 2001
From: unknown 
Date: Wed, 20 Feb 2019 10:19:44 -0800
Subject: [PATCH 125/474] use root bone name from skeleton list

---
 .../Assets/Editor/AvatarExporter.cs           |  26 ++++++++++++------
 tools/unity-avatar-exporter/Assets/README.txt |   2 +-
 .../avatarExporter.unitypackage               | Bin 13667 -> 13822 bytes
 3 files changed, 19 insertions(+), 9 deletions(-)

diff --git a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs
index 7b90145223..65ba0312a6 100644
--- a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs
+++ b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs
@@ -14,7 +14,7 @@ using System.Collections.Generic;
 
 class AvatarExporter : MonoBehaviour {
     // update version number for every PR that changes this file, also set updated version in README file
-    static readonly string AVATAR_EXPORTER_VERSION = "0.2";
+    static readonly string AVATAR_EXPORTER_VERSION = "0.3";
     
     static readonly float HIPS_GROUND_MIN_Y = 0.01f;
     static readonly float HIPS_SPINE_CHEST_MIN_SEPARATION = 0.001f;
@@ -264,7 +264,7 @@ class AvatarExporter : MonoBehaviour {
     static string assetName = "";
     static HumanDescription humanDescription;
     static Dictionary dependencyTextures = new Dictionary();
-
+    
     [MenuItem("High Fidelity/Export New Avatar")]
     static void ExportNewAvatar() {
         ExportSelectedAvatar(false);
@@ -302,11 +302,11 @@ class AvatarExporter : MonoBehaviour {
                                                  " the Rig section of it's Inspector window.", "Ok");
             return;
         }
-
+        
         humanDescription = modelImporter.humanDescription;
         SetUserBoneInformation();
         string textureWarnings = SetTextureDependencies();
-        
+
         // check if we should be substituting a bone for a missing UpperChest mapping
         AdjustUpperChestMapping();
 
@@ -334,7 +334,7 @@ class AvatarExporter : MonoBehaviour {
             EditorUtility.DisplayDialog("Error", boneErrors, "Ok");
             return;
         }
-
+        
         string documentsFolder = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments);
         string hifiFolder = documentsFolder + "\\High Fidelity Projects";
         if (updateAvatar) { // Update Existing Avatar menu option
@@ -630,12 +630,14 @@ class AvatarExporter : MonoBehaviour {
             string boneName = modelBone.name;
             if (modelBone.parent == null) {
                 // if no parent then this is actual root bone node of the user avatar, so consider it's parent as "root"
+                boneName = GetRootBoneName(); // ensure we use the root bone name from the skeleton list for consistency
                 userBoneTree = new BoneTreeNode(boneName); // initialize root of tree
                 userBoneInfo.parentName = "root";
                 userBoneInfo.boneTreeNode = userBoneTree;
             } else {
                 // otherwise add this bone node as a child to it's parent's children list
-                string parentName = modelBone.parent.name;
+                // if its a child of the root bone, use the root bone name from the skeleton list as the parent for consistency
+                string parentName = modelBone.parent.parent == null ? GetRootBoneName() : modelBone.parent.name;
                 BoneTreeNode boneTreeNode = new BoneTreeNode(boneName);
                 userBoneInfos[parentName].boneTreeNode.children.Add(boneTreeNode);
                 userBoneInfo.parentName = parentName;
@@ -658,7 +660,7 @@ class AvatarExporter : MonoBehaviour {
         }
         return result;
     }
-    
+
     static void AdjustUpperChestMapping() {
         if (!humanoidToUserBoneMappings.ContainsKey("UpperChest")) {
             // if parent of Neck is not Chest then map the parent to UpperChest
@@ -682,6 +684,14 @@ class AvatarExporter : MonoBehaviour {
         }
     }
     
+    static string GetRootBoneName() {
+        // the "root" bone is the first element in the human skeleton bone list
+        if (humanDescription.skeleton.Length > 0) {
+            return humanDescription.skeleton[0].name;
+        }
+        return "";
+    }
+    
     static void SetFailedBoneRules() {
         failedBoneRules.Clear();
         
@@ -901,7 +911,7 @@ class AvatarExporter : MonoBehaviour {
         textureDirectory = textureDirectory.Replace("\\\\", "\\");
         return textureDirectory;
     }
-    
+
     static string SetTextureDependencies() {
         string textureWarnings = "";
         dependencyTextures.Clear();
diff --git a/tools/unity-avatar-exporter/Assets/README.txt b/tools/unity-avatar-exporter/Assets/README.txt
index b81a620406..6516e4b150 100644
--- a/tools/unity-avatar-exporter/Assets/README.txt
+++ b/tools/unity-avatar-exporter/Assets/README.txt
@@ -1,6 +1,6 @@
 High Fidelity, Inc.
 Avatar Exporter
-Version 0.2
+Version 0.3
 
 Note: It is recommended to use Unity versions between 2017.4.17f1 and 2018.2.12f1 for this Avatar Exporter.
 
diff --git a/tools/unity-avatar-exporter/avatarExporter.unitypackage b/tools/unity-avatar-exporter/avatarExporter.unitypackage
index 95c000e7c61b7623e0da0a1f9974e0744375014e..4c1474631204788903d573f18fc418d9c8cb1027 100644
GIT binary patch
literal 13822
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
As{jB1

literal 13667
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
B&65BC


From 2202b695751a70837e321240581eeab51a955ec1 Mon Sep 17 00:00:00 2001
From: David Kelly 
Date: Wed, 20 Feb 2019 10:18:55 -0700
Subject: [PATCH 126/474] Guard against accidentally calling wrong
 api/v1/places endpoint

spaces
---
 domain-server/src/DomainServer.cpp          | 2 +-
 libraries/networking/src/AddressManager.cpp | 6 ++++--
 scripts/system/interstitialPage.js          | 5 +++++
 3 files changed, 10 insertions(+), 3 deletions(-)

diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp
index 258038b8f1..85b116129c 100644
--- a/domain-server/src/DomainServer.cpp
+++ b/domain-server/src/DomainServer.cpp
@@ -2548,7 +2548,7 @@ bool DomainServer::processPendingContent(HTTPConnection* connection, QString ite
         _pendingFileContent.seek(_pendingFileContent.size());
         _pendingFileContent.write(dataChunk);
         _pendingFileContent.close();
-        
+
         // Respond immediately - will timeout if we wait for restore.
         connection->respond(HTTPConnection::StatusCode200);
         if (itemName == "restore-file" || itemName == "restore-file-chunk-final" || itemName == "restore-file-chunk-only") {
diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp
index 9145b4a79e..f4221e3d49 100644
--- a/libraries/networking/src/AddressManager.cpp
+++ b/libraries/networking/src/AddressManager.cpp
@@ -315,7 +315,9 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) {
 
                 // wasn't an address - lookup the place name
                 // we may have a path that defines a relative viewpoint - pass that through the lookup so we can go to it after
-                attemptPlaceNameLookup(lookupUrl.host(), lookupUrl.path(), trigger);
+                if (!lookupUrl.host().isNull() && !lookupUrl.host().isEmpty()) {
+                    attemptPlaceNameLookup(lookupUrl.host(), lookupUrl.path(), trigger);
+                }
             }
         }
 
@@ -337,7 +339,7 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) {
         // be loaded over http(s)
         // lookupUrl.scheme() == URL_SCHEME_HTTP ||
         // lookupUrl.scheme() == HIFI_URL_SCHEME_HTTPS ||
-        // TODO once a file can return a connection refusal if there were to be some kind of load error, we'd 
+        // TODO once a file can return a connection refusal if there were to be some kind of load error, we'd
         // need to store the previous domain tried in _lastVisitedURL. For now , do not store it.
 
         _previousAPILookup.clear();
diff --git a/scripts/system/interstitialPage.js b/scripts/system/interstitialPage.js
index 8dd94623b7..8ecc982dab 100644
--- a/scripts/system/interstitialPage.js
+++ b/scripts/system/interstitialPage.js
@@ -325,6 +325,11 @@
                 leftMargin: domainNameLeftMargin
             };
 
+            // check to be sure we are going to look for an actual domain
+            if (!domain) {
+                doRequest = false;
+            }
+
             if (doRequest) {
                 var url = Account.metaverseServerURL + '/api/v1/places/' + domain;
                 request({

From dca166f8219909e1a1f921638d1fc4794ec66b35 Mon Sep 17 00:00:00 2001
From: Roxanne Skelly 
Date: Wed, 20 Feb 2019 14:23:54 -0800
Subject: [PATCH 127/474] Stand-alone Optimized Tagging - Add tagging for GOTO
 User Stories

---
 interface/resources/qml/hifi/Card.qml         | 39 ++++++++++---------
 interface/resources/qml/hifi/Feed.qml         |  2 +
 .../hifi/commerce/marketplace/Marketplace.qml |  8 ++++
 3 files changed, 30 insertions(+), 19 deletions(-)

diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml
index 67abc1c3f9..9beb108b36 100644
--- a/interface/resources/qml/hifi/Card.qml
+++ b/interface/resources/qml/hifi/Card.qml
@@ -30,6 +30,7 @@ Item {
     property string imageUrl: "";
     property var goFunction: null;
     property string storyId: "";
+    property bool standaloneOptimized: false;
 
     property bool drillDownToPlace: false;
     property bool showPlace: isConcurrency;
@@ -39,7 +40,7 @@ Item {
     property bool isConcurrency: action === 'concurrency';
     property bool isAnnouncement: action === 'announcement';
     property bool isStacked: !isConcurrency && drillDownToPlace;
-    property bool standaloneOptimized: true;
+
 
     property int textPadding: 10;
     property int smallMargin: 4;
@@ -271,6 +272,24 @@ Item {
             goFunction("hifi://" + hifiUrl);
         }
     }
+
+    HiFiGlyphs {
+        id: standaloneOptomizedBadge
+
+        anchors {
+            right: actionIcon.left
+            bottom: parent.bottom;
+        }
+        height: (root.isConcurrency && root.standaloneOptimized) ? 34 : 0
+
+        visible: root.isConcurrency && root.standaloneOptimized
+
+        text: hifi.glyphs.hmd
+        size: 34
+        horizontalAlignment: Text.AlignHCenter
+        color: messageColor
+     }
+
     StateImage {
         id: actionIcon;
         visible: !isAnnouncement;
@@ -282,24 +301,6 @@ Item {
             right: parent.right;
             margins: smallMargin;
         }
-        
-    }
-    HiFiGlyphs {
-        id: standaloneOptomizedBadge
-
-        anchors {
-            right: actionIcon.left
-            verticalCenter: parent.verticalCenter
-            bottom: parent.bottom;
-        }
-        height: root.standaloneOptimized ? 34 : 0
-        
-        visible: standaloneOptimized
-
-        text: hifi.glyphs.hmd
-        size: 34
-        horizontalAlignment: Text.AlignHCenter
-        color: hifi.colors.blueHighlight
     }  
 
     function go() {
diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml
index 1e89971938..68aab2fdd2 100644
--- a/interface/resources/qml/hifi/Feed.qml
+++ b/interface/resources/qml/hifi/Feed.qml
@@ -82,6 +82,7 @@ Column {
             action: data.action || "",
             thumbnail_url: resolveUrl(thumbnail_url),
             image_url: resolveUrl(data.details && data.details.image_url),
+            standalone_optimized: data.standalone_optimized,
 
             metaverseId: (data.id || "").toString(), // Some are strings from server while others are numbers. Model objects require uniformity.
 
@@ -127,6 +128,7 @@ Column {
             hifiUrl: model.place_name + model.path;
             thumbnail: model.thumbnail_url;
             imageUrl: model.image_url;
+            standaloneOptimized: model.standalone_optimized;
             action: model.action;
             timestamp: model.created_at;
             onlineUsers: model.online_users;
diff --git a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml
index 8c7e050ec2..5f97e64e6c 100644
--- a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml
+++ b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml
@@ -90,6 +90,14 @@ Rectangle {
                     id: -1,
                     name: "Everything"
                 });
+                categoriesModel.append({
+                    id: -1,
+                    name: "Standalone Optimized"
+                });
+                categoriesModel.append({
+                    id: -1,
+                    name: "Standalone Compatible"
+                });
                 result.data.items.forEach(function(category) {
                     categoriesModel.append({
                         id: category.id,

From 95b3fbdc35de9d9569052aa97840965005d9fb52 Mon Sep 17 00:00:00 2001
From: amantley 
Date: Wed, 20 Feb 2019 15:02:12 -0800
Subject: [PATCH 128/474] removed ulnar coeff, too jumpy

---
 libraries/animation/src/Rig.cpp | 282 ++++++++++++++------------------
 1 file changed, 119 insertions(+), 163 deletions(-)

diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp
index 86106e0707..7273ccf637 100644
--- a/libraries/animation/src/Rig.cpp
+++ b/libraries/animation/src/Rig.cpp
@@ -1738,33 +1738,16 @@ static float getHandPositionTheta(glm::vec3 armToHand, float defaultArmLength, b
 }
 
 static float computeUlnarRadialCompensation(float ulnarRadialTheta, float twistTheta, bool left) {
-    const float ULNAR_BOUNDARY_MINUS = PI / 6.0f;
+    const float ULNAR_BOUNDARY_MINUS = -PI / 4.0f;
     const float ULNAR_BOUNDARY_PLUS = -PI / 4.0f;
     float ulnarDiff = 0.0f;
     float ulnarCorrection = 0.0f;
     float currentWristCoefficient = 0.0f;
-    /*
     if (left) {
-        if (ulnarRadialTheta > ULNAR_BOUNDARY_MINUS) {
-            ulnarDiff = ulnarRadialTheta - ULNAR_BOUNDARY_MINUS;
-        } else if (ulnarRadialTheta < ULNAR_BOUNDARY_PLUS) {
-            ulnarDiff = ulnarRadialTheta - ULNAR_BOUNDARY_PLUS;
-        }
-        if (fabsf(ulnarDiff) > 0.0f) {
-            float twistCoefficient = (fabsf(twistTheta) / (PI / 20.0f));
-            if (twistCoefficient > 1.0f) {
-                twistCoefficient = 1.0f;
-            }
-            if (twistTheta < 0.0f) {
-                ulnarCorrection -= glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 45.0f * twistCoefficient;
-            } else {
-                ulnarCorrection += glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 45.0f * twistCoefficient;
-            }
-            if (fabsf(ulnarCorrection) > 20.0f) {
-                ulnarCorrection = glm::sign(ulnarCorrection) * 20.0f;
-            }
-            // return this --V
-            currentWristCoefficient += ulnarCorrection;
+        if (ulnarRadialTheta > -ULNAR_BOUNDARY_MINUS) {
+            ulnarDiff = ulnarRadialTheta - (-ULNAR_BOUNDARY_MINUS);
+        } else if (ulnarRadialTheta < -ULNAR_BOUNDARY_PLUS) {
+            ulnarDiff = ulnarRadialTheta - (-ULNAR_BOUNDARY_PLUS);
         }
     } else {
         if (ulnarRadialTheta > ULNAR_BOUNDARY_MINUS) {
@@ -1772,48 +1755,43 @@ static float computeUlnarRadialCompensation(float ulnarRadialTheta, float twistT
         } else if (ulnarRadialTheta < ULNAR_BOUNDARY_PLUS) {
             ulnarDiff = ulnarRadialTheta - ULNAR_BOUNDARY_PLUS;
         }
-        if (fabsf(ulnarDiff) > 0.0f) {
-            float twistCoefficient = (fabsf(twistTheta) / (PI / 20.0f));
-            if (twistCoefficient > 1.0f) {
-                twistCoefficient = 1.0f;
-            }
-            if (twistTheta < 0.0f) {
-                ulnarCorrection += glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 45.0f * twistCoefficient;
-            } else {
-                ulnarCorrection -= glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 45.0f * twistCoefficient;
-            }
-            if (fabsf(ulnarCorrection) > 20.0f) {
-                ulnarCorrection = glm::sign(ulnarCorrection) * 20.0f;
-            }
-            currentWristCoefficient += ulnarCorrection;
-        }
-    }
-    */
-    if (ulnarRadialTheta > ULNAR_BOUNDARY_MINUS) {
-        ulnarDiff = ulnarRadialTheta - ULNAR_BOUNDARY_MINUS;
-    } else if (ulnarRadialTheta < ULNAR_BOUNDARY_PLUS) {
-        ulnarDiff = ulnarRadialTheta - ULNAR_BOUNDARY_PLUS;
+
     }
     if (fabsf(ulnarDiff) > 0.0f) {
-        float twistCoefficient = (fabsf(twistTheta) / (PI / 20.0f));
-        if (twistCoefficient > 1.0f) {
-            twistCoefficient = 1.0f;
+        float twistCoefficient = 0.0f;
+        
+        if (left) {
+            twistCoefficient = twistTheta;
+            if (twistCoefficient > (PI / 6.0f)) {
+                twistCoefficient = 1.0f;
+            } else {
+                twistCoefficient = 0.0f;
+            }
+        } else {
+            twistCoefficient = twistTheta;
+            if (twistCoefficient < (-PI / 6.0f)) {
+                twistCoefficient = 1.0f;
+            } else {
+                twistCoefficient = 0.0f;
+            }
+
         }
+        
         if (twistTheta < 0.0f) {
             if (left) {
-                ulnarCorrection -= glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 45.0f * twistCoefficient;
+                ulnarCorrection -= glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 180.0f * twistCoefficient;
             } else {
-                ulnarCorrection += glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 45.0f * twistCoefficient;
+                ulnarCorrection += glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 180.0f * twistCoefficient;
             }
         } else {
             if (left) {
-                ulnarCorrection += glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 45.0f * twistCoefficient;
+                ulnarCorrection += glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 180.0f * twistCoefficient;
             } else {
-                ulnarCorrection -= glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 45.0f * twistCoefficient;
+                ulnarCorrection -= glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 180.0f * twistCoefficient;
             }
         }
-        if (fabsf(ulnarCorrection) > 20.0f) {
-            ulnarCorrection = glm::sign(ulnarCorrection) * 20.0f;
+        if (fabsf(ulnarCorrection) > 100.0f) {
+            ulnarCorrection = glm::sign(ulnarCorrection) * 100.0f;
         }
         currentWristCoefficient += ulnarCorrection;
     }
@@ -1829,7 +1807,7 @@ static float computeTwistCompensation(float twistTheta, bool left) {
     float twistCorrection = 0.0f;
     
     if (fabsf(twistTheta) > TWIST_DEADZONE) {
-        twistCorrection = glm::sign(twistTheta) * ((fabsf(twistTheta) - TWIST_DEADZONE) / PI) * 100.0f;
+        twistCorrection = glm::sign(twistTheta) * ((fabsf(twistTheta) - TWIST_DEADZONE) / PI) * 90.0f;
     }
     // limit the twist correction
     if (fabsf(twistCorrection) > 30.0f) {
@@ -1864,13 +1842,22 @@ static float computeFlexCompensation(float flexTheta, bool left) {
 
 }
 
+static float getAxisThetaFromRotation(glm::vec3 axis, glm::quat rotation) {
+
+    //get the flex/extension of the wrist rotation
+    glm::quat rotationAboutTheAxis;
+    glm::quat rotationOrthoganalToAxis;
+    swingTwistDecomposition(rotation, axis, rotationOrthoganalToAxis, rotationAboutTheAxis);
+    if (rotationAboutTheAxis.w < 0.0f) {
+        rotationAboutTheAxis *= -1.0f;
+    }
+    glm::vec3 rotAxis = glm::axis(rotationAboutTheAxis);
+    float axisTheta = glm::sign(glm::dot(rotAxis, axis)) * glm::angle(rotationAboutTheAxis);
+
+    return axisTheta;
+}
+
 bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int shoulderIndex, bool left, glm::vec3& poleVector) {
-    // get the default poses for the upper and lower arm
-    // then use this length to judge how far the hand is away from the shoulder.
-    // then create weights that make the elbow angle less when the x value is large in either direction.
-    // make the angle less when z is small.
-    // lower y with x center lower angle
-    // lower y with x out higher angle
 
     AnimPose handPose = _externalPoseSet._absolutePoses[handIndex];
     AnimPose shoulderPose = _externalPoseSet._absolutePoses[shoulderIndex];
@@ -1879,6 +1866,7 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s
     AnimPose absoluteShoulderPose = getAbsoluteDefaultPose(shoulderIndex);
     AnimPose absoluteHandPose = getAbsoluteDefaultPose(handIndex);
     float defaultArmLength = glm::length(absoluteHandPose.trans() - absoluteShoulderPose.trans());
+
     glm::vec3 armToHand = handPose.trans() - shoulderPose.trans();
     glm::vec3 unitAxis;
     float axisLength = glm::length(armToHand);
@@ -1888,9 +1876,11 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s
         unitAxis = Vectors::UNIT_Y;
     }
    
+    // get the pole vector theta based on the hand position relative to the shoulder.
     float positionalTheta = getHandPositionTheta(armToHand, defaultArmLength, left);
-    qCDebug(animation) << "hand position theta " << left << " " << positionalTheta;
+    //qCDebug(animation) << "hand position theta " << left << " " << positionalTheta;
 
+    /*
     float deltaTheta = 0.0f;
     if (left) {
         deltaTheta = positionalTheta - _lastThetaLeft;
@@ -1907,166 +1897,132 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s
     glm::quat nonAxisRotation;
     swingTwistDecomposition(updatedBase.rot(), unitAxis, nonAxisRotation, axisRotation);
     //qCDebug(animation) << "the rotation about the axis of the arm " << (glm::sign(glm::axis(axisRotation)[2]) * glm::angle(axisRotation) / PI)*180.0f << " delta Rot theta " << deltaTheta;
+    
+    //glm::quat relativeHandRotation = (newAbsMid.inverse() * handPose).rot();
+    */
 
     // now we calculate the contribution of the hand rotation relative to the arm
-    // we are adding in the delta rotation so that we have the hand correction relative to the
-    // latest theta for hand position
-    //glm::quat relativeHandRotation = (newAbsMid.inverse() * handPose).rot();
     glm::quat relativeHandRotation = (elbowPose.inverse() * handPose).rot();
     if (relativeHandRotation.w < 0.0f) {
         relativeHandRotation *= -1.0f;
     }
 
-    glm::quat ulnarDeviation;
-    glm::quat nonUlnarDeviation;
-    swingTwistDecomposition(relativeHandRotation, Vectors::UNIT_Z, nonUlnarDeviation, ulnarDeviation);
-    if (ulnarDeviation.w < 0.0f) {
-        ulnarDeviation *= 1.0f;
-    }
-    glm::vec3 ulnarAxis = glm::axis(ulnarDeviation);
-    float ulnarDeviationTheta = glm::sign(ulnarAxis[2]) * glm::angle(ulnarDeviation);
+    // find the thetas, hand relative to avatar arm
+    const glm::vec3 ULNAR_ROTATION_AXIS = Vectors::UNIT_Z;
+    const glm::vec3 TWIST_ROTATION_AXIS = Vectors::UNIT_Y;
+    const glm::vec3 FLEX__ROTATION_AXIS = Vectors::UNIT_X;
+
+    float ulnarDeviationTheta = getAxisThetaFromRotation(ULNAR_ROTATION_AXIS, relativeHandRotation);
+    float flexTheta = getAxisThetaFromRotation(FLEX__ROTATION_AXIS, relativeHandRotation);
+    float trueTwistTheta = getAxisThetaFromRotation(TWIST_ROTATION_AXIS, relativeHandRotation);
+
+    const float HALFWAY_ANGLE = PI / 2.0f;
+    const float SMOOTHING_COEFFICIENT = 0.5f;
     if (left) {
-        if (glm::sign(ulnarDeviationTheta) != glm::sign(_ulnarRadialThetaRunningAverageLeft) && fabsf(ulnarDeviationTheta) > (PI / 2.0f)) {
-            // don't allow the theta to cross the 180 degree limit.
+
+        if (glm::sign(ulnarDeviationTheta) != glm::sign(_ulnarRadialThetaRunningAverageLeft) && fabsf(ulnarDeviationTheta) > HALFWAY_ANGLE) {
+            // don't allow the theta to cross the 180 degree limit. ie don't go from 179 to -179 degrees
             ulnarDeviationTheta = -1.0f * ulnarDeviationTheta;
         }
-        // put some smoothing on the theta
+        if (glm::sign(flexTheta) != glm::sign(_flexThetaRunningAverageLeft) && fabsf(flexTheta) > HALFWAY_ANGLE) {
+            // don't allow the theta to cross the 180 degree limit.
+            flexTheta = -1.0f * flexTheta;
+        }
+        if (glm::sign(trueTwistTheta) != glm::sign(_twistThetaRunningAverageLeft) && fabsf(trueTwistTheta) > HALFWAY_ANGLE) {
+            // don't allow the theta to cross the 180 degree limit.
+            trueTwistTheta = -1.0f * trueTwistTheta;
+        }
+
+        // put some smoothing on the thetas
         _ulnarRadialThetaRunningAverageLeft = ulnarDeviationTheta;
+        _flexThetaRunningAverageLeft = SMOOTHING_COEFFICIENT * _flexThetaRunningAverageLeft + (1.0f - SMOOTHING_COEFFICIENT) * flexTheta;
+        _twistThetaRunningAverageLeft = SMOOTHING_COEFFICIENT * _twistThetaRunningAverageLeft + (1.0f - SMOOTHING_COEFFICIENT) * trueTwistTheta;
+
     } else {
-        if (glm::sign(ulnarDeviationTheta) != glm::sign(_ulnarRadialThetaRunningAverageRight) && fabsf(ulnarDeviationTheta) > (PI / 2.0f)) {
-            // don't allow the theta to cross the 180 degree limit.
+
+        if (glm::sign(ulnarDeviationTheta) != glm::sign(_ulnarRadialThetaRunningAverageRight) && fabsf(ulnarDeviationTheta) > HALFWAY_ANGLE) {
+            // don't allow the theta to cross the 180 degree limit. ie don't go from 179 to -179 degrees
             ulnarDeviationTheta = -1.0f * ulnarDeviationTheta;
         }
-        // put some smoothing on the theta
+        if (glm::sign(flexTheta) != glm::sign(_flexThetaRunningAverageRight) && fabsf(flexTheta) > HALFWAY_ANGLE) {
+            // don't allow the theta to cross the 180 degree limit.
+            flexTheta = -1.0f * flexTheta;
+        }
+        if (glm::sign(trueTwistTheta) != glm::sign(_twistThetaRunningAverageRight) && fabsf(trueTwistTheta) > HALFWAY_ANGLE) {
+            // don't allow the theta to cross the 180 degree limit.
+            trueTwistTheta = -1.0f * trueTwistTheta;
+        }
+
+        // put some smoothing on the thetas
+        _twistThetaRunningAverageRight = SMOOTHING_COEFFICIENT * _twistThetaRunningAverageRight + (1.0f - SMOOTHING_COEFFICIENT) * trueTwistTheta;
+        _flexThetaRunningAverageRight = SMOOTHING_COEFFICIENT * _flexThetaRunningAverageRight + (1.0f - SMOOTHING_COEFFICIENT) * flexTheta;
         _ulnarRadialThetaRunningAverageRight = ulnarDeviationTheta;
     }
 
-    //get the flex/extension of the wrist rotation
-    glm::quat flex;
-    glm::quat nonFlex;
-    swingTwistDecomposition(relativeHandRotation, Vectors::UNIT_X, nonFlex, flex);
-    if (flex.w < 0.0f) {
-        flex *= 1.0f;
-    }
-    glm::vec3 flexAxis = glm::axis(flex);
-    float flexTheta = glm::sign(flexAxis[0]) * glm::angle(flex);
-
-    if (left) {
-        if (glm::sign(flexTheta) != glm::sign(_flexThetaRunningAverageLeft) && fabsf(flexTheta) > (PI / 2.0f)) {
-            // don't allow the theta to cross the 180 degree limit.
-            flexTheta = -1.0f * flexTheta;
-        }
-        // put some smoothing on the theta
-        _flexThetaRunningAverageLeft = 0.5f * _flexThetaRunningAverageLeft + 0.5f * flexTheta;
-    } else {
-        if (glm::sign(flexTheta) != glm::sign(_flexThetaRunningAverageRight) && fabsf(flexTheta) > (PI / 2.0f)) {
-            // don't allow the theta to cross the 180 degree limit.
-            flexTheta = -1.0f * flexTheta;
-        }
-        // put some smoothing on the theta
-        _flexThetaRunningAverageRight = 0.5f * _flexThetaRunningAverageRight + 0.5f * flexTheta;
-    }
-
-    glm::quat twist;
-    glm::quat nonTwist;
-    swingTwistDecomposition(relativeHandRotation, Vectors::UNIT_Y, nonTwist, twist);
-    if (twist.w < 0.0f) {
-        twist *= 1.0f;
-    }
-    glm::vec3 trueTwistAxis = glm::axis(twist);
-    float trueTwistTheta = glm::sign(trueTwistAxis[1]) * glm::angle(twist);
-    if (left) {
-        if (glm::sign(trueTwistTheta) != glm::sign(_twistThetaRunningAverageLeft) && fabsf(trueTwistTheta) > (PI / 2.0f)) {
-            // don't allow the theta to cross the 180 degree limit.
-            trueTwistTheta = -1.0f * trueTwistTheta;
-        }
-        // put some smoothing on the theta
-        _twistThetaRunningAverageLeft = 0.5f * _twistThetaRunningAverageLeft + 0.5f * trueTwistTheta;
-    } else {
-        if (glm::sign(trueTwistTheta) != glm::sign(_twistThetaRunningAverageRight) && fabsf(trueTwistTheta) > (PI / 2.0f)) {
-            // don't allow the theta to cross the 180 degree limit.
-            trueTwistTheta = -1.0f * trueTwistTheta;
-        }
-        // put some smoothing on the theta
-        _twistThetaRunningAverageRight = 0.5f * _twistThetaRunningAverageRight + 0.5f * trueTwistTheta;
-    }
-
+    // get the correction angle for each axis and add it to the base pole vector theta
     float currentWristCoefficient = 0.0f;
-    
     if (left) {
-        currentWristCoefficient += computeUlnarRadialCompensation(_ulnarRadialThetaRunningAverageLeft, _twistThetaRunningAverageLeft, left);
         currentWristCoefficient += computeTwistCompensation(_twistThetaRunningAverageLeft, left);
         currentWristCoefficient += computeFlexCompensation(_flexThetaRunningAverageLeft, left);
+        //currentWristCoefficient += computeUlnarRadialCompensation(_ulnarRadialThetaRunningAverageLeft, _twistThetaRunningAverageLeft, left);
     } else {
-        currentWristCoefficient += computeUlnarRadialCompensation(_ulnarRadialThetaRunningAverageRight, _twistThetaRunningAverageRight, left);
         currentWristCoefficient += computeTwistCompensation(_twistThetaRunningAverageRight, left);
         currentWristCoefficient += computeFlexCompensation(_flexThetaRunningAverageRight, left);
+        //currentWristCoefficient += computeUlnarRadialCompensation(_ulnarRadialThetaRunningAverageRight, _twistThetaRunningAverageRight, left);
     }
-    
-    // i think limit theta here so we don't subtract more than is possible from last theta.
-    // actually theta is limited.  to what though?
-    float theta = 0.0f;
+
+    // find the previous contribution of the wrist and add the current wrist correction to it
     if (left) {
         _lastWristCoefficientLeft = _lastThetaLeft - _lastPositionThetaLeft;
         _lastWristCoefficientLeft += currentWristCoefficient;
         _lastPositionThetaLeft = positionalTheta;
-        theta = positionalTheta + _lastWristCoefficientLeft;
-        if (theta > 0.0f) {
-            theta = 0.0f;
-        }
-        //qCDebug(animation) << "theta " << theta << " lastThetaLeft " << _lastThetaLeft << "last position theta left"<<_lastPositionThetaLeft  << "last wrist coeff " << _lastWristCoefficientLeft;
+        _lastThetaLeft = positionalTheta + _lastWristCoefficientLeft;
     } else {
         _lastWristCoefficientRight = _lastThetaRight - _lastPositionThetaRight;
         _lastWristCoefficientRight += currentWristCoefficient;
         _lastPositionThetaRight = positionalTheta;
-        theta += positionalTheta + _lastWristCoefficientRight;
-        if (theta < 0.0f) {
-           theta = 0.0f;
-        }
+        _lastThetaRight = positionalTheta + _lastWristCoefficientRight;
     }
     
-    if (!left) {
-       // qCDebug(animation) << "theta " << theta << "Last wrist" << _lastWristCoefficientRight << " flex ave: " << (_flexThetaRunningAverageRight / PI) * 180.0f << " twist ave: " << (_twistThetaRunningAverageRight/ PI) * 180.0f << " ulnar deviation ave: " << (_ulnarRadialThetaRunningAverageRight / PI) * 180.0f;
+    if (left) {
+        qCDebug(animation) <<  " ulnar deviation ave: " << (_ulnarRadialThetaRunningAverageLeft / PI) * 180.0f  << " ulnar correction " << currentWristCoefficient << " twist theta " << (trueTwistTheta / PI) * 180.0f;
     }
 
-    // global limiting
+    // limit the correction anatomically possible angles and change to radians
+    const float LOWER_ANATOMICAL_ANGLE = 175.0f;
+    const float UPPER_ANATOMICAL_ANGLE = 50.0f;
     float thetaRadians = 0.0f;
     if (left) {
-        // final global smoothing
-        //_lastThetaLeft = 0.5f * _lastThetaLeft + 0.5f * theta;
-        _lastThetaLeft = theta;
 
         if (_lastThetaLeft > -50.0f) {
             _lastThetaLeft =  -50.0f;
         }
-        if (_lastThetaLeft < -175.0f) {
-            _lastThetaLeft = -175.0f;
+        if (_lastThetaLeft < -LOWER_ANATOMICAL_ANGLE) {
+            _lastThetaLeft = -LOWER_ANATOMICAL_ANGLE;
         }
         const float MIN_VALUE = 0.0001f;
         if (fabsf(_lastPositionThetaLeft - _lastThetaLeft) > MIN_VALUE) {
-            qCDebug(animation) << "theta " << theta << " lastThetaLeft " << _lastThetaLeft << "last position theta left" << _lastPositionThetaLeft << "last wrist coeff " << _lastWristCoefficientLeft;
+            //qCDebug(animation) << " lastThetaLeft " << _lastThetaLeft << "last position theta left" << _lastPositionThetaLeft << "last wrist coeff " << _lastWristCoefficientLeft;
         }
         // convert to radians and make 180 0 to match pole vector theta
         thetaRadians = ((180.0f - _lastThetaLeft) / 180.0f)*PI;
     } else {
-        // final global smoothing
-        _lastThetaRight = theta; // 0.5f * _lastThetaRight + 0.5f * theta;
 
-
-        if (_lastThetaRight < 50.0f) {
-            _lastThetaRight = 50.0f;
+        if (_lastThetaRight < UPPER_ANATOMICAL_ANGLE) {
+            _lastThetaRight = UPPER_ANATOMICAL_ANGLE;
         }
-        if (_lastThetaRight > 175.0f) {
-            _lastThetaRight = 175.0f;
+        if (_lastThetaRight > LOWER_ANATOMICAL_ANGLE) {
+            _lastThetaRight = LOWER_ANATOMICAL_ANGLE;
         }
         // convert to radians and make 180 0 to match pole vector theta
         thetaRadians = ((180.0f - _lastThetaRight) / 180.0f)*PI;
     }
 
-    float xValue = -1.0f * sin(thetaRadians);
-    float yValue = -1.0f * cos(thetaRadians);
-    float zValue = 0.0f;
-    glm::vec3 thetaVector(xValue, yValue, zValue);
+    // convert the final theta to a pole vector value
+    float poleVectorXValue = -1.0f * sin(thetaRadians);
+    float poleVectorYValue = -1.0f * cos(thetaRadians);
+    float poleVectorZValue = 0.0f;
+    glm::vec3 thetaVector(poleVectorXValue, poleVectorYValue, poleVectorZValue);
 
     glm::vec3 up = Vectors::UNIT_Y;
     glm::vec3 fwd = armToHand/glm::length(armToHand);

From 27bfe2f0fe1dbfbb5e5c2bfb967ec026ea8d3649 Mon Sep 17 00:00:00 2001
From: amantley 
Date: Wed, 20 Feb 2019 15:14:12 -0800
Subject: [PATCH 129/474] changed name of pre processor variable

---
 interface/src/avatar/MyAvatar.cpp             |  3 +-
 .../src/AnimPoleVectorConstraint.cpp          |  4 --
 libraries/animation/src/Rig.cpp               | 49 ++++---------------
 3 files changed, 10 insertions(+), 46 deletions(-)

diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index c2e7292a0a..8ae59ebdc1 100755
--- a/interface/src/avatar/MyAvatar.cpp
+++ b/interface/src/avatar/MyAvatar.cpp
@@ -2943,7 +2943,6 @@ void MyAvatar::setAnimGraphUrl(const QUrl& url) {
     connect(&(_skeletonModel->getRig()), SIGNAL(onLoadComplete()), this, SLOT(animGraphLoaded()));
 }
 
-#define USE_Q_OS_ANDROID
 void MyAvatar::initAnimGraph() {
     QUrl graphUrl;
     if (!_prefOverrideAnimGraphUrl.get().isEmpty()) {
@@ -2953,7 +2952,7 @@ void MyAvatar::initAnimGraph() {
     } else {
         graphUrl = PathUtils::resourcesUrl("avatar/avatar-animation.json");
 
-    #if defined(Q_OS_ANDROID) || defined(USE_Q_OS_ANDROID)
+    #if defined(Q_OS_ANDROID) || defined(HIFI_USE_Q_OS_ANDROID)
         graphUrl = PathUtils::resourcesUrl("avatar/avatar-animation_withSplineIKNode.json");
     #endif
     }
diff --git a/libraries/animation/src/AnimPoleVectorConstraint.cpp b/libraries/animation/src/AnimPoleVectorConstraint.cpp
index 36c58ecb4e..c0600ee253 100644
--- a/libraries/animation/src/AnimPoleVectorConstraint.cpp
+++ b/libraries/animation/src/AnimPoleVectorConstraint.cpp
@@ -123,10 +123,6 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim
 
         glm::quat deltaRot = glm::angleAxis(theta, unitAxis);
 
-        if (_tipJointName == "RightHand") {
-            //qCDebug(animation) << "anim ik theta " << (theta / PI)*180.0f;
-        }
-
         // transform result back into parent relative frame.
         glm::quat relBaseRot = glm::inverse(baseParentPose.rot()) * deltaRot * basePose.rot();
         ikChain.setRelativePoseAtJointIndex(_baseJointIndex, AnimPose(relBaseRot, underPoses[_baseJointIndex].trans()));
diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp
index 7273ccf637..2681777594 100644
--- a/libraries/animation/src/Rig.cpp
+++ b/libraries/animation/src/Rig.cpp
@@ -34,7 +34,6 @@
 #include "IKTarget.h"
 #include "PathUtils.h"
 
-#define USE_Q_OS_ANDROID
 static int nextRigId = 1;
 static std::map rigRegistry;
 static std::mutex rigRegistryMutex;
@@ -1460,7 +1459,7 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab
         int oppositeArmJointIndex = _animSkeleton->nameToJointIndex("RightArm");
         if (ENABLE_POLE_VECTORS && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0 && oppositeArmJointIndex >= 0) {
             glm::vec3 poleVector;
-#if defined(Q_OS_ANDROID) || defined(USE_Q_OS_ANDROID)
+#if defined(Q_OS_ANDROID) || defined(HIFI_USE_Q_OS_ANDROID)
             bool isLeft = true;
             bool usePoleVector = calculateElbowPoleVectorOptimized(handJointIndex, elbowJointIndex, armJointIndex, isLeft, poleVector);
 #else
@@ -1520,7 +1519,7 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab
 
         if (ENABLE_POLE_VECTORS && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0 && oppositeArmJointIndex >= 0) {
             glm::vec3 poleVector;
-#if defined(Q_OS_ANDROID) || defined(USE_Q_OS_ANDROID)
+#if defined(Q_OS_ANDROID) || defined(HIFI_USE_Q_OS_ANDROID)
             bool isLeft = false;
             bool usePoleVector = calculateElbowPoleVectorOptimized(handJointIndex, elbowJointIndex, armJointIndex, isLeft, poleVector);
 #else
@@ -1702,7 +1701,6 @@ static float getHandPositionTheta(glm::vec3 armToHand, float defaultArmLength, b
     const float zWeightBottom = -100.0f;
     const glm::vec3 weights(-50.0f, 60.0f, 90.0f);
 
-
     float yFactor = (fabsf(armToHand[1] / defaultArmLength) * weights[1]) + biases[1];
 
     float zFactor;
@@ -1714,7 +1712,6 @@ static float getHandPositionTheta(glm::vec3 armToHand, float defaultArmLength, b
 
     float xFactor;
     if (left) {
-        //xFactor = weights[0] * glm::max(-1.0f * (armToHand[0] / defaultArmLength) + xStart, 0.0f);
         xFactor = weights[0] * ((-1.0f * (armToHand[0] / defaultArmLength) + xStart) - (0.2f) * ((1.0f + (armToHand[1] / defaultArmLength)) / 2.0f));
     } else {
         xFactor = weights[0] * (((armToHand[0] / defaultArmLength) + xStart) - (0.2f) * ((1.0f + (armToHand[1] / defaultArmLength)) / 2.0f));
@@ -1722,18 +1719,19 @@ static float getHandPositionTheta(glm::vec3 armToHand, float defaultArmLength, b
 
     handPositionTheta = xFactor + yFactor + zFactor;
 
-    if (handPositionTheta < 50.0f) {
-        handPositionTheta = 50.0f;
+    const float LOWER_ANATOMICAL_ANGLE = 175.0f;
+    const float UPPER_ANATOMICAL_ANGLE = 50.0f;
+    if (handPositionTheta < LOWER_ANATOMICAL_ANGLE) {
+        handPositionTheta = LOWER_ANATOMICAL_ANGLE;
     }
-    if (handPositionTheta > 175.0f) {
-        handPositionTheta = 175.0f;
+    if (handPositionTheta > UPPER_ANATOMICAL_ANGLE) {
+        handPositionTheta = UPPER_ANATOMICAL_ANGLE;
     }
 
     if (left) {
         handPositionTheta *= -1.0f;
     }
 
-
     return handPositionTheta;
 }
 
@@ -1875,31 +1873,10 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s
     } else {
         unitAxis = Vectors::UNIT_Y;
     }
-   
+
     // get the pole vector theta based on the hand position relative to the shoulder.
     float positionalTheta = getHandPositionTheta(armToHand, defaultArmLength, left);
-    //qCDebug(animation) << "hand position theta " << left << " " << positionalTheta;
 
-    /*
-    float deltaTheta = 0.0f;
-    if (left) {
-        deltaTheta = positionalTheta - _lastThetaLeft;
-    } else {
-        deltaTheta = positionalTheta - _lastThetaRight;
-    }
-    float deltaThetaRadians = (deltaTheta / 180.0f)*PI;
-    AnimPose deltaRot(glm::angleAxis(deltaThetaRadians, unitAxis), glm::vec3());
-    AnimPose relMid = shoulderPose.inverse() * elbowPose;
-    AnimPose updatedBase = shoulderPose * deltaRot;
-    AnimPose newAbsMid = updatedBase * relMid;
-
-    glm::quat axisRotation;
-    glm::quat nonAxisRotation;
-    swingTwistDecomposition(updatedBase.rot(), unitAxis, nonAxisRotation, axisRotation);
-    //qCDebug(animation) << "the rotation about the axis of the arm " << (glm::sign(glm::axis(axisRotation)[2]) * glm::angle(axisRotation) / PI)*180.0f << " delta Rot theta " << deltaTheta;
-    
-    //glm::quat relativeHandRotation = (newAbsMid.inverse() * handPose).rot();
-    */
 
     // now we calculate the contribution of the hand rotation relative to the arm
     glm::quat relativeHandRotation = (elbowPose.inverse() * handPose).rot();
@@ -1983,10 +1960,6 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s
         _lastPositionThetaRight = positionalTheta;
         _lastThetaRight = positionalTheta + _lastWristCoefficientRight;
     }
-    
-    if (left) {
-        qCDebug(animation) <<  " ulnar deviation ave: " << (_ulnarRadialThetaRunningAverageLeft / PI) * 180.0f  << " ulnar correction " << currentWristCoefficient << " twist theta " << (trueTwistTheta / PI) * 180.0f;
-    }
 
     // limit the correction anatomically possible angles and change to radians
     const float LOWER_ANATOMICAL_ANGLE = 175.0f;
@@ -2000,10 +1973,6 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s
         if (_lastThetaLeft < -LOWER_ANATOMICAL_ANGLE) {
             _lastThetaLeft = -LOWER_ANATOMICAL_ANGLE;
         }
-        const float MIN_VALUE = 0.0001f;
-        if (fabsf(_lastPositionThetaLeft - _lastThetaLeft) > MIN_VALUE) {
-            //qCDebug(animation) << " lastThetaLeft " << _lastThetaLeft << "last position theta left" << _lastPositionThetaLeft << "last wrist coeff " << _lastWristCoefficientLeft;
-        }
         // convert to radians and make 180 0 to match pole vector theta
         thetaRadians = ((180.0f - _lastThetaLeft) / 180.0f)*PI;
     } else {

From 97da20781e1d4abcb68ad2a8cba34fd4ea1f5593 Mon Sep 17 00:00:00 2001
From: amantley 
Date: Wed, 20 Feb 2019 15:19:36 -0800
Subject: [PATCH 130/474] removed whitespace

---
 libraries/animation/src/Rig.cpp | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp
index 2681777594..ac026d192f 100644
--- a/libraries/animation/src/Rig.cpp
+++ b/libraries/animation/src/Rig.cpp
@@ -1757,7 +1757,7 @@ static float computeUlnarRadialCompensation(float ulnarRadialTheta, float twistT
     }
     if (fabsf(ulnarDiff) > 0.0f) {
         float twistCoefficient = 0.0f;
-        
+
         if (left) {
             twistCoefficient = twistTheta;
             if (twistCoefficient > (PI / 6.0f)) {
@@ -1774,7 +1774,7 @@ static float computeUlnarRadialCompensation(float ulnarRadialTheta, float twistT
             }
 
         }
-        
+
         if (twistTheta < 0.0f) {
             if (left) {
                 ulnarCorrection -= glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 180.0f * twistCoefficient;
@@ -1803,7 +1803,7 @@ static float computeTwistCompensation(float twistTheta, bool left) {
 
     const float TWIST_DEADZONE = PI / 2.0f;
     float twistCorrection = 0.0f;
-    
+
     if (fabsf(twistTheta) > TWIST_DEADZONE) {
         twistCorrection = glm::sign(twistTheta) * ((fabsf(twistTheta) - TWIST_DEADZONE) / PI) * 90.0f;
     }
@@ -1821,7 +1821,7 @@ static float computeFlexCompensation(float flexTheta, bool left) {
     const float EXTEND_BOUNDARY = -PI / 4.0f;
     float flexCorrection = 0.0f;
     float currentWristCoefficient = 0.0f;
-  
+
     if (flexTheta > FLEX_BOUNDARY) {
         flexCorrection = ((flexTheta - FLEX_BOUNDARY) / PI) * 60.0f;
     } else if (flexTheta < EXTEND_BOUNDARY) {

From f8a74efdc247cb6f2f57f0b1c97f91591b56c0fa Mon Sep 17 00:00:00 2001
From: amantley 
Date: Wed, 20 Feb 2019 17:59:45 -0800
Subject: [PATCH 131/474] fixed build errors from jenkins

---
 CMakeLists.txt                           | 5 +++++
 interface/src/avatar/MySkeletonModel.cpp | 2 --
 libraries/animation/src/AnimSplineIK.cpp | 3 ---
 libraries/animation/src/Rig.cpp          | 4 ++--
 4 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index c8710eed05..045234ebe1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -236,6 +236,11 @@ if (BUILD_TESTS)
   endif()
 endif()
 
+set(HIFI_USE_Q_OS_ANDROID=TRUE)
+if (HIFI_USE_Q_OS_ANDROID)
+  add_definitions(-DHIFI_USE_Q_OS_ANDROID=TRUE)
+endif()
+
 if (BUILD_INSTALLER)
   if (UNIX)
     install(
diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp
index 32a8e1e38d..5837e0a84c 100755
--- a/interface/src/avatar/MySkeletonModel.cpp
+++ b/interface/src/avatar/MySkeletonModel.cpp
@@ -15,8 +15,6 @@
 #include "InterfaceLogging.h"
 #include "AnimUtil.h"
 
-#define USE_Q_OS_ANDROID
-
 
 MySkeletonModel::MySkeletonModel(Avatar* owningAvatar, QObject* parent) : SkeletonModel(owningAvatar, parent) {
 }
diff --git a/libraries/animation/src/AnimSplineIK.cpp b/libraries/animation/src/AnimSplineIK.cpp
index 30e0a42e65..cfb34560ff 100644
--- a/libraries/animation/src/AnimSplineIK.cpp
+++ b/libraries/animation/src/AnimSplineIK.cpp
@@ -362,7 +362,6 @@ void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, int base, c
 
             AnimPose relPose = parentAbsPose.inverse() * flexedAbsPose;
 
-            bool constrained = false;
             if (splineJointInfo.jointIndex != base) {
                 // constrain the amount the spine can stretch or compress
                 float length = glm::length(relPose.trans());
@@ -374,10 +373,8 @@ void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, int base, c
                     const float MIN_LENGTH = defaultLength * (1.0f - STRETCH_COMPRESS_PERCENTAGE);
                     if (length > MAX_LENGTH) {
                         relPose.trans() = (relPose.trans() / length) * MAX_LENGTH;
-                        constrained = true;
                     } else if (length < MIN_LENGTH) {
                         relPose.trans() = (relPose.trans() / length) * MIN_LENGTH;
-                        constrained = true;
                     }
                 } else {
                     relPose.trans() = glm::vec3(0.0f);
diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp
index ac026d192f..9139776612 100644
--- a/libraries/animation/src/Rig.cpp
+++ b/libraries/animation/src/Rig.cpp
@@ -1988,8 +1988,8 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s
     }
 
     // convert the final theta to a pole vector value
-    float poleVectorXValue = -1.0f * sin(thetaRadians);
-    float poleVectorYValue = -1.0f * cos(thetaRadians);
+    float poleVectorXValue = -1.0f * sinf(thetaRadians);
+    float poleVectorYValue = -1.0f * cosf(thetaRadians);
     float poleVectorZValue = 0.0f;
     glm::vec3 thetaVector(poleVectorXValue, poleVectorYValue, poleVectorZValue);
 

From b6bc467f4b66702e8cbafbe08b697ae448f5d650 Mon Sep 17 00:00:00 2001
From: Angus Antley 
Date: Thu, 21 Feb 2019 06:42:55 -0800
Subject: [PATCH 132/474] added HIFI_USE_Q_OS_ANDROID to cmake lists and to
 MySkeletonModel

---
 CMakeLists.txt                           | 15 +++++++++------
 interface/src/avatar/MySkeletonModel.cpp |  4 ++--
 2 files changed, 11 insertions(+), 8 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 045234ebe1..5bd4d4620b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -52,6 +52,7 @@ else()
   set(MOBILE 0)
 endif()
 
+set(HIFI_USE_Q_OS_ANDROID_OPTION OFF)
 set(BUILD_CLIENT_OPTION ON)
 set(BUILD_SERVER_OPTION ON)
 set(BUILD_TESTS_OPTION OFF)
@@ -108,7 +109,7 @@ if (USE_GLES AND (NOT ANDROID))
   set(DISABLE_QML_OPTION ON)
 endif()
 
-
+option(HIFI_USE_Q_OS_ANDROID "USE OPTIMIZED IK" ${HIFI_USE_Q_OS_ANDROID_OPTION})
 option(BUILD_CLIENT "Build client components" ${BUILD_CLIENT_OPTION})
 option(BUILD_SERVER "Build server components" ${BUILD_SERVER_OPTION})
 option(BUILD_TESTS "Build tests" ${BUILD_TESTS_OPTION})
@@ -139,6 +140,7 @@ foreach(PLATFORM_QT_COMPONENT ${PLATFORM_QT_COMPONENTS})
   list(APPEND PLATFORM_QT_LIBRARIES "Qt5::${PLATFORM_QT_COMPONENT}")
 endforeach()
 
+MESSAGE(STATUS "USE OPTIMIZED IK:      " ${HIFI_USE_Q_OS_ANDROID})
 MESSAGE(STATUS "Build server:          " ${BUILD_SERVER})
 MESSAGE(STATUS "Build client:          " ${BUILD_CLIENT})
 MESSAGE(STATUS "Build tests:           " ${BUILD_TESTS})
@@ -184,6 +186,12 @@ find_package( Threads )
 add_definitions(-DGLM_FORCE_RADIANS)
 add_definitions(-DGLM_ENABLE_EXPERIMENTAL)
 add_definitions(-DGLM_FORCE_CTOR_INIT)
+#add_definitions(-DHIFI_USE_Q_OS_ANDROID)
+#option(HIFI_USE_Q_OS_ANDROID_OPTION "hifi_use_optimized_ik" OFF)
+if (HIFI_USE_Q_OS_ANDROID)
+  MESSAGE(STATUS "SET THE USE IK DEFINITION ")
+  add_definitions(-DHIFI_USE_Q_OS_ANDROID)
+endif()
 set(HIFI_LIBRARY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libraries")
 
 set(EXTERNAL_PROJECT_PREFIX "project")
@@ -236,11 +244,6 @@ if (BUILD_TESTS)
   endif()
 endif()
 
-set(HIFI_USE_Q_OS_ANDROID=TRUE)
-if (HIFI_USE_Q_OS_ANDROID)
-  add_definitions(-DHIFI_USE_Q_OS_ANDROID=TRUE)
-endif()
-
 if (BUILD_INSTALLER)
   if (UNIX)
     install(
diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp
index 5837e0a84c..86a0dceae0 100755
--- a/interface/src/avatar/MySkeletonModel.cpp
+++ b/interface/src/avatar/MySkeletonModel.cpp
@@ -250,7 +250,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
             AnimPose headAvatarSpace(avatarHeadPose.getRotation(), avatarHeadPose.getTranslation());
             AnimPose headRigSpace = avatarToRigPose * headAvatarSpace;
             AnimPose hipsRigSpace = sensorToRigPose * sensorHips;
-#if defined(Q_OS_ANDROID) || defined(USE_Q_OS_ANDROID)
+#if defined(Q_OS_ANDROID) || defined(HIFI_USE_Q_OS_ANDROID)
             glm::vec3 spine2TargetTranslation = computeSpine2WithHeadHipsSpline(myAvatar, hipsRigSpace, headRigSpace);
 #endif
             const float SPINE2_ROTATION_FILTER = 0.5f;
@@ -274,7 +274,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
                 }
                 generateBasisVectors(up, fwd, u, v, w);
                 AnimPose newSpinePose(glm::mat4(glm::vec4(w, 0.0f), glm::vec4(u, 0.0f), glm::vec4(v, 0.0f), glm::vec4(glm::vec3(0.0f, 0.0f, 0.0f), 1.0f)));
-#if defined(Q_OS_ANDROID) || defined(USE_Q_OS_ANDROID)
+#if defined(Q_OS_ANDROID) || defined(HIFI_USE_Q_OS_ANDROID)
                 currentSpine2Pose.trans() = spine2TargetTranslation;
 #endif
                 currentSpine2Pose.rot() = safeLerp(currentSpine2Pose.rot(), newSpinePose.rot(), SPINE2_ROTATION_FILTER);

From 33fd64c68c908800ca24d569e244d08e23ba2f30 Mon Sep 17 00:00:00 2001
From: amantley 
Date: Thu, 21 Feb 2019 09:00:23 -0800
Subject: [PATCH 133/474] direction on compare in positional theta function

---
 libraries/animation/src/Rig.cpp | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp
index 9139776612..f2b4be077f 100644
--- a/libraries/animation/src/Rig.cpp
+++ b/libraries/animation/src/Rig.cpp
@@ -1721,10 +1721,10 @@ static float getHandPositionTheta(glm::vec3 armToHand, float defaultArmLength, b
 
     const float LOWER_ANATOMICAL_ANGLE = 175.0f;
     const float UPPER_ANATOMICAL_ANGLE = 50.0f;
-    if (handPositionTheta < LOWER_ANATOMICAL_ANGLE) {
+    if (handPositionTheta > LOWER_ANATOMICAL_ANGLE) {
         handPositionTheta = LOWER_ANATOMICAL_ANGLE;
     }
-    if (handPositionTheta > UPPER_ANATOMICAL_ANGLE) {
+    if (handPositionTheta < UPPER_ANATOMICAL_ANGLE) {
         handPositionTheta = UPPER_ANATOMICAL_ANGLE;
     }
 
@@ -1876,7 +1876,9 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s
 
     // get the pole vector theta based on the hand position relative to the shoulder.
     float positionalTheta = getHandPositionTheta(armToHand, defaultArmLength, left);
-
+    if (left) {
+        qCDebug(animation) << "positional theta left "<< positionalTheta;
+    }
 
     // now we calculate the contribution of the hand rotation relative to the arm
     glm::quat relativeHandRotation = (elbowPose.inverse() * handPose).rot();

From 4fdf556d5dc37bd62df39f7e4cd001a513a922de Mon Sep 17 00:00:00 2001
From: SamGondelman 
Date: Thu, 21 Feb 2019 10:29:21 -0800
Subject: [PATCH 134/474] fix billboard mode in secondary camera

---
 interface/src/Application.cpp                  | 18 +++++++++---------
 .../src/RenderableImageEntityItem.cpp          |  2 +-
 .../src/RenderableTextEntityItem.cpp           |  2 +-
 .../src/RenderableWebEntityItem.cpp            |  2 +-
 libraries/entities/src/EntityItem.cpp          |  3 ++-
 libraries/entities/src/EntityItem.h            |  9 ++++++---
 libraries/entities/src/ImageEntityItem.cpp     |  2 +-
 libraries/entities/src/TextEntityItem.cpp      |  2 +-
 libraries/entities/src/WebEntityItem.cpp       |  2 +-
 9 files changed, 23 insertions(+), 19 deletions(-)

diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index a611738445..83b287b7ae 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -2301,31 +2301,31 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
         DependencyManager::get()->setPrecisionPicking(rayPickID, value);
     });
 
-    EntityItem::setBillboardRotationOperator([this](const glm::vec3& position, const glm::quat& rotation, BillboardMode billboardMode) {
+    EntityItem::setBillboardRotationOperator([this](const glm::vec3& position, const glm::quat& rotation, BillboardMode billboardMode, const glm::vec3& frustumPos) {
         if (billboardMode == BillboardMode::YAW) {
             //rotate about vertical to face the camera
-            ViewFrustum frustum;
-            copyViewFrustum(frustum);
-            glm::vec3 dPosition = frustum.getPosition() - position;
+            glm::vec3 dPosition = frustumPos - position;
             // If x and z are 0, atan(x, z) is undefined, so default to 0 degrees
             float yawRotation = dPosition.x == 0.0f && dPosition.z == 0.0f ? 0.0f : glm::atan(dPosition.x, dPosition.z);
             return glm::quat(glm::vec3(0.0f, yawRotation, 0.0f));
         } else if (billboardMode == BillboardMode::FULL) {
-            ViewFrustum frustum;
-            copyViewFrustum(frustum);
-            glm::vec3 cameraPos = frustum.getPosition();
             // use the referencial from the avatar, y isn't always up
             glm::vec3 avatarUP = DependencyManager::get()->getMyAvatar()->getWorldOrientation() * Vectors::UP;
             // check to see if glm::lookAt will work / using glm::lookAt variable name
-            glm::highp_vec3 s(glm::cross(position - cameraPos, avatarUP));
+            glm::highp_vec3 s(glm::cross(position - frustumPos, avatarUP));
 
             // make sure s is not NaN for any component
             if (glm::length2(s) > 0.0f) {
-                return glm::conjugate(glm::toQuat(glm::lookAt(cameraPos, position, avatarUP)));
+                return glm::conjugate(glm::toQuat(glm::lookAt(frustumPos, position, avatarUP)));
             }
         }
         return rotation;
     });
+    EntityItem::setPrimaryViewFrustumPositionOperator([this]() {
+        ViewFrustum viewFrustum;
+        copyViewFrustum(viewFrustum);
+        return viewFrustum.getPosition();
+    });
 
     render::entities::WebEntityRenderer::setAcquireWebSurfaceOperator([this](const QString& url, bool htmlContent, QSharedPointer& webSurface, bool& cachedWebSurface) {
         bool isTablet = url == TabletScriptingInterface::QML;
diff --git a/libraries/entities-renderer/src/RenderableImageEntityItem.cpp b/libraries/entities-renderer/src/RenderableImageEntityItem.cpp
index 96dd1733e7..6638bc0687 100644
--- a/libraries/entities-renderer/src/RenderableImageEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableImageEntityItem.cpp
@@ -170,7 +170,7 @@ void ImageEntityRenderer::doRender(RenderArgs* args) {
     Q_ASSERT(args->_batch);
     gpu::Batch* batch = args->_batch;
 
-    transform.setRotation(EntityItem::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode));
+    transform.setRotation(EntityItem::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode, args->getViewFrustum().getPosition()));
     transform.postScale(dimensions);
 
     batch->setModelTransform(transform);
diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp
index 99912e9d91..dfc9277bf0 100644
--- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp
@@ -181,7 +181,7 @@ void TextEntityRenderer::doRender(RenderArgs* args) {
     gpu::Batch& batch = *args->_batch;
 
     auto transformToTopLeft = modelTransform;
-    transformToTopLeft.setRotation(EntityItem::getBillboardRotation(transformToTopLeft.getTranslation(), transformToTopLeft.getRotation(), _billboardMode));
+    transformToTopLeft.setRotation(EntityItem::getBillboardRotation(transformToTopLeft.getTranslation(), transformToTopLeft.getRotation(), _billboardMode, args->getViewFrustum().getPosition()));
     transformToTopLeft.postTranslate(dimensions * glm::vec3(-0.5f, 0.5f, 0.0f)); // Go to the top left
     transformToTopLeft.setScale(1.0f); // Use a scale of one so that the text is not deformed
 
diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp
index bf7820fecd..ccd815b74a 100644
--- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp
@@ -323,7 +323,7 @@ void WebEntityRenderer::doRender(RenderArgs* args) {
     });
     batch.setResourceTexture(0, _texture);
 
-    transform.setRotation(EntityItem::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode));
+    transform.setRotation(EntityItem::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode, args->getViewFrustum().getPosition()));
     batch.setModelTransform(transform);
 
     // Turn off jitter for these entities
diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp
index 9f11b3c018..a431c82390 100644
--- a/libraries/entities/src/EntityItem.cpp
+++ b/libraries/entities/src/EntityItem.cpp
@@ -49,7 +49,8 @@ int EntityItem::_maxActionsDataSize = 800;
 quint64 EntityItem::_rememberDeletedActionTime = 20 * USECS_PER_SECOND;
 QString EntityItem::_marketplacePublicKey;
 
-std::function EntityItem::_getBillboardRotationOperator = [](const glm::vec3&, const glm::quat& rotation, BillboardMode) { return rotation; };
+std::function EntityItem::_getBillboardRotationOperator = [](const glm::vec3&, const glm::quat& rotation, BillboardMode, const glm::vec3&) { return rotation; };
+std::function EntityItem::_getPrimaryViewFrustumPositionOperator = []() { return glm::vec3(0.0f); };
 
 EntityItem::EntityItem(const EntityItemID& entityItemID) :
     SpatiallyNestable(NestableType::Entity, entityItemID)
diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h
index 824261c022..0ca851e228 100644
--- a/libraries/entities/src/EntityItem.h
+++ b/libraries/entities/src/EntityItem.h
@@ -557,8 +557,10 @@ public:
     virtual void removeGrab(GrabPointer grab) override;
     virtual void disableGrab(GrabPointer grab) override;
 
-    static void setBillboardRotationOperator(std::function getBillboardRotationOperator) { _getBillboardRotationOperator = getBillboardRotationOperator; }
-    static glm::quat getBillboardRotation(const glm::vec3& position, const glm::quat& rotation, BillboardMode billboardMode) { return _getBillboardRotationOperator(position, rotation, billboardMode); }
+    static void setBillboardRotationOperator(std::function getBillboardRotationOperator) { _getBillboardRotationOperator = getBillboardRotationOperator; }
+    static glm::quat getBillboardRotation(const glm::vec3& position, const glm::quat& rotation, BillboardMode billboardMode, const glm::vec3& frustumPos) { return _getBillboardRotationOperator(position, rotation, billboardMode, frustumPos); }
+    static void setPrimaryViewFrustumPositionOperator(std::function getPrimaryViewFrustumPositionOperator) { _getPrimaryViewFrustumPositionOperator = getPrimaryViewFrustumPositionOperator; }
+    static glm::vec3 getPrimaryViewFrustumPosition() { return _getPrimaryViewFrustumPositionOperator(); }
 
 signals:
     void requestRenderUpdate();
@@ -748,7 +750,8 @@ protected:
     QHash _grabActions;
 
 private:
-    static std::function _getBillboardRotationOperator;
+    static std::function _getBillboardRotationOperator;
+    static std::function _getPrimaryViewFrustumPositionOperator;
 };
 
 #endif // hifi_EntityItem_h
diff --git a/libraries/entities/src/ImageEntityItem.cpp b/libraries/entities/src/ImageEntityItem.cpp
index 837e824f4a..090ae91277 100644
--- a/libraries/entities/src/ImageEntityItem.cpp
+++ b/libraries/entities/src/ImageEntityItem.cpp
@@ -159,7 +159,7 @@ bool ImageEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const
     glm::vec2 xyDimensions(dimensions.x, dimensions.y);
     glm::quat rotation = getWorldOrientation();
     glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint()));
-    rotation = EntityItem::getBillboardRotation(position, rotation, _billboardMode);
+    rotation = EntityItem::getBillboardRotation(position, rotation, _billboardMode, EntityItem::getPrimaryViewFrustumPosition());
 
     if (findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance)) {
         glm::vec3 forward = rotation * Vectors::FRONT;
diff --git a/libraries/entities/src/TextEntityItem.cpp b/libraries/entities/src/TextEntityItem.cpp
index bc98c61ff7..5dff645c89 100644
--- a/libraries/entities/src/TextEntityItem.cpp
+++ b/libraries/entities/src/TextEntityItem.cpp
@@ -199,7 +199,7 @@ bool TextEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const
     glm::vec2 xyDimensions(dimensions.x, dimensions.y);
     glm::quat rotation = getWorldOrientation();
     glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint()));
-    rotation = EntityItem::getBillboardRotation(position, rotation, _billboardMode);
+    rotation = EntityItem::getBillboardRotation(position, rotation, _billboardMode, EntityItem::getPrimaryViewFrustumPosition());
 
     if (findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance)) {
         glm::vec3 forward = rotation * Vectors::FRONT;
diff --git a/libraries/entities/src/WebEntityItem.cpp b/libraries/entities/src/WebEntityItem.cpp
index 5a948fbfd4..0748790df9 100644
--- a/libraries/entities/src/WebEntityItem.cpp
+++ b/libraries/entities/src/WebEntityItem.cpp
@@ -180,7 +180,7 @@ bool WebEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const g
     glm::vec2 xyDimensions(dimensions.x, dimensions.y);
     glm::quat rotation = getWorldOrientation();
     glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint()));
-    rotation = EntityItem::getBillboardRotation(position, rotation, _billboardMode);
+    rotation = EntityItem::getBillboardRotation(position, rotation, _billboardMode, EntityItem::getPrimaryViewFrustumPosition());
 
     if (findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance)) {
         glm::vec3 forward = rotation * Vectors::FRONT;

From ec4d069011c6dea61029644222a22a10128d1fef Mon Sep 17 00:00:00 2001
From: luiscuenca 
Date: Thu, 21 Feb 2019 12:00:19 -0700
Subject: [PATCH 135/474] Allow threads with only one joint, remove dummy
 joints and unused constants

---
 libraries/animation/src/Flow.cpp | 79 ++++++++++++++------------------
 libraries/animation/src/Flow.h   | 47 ++++++-------------
 libraries/animation/src/Rig.cpp  | 11 ++---
 3 files changed, 54 insertions(+), 83 deletions(-)

diff --git a/libraries/animation/src/Flow.cpp b/libraries/animation/src/Flow.cpp
index 480ded2002..9fb0306fa3 100644
--- a/libraries/animation/src/Flow.cpp
+++ b/libraries/animation/src/Flow.cpp
@@ -252,6 +252,7 @@ void FlowJoint::setInitialData(const glm::vec3& initialPosition, const glm::vec3
     _previousPosition = initialPosition;
     _currentPosition = initialPosition;
     _initialTranslation = initialTranslation;
+    _currentRotation = initialRotation;
     _initialRotation = initialRotation;
     _translationDirection = glm::normalize(_initialTranslation);
     _parentPosition = parentPosition;
@@ -280,7 +281,7 @@ void FlowJoint::update(float deltaTime) {
     }
     FlowNode::update(deltaTime, accelerationOffset);
     if (_anchored) {
-        if (!_isDummy) {
+        if (!_isHelper) {
             _currentPosition = _updatedPosition;
         } else {
             _currentPosition = _parentPosition;
@@ -301,19 +302,10 @@ void FlowJoint::solve(const FlowCollisionResult& collision) {
     FlowNode::solve(_parentPosition, _length, collision);
 };
 
-FlowDummyJoint::FlowDummyJoint(const glm::vec3& initialPosition, int index, int parentIndex, int childIndex, FlowPhysicsSettings settings) :
-    FlowJoint(index, parentIndex, childIndex, DUMMY_KEYWORD + "_" + index, DUMMY_KEYWORD, settings) {
-    _isDummy = true;
+void FlowJoint::toHelperJoint(const glm::vec3& initialPosition, float length) {
     _initialPosition = initialPosition;
-    _length = DUMMY_JOINT_DISTANCE;
-}
-
-void FlowDummyJoint::toIsolatedJoint(float length, int childIndex, const QString& group) {
-    _isDummy = false;
+    _isHelper = true;
     _length = length;
-    _childIndex = childIndex;
-    _group = group;
-
 }
 
 FlowThread::FlowThread(int rootIndex, std::map* joints) {
@@ -344,6 +336,7 @@ void FlowThread::computeFlowThread(int rootIndex) {
             break;
         }
     }
+    _length = 0.0f;
     for (size_t i = 0; i < indexes.size(); i++) {
         int index = indexes[i];
         _joints.push_back(index);
@@ -456,6 +449,7 @@ FlowThread& FlowThread::operator=(const FlowThread& otherFlowThread) {
         myJoint._updatedPosition = joint._updatedPosition;
         myJoint._updatedRotation = joint._updatedRotation;
         myJoint._updatedTranslation = joint._updatedTranslation;
+        myJoint._isHelper = joint._isHelper;
     }
     return *this;
 }
@@ -532,9 +526,9 @@ void Flow::calculateConstraints(const std::shared_ptr& skeleton,
         glm::vec3 jointPosition, parentPosition, jointTranslation;
         glm::quat jointRotation;
         getJointPositionInWorldFrame(absolutePoses, jointIndex, jointPosition, _entityPosition, _entityRotation);
-        getJointPositionInWorldFrame(absolutePoses, jointData.second.getParentIndex(), parentPosition, _entityPosition, _entityRotation);
         getJointTranslation(relativePoses, jointIndex, jointTranslation);
         getJointRotation(relativePoses, jointIndex, jointRotation);
+        getJointPositionInWorldFrame(absolutePoses, jointData.second.getParentIndex(), parentPosition, _entityPosition, _entityRotation);
 
         jointData.second.setInitialData(jointPosition, jointTranslation, jointRotation, parentPosition);
     }
@@ -549,47 +543,36 @@ void Flow::calculateConstraints(const std::shared_ptr& skeleton,
             _flowJointData[joint.second.getParentIndex()].setChildIndex(joint.first);
         }
     }
-
+    int extraIndex = -1;
     for (size_t i = 0; i < roots.size(); i++) {
         FlowThread thread = FlowThread(roots[i], &_flowJointData);
         // add threads with at least 2 joints
         if (thread._joints.size() > 0) {
             if (thread._joints.size() == 1) {
                 int jointIndex = roots[i];
-                auto joint = _flowJointData[jointIndex];
-                auto jointPosition = joint.getUpdatedPosition();
-                auto newSettings = FlowPhysicsSettings(joint.getSettings());
-                newSettings._stiffness = ISOLATED_JOINT_STIFFNESS;
-                int extraIndex = (int)_flowJointData.size();
-                auto newJoint = FlowDummyJoint(jointPosition, extraIndex, jointIndex, -1, newSettings);
-                newJoint.toIsolatedJoint(ISOLATED_JOINT_LENGTH, extraIndex, _flowJointData[jointIndex].getGroup());
-                thread = FlowThread(jointIndex, &_flowJointData);
+                auto &joint = _flowJointData[jointIndex];
+                auto &jointPosition = joint.getUpdatedPosition();
+                auto newSettings = joint.getSettings();
+                extraIndex = extraIndex > -1 ? extraIndex + 1 : skeleton->getNumJoints();
+                joint.setChildIndex(extraIndex);
+                auto newJoint = FlowJoint(extraIndex, jointIndex, -1, joint.getName(), joint.getGroup(), newSettings);
+                newJoint.toHelperJoint(jointPosition, HELPER_JOINT_LENGTH);
+                glm::vec3 translation = glm::vec3(0.0f, HELPER_JOINT_LENGTH, 0.0f);
+                newJoint.setInitialData(jointPosition + translation, 100.0f * translation , Quaternions::IDENTITY, jointPosition);
+                _flowJointData.insert(std::pair(extraIndex, newJoint));
+                FlowThread newThread = FlowThread(jointIndex, &_flowJointData);
+                if (newThread._joints.size() > 1) {
+                    _jointThreads.push_back(newThread);
+                }
+            } else {
+                _jointThreads.push_back(thread);
             }
-            _jointThreads.push_back(thread);
         }
     }
     
     if (_jointThreads.size() == 0) {
         onCleanup();
     }
-    if (SHOW_DUMMY_JOINTS && rightHandIndex > -1) {
-        int jointCount = (int)_flowJointData.size();
-        int extraIndex = (int)_flowJointData.size();
-        glm::vec3 rightHandPosition;
-        getJointPositionInWorldFrame(absolutePoses, rightHandIndex, rightHandPosition, _entityPosition, _entityRotation);
-        int parentIndex = rightHandIndex;
-        for (int i = 0; i < DUMMY_JOINT_COUNT; i++) {
-            int childIndex = (i == (DUMMY_JOINT_COUNT - 1)) ? -1 : extraIndex + 1;
-            auto newJoint = FlowDummyJoint(rightHandPosition, extraIndex, parentIndex, childIndex, DEFAULT_JOINT_SETTINGS);
-            _flowJointData.insert(std::pair(extraIndex, newJoint));
-            parentIndex = extraIndex;
-            extraIndex++;
-        }
-        _flowJointData[jointCount].setAnchored(true);
-
-        auto extraThread = FlowThread(jointCount, &_flowJointData);
-        _jointThreads.push_back(extraThread);
-    }
     if (handsIndices.size() > 0) {
         FlowCollisionSettings handSettings;
         handSettings._radius = HAND_COLLISION_RADIUS;
@@ -699,10 +682,16 @@ void Flow::updateJoints(AnimPoseVec& relativePoses, AnimPoseVec& absolutePoses)
         int jointIndex = jointData.first;
         glm::vec3 jointPosition, parentPosition, jointTranslation;
         glm::quat jointRotation, parentWorldRotation;
-        getJointPositionInWorldFrame(absolutePoses, jointIndex, jointPosition, _entityPosition, _entityRotation);
+        if (!jointData.second.isHelper()) {
+            getJointPositionInWorldFrame(absolutePoses, jointIndex, jointPosition, _entityPosition, _entityRotation);
+            getJointTranslation(relativePoses, jointIndex, jointTranslation);
+            getJointRotation(relativePoses, jointIndex, jointRotation);
+        } else {
+            jointPosition = jointData.second.getCurrentPosition();
+            jointTranslation = jointData.second.getCurrentTranslation();
+            jointRotation = jointData.second.getCurrentRotation();
+        }
         getJointPositionInWorldFrame(absolutePoses, jointData.second.getParentIndex(), parentPosition, _entityPosition, _entityRotation);
-        getJointTranslation(relativePoses, jointIndex, jointTranslation);
-        getJointRotation(relativePoses, jointIndex, jointRotation);
         getJointRotationInWorldFrame(absolutePoses, jointData.second.getParentIndex(), parentWorldRotation, _entityRotation);
         jointData.second.setUpdatedData(jointPosition, jointTranslation, jointRotation, parentPosition, parentWorldRotation);
     }
@@ -753,7 +742,7 @@ bool Flow::getJointPositionInWorldFrame(const AnimPoseVec& absolutePoses, int jo
         } else {
             position = glm::vec3(0.0f);
         }
-    } 
+    }
     return false;
 }
 
diff --git a/libraries/animation/src/Flow.h b/libraries/animation/src/Flow.h
index 5fe7b7acbc..35464e9420 100644
--- a/libraries/animation/src/Flow.h
+++ b/libraries/animation/src/Flow.h
@@ -23,11 +23,6 @@
 class Rig;
 class AnimSkeleton;
 
-const bool SHOW_DUMMY_JOINTS = false;
-
-const int LEFT_HAND = 0;
-const int RIGHT_HAND = 1;
-
 const float HAPTIC_TOUCH_STRENGTH = 0.25f;
 const float HAPTIC_TOUCH_DURATION = 10.0f;
 const float HAPTIC_SLOPE = 0.18f;
@@ -38,19 +33,8 @@ const QString SIM_JOINT_PREFIX = "sim";
 
 const std::vector HAND_COLLISION_JOINTS = { "RightHandMiddle1", "RightHandThumb3", "LeftHandMiddle1", "LeftHandThumb3", "RightHandMiddle3", "LeftHandMiddle3" };
 
-const QString JOINT_COLLISION_PREFIX = "joint_";
-const QString HAND_COLLISION_PREFIX = "hand_";
 const float HAND_COLLISION_RADIUS = 0.03f;
-const float HAND_TOUCHING_DISTANCE = 2.0f;
-
-const int COLLISION_SHAPES_LIMIT = 4;
-
-const QString DUMMY_KEYWORD = "Extra";
-const int DUMMY_JOINT_COUNT = 8;
-const float DUMMY_JOINT_DISTANCE = 0.05f;
-
-const float ISOLATED_JOINT_STIFFNESS = 0.0f;
-const float ISOLATED_JOINT_LENGTH = 0.05f;
+const float HELPER_JOINT_LENGTH = 0.05f;
 
 const float DEFAULT_STIFFNESS = 0.0f;
 const float DEFAULT_GRAVITY = -0.0096f;
@@ -212,6 +196,7 @@ public:
 
     FlowJoint(): FlowNode() {};
     FlowJoint(int jointIndex, int parentIndex, int childIndex, const QString& name, const QString& group, const FlowPhysicsSettings& settings);
+    void toHelperJoint(const glm::vec3& initialPosition, float length);
     void setInitialData(const glm::vec3& initialPosition, const glm::vec3& initialTranslation, const glm::quat& initialRotation, const glm::vec3& parentPosition);
     void setUpdatedData(const glm::vec3& updatedPosition, const glm::vec3& updatedTranslation, const glm::quat& updatedRotation, const glm::vec3& parentPosition, const glm::quat& parentWorldRotation);
     void setRecoveryPosition(const glm::vec3& recoveryPosition);
@@ -219,19 +204,23 @@ public:
     void solve(const FlowCollisionResult& collision);
 
     void setScale(float scale, bool initScale);
-    bool isAnchored() { return _anchored; }
+    bool isAnchored() const { return _anchored; }
     void setAnchored(bool anchored) { _anchored = anchored; }
+    bool isHelper() const { return _isHelper; }
 
     const FlowPhysicsSettings& getSettings() { return _settings; }
     void setSettings(const FlowPhysicsSettings& settings) { _settings = settings; }
 
-    const glm::vec3& getCurrentPosition() { return _currentPosition; }
-    int getIndex() { return _index; }
-    int getParentIndex() { return _parentIndex; }
+    const glm::vec3& getCurrentPosition() const { return _currentPosition; }
+    int getIndex() const { return _index; }
+    int getParentIndex() const { return _parentIndex; }
     void setChildIndex(int index) { _childIndex = index; }
-    const glm::vec3& getUpdatedPosition() { return _updatedPosition; }
-    const QString& getGroup() { return _group; }
-    const glm::quat& getCurrentRotation() { return _currentRotation; }
+    const glm::vec3& getUpdatedPosition() const { return _updatedPosition; }
+    const QString& getGroup() const { return _group; }
+    const QString& getName() const { return _name; }
+    const glm::quat& getCurrentRotation() const { return _currentRotation; }
+    const glm::vec3& getCurrentTranslation() const { return _initialTranslation; }
+    const glm::vec3& getInitialPosition() const { return _initialPosition; }
 
 protected:
 
@@ -240,7 +229,8 @@ protected:
     int _childIndex{ -1 };
     QString _name;
     QString _group;
-    bool _isDummy{ false };
+
+    bool _isHelper{ false };
 
     glm::vec3 _initialTranslation;
     glm::quat _initialRotation;
@@ -262,13 +252,6 @@ protected:
     bool _applyRecovery { false };
 };
 
-class FlowDummyJoint : public FlowJoint {
-public:
-    FlowDummyJoint(const glm::vec3& initialPosition, int index, int parentIndex, int childIndex, FlowPhysicsSettings settings);
-    void toIsolatedJoint(float length, int childIndex, const QString& group);
-};
-
-
 class FlowThread {
 public:
     FlowThread() {};
diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp
index eb02f07e62..7a00a8ecf2 100644
--- a/libraries/animation/src/Rig.cpp
+++ b/libraries/animation/src/Rig.cpp
@@ -1911,14 +1911,12 @@ void Rig::initAnimGraph(const QUrl& url) {
             qCritical(animation) << "Error loading: code = " << error << "str =" << str;
         });
 
-        /*
         connect(this, &Rig::onLoadComplete, [&]() {
-            if (_internalFlow.getActive()) {
-                _internalFlow.calculateConstraints(_animSkeleton, _internalPoseSet._relativePoses, _internalPoseSet._absolutePoses);
-                _networkFlow.calculateConstraints(_animSkeleton, _internalPoseSet._relativePoses, _internalPoseSet._absolutePoses);
-            }
+            _internalFlow.setActive(false);
+            _internalFlow.cleanUp();
+            _networkFlow.setActive(false);
+            _networkFlow.cleanUp();
         });
-        */
     }
 }
 
@@ -2136,5 +2134,6 @@ void Rig::initFlow(bool isActive) {
         }
     } else {
         _internalFlow.cleanUp();
+        _networkFlow.cleanUp();
     }
 }
\ No newline at end of file

From afed0b54421a662da8910d3e930be5218532a86b Mon Sep 17 00:00:00 2001
From: amantley 
Date: Thu, 21 Feb 2019 11:08:29 -0800
Subject: [PATCH 136/474] review changes

---
 interface/src/avatar/MyAvatar.cpp                        | 4 ++--
 interface/src/avatar/MySkeletonModel.cpp                 | 2 ++
 interface/src/avatar/MySkeletonModel.h                   | 1 -
 libraries/animation/src/AnimPoleVectorConstraint.h       | 4 ----
 libraries/avatars-renderer/src/avatars-renderer/Avatar.h | 1 -
 5 files changed, 4 insertions(+), 8 deletions(-)

diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index 8ae59ebdc1..5bccd4650b 100755
--- a/interface/src/avatar/MyAvatar.cpp
+++ b/interface/src/avatar/MyAvatar.cpp
@@ -2952,9 +2952,9 @@ void MyAvatar::initAnimGraph() {
     } else {
         graphUrl = PathUtils::resourcesUrl("avatar/avatar-animation.json");
 
-    #if defined(Q_OS_ANDROID) || defined(HIFI_USE_Q_OS_ANDROID)
+#if defined(Q_OS_ANDROID) || defined(HIFI_USE_Q_OS_ANDROID)
         graphUrl = PathUtils::resourcesUrl("avatar/avatar-animation_withSplineIKNode.json");
-    #endif
+#endif
     }
 
     emit animGraphUrlChanged(graphUrl);
diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp
index 86a0dceae0..58071cfab1 100755
--- a/interface/src/avatar/MySkeletonModel.cpp
+++ b/interface/src/avatar/MySkeletonModel.cpp
@@ -10,12 +10,14 @@
 
 #include 
 #include 
+#include 
 
 #include "Application.h"
 #include "InterfaceLogging.h"
 #include "AnimUtil.h"
 
 
+
 MySkeletonModel::MySkeletonModel(Avatar* owningAvatar, QObject* parent) : SkeletonModel(owningAvatar, parent) {
 }
 
diff --git a/interface/src/avatar/MySkeletonModel.h b/interface/src/avatar/MySkeletonModel.h
index 7ea142b011..9a3559ddf7 100644
--- a/interface/src/avatar/MySkeletonModel.h
+++ b/interface/src/avatar/MySkeletonModel.h
@@ -12,7 +12,6 @@
 #include 
 #include 
 #include "MyAvatar.h"
-#include 
 
 /// A skeleton loaded from a model.
 class MySkeletonModel : public SkeletonModel {
diff --git a/libraries/animation/src/AnimPoleVectorConstraint.h b/libraries/animation/src/AnimPoleVectorConstraint.h
index d0c80a393b..44e22671c1 100644
--- a/libraries/animation/src/AnimPoleVectorConstraint.h
+++ b/libraries/animation/src/AnimPoleVectorConstraint.h
@@ -25,8 +25,6 @@ public:
                              const QString& enabledVar, const QString& poleVectorVar);
     virtual ~AnimPoleVectorConstraint() override;
 
-    float findThetaNewWay(const glm::vec3& hand, const glm::vec3& shoulder, bool left) const;
-
     virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override;
 
 protected:
@@ -66,8 +64,6 @@ protected:
     float _interpAlphaVel { 0.0f };
     float _interpAlpha { 0.0f };
 
-    float _lastTheta { 0.0f };
-
     AnimChain _snapshotChain;
 
     // no copies
diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h
index 11bf2986d1..c9f1c4bdfe 100644
--- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h
+++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h
@@ -25,7 +25,6 @@
 #include 
 #include 
 #include 
-#include 
 #include 
 
 #include 

From 64ec28ef449c79cc9de41fb4476a711ed61a8859 Mon Sep 17 00:00:00 2001
From: amantley 
Date: Thu, 21 Feb 2019 11:37:14 -0800
Subject: [PATCH 137/474] added back missing unity meta files

---
 .../avatars-renderer/src/avatars-renderer/Avatar.cpp      | 1 +
 libraries/shared/src/AvatarConstants.h                    | 2 +-
 tools/unity-avatar-exporter/Assets/Editor.meta            | 8 ++++++++
 tools/unity-avatar-exporter/Assets/README.txt.meta        | 7 +++++++
 4 files changed, 17 insertions(+), 1 deletion(-)
 create mode 100644 tools/unity-avatar-exporter/Assets/Editor.meta
 create mode 100644 tools/unity-avatar-exporter/Assets/README.txt.meta

diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp
index ff99f0dc29..2a3b7e8b46 100644
--- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp
+++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp
@@ -37,6 +37,7 @@
 #include "RenderableModelEntityItem.h"
 
 #include 
+#include 
 
 #include "Logging.h"
 
diff --git a/libraries/shared/src/AvatarConstants.h b/libraries/shared/src/AvatarConstants.h
index 10bfcc77d3..94c2500a04 100644
--- a/libraries/shared/src/AvatarConstants.h
+++ b/libraries/shared/src/AvatarConstants.h
@@ -20,7 +20,7 @@ const float DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD = 0.11f; // meters
 const float DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD = 0.185f; // meters
 const float DEFAULT_AVATAR_NECK_HEIGHT = DEFAULT_AVATAR_HEIGHT - DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD;
 const float DEFAULT_AVATAR_EYE_HEIGHT = DEFAULT_AVATAR_HEIGHT - DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD;
-const float DEFAULT_SPINE2_SPLINE_PROPORTION = 0.75f;
+const float DEFAULT_SPINE2_SPLINE_PROPORTION = 0.71f; 
 const float DEFAULT_AVATAR_SUPPORT_BASE_LEFT  = -0.25f;
 const float DEFAULT_AVATAR_SUPPORT_BASE_RIGHT =  0.25f;
 const float DEFAULT_AVATAR_SUPPORT_BASE_FRONT = -0.20f;
diff --git a/tools/unity-avatar-exporter/Assets/Editor.meta b/tools/unity-avatar-exporter/Assets/Editor.meta
new file mode 100644
index 0000000000..cf7dcf12dd
--- /dev/null
+++ b/tools/unity-avatar-exporter/Assets/Editor.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 02111c50e71dd664da8ad5c6a6eca767
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/tools/unity-avatar-exporter/Assets/README.txt.meta b/tools/unity-avatar-exporter/Assets/README.txt.meta
new file mode 100644
index 0000000000..148fd21fdd
--- /dev/null
+++ b/tools/unity-avatar-exporter/Assets/README.txt.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 30b2b6221fd08234eb07c4d6d525d32e
+TextScriptImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

From 77d4060fdb8b78ac2e1bdfeffaa9d75502bb3861 Mon Sep 17 00:00:00 2001
From: amer cerkic 
Date: Thu, 21 Feb 2019 11:37:50 -0800
Subject: [PATCH 138/474] bypassing onStop invoke.Delegate command which
 prevents the disconnect from Java hooks that are not initiated on restart. 
 On Destroy, the onstop function is called to allow the hooks to finally
 disconnect and then terminate the app.

---
 .../oculus/OculusMobileActivity.java          | 24 +++++++++++--------
 .../qt5/android/bindings/QtActivity.java      |  7 +++++-
 2 files changed, 20 insertions(+), 11 deletions(-)

diff --git a/android/libraries/oculus/src/main/java/io/highfidelity/oculus/OculusMobileActivity.java b/android/libraries/oculus/src/main/java/io/highfidelity/oculus/OculusMobileActivity.java
index 9ab07bb4dd..2aa7b4da05 100644
--- a/android/libraries/oculus/src/main/java/io/highfidelity/oculus/OculusMobileActivity.java
+++ b/android/libraries/oculus/src/main/java/io/highfidelity/oculus/OculusMobileActivity.java
@@ -34,7 +34,6 @@ public class OculusMobileActivity extends QtActivity implements SurfaceHolder.Ca
     private native void questNativeOnResume();
     private native void questOnAppAfterLoad();
 
-
     private SurfaceView mView;
     private SurfaceHolder mSurfaceHolder;
 
@@ -57,12 +56,15 @@ public class OculusMobileActivity extends QtActivity implements SurfaceHolder.Ca
             setContentView(mView);
             questOnAppAfterLoad();
         });
+
+
     }
 
     @Override
     protected void onDestroy() {
         Log.w(TAG, "QQQ onDestroy");
-
+        isPausing=false;
+        super.onStop();
         nativeOnSurfaceChanged(null);
 
         Log.w(TAG, "QQQ onDestroy -- SUPER onDestroy");
@@ -78,6 +80,7 @@ public class OculusMobileActivity extends QtActivity implements SurfaceHolder.Ca
 
         questNativeOnResume();
         nativeOnResume();
+        isPausing=false;
     }
 
     @Override
@@ -87,40 +90,41 @@ public class OculusMobileActivity extends QtActivity implements SurfaceHolder.Ca
 
         questNativeOnPause();
         nativeOnPause();
+        isPausing=true;
     }
 
     @Override
     protected void onStop(){
         super.onStop();
-        Log.w(TAG, "QQQ Onstop called");
+        Log.w(TAG, "QQQ_ Onstop called");
     }
 
     @Override
-    protected void onRestart(){
+    protected void onRestart() {
         super.onRestart();
-        Log.w(TAG, "QQQ onRestart called ****");
+        Log.w(TAG, "QQQ_ onRestart called ****");
         questOnAppAfterLoad();
     }
 
     @Override
     public void surfaceCreated(SurfaceHolder holder) {
-        Log.w(TAG, "QQQ surfaceCreated ************************************");
+        Log.w(TAG, "QQQ_ surfaceCreated ************************************");
         nativeOnSurfaceChanged(holder.getSurface());
         mSurfaceHolder = holder;
     }
 
     @Override
     public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
-        Log.w(TAG, "QQQ surfaceChanged");
+        Log.w(TAG, "QQQ_ surfaceChanged");
         nativeOnSurfaceChanged(holder.getSurface());
         mSurfaceHolder = holder;
     }
 
     @Override
     public void surfaceDestroyed(SurfaceHolder holder) {
-        Log.w(TAG, "QQQ surfaceDestroyed ***************************************************");
-        nativeOnSurfaceChanged(null);
-        mSurfaceHolder = null;
+        Log.w(TAG, "QQQ_ surfaceDestroyed ***************************************************");
+      //  nativeOnSurfaceChanged(null);
+       // mSurfaceHolder = null;
 
     }
 }
\ No newline at end of file
diff --git a/android/libraries/qt/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java b/android/libraries/qt/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java
index 46f2af46e7..85e93a4267 100644
--- a/android/libraries/qt/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java
+++ b/android/libraries/qt/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java
@@ -70,6 +70,7 @@ public class QtActivity extends Activity {
     public final String QT_ANDROID_DEFAULT_THEME = QT_ANDROID_THEMES[0]; // sets the default theme.
     private QtActivityLoader m_loader = new QtActivityLoader(this);
 
+    public boolean isPausing=false;
     public QtActivity() {
     }
 
@@ -650,9 +651,13 @@ public class QtActivity extends Activity {
     @Override
     protected void onStop() {
         super.onStop();
-        QtApplication.invokeDelegate();
+
+        if(!isPausing){
+            QtApplication.invokeDelegate();
+        }
     }
 
+
     //---------------------------------------------------------------------------
 
     @Override

From de10aebbb51b438cf5c85a1f85bf96e27ba04a56 Mon Sep 17 00:00:00 2001
From: amantley 
Date: Thu, 21 Feb 2019 11:44:18 -0800
Subject: [PATCH 139/474] removed white space

---
 libraries/shared/src/AvatarConstants.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libraries/shared/src/AvatarConstants.h b/libraries/shared/src/AvatarConstants.h
index 94c2500a04..d55a63b960 100644
--- a/libraries/shared/src/AvatarConstants.h
+++ b/libraries/shared/src/AvatarConstants.h
@@ -20,7 +20,7 @@ const float DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD = 0.11f; // meters
 const float DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD = 0.185f; // meters
 const float DEFAULT_AVATAR_NECK_HEIGHT = DEFAULT_AVATAR_HEIGHT - DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD;
 const float DEFAULT_AVATAR_EYE_HEIGHT = DEFAULT_AVATAR_HEIGHT - DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD;
-const float DEFAULT_SPINE2_SPLINE_PROPORTION = 0.71f; 
+const float DEFAULT_SPINE2_SPLINE_PROPORTION = 0.71f;
 const float DEFAULT_AVATAR_SUPPORT_BASE_LEFT  = -0.25f;
 const float DEFAULT_AVATAR_SUPPORT_BASE_RIGHT =  0.25f;
 const float DEFAULT_AVATAR_SUPPORT_BASE_FRONT = -0.20f;

From 0bdc527ce71defad80f1d0405bbe40fab1bf52d7 Mon Sep 17 00:00:00 2001
From: amantley 
Date: Thu, 21 Feb 2019 13:14:51 -0800
Subject: [PATCH 140/474] turned off pole vector when hand behind back

---
 libraries/animation/src/Rig.cpp | 26 +++++++++++++++++---------
 1 file changed, 17 insertions(+), 9 deletions(-)

diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp
index f2b4be077f..93c9c0cd1b 100644
--- a/libraries/animation/src/Rig.cpp
+++ b/libraries/animation/src/Rig.cpp
@@ -1874,11 +1874,13 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s
         unitAxis = Vectors::UNIT_Y;
     }
 
+    if ((armToHand.z < 0.0f) && (armToHand.y < 0.0f)) {
+        // turn off the poleVector when the hand is back and down
+        return false;
+    }
+
     // get the pole vector theta based on the hand position relative to the shoulder.
     float positionalTheta = getHandPositionTheta(armToHand, defaultArmLength, left);
-    if (left) {
-        qCDebug(animation) << "positional theta left "<< positionalTheta;
-    }
 
     // now we calculate the contribution of the hand rotation relative to the arm
     glm::quat relativeHandRotation = (elbowPose.inverse() * handPose).rot();
@@ -1966,14 +1968,20 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s
     // limit the correction anatomically possible angles and change to radians
     const float LOWER_ANATOMICAL_ANGLE = 175.0f;
     const float UPPER_ANATOMICAL_ANGLE = 50.0f;
+
+    // make the lower boundary vary with the body 
+    float lowerBoundary = LOWER_ANATOMICAL_ANGLE;
+    if (fabsf(positionalTheta) < LOWER_ANATOMICAL_ANGLE) {
+        lowerBoundary = positionalTheta;
+    }
     float thetaRadians = 0.0f;
     if (left) {
 
-        if (_lastThetaLeft > -50.0f) {
-            _lastThetaLeft =  -50.0f;
+        if (_lastThetaLeft > -UPPER_ANATOMICAL_ANGLE) {
+            _lastThetaLeft =  -UPPER_ANATOMICAL_ANGLE;
         }
-        if (_lastThetaLeft < -LOWER_ANATOMICAL_ANGLE) {
-            _lastThetaLeft = -LOWER_ANATOMICAL_ANGLE;
+        if (_lastThetaLeft < lowerBoundary) {
+            _lastThetaLeft = lowerBoundary;
         }
         // convert to radians and make 180 0 to match pole vector theta
         thetaRadians = ((180.0f - _lastThetaLeft) / 180.0f)*PI;
@@ -1982,8 +1990,8 @@ bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int s
         if (_lastThetaRight < UPPER_ANATOMICAL_ANGLE) {
             _lastThetaRight = UPPER_ANATOMICAL_ANGLE;
         }
-        if (_lastThetaRight > LOWER_ANATOMICAL_ANGLE) {
-            _lastThetaRight = LOWER_ANATOMICAL_ANGLE;
+        if (_lastThetaRight > lowerBoundary) {
+            _lastThetaRight = lowerBoundary;
         }
         // convert to radians and make 180 0 to match pole vector theta
         thetaRadians = ((180.0f - _lastThetaRight) / 180.0f)*PI;

From 50bc8d3646a3a73cc1e9139cb66c43e664f59d9e Mon Sep 17 00:00:00 2001
From: amantley 
Date: Thu, 21 Feb 2019 13:32:28 -0800
Subject: [PATCH 141/474] added protected function to cubic hermite spline

---
 libraries/shared/src/CubicHermiteSpline.h | 42 ++++++++++-------------
 1 file changed, 18 insertions(+), 24 deletions(-)

diff --git a/libraries/shared/src/CubicHermiteSpline.h b/libraries/shared/src/CubicHermiteSpline.h
index 4742bb2e09..c83000996b 100644
--- a/libraries/shared/src/CubicHermiteSpline.h
+++ b/libraries/shared/src/CubicHermiteSpline.h
@@ -66,19 +66,8 @@ public:
         memset(_values, 0, sizeof(float) * (NUM_SUBDIVISIONS + 1));
     }
     CubicHermiteSplineFunctorWithArcLength(const glm::vec3& p0, const glm::vec3& m0, const glm::vec3& p1, const glm::vec3& m1) : CubicHermiteSplineFunctor(p0, m0, p1, m1) {
-        // initialize _values with the accumulated arcLength along the spline.
-        const float DELTA = 1.0f / NUM_SUBDIVISIONS;
-        float alpha = 0.0f;
-        float accum = 0.0f;
-        _values[0] = 0.0f;
-        glm::vec3 prevValue = this->operator()(alpha);
-        for (int i = 1; i < NUM_SUBDIVISIONS + 1; i++) {
-            glm::vec3 nextValue = this->operator()(alpha + DELTA);
-            accum += glm::distance(prevValue, nextValue);
-            alpha += DELTA;
-            _values[i] = accum;
-            prevValue = nextValue;
-        }
+
+        initValues();
     }
 
     CubicHermiteSplineFunctorWithArcLength(const glm::quat& tipRot, const glm::vec3& tipTrans, const glm::quat& baseRot, const glm::vec3& baseTrans, float baseGain = 1.0f, float tipGain = 1.0f) : CubicHermiteSplineFunctor() {
@@ -89,17 +78,7 @@ public:
         _p1 = tipTrans;
         _m1 = tipGain * linearDistance * (tipRot * Vectors::UNIT_Y);
 
-        // initialize _values with the accumulated arcLength along the spline.
-        const float DELTA = 1.0f / NUM_SUBDIVISIONS;
-        float alpha = 0.0f;
-        float accum = 0.0f;
-        _values[0] = 0.0f;
-        for (int i = 1; i < NUM_SUBDIVISIONS + 1; i++) {
-            accum += glm::distance(this->operator()(alpha),
-                this->operator()(alpha + DELTA));
-            alpha += DELTA;
-            _values[i] = accum;
-        }
+        initValues();
     }
 
     CubicHermiteSplineFunctorWithArcLength(const CubicHermiteSplineFunctorWithArcLength& orig) : CubicHermiteSplineFunctor(orig) {
@@ -131,6 +110,21 @@ public:
     }
 protected:
     float _values[NUM_SUBDIVISIONS + 1];
+
+    void initValues() {
+        // initialize _values with the accumulated arcLength along the spline.
+        const float DELTA = 1.0f / NUM_SUBDIVISIONS;
+        float alpha = 0.0f;
+        float accum = 0.0f;
+        _values[0] = 0.0f;
+        for (int i = 1; i < NUM_SUBDIVISIONS + 1; i++) {
+            accum += glm::distance(this->operator()(alpha),
+                this->operator()(alpha + DELTA));
+            alpha += DELTA;
+            _values[i] = accum;
+        }
+
+    }
 };
 
 #endif // hifi_CubicHermiteSpline_h

From 544f54e69a7b55e2b01b0008a26f257dd7c04f4b Mon Sep 17 00:00:00 2001
From: SamGondelman 
Date: Thu, 21 Feb 2019 14:10:36 -0800
Subject: [PATCH 142/474] fix model scale

---
 interface/src/ui/overlays/Overlays.cpp        | 15 +++++++--
 .../entities/src/EntityItemProperties.cpp     | 10 ++++++
 libraries/entities/src/EntityItemProperties.h |  1 +
 libraries/entities/src/EntityPropertyFlags.h  | 31 ++++++++++---------
 libraries/entities/src/ModelEntityItem.cpp    | 17 ++++++++++
 libraries/entities/src/ModelEntityItem.h      |  4 +++
 libraries/networking/src/udt/PacketHeaders.h  |  1 +
 libraries/shared/src/SpatiallyNestable.cpp    |  2 +-
 8 files changed, 62 insertions(+), 19 deletions(-)

diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp
index 660220c731..081085408d 100644
--- a/interface/src/ui/overlays/Overlays.cpp
+++ b/interface/src/ui/overlays/Overlays.cpp
@@ -311,7 +311,11 @@ EntityItemProperties Overlays::convertOverlayToEntityProperties(QVariantMap& ove
         RENAME_PROP(start, position);
     }
     RENAME_PROP(point, position);
-    RENAME_PROP(scale, dimensions);
+    if (type != "Model") {
+        RENAME_PROP(scale, dimensions);
+    } else {
+        RENAME_PROP(scale, modelScale);
+    }
     RENAME_PROP(size, dimensions);
     RENAME_PROP(orientation, rotation);
     RENAME_PROP(localOrientation, localRotation);
@@ -636,7 +640,11 @@ QVariantMap Overlays::convertEntityToOverlayProperties(const EntityItemPropertie
         RENAME_PROP(position, start);
     }
     RENAME_PROP(position, point);
-    RENAME_PROP(dimensions, scale);
+    if (type != "Model") {
+        RENAME_PROP(dimensions, scale);
+    } else {
+        RENAME_PROP(modelScale, scale);
+    }
     RENAME_PROP(dimensions, size);
     RENAME_PROP(ignorePickIntersection, ignoreRayIntersection);
 
@@ -1718,7 +1726,8 @@ QVector Overlays::findOverlays(const glm::vec3& center, float radius) {
  *
  * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and
  *     start.
- * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: scale, size.
+ * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: size.
+ * @property {Vec3} scale - The scale factor applied to the model's dimensions.
  * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation.
  * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a
  *     parentID set, otherwise the same value as position.
diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp
index 6738b1cedd..75e2069471 100644
--- a/libraries/entities/src/EntityItemProperties.cpp
+++ b/libraries/entities/src/EntityItemProperties.cpp
@@ -580,6 +580,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
 
     // Model
     CHECK_PROPERTY_CHANGE(PROP_MODEL_URL, modelURL);
+    CHECK_PROPERTY_CHANGE(PROP_MODEL_SCALE, modelScale);
     CHECK_PROPERTY_CHANGE(PROP_JOINT_ROTATIONS_SET, jointRotationsSet);
     CHECK_PROPERTY_CHANGE(PROP_JOINT_ROTATIONS, jointRotations);
     CHECK_PROPERTY_CHANGE(PROP_JOINT_TRANSLATIONS_SET, jointTranslationsSet);
@@ -1012,6 +1013,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
  * @property {Vec3} dimensions=0.1,0.1,0.1 - The dimensions of the entity. When adding an entity, if no dimensions 
  *     value is specified then the model is automatically sized to its 
  *     {@link Entities.EntityProperties|naturalDimensions}.
+ * @property {Vec3} modelScale - The scale factor applied to the model's dimensions.
  * @property {Color} color=255,255,255 - Currently not used.
  * @property {string} modelURL="" - The URL of the FBX of OBJ model. Baked FBX models' URLs end in ".baked.fbx".
* @property {string} textures="" - A JSON string of texture name, URL pairs used when rendering the model in place of the @@ -1683,6 +1685,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_TEXTURES, textures); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MODEL_URL, modelURL); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MODEL_SCALE, modelScale); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_JOINT_ROTATIONS_SET, jointRotationsSet); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_JOINT_ROTATIONS, jointRotations); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_JOINT_TRANSLATIONS_SET, jointTranslationsSet); @@ -2078,6 +2081,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool // Model COPY_PROPERTY_FROM_QSCRIPTVALUE(modelURL, QString, setModelURL); + COPY_PROPERTY_FROM_QSCRIPTVALUE(modelScale, vec3, setModelScale); COPY_PROPERTY_FROM_QSCRIPTVALUE(jointRotationsSet, qVectorBool, setJointRotationsSet); COPY_PROPERTY_FROM_QSCRIPTVALUE(jointRotations, qVectorQuat, setJointRotations); COPY_PROPERTY_FROM_QSCRIPTVALUE(jointTranslationsSet, qVectorBool, setJointTranslationsSet); @@ -2357,6 +2361,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { // Model COPY_PROPERTY_IF_CHANGED(modelURL); + COPY_PROPERTY_IF_CHANGED(modelScale); COPY_PROPERTY_IF_CHANGED(jointRotationsSet); COPY_PROPERTY_IF_CHANGED(jointRotations); COPY_PROPERTY_IF_CHANGED(jointTranslationsSet); @@ -2700,6 +2705,7 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr // Model ADD_PROPERTY_TO_MAP(PROP_MODEL_URL, ModelURL, modelURL, QString); + ADD_PROPERTY_TO_MAP(PROP_MODEL_SCALE, ModelScale, modelScale, vec3); ADD_PROPERTY_TO_MAP(PROP_JOINT_ROTATIONS_SET, JointRotationsSet, jointRotationsSet, QVector); ADD_PROPERTY_TO_MAP(PROP_JOINT_ROTATIONS, JointRotations, jointRotations, QVector); ADD_PROPERTY_TO_MAP(PROP_JOINT_TRANSLATIONS_SET, JointTranslationsSet, jointTranslationsSet, QVector); @@ -3989,6 +3995,7 @@ void EntityItemProperties::markAllChanged() { // Model _modelURLChanged = true; + _modelScaleChanged = true; _jointRotationsSetChanged = true; _jointRotationsChanged = true; _jointTranslationsSetChanged = true; @@ -4526,6 +4533,9 @@ QList EntityItemProperties::listChangedProperties() { if (modelURLChanged()) { out += "modelURL"; } + if (modelScaleChanged()) { + out += "scale"; + } if (jointRotationsSetChanged()) { out += "jointRotationsSet"; } diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 712f2d120f..afc3537559 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -279,6 +279,7 @@ public: // Model DEFINE_PROPERTY_REF(PROP_MODEL_URL, ModelURL, modelURL, QString, ""); + DEFINE_PROPERTY_REF(PROP_MODEL_SCALE, ModelScale, modelScale, glm::vec3, glm::vec3(1.0f)); DEFINE_PROPERTY_REF(PROP_JOINT_ROTATIONS_SET, JointRotationsSet, jointRotationsSet, QVector, QVector()); DEFINE_PROPERTY_REF(PROP_JOINT_ROTATIONS, JointRotations, jointRotations, QVector, QVector()); DEFINE_PROPERTY_REF(PROP_JOINT_TRANSLATIONS_SET, JointTranslationsSet, jointTranslationsSet, QVector, QVector()); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 093df92dc1..cce30c9614 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -202,22 +202,23 @@ enum EntityPropertyList { // Model PROP_MODEL_URL = PROP_DERIVED_0, - PROP_JOINT_ROTATIONS_SET = PROP_DERIVED_1, - PROP_JOINT_ROTATIONS = PROP_DERIVED_2, - PROP_JOINT_TRANSLATIONS_SET = PROP_DERIVED_3, - PROP_JOINT_TRANSLATIONS = PROP_DERIVED_4, - PROP_RELAY_PARENT_JOINTS = PROP_DERIVED_5, - PROP_GROUP_CULLED = PROP_DERIVED_6, + PROP_MODEL_SCALE = PROP_DERIVED_1, + PROP_JOINT_ROTATIONS_SET = PROP_DERIVED_2, + PROP_JOINT_ROTATIONS = PROP_DERIVED_3, + PROP_JOINT_TRANSLATIONS_SET = PROP_DERIVED_4, + PROP_JOINT_TRANSLATIONS = PROP_DERIVED_5, + PROP_RELAY_PARENT_JOINTS = PROP_DERIVED_6, + PROP_GROUP_CULLED = PROP_DERIVED_7, // Animation - PROP_ANIMATION_URL = PROP_DERIVED_7, - PROP_ANIMATION_ALLOW_TRANSLATION = PROP_DERIVED_8, - PROP_ANIMATION_FPS = PROP_DERIVED_9, - PROP_ANIMATION_FRAME_INDEX = PROP_DERIVED_10, - PROP_ANIMATION_PLAYING = PROP_DERIVED_11, - PROP_ANIMATION_LOOP = PROP_DERIVED_12, - PROP_ANIMATION_FIRST_FRAME = PROP_DERIVED_13, - PROP_ANIMATION_LAST_FRAME = PROP_DERIVED_14, - PROP_ANIMATION_HOLD = PROP_DERIVED_15, + PROP_ANIMATION_URL = PROP_DERIVED_8, + PROP_ANIMATION_ALLOW_TRANSLATION = PROP_DERIVED_9, + PROP_ANIMATION_FPS = PROP_DERIVED_10, + PROP_ANIMATION_FRAME_INDEX = PROP_DERIVED_11, + PROP_ANIMATION_PLAYING = PROP_DERIVED_12, + PROP_ANIMATION_LOOP = PROP_DERIVED_13, + PROP_ANIMATION_FIRST_FRAME = PROP_DERIVED_14, + PROP_ANIMATION_LAST_FRAME = PROP_DERIVED_15, + PROP_ANIMATION_HOLD = PROP_DERIVED_16, // Light PROP_IS_SPOTLIGHT = PROP_DERIVED_0, diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index e365d0a7b6..bb8f375302 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -63,6 +63,7 @@ EntityItemProperties ModelEntityItem::getProperties(const EntityPropertyFlags& d COPY_ENTITY_PROPERTY_TO_PROPERTIES(textures, getTextures); COPY_ENTITY_PROPERTY_TO_PROPERTIES(modelURL, getModelURL); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(modelScale, getModelScale); COPY_ENTITY_PROPERTY_TO_PROPERTIES(jointRotationsSet, getJointRotationsSet); COPY_ENTITY_PROPERTY_TO_PROPERTIES(jointRotations, getJointRotations); COPY_ENTITY_PROPERTY_TO_PROPERTIES(jointTranslationsSet, getJointTranslationsSet); @@ -85,6 +86,7 @@ bool ModelEntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(textures, setTextures); SET_ENTITY_PROPERTY_FROM_PROPERTIES(modelURL, setModelURL); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(modelScale, setModelScale); SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointRotationsSet, setJointRotationsSet); SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointRotations, setJointRotations); SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointTranslationsSet, setJointTranslationsSet); @@ -128,6 +130,7 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, READ_ENTITY_PROPERTY(PROP_TEXTURES, QString, setTextures); READ_ENTITY_PROPERTY(PROP_MODEL_URL, QString, setModelURL); + READ_ENTITY_PROPERTY(PROP_MODEL_SCALE, glm::vec3, setModelScale); READ_ENTITY_PROPERTY(PROP_JOINT_ROTATIONS_SET, QVector, setJointRotationsSet); READ_ENTITY_PROPERTY(PROP_JOINT_ROTATIONS, QVector, setJointRotations); READ_ENTITY_PROPERTY(PROP_JOINT_TRANSLATIONS_SET, QVector, setJointTranslationsSet); @@ -165,6 +168,7 @@ EntityPropertyFlags ModelEntityItem::getEntityProperties(EncodeBitstreamParams& requestedProperties += PROP_TEXTURES; requestedProperties += PROP_MODEL_URL; + requestedProperties += PROP_MODEL_SCALE; requestedProperties += PROP_JOINT_ROTATIONS_SET; requestedProperties += PROP_JOINT_ROTATIONS; requestedProperties += PROP_JOINT_TRANSLATIONS_SET; @@ -192,6 +196,7 @@ void ModelEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit APPEND_ENTITY_PROPERTY(PROP_TEXTURES, getTextures()); APPEND_ENTITY_PROPERTY(PROP_MODEL_URL, getModelURL()); + APPEND_ENTITY_PROPERTY(PROP_MODEL_SCALE, getModelScale()); APPEND_ENTITY_PROPERTY(PROP_JOINT_ROTATIONS_SET, getJointRotationsSet()); APPEND_ENTITY_PROPERTY(PROP_JOINT_ROTATIONS, getJointRotations()); APPEND_ENTITY_PROPERTY(PROP_JOINT_TRANSLATIONS_SET, getJointTranslationsSet()); @@ -708,3 +713,15 @@ bool ModelEntityItem::applyNewAnimationProperties(AnimationPropertyGroup newProp } return somethingChanged; } + +glm::vec3 ModelEntityItem::getModelScale() const { + return _modelScaleLock.resultWithReadLock([&] { + return getSNScale(); + }); +} + +void ModelEntityItem::setModelScale(const glm::vec3& modelScale) { + _modelScaleLock.withWriteLock([&] { + setSNScale(modelScale); + }); +} \ No newline at end of file diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index 649a6cb50f..234cfa435e 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -126,6 +126,9 @@ public: QVector getJointTranslations() const; QVector getJointTranslationsSet() const; + glm::vec3 getModelScale() const; + void setModelScale(const glm::vec3& modelScale); + private: void setAnimationSettings(const QString& value); // only called for old bitstream format bool applyNewAnimationProperties(AnimationPropertyGroup newProperties); @@ -141,6 +144,7 @@ protected: // they aren't currently updated from data in the model/rig, and they don't have a direct effect // on what's rendered. ReadWriteLockable _jointDataLock; + ReadWriteLockable _modelScaleLock; bool _jointRotationsExplicitlySet { false }; // were the joints set as a property or just side effect of animations bool _jointTranslationsExplicitlySet{ false }; // were the joints set as a property or just side effect of animations diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 5f55c189ce..b6fcd2f2ce 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -262,6 +262,7 @@ enum class EntityVersion : PacketVersion { RingGizmoEntities, ShowKeyboardFocusHighlight, WebBillboardMode, + ModelScale, // Add new versions above here NUM_PACKET_TYPE, diff --git a/libraries/shared/src/SpatiallyNestable.cpp b/libraries/shared/src/SpatiallyNestable.cpp index 19fafdccf4..b48b8d0e16 100644 --- a/libraries/shared/src/SpatiallyNestable.cpp +++ b/libraries/shared/src/SpatiallyNestable.cpp @@ -861,7 +861,7 @@ void SpatiallyNestable::setSNScale(const glm::vec3& scale, bool& success) { } }); if (success && changed) { - locationChanged(); + dimensionsChanged(); } } From 6323f49f26217f39aeda8dde12c68213e7add993 Mon Sep 17 00:00:00 2001 From: amantley Date: Thu, 21 Feb 2019 14:36:05 -0800 Subject: [PATCH 143/474] changed the define variable to HIFI_USE_OPTIMIZED_IK --- CMakeLists.txt | 12 +- interface/src/avatar/MyAvatar.cpp | 2 +- interface/src/avatar/MySkeletonModel.cpp | 4 +- libraries/animation/src/Rig.cpp | 334 ----------------------- libraries/animation/src/Rig.h | 14 - 5 files changed, 8 insertions(+), 358 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5bd4d4620b..f88f8fbff7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,7 +52,7 @@ else() set(MOBILE 0) endif() -set(HIFI_USE_Q_OS_ANDROID_OPTION OFF) +set(HIFI_USE_OPTIMIZED_IK OFF) set(BUILD_CLIENT_OPTION ON) set(BUILD_SERVER_OPTION ON) set(BUILD_TESTS_OPTION OFF) @@ -109,7 +109,7 @@ if (USE_GLES AND (NOT ANDROID)) set(DISABLE_QML_OPTION ON) endif() -option(HIFI_USE_Q_OS_ANDROID "USE OPTIMIZED IK" ${HIFI_USE_Q_OS_ANDROID_OPTION}) +option(HIFI_USE_OPTIMIZED_IK "USE OPTIMIZED IK" ${HIFI_USE_OPTIMIZED_IK_OPTION}) option(BUILD_CLIENT "Build client components" ${BUILD_CLIENT_OPTION}) option(BUILD_SERVER "Build server components" ${BUILD_SERVER_OPTION}) option(BUILD_TESTS "Build tests" ${BUILD_TESTS_OPTION}) @@ -140,7 +140,7 @@ foreach(PLATFORM_QT_COMPONENT ${PLATFORM_QT_COMPONENTS}) list(APPEND PLATFORM_QT_LIBRARIES "Qt5::${PLATFORM_QT_COMPONENT}") endforeach() -MESSAGE(STATUS "USE OPTIMIZED IK: " ${HIFI_USE_Q_OS_ANDROID}) +MESSAGE(STATUS "USE OPTIMIZED IK: " ${HIFI_USE_OPTIMIZED_IK}) MESSAGE(STATUS "Build server: " ${BUILD_SERVER}) MESSAGE(STATUS "Build client: " ${BUILD_CLIENT}) MESSAGE(STATUS "Build tests: " ${BUILD_TESTS}) @@ -186,11 +186,9 @@ find_package( Threads ) add_definitions(-DGLM_FORCE_RADIANS) add_definitions(-DGLM_ENABLE_EXPERIMENTAL) add_definitions(-DGLM_FORCE_CTOR_INIT) -#add_definitions(-DHIFI_USE_Q_OS_ANDROID) -#option(HIFI_USE_Q_OS_ANDROID_OPTION "hifi_use_optimized_ik" OFF) -if (HIFI_USE_Q_OS_ANDROID) +if (HIFI_USE_OPTIMIZED_IK) MESSAGE(STATUS "SET THE USE IK DEFINITION ") - add_definitions(-DHIFI_USE_Q_OS_ANDROID) + add_definitions(-DHIFI_USE_OPTIMIZED_IK) endif() set(HIFI_LIBRARY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libraries") diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 5bccd4650b..ff865172ae 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2952,7 +2952,7 @@ void MyAvatar::initAnimGraph() { } else { graphUrl = PathUtils::resourcesUrl("avatar/avatar-animation.json"); -#if defined(Q_OS_ANDROID) || defined(HIFI_USE_Q_OS_ANDROID) +#if defined(Q_OS_ANDROID) || defined(HIFI_USE_OPTIMIZED_IK) graphUrl = PathUtils::resourcesUrl("avatar/avatar-animation_withSplineIKNode.json"); #endif } diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 58071cfab1..13cdedc830 100755 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -252,7 +252,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { AnimPose headAvatarSpace(avatarHeadPose.getRotation(), avatarHeadPose.getTranslation()); AnimPose headRigSpace = avatarToRigPose * headAvatarSpace; AnimPose hipsRigSpace = sensorToRigPose * sensorHips; -#if defined(Q_OS_ANDROID) || defined(HIFI_USE_Q_OS_ANDROID) +#if defined(Q_OS_ANDROID) || defined(HIFI_USE_OPTIMIZED_IK) glm::vec3 spine2TargetTranslation = computeSpine2WithHeadHipsSpline(myAvatar, hipsRigSpace, headRigSpace); #endif const float SPINE2_ROTATION_FILTER = 0.5f; @@ -276,7 +276,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { } generateBasisVectors(up, fwd, u, v, w); AnimPose newSpinePose(glm::mat4(glm::vec4(w, 0.0f), glm::vec4(u, 0.0f), glm::vec4(v, 0.0f), glm::vec4(glm::vec3(0.0f, 0.0f, 0.0f), 1.0f))); -#if defined(Q_OS_ANDROID) || defined(HIFI_USE_Q_OS_ANDROID) +#if defined(Q_OS_ANDROID) || defined(HIFI_USE_OPTIMIZED_IK) currentSpine2Pose.trans() = spine2TargetTranslation; #endif currentSpine2Pose.rot() = safeLerp(currentSpine2Pose.rot(), newSpinePose.rot(), SPINE2_ROTATION_FILTER); diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 93c9c0cd1b..be6240017f 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1459,12 +1459,7 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab int oppositeArmJointIndex = _animSkeleton->nameToJointIndex("RightArm"); if (ENABLE_POLE_VECTORS && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0 && oppositeArmJointIndex >= 0) { glm::vec3 poleVector; -#if defined(Q_OS_ANDROID) || defined(HIFI_USE_Q_OS_ANDROID) - bool isLeft = true; - bool usePoleVector = calculateElbowPoleVectorOptimized(handJointIndex, elbowJointIndex, armJointIndex, isLeft, poleVector); -#else bool usePoleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, oppositeArmJointIndex, poleVector); -#endif if (usePoleVector) { glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector); _animVars.set("leftHandPoleVectorEnabled", true); @@ -1519,12 +1514,7 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab if (ENABLE_POLE_VECTORS && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0 && oppositeArmJointIndex >= 0) { glm::vec3 poleVector; -#if defined(Q_OS_ANDROID) || defined(HIFI_USE_Q_OS_ANDROID) - bool isLeft = false; - bool usePoleVector = calculateElbowPoleVectorOptimized(handJointIndex, elbowJointIndex, armJointIndex, isLeft, poleVector); -#else bool usePoleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, oppositeArmJointIndex, poleVector); -#endif if (usePoleVector) { glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector); _animVars.set("rightHandPoleVectorEnabled", true); @@ -1690,330 +1680,6 @@ void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm } } -static float getHandPositionTheta(glm::vec3 armToHand, float defaultArmLength, bool left) { - float handPositionTheta = 0.0f; - //calculate the hand position influence on theta - const float zStart = 0.6f; - const float xStart = 0.1f; - // biases - const glm::vec3 biases(0.0f, 135.0f, 0.0f); - // weights - const float zWeightBottom = -100.0f; - const glm::vec3 weights(-50.0f, 60.0f, 90.0f); - - float yFactor = (fabsf(armToHand[1] / defaultArmLength) * weights[1]) + biases[1]; - - float zFactor; - if (armToHand[1] > 0.0f) { - zFactor = weights[2] * glm::max(zStart - (armToHand[2] / defaultArmLength), 0.0f) * glm::max(fabsf((armToHand[1] - 0.1f) / defaultArmLength), 0.0f); - } else { - zFactor = zWeightBottom * glm::max(zStart - (armToHand[2] / defaultArmLength), 0.0f) * fabsf(armToHand[1] / defaultArmLength); - } - - float xFactor; - if (left) { - xFactor = weights[0] * ((-1.0f * (armToHand[0] / defaultArmLength) + xStart) - (0.2f) * ((1.0f + (armToHand[1] / defaultArmLength)) / 2.0f)); - } else { - xFactor = weights[0] * (((armToHand[0] / defaultArmLength) + xStart) - (0.2f) * ((1.0f + (armToHand[1] / defaultArmLength)) / 2.0f)); - } - - handPositionTheta = xFactor + yFactor + zFactor; - - const float LOWER_ANATOMICAL_ANGLE = 175.0f; - const float UPPER_ANATOMICAL_ANGLE = 50.0f; - if (handPositionTheta > LOWER_ANATOMICAL_ANGLE) { - handPositionTheta = LOWER_ANATOMICAL_ANGLE; - } - if (handPositionTheta < UPPER_ANATOMICAL_ANGLE) { - handPositionTheta = UPPER_ANATOMICAL_ANGLE; - } - - if (left) { - handPositionTheta *= -1.0f; - } - - return handPositionTheta; -} - -static float computeUlnarRadialCompensation(float ulnarRadialTheta, float twistTheta, bool left) { - const float ULNAR_BOUNDARY_MINUS = -PI / 4.0f; - const float ULNAR_BOUNDARY_PLUS = -PI / 4.0f; - float ulnarDiff = 0.0f; - float ulnarCorrection = 0.0f; - float currentWristCoefficient = 0.0f; - if (left) { - if (ulnarRadialTheta > -ULNAR_BOUNDARY_MINUS) { - ulnarDiff = ulnarRadialTheta - (-ULNAR_BOUNDARY_MINUS); - } else if (ulnarRadialTheta < -ULNAR_BOUNDARY_PLUS) { - ulnarDiff = ulnarRadialTheta - (-ULNAR_BOUNDARY_PLUS); - } - } else { - if (ulnarRadialTheta > ULNAR_BOUNDARY_MINUS) { - ulnarDiff = ulnarRadialTheta - ULNAR_BOUNDARY_MINUS; - } else if (ulnarRadialTheta < ULNAR_BOUNDARY_PLUS) { - ulnarDiff = ulnarRadialTheta - ULNAR_BOUNDARY_PLUS; - } - - } - if (fabsf(ulnarDiff) > 0.0f) { - float twistCoefficient = 0.0f; - - if (left) { - twistCoefficient = twistTheta; - if (twistCoefficient > (PI / 6.0f)) { - twistCoefficient = 1.0f; - } else { - twistCoefficient = 0.0f; - } - } else { - twistCoefficient = twistTheta; - if (twistCoefficient < (-PI / 6.0f)) { - twistCoefficient = 1.0f; - } else { - twistCoefficient = 0.0f; - } - - } - - if (twistTheta < 0.0f) { - if (left) { - ulnarCorrection -= glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 180.0f * twistCoefficient; - } else { - ulnarCorrection += glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 180.0f * twistCoefficient; - } - } else { - if (left) { - ulnarCorrection += glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 180.0f * twistCoefficient; - } else { - ulnarCorrection -= glm::sign(ulnarDiff) * (fabsf(ulnarDiff) / PI) * 180.0f * twistCoefficient; - } - } - if (fabsf(ulnarCorrection) > 100.0f) { - ulnarCorrection = glm::sign(ulnarCorrection) * 100.0f; - } - currentWristCoefficient += ulnarCorrection; - } - - return currentWristCoefficient; - - -} - -static float computeTwistCompensation(float twistTheta, bool left) { - - const float TWIST_DEADZONE = PI / 2.0f; - float twistCorrection = 0.0f; - - if (fabsf(twistTheta) > TWIST_DEADZONE) { - twistCorrection = glm::sign(twistTheta) * ((fabsf(twistTheta) - TWIST_DEADZONE) / PI) * 90.0f; - } - // limit the twist correction - if (fabsf(twistCorrection) > 30.0f) { - twistCorrection = glm::sign(twistCorrection) * 30.0f; - } - - return twistCorrection; -} - -static float computeFlexCompensation(float flexTheta, bool left) { - - const float FLEX_BOUNDARY = PI / 6.0f; - const float EXTEND_BOUNDARY = -PI / 4.0f; - float flexCorrection = 0.0f; - float currentWristCoefficient = 0.0f; - - if (flexTheta > FLEX_BOUNDARY) { - flexCorrection = ((flexTheta - FLEX_BOUNDARY) / PI) * 60.0f; - } else if (flexTheta < EXTEND_BOUNDARY) { - flexCorrection = ((flexTheta - EXTEND_BOUNDARY) / PI) * 60.0f; - } - if (fabsf(flexCorrection) > 175.0f) { - flexCorrection = glm::sign(flexCorrection) * 175.0f; - } - if (left) { - currentWristCoefficient += flexCorrection; - } else { - currentWristCoefficient -= flexCorrection; - } - - return currentWristCoefficient; - -} - -static float getAxisThetaFromRotation(glm::vec3 axis, glm::quat rotation) { - - //get the flex/extension of the wrist rotation - glm::quat rotationAboutTheAxis; - glm::quat rotationOrthoganalToAxis; - swingTwistDecomposition(rotation, axis, rotationOrthoganalToAxis, rotationAboutTheAxis); - if (rotationAboutTheAxis.w < 0.0f) { - rotationAboutTheAxis *= -1.0f; - } - glm::vec3 rotAxis = glm::axis(rotationAboutTheAxis); - float axisTheta = glm::sign(glm::dot(rotAxis, axis)) * glm::angle(rotationAboutTheAxis); - - return axisTheta; -} - -bool Rig::calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int shoulderIndex, bool left, glm::vec3& poleVector) { - - AnimPose handPose = _externalPoseSet._absolutePoses[handIndex]; - AnimPose shoulderPose = _externalPoseSet._absolutePoses[shoulderIndex]; - AnimPose elbowPose = _externalPoseSet._absolutePoses[elbowIndex]; - - AnimPose absoluteShoulderPose = getAbsoluteDefaultPose(shoulderIndex); - AnimPose absoluteHandPose = getAbsoluteDefaultPose(handIndex); - float defaultArmLength = glm::length(absoluteHandPose.trans() - absoluteShoulderPose.trans()); - - glm::vec3 armToHand = handPose.trans() - shoulderPose.trans(); - glm::vec3 unitAxis; - float axisLength = glm::length(armToHand); - if (axisLength > 0.0f) { - unitAxis = armToHand / axisLength; - } else { - unitAxis = Vectors::UNIT_Y; - } - - if ((armToHand.z < 0.0f) && (armToHand.y < 0.0f)) { - // turn off the poleVector when the hand is back and down - return false; - } - - // get the pole vector theta based on the hand position relative to the shoulder. - float positionalTheta = getHandPositionTheta(armToHand, defaultArmLength, left); - - // now we calculate the contribution of the hand rotation relative to the arm - glm::quat relativeHandRotation = (elbowPose.inverse() * handPose).rot(); - if (relativeHandRotation.w < 0.0f) { - relativeHandRotation *= -1.0f; - } - - // find the thetas, hand relative to avatar arm - const glm::vec3 ULNAR_ROTATION_AXIS = Vectors::UNIT_Z; - const glm::vec3 TWIST_ROTATION_AXIS = Vectors::UNIT_Y; - const glm::vec3 FLEX__ROTATION_AXIS = Vectors::UNIT_X; - - float ulnarDeviationTheta = getAxisThetaFromRotation(ULNAR_ROTATION_AXIS, relativeHandRotation); - float flexTheta = getAxisThetaFromRotation(FLEX__ROTATION_AXIS, relativeHandRotation); - float trueTwistTheta = getAxisThetaFromRotation(TWIST_ROTATION_AXIS, relativeHandRotation); - - const float HALFWAY_ANGLE = PI / 2.0f; - const float SMOOTHING_COEFFICIENT = 0.5f; - if (left) { - - if (glm::sign(ulnarDeviationTheta) != glm::sign(_ulnarRadialThetaRunningAverageLeft) && fabsf(ulnarDeviationTheta) > HALFWAY_ANGLE) { - // don't allow the theta to cross the 180 degree limit. ie don't go from 179 to -179 degrees - ulnarDeviationTheta = -1.0f * ulnarDeviationTheta; - } - if (glm::sign(flexTheta) != glm::sign(_flexThetaRunningAverageLeft) && fabsf(flexTheta) > HALFWAY_ANGLE) { - // don't allow the theta to cross the 180 degree limit. - flexTheta = -1.0f * flexTheta; - } - if (glm::sign(trueTwistTheta) != glm::sign(_twistThetaRunningAverageLeft) && fabsf(trueTwistTheta) > HALFWAY_ANGLE) { - // don't allow the theta to cross the 180 degree limit. - trueTwistTheta = -1.0f * trueTwistTheta; - } - - // put some smoothing on the thetas - _ulnarRadialThetaRunningAverageLeft = ulnarDeviationTheta; - _flexThetaRunningAverageLeft = SMOOTHING_COEFFICIENT * _flexThetaRunningAverageLeft + (1.0f - SMOOTHING_COEFFICIENT) * flexTheta; - _twistThetaRunningAverageLeft = SMOOTHING_COEFFICIENT * _twistThetaRunningAverageLeft + (1.0f - SMOOTHING_COEFFICIENT) * trueTwistTheta; - - } else { - - if (glm::sign(ulnarDeviationTheta) != glm::sign(_ulnarRadialThetaRunningAverageRight) && fabsf(ulnarDeviationTheta) > HALFWAY_ANGLE) { - // don't allow the theta to cross the 180 degree limit. ie don't go from 179 to -179 degrees - ulnarDeviationTheta = -1.0f * ulnarDeviationTheta; - } - if (glm::sign(flexTheta) != glm::sign(_flexThetaRunningAverageRight) && fabsf(flexTheta) > HALFWAY_ANGLE) { - // don't allow the theta to cross the 180 degree limit. - flexTheta = -1.0f * flexTheta; - } - if (glm::sign(trueTwistTheta) != glm::sign(_twistThetaRunningAverageRight) && fabsf(trueTwistTheta) > HALFWAY_ANGLE) { - // don't allow the theta to cross the 180 degree limit. - trueTwistTheta = -1.0f * trueTwistTheta; - } - - // put some smoothing on the thetas - _twistThetaRunningAverageRight = SMOOTHING_COEFFICIENT * _twistThetaRunningAverageRight + (1.0f - SMOOTHING_COEFFICIENT) * trueTwistTheta; - _flexThetaRunningAverageRight = SMOOTHING_COEFFICIENT * _flexThetaRunningAverageRight + (1.0f - SMOOTHING_COEFFICIENT) * flexTheta; - _ulnarRadialThetaRunningAverageRight = ulnarDeviationTheta; - } - - // get the correction angle for each axis and add it to the base pole vector theta - float currentWristCoefficient = 0.0f; - if (left) { - currentWristCoefficient += computeTwistCompensation(_twistThetaRunningAverageLeft, left); - currentWristCoefficient += computeFlexCompensation(_flexThetaRunningAverageLeft, left); - //currentWristCoefficient += computeUlnarRadialCompensation(_ulnarRadialThetaRunningAverageLeft, _twistThetaRunningAverageLeft, left); - } else { - currentWristCoefficient += computeTwistCompensation(_twistThetaRunningAverageRight, left); - currentWristCoefficient += computeFlexCompensation(_flexThetaRunningAverageRight, left); - //currentWristCoefficient += computeUlnarRadialCompensation(_ulnarRadialThetaRunningAverageRight, _twistThetaRunningAverageRight, left); - } - - // find the previous contribution of the wrist and add the current wrist correction to it - if (left) { - _lastWristCoefficientLeft = _lastThetaLeft - _lastPositionThetaLeft; - _lastWristCoefficientLeft += currentWristCoefficient; - _lastPositionThetaLeft = positionalTheta; - _lastThetaLeft = positionalTheta + _lastWristCoefficientLeft; - } else { - _lastWristCoefficientRight = _lastThetaRight - _lastPositionThetaRight; - _lastWristCoefficientRight += currentWristCoefficient; - _lastPositionThetaRight = positionalTheta; - _lastThetaRight = positionalTheta + _lastWristCoefficientRight; - } - - // limit the correction anatomically possible angles and change to radians - const float LOWER_ANATOMICAL_ANGLE = 175.0f; - const float UPPER_ANATOMICAL_ANGLE = 50.0f; - - // make the lower boundary vary with the body - float lowerBoundary = LOWER_ANATOMICAL_ANGLE; - if (fabsf(positionalTheta) < LOWER_ANATOMICAL_ANGLE) { - lowerBoundary = positionalTheta; - } - float thetaRadians = 0.0f; - if (left) { - - if (_lastThetaLeft > -UPPER_ANATOMICAL_ANGLE) { - _lastThetaLeft = -UPPER_ANATOMICAL_ANGLE; - } - if (_lastThetaLeft < lowerBoundary) { - _lastThetaLeft = lowerBoundary; - } - // convert to radians and make 180 0 to match pole vector theta - thetaRadians = ((180.0f - _lastThetaLeft) / 180.0f)*PI; - } else { - - if (_lastThetaRight < UPPER_ANATOMICAL_ANGLE) { - _lastThetaRight = UPPER_ANATOMICAL_ANGLE; - } - if (_lastThetaRight > lowerBoundary) { - _lastThetaRight = lowerBoundary; - } - // convert to radians and make 180 0 to match pole vector theta - thetaRadians = ((180.0f - _lastThetaRight) / 180.0f)*PI; - } - - // convert the final theta to a pole vector value - float poleVectorXValue = -1.0f * sinf(thetaRadians); - float poleVectorYValue = -1.0f * cosf(thetaRadians); - float poleVectorZValue = 0.0f; - glm::vec3 thetaVector(poleVectorXValue, poleVectorYValue, poleVectorZValue); - - glm::vec3 up = Vectors::UNIT_Y; - glm::vec3 fwd = armToHand/glm::length(armToHand); - glm::vec3 u, v, w; - - generateBasisVectors(fwd, up, u, v, w); - AnimPose armAxisPose(glm::mat4(glm::vec4(-w, 0.0f), glm::vec4(v, 0.0f), glm::vec4(u, 0.0f), glm::vec4(glm::vec3(0.0f, 0.0f, 0.0f), 1.0f))); - poleVector = armAxisPose * thetaVector; - - return true; -} - bool Rig::calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex, int oppositeArmIndex, glm::vec3& poleVector) const { // The resulting Pole Vector is calculated as the sum of a three vectors. // The first is the vector with direction shoulder-hand. The module of this vector is inversely proportional to the strength of the resulting Pole Vector. diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 6f1a5906bb..41c25a3c3e 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -258,7 +258,6 @@ protected: void calcAnimAlpha(float speed, const std::vector& referenceSpeeds, float* alphaOut) const; bool calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex, int oppositeArmIndex, glm::vec3& poleVector) const; - bool calculateElbowPoleVectorOptimized(int handIndex, int elbowIndex, int shoulderIndex, bool left, glm::vec3& poleVector); glm::vec3 calculateKneePoleVector(int footJointIndex, int kneeJoint, int upLegIndex, int hipsIndex, const AnimPose& targetFootPose) const; glm::vec3 deflectHandFromTorso(const glm::vec3& handPosition, const HFMJointShapeInfo& hipsShapeInfo, const HFMJointShapeInfo& spineShapeInfo, const HFMJointShapeInfo& spine1ShapeInfo, const HFMJointShapeInfo& spine2ShapeInfo) const; @@ -420,19 +419,6 @@ protected: bool _computeNetworkAnimation { false }; bool _sendNetworkNode { false }; - float _twistThetaRunningAverageLeft { 0.0f }; - float _flexThetaRunningAverageLeft { 0.0f }; - float _ulnarRadialThetaRunningAverageLeft { 0.0f }; - float _twistThetaRunningAverageRight { 0.0f }; - float _flexThetaRunningAverageRight { 0.0f }; - float _ulnarRadialThetaRunningAverageRight { 0.0f }; - float _lastThetaLeft { 0.0f }; - float _lastThetaRight { 0.0f }; - float _lastWristCoefficientRight { 0.0f }; - float _lastWristCoefficientLeft { 0.0f }; - float _lastPositionThetaLeft { 0.0f }; - float _lastPositionThetaRight { 0.0f }; - AnimContext _lastContext; AnimVariantMap _lastAnimVars; From fe33aadd698c6cc415b960418ca7de111e877509 Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Thu, 21 Feb 2019 14:58:06 -0800 Subject: [PATCH 144/474] Assigning the default world detail and max render rate target in vr quest --- interface/src/LODManager.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/interface/src/LODManager.h b/interface/src/LODManager.h index 559bae1779..5817eafc25 100644 --- a/interface/src/LODManager.h +++ b/interface/src/LODManager.h @@ -19,13 +19,20 @@ #include #include +#include + #ifdef Q_OS_ANDROID -const float LOD_DEFAULT_QUALITY_LEVEL = 0.75f; // default quality level setting is High (lower framerate) +const float LOD_DEFAULT_QUALITY_LEVEL = 0.2; // default quality level setting is High (lower framerate) #else const float LOD_DEFAULT_QUALITY_LEVEL = 0.5f; // default quality level setting is Mid #endif const float LOD_MAX_LIKELY_DESKTOP_FPS = 60.0f; // this is essentially, V-synch fps +#ifdef Q_OS_ANDROID +const float LOD_MAX_LIKELY_HMD_FPS = 36.0f; // this is essentially, V-synch fps +#else const float LOD_MAX_LIKELY_HMD_FPS = 90.0f; // this is essentially, V-synch fps +#endif + const float LOD_OFFSET_FPS = 5.0f; // offset of FPS to add for computing the target framerate class AABox; From 5cf8a963cdd4481d67254b4adad946c065dbdc51 Mon Sep 17 00:00:00 2001 From: amantley Date: Thu, 21 Feb 2019 15:41:16 -0800 Subject: [PATCH 145/474] make spine2 rotation damping only happen in optimized code. the regular code is already damped --- interface/src/avatar/MySkeletonModel.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 13cdedc830..aa45a1f0bb 100755 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -265,7 +265,9 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { if (spine2Exists && headExists && hipsExists) { AnimPose rigSpaceYaw(myAvatar->getSpine2RotationRigSpace()); +#if defined(Q_OS_ANDROID) || defined(HIFI_USE_OPTIMIZED_IK) rigSpaceYaw.rot() = safeLerp(Quaternions::IDENTITY, rigSpaceYaw.rot(), 0.5f); +#endif glm::vec3 u, v, w; glm::vec3 fwd = rigSpaceYaw.rot() * glm::vec3(0.0f, 0.0f, 1.0f); glm::vec3 up = currentHeadPose.trans() - currentHipsPose.trans(); From dcbf57ee0b5df0fcde392bc14534c26db6ee85dc Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Thu, 21 Feb 2019 16:41:24 -0700 Subject: [PATCH 146/474] Fix linux warning and HMD breaks flow --- libraries/animation/src/Flow.cpp | 4 ---- libraries/animation/src/Rig.cpp | 7 ------- libraries/render-utils/src/Model.cpp | 1 + 3 files changed, 1 insertion(+), 11 deletions(-) diff --git a/libraries/animation/src/Flow.cpp b/libraries/animation/src/Flow.cpp index 9fb0306fa3..3bb80b9375 100644 --- a/libraries/animation/src/Flow.cpp +++ b/libraries/animation/src/Flow.cpp @@ -460,16 +460,12 @@ void Flow::calculateConstraints(const std::shared_ptr& skeleton, if (!skeleton) { return; } - int rightHandIndex = -1; auto flowPrefix = FLOW_JOINT_PREFIX.toUpper(); auto simPrefix = SIM_JOINT_PREFIX.toUpper(); std::vector handsIndices; for (int i = 0; i < skeleton->getNumJoints(); i++) { auto name = skeleton->getJointName(i); - if (name == "RightHand") { - rightHandIndex = i; - } if (std::find(HAND_COLLISION_JOINTS.begin(), HAND_COLLISION_JOINTS.end(), name) != HAND_COLLISION_JOINTS.end()) { handsIndices.push_back(i); } diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 7a00a8ecf2..4d5488cad2 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1910,13 +1910,6 @@ void Rig::initAnimGraph(const QUrl& url) { connect(_networkLoader.get(), &AnimNodeLoader::error, [networkUrl](int error, QString str) { qCritical(animation) << "Error loading: code = " << error << "str =" << str; }); - - connect(this, &Rig::onLoadComplete, [&]() { - _internalFlow.setActive(false); - _internalFlow.cleanUp(); - _networkFlow.setActive(false); - _networkFlow.cleanUp(); - }); } } diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 260e25009e..f61ae28db4 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1158,6 +1158,7 @@ void Model::setURL(const QUrl& url) { resource->setLoadPriority(this, _loadingPriority); _renderWatcher.setResource(resource); } + _rig.initFlow(false); onInvalidate(); } From 3bf5c44f9885a218515bccee41dc418cf2d3f990 Mon Sep 17 00:00:00 2001 From: amantley Date: Thu, 21 Feb 2019 16:24:46 -0800 Subject: [PATCH 147/474] fixed build warnings --- interface/src/avatar/MySkeletonModel.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index aa45a1f0bb..55c29b66c1 100755 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -35,6 +35,7 @@ Rig::CharacterControllerState convertCharacterControllerState(CharacterControlle }; } +#if defined(Q_OS_ANDROID) || defined(HIFI_USE_OPTIMIZED_IK) static glm::vec3 computeSpine2WithHeadHipsSpline(MyAvatar* myAvatar, AnimPose hipsIKTargetPose, AnimPose headIKTargetPose) { // the the ik targets to compute the spline with @@ -48,6 +49,7 @@ static glm::vec3 computeSpine2WithHeadHipsSpline(MyAvatar* myAvatar, AnimPose hi return spine2Translation + myAvatar->getSpine2SplineOffset(); } +#endif static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) { glm::mat4 worldToSensorMat = glm::inverse(myAvatar->getSensorToWorldMatrix()); @@ -249,10 +251,10 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { myAvatar->getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND).isValid() && !(params.primaryControllerFlags[Rig::PrimaryControllerType_Spine2] & (uint8_t)Rig::ControllerFlags::Enabled)) { +#if defined(Q_OS_ANDROID) || defined(HIFI_USE_OPTIMIZED_IK) AnimPose headAvatarSpace(avatarHeadPose.getRotation(), avatarHeadPose.getTranslation()); AnimPose headRigSpace = avatarToRigPose * headAvatarSpace; AnimPose hipsRigSpace = sensorToRigPose * sensorHips; -#if defined(Q_OS_ANDROID) || defined(HIFI_USE_OPTIMIZED_IK) glm::vec3 spine2TargetTranslation = computeSpine2WithHeadHipsSpline(myAvatar, hipsRigSpace, headRigSpace); #endif const float SPINE2_ROTATION_FILTER = 0.5f; From 9097d9c57cdf1416c32e1a661144d096fd8f8f79 Mon Sep 17 00:00:00 2001 From: amer cerkic Date: Thu, 21 Feb 2019 16:33:25 -0800 Subject: [PATCH 148/474] working on pause -> away mode. Even doesn't seem to be firing. Also found a spot where I left commented out code from lifecycle testing. --- android/apps/questInterface/src/main/cpp/native.cpp | 6 +++++- .../io/highfidelity/oculus/OculusMobileActivity.java | 10 +++++++--- interface/src/AndroidHelper.cpp | 4 ++++ interface/src/AndroidHelper.h | 3 ++- interface/src/Application.cpp | 10 ++++++++++ interface/src/Application.h | 3 ++- scripts/+android_questInterface/defaultScripts.js | 4 ++-- scripts/system/away.js | 3 ++- 8 files changed, 34 insertions(+), 9 deletions(-) diff --git a/android/apps/questInterface/src/main/cpp/native.cpp b/android/apps/questInterface/src/main/cpp/native.cpp index 3c1563c93d..547874b84e 100644 --- a/android/apps/questInterface/src/main/cpp/native.cpp +++ b/android/apps/questInterface/src/main/cpp/native.cpp @@ -61,7 +61,7 @@ extern "C" { Java_io_highfidelity_oculus_OculusMobileActivity_nativeInitOculusPlatform(JNIEnv *env, jobject obj){ initOculusPlatform(env, obj); } -QAndroidJniObject __interfaceActivity; + QAndroidJniObject __interfaceActivity; JNIEXPORT void JNICALL Java_io_highfidelity_oculus_OculusMobileActivity_questNativeOnCreate(JNIEnv *env, jobject obj) { @@ -80,6 +80,10 @@ QAndroidJniObject __interfaceActivity; }); } + JNIEXPORT void JNICALL + Java_io_highfidelity_oculus_OculusMobileActivity_questNativeAwayMode(JNIEnv *env, jobject obj) { + AndroidHelper::instance().toggleAwayMode(); + } JNIEXPORT void Java_io_highfidelity_oculus_OculusMobileActivity_questOnAppAfterLoad(JNIEnv* env, jobject obj) { diff --git a/android/libraries/oculus/src/main/java/io/highfidelity/oculus/OculusMobileActivity.java b/android/libraries/oculus/src/main/java/io/highfidelity/oculus/OculusMobileActivity.java index 2aa7b4da05..71ccfa84cd 100644 --- a/android/libraries/oculus/src/main/java/io/highfidelity/oculus/OculusMobileActivity.java +++ b/android/libraries/oculus/src/main/java/io/highfidelity/oculus/OculusMobileActivity.java @@ -34,6 +34,7 @@ public class OculusMobileActivity extends QtActivity implements SurfaceHolder.Ca private native void questNativeOnResume(); private native void questOnAppAfterLoad(); + private native void questNativeAwayMode(); private SurfaceView mView; private SurfaceHolder mSurfaceHolder; @@ -55,6 +56,7 @@ public class OculusMobileActivity extends QtActivity implements SurfaceHolder.Ca runOnUiThread(() -> { setContentView(mView); questOnAppAfterLoad(); + }); @@ -91,12 +93,14 @@ public class OculusMobileActivity extends QtActivity implements SurfaceHolder.Ca questNativeOnPause(); nativeOnPause(); isPausing=true; + } @Override protected void onStop(){ super.onStop(); Log.w(TAG, "QQQ_ Onstop called"); + questNativeAwayMode(); } @Override @@ -104,6 +108,7 @@ public class OculusMobileActivity extends QtActivity implements SurfaceHolder.Ca super.onRestart(); Log.w(TAG, "QQQ_ onRestart called ****"); questOnAppAfterLoad(); + questNativeAwayMode(); } @Override @@ -123,8 +128,7 @@ public class OculusMobileActivity extends QtActivity implements SurfaceHolder.Ca @Override public void surfaceDestroyed(SurfaceHolder holder) { Log.w(TAG, "QQQ_ surfaceDestroyed ***************************************************"); - // nativeOnSurfaceChanged(null); - // mSurfaceHolder = null; - + nativeOnSurfaceChanged(null); + mSurfaceHolder = null; } } \ No newline at end of file diff --git a/interface/src/AndroidHelper.cpp b/interface/src/AndroidHelper.cpp index 4f75d5bdb2..e5007d706e 100644 --- a/interface/src/AndroidHelper.cpp +++ b/interface/src/AndroidHelper.cpp @@ -45,6 +45,10 @@ void AndroidHelper::notifyBeforeEnterBackground() { emit beforeEnterBackground(); } +void AndroidHelper::notifyToggleAwayMode() { + emit toggleAwayMode(); +} + void AndroidHelper::notifyEnterBackground() { emit enterBackground(); } diff --git a/interface/src/AndroidHelper.h b/interface/src/AndroidHelper.h index f1cec6a43b..fca035a217 100644 --- a/interface/src/AndroidHelper.h +++ b/interface/src/AndroidHelper.h @@ -31,6 +31,7 @@ public: void notifyEnterForeground(); void notifyBeforeEnterBackground(); void notifyEnterBackground(); + void notifyToggleAwayMode(); void performHapticFeedback(int duration); void processURL(const QString &url); @@ -55,7 +56,7 @@ signals: void enterForeground(); void beforeEnterBackground(); void enterBackground(); - + void toggleAwayMode(); void hapticFeedbackRequested(int duration); void handleSignupCompleted(); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index a611738445..a8d0cf6125 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2411,6 +2411,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(&AndroidHelper::instance(), &AndroidHelper::beforeEnterBackground, this, &Application::beforeEnterBackground); connect(&AndroidHelper::instance(), &AndroidHelper::enterBackground, this, &Application::enterBackground); connect(&AndroidHelper::instance(), &AndroidHelper::enterForeground, this, &Application::enterForeground); + connect(&AndroidHelper::instance(), &AndroidHelper::toggleAwayMode, this, &Application::toggleAwayMode); + AndroidHelper::instance().notifyLoadComplete(); #endif pauseUntilLoginDetermined(); @@ -9135,6 +9137,8 @@ void Application::beforeEnterBackground() { clearDomainOctreeDetails(); } + + void Application::enterBackground() { QMetaObject::invokeMethod(DependencyManager::get().data(), "stop", Qt::BlockingQueuedConnection); @@ -9160,4 +9164,10 @@ void Application::enterForeground() { } #endif +void Application::toggleAwayMode(){ + auto key = QKeyEvent(QEvent::KeyPress,Qt::Key_Escape,Qt::NoModifier); + _keyboardMouseDevice->keyPressEvent(&key); + qDebug()<<"QQQ_ AWAY MODE "; +} + #include "Application.moc" diff --git a/interface/src/Application.h b/interface/src/Application.h index afd9f5f12f..d856297e41 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -338,7 +338,8 @@ public: void beforeEnterBackground(); void enterBackground(); void enterForeground(); -#endif + void toggleAwayMode(); + #endif signals: void svoImportRequested(const QString& url); diff --git a/scripts/+android_questInterface/defaultScripts.js b/scripts/+android_questInterface/defaultScripts.js index d22716302c..e996f71908 100644 --- a/scripts/+android_questInterface/defaultScripts.js +++ b/scripts/+android_questInterface/defaultScripts.js @@ -14,8 +14,8 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/request-service.js", "system/progress.js", - //"system/away.js", - "system/hmd.js", + "system/away.js", + //"system/hmd.js", "system/menu.js", "system/bubble.js", "system/pal.js", // "system/mod.js", // older UX, if you prefer diff --git a/scripts/system/away.js b/scripts/system/away.js index 45b6f43b73..c75d58a240 100644 --- a/scripts/system/away.js +++ b/scripts/system/away.js @@ -154,7 +154,7 @@ function goAway(fromStartup) { if (!isEnabled || isAway) { return; } - + console.warn('QQQ_ JS going away); // If we're entering away mode from some other state than startup, then we create our move timer immediately. // However if we're just stating up, we need to delay this process so that we don't think the initial teleport // is actually a move. @@ -176,6 +176,7 @@ function goActive() { return; } + console.warn('QQQ_ JS going active); UserActivityLogger.toggledAway(false); MyAvatar.isAway = false; From 982c4c2bc40d607bbd152f09454eda5428b63441 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 21 Feb 2019 16:33:31 -0800 Subject: [PATCH 149/474] do not Space::clear() in clearNonLocalEntities() --- libraries/entities-renderer/src/EntityTreeRenderer.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 9d55d936a2..914c0f97a0 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -219,7 +219,6 @@ void EntityTreeRenderer::clearNonLocalEntities() { std::unordered_map savedEntities; // remove all entities from the scene - _space->clear(); auto scene = _viewState->getMain3DScene(); if (scene) { render::Transaction transaction; @@ -1392,4 +1391,4 @@ bool EntityTreeRenderer::removeMaterialFromAvatar(const QUuid& avatarID, graphic return _removeMaterialFromAvatarOperator(avatarID, material, parentMaterialName); } return false; -} \ No newline at end of file +} From 94fc60f28517a2409be134ebfe359fedb09f507f Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Thu, 21 Feb 2019 16:34:50 -0800 Subject: [PATCH 150/474] Bad idea to include global here --- interface/src/LODManager.h | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/LODManager.h b/interface/src/LODManager.h index 5817eafc25..87e871a3ab 100644 --- a/interface/src/LODManager.h +++ b/interface/src/LODManager.h @@ -19,7 +19,6 @@ #include #include -#include #ifdef Q_OS_ANDROID const float LOD_DEFAULT_QUALITY_LEVEL = 0.2; // default quality level setting is High (lower framerate) From ddc42585d81e53386c2213b4a8af3aaacc3430a6 Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Thu, 21 Feb 2019 16:41:50 -0800 Subject: [PATCH 151/474] Cleanup syntax --- interface/src/LODManager.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/LODManager.h b/interface/src/LODManager.h index 87e871a3ab..77cb1a0d39 100644 --- a/interface/src/LODManager.h +++ b/interface/src/LODManager.h @@ -21,7 +21,7 @@ #ifdef Q_OS_ANDROID -const float LOD_DEFAULT_QUALITY_LEVEL = 0.2; // default quality level setting is High (lower framerate) +const float LOD_DEFAULT_QUALITY_LEVEL = 0.2f; // default quality level setting is High (lower framerate) #else const float LOD_DEFAULT_QUALITY_LEVEL = 0.5f; // default quality level setting is Mid #endif From ae6a73c71033cd0d4815a7e7f754a8793e835f46 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 21 Feb 2019 16:42:47 -0800 Subject: [PATCH 152/474] Automatically go to quest-dev domain on startup --- .../src/OculusMobileDisplayPlugin.cpp | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp b/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp index 78d80443d8..bc8e1a5113 100644 --- a/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp +++ b/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp @@ -168,10 +168,8 @@ bool OculusMobileDisplayPlugin::isHmdMounted() const { static void goToDevMobile() { auto addressManager = DependencyManager::get(); auto currentAddress = addressManager->currentAddress().toString().toStdString(); - if (std::string::npos == currentAddress.find("dev-mobile")) { - addressManager->handleLookupString("hifi://dev-mobile/495.236,501.017,482.434/0,0.97452,0,-0.224301"); - //addressManager->handleLookupString("hifi://dev-mobile/504,498,491/0,0,0,0"); - //addressManager->handleLookupString("hifi://dev-mobile/0,-1,1"); + if (std::string::npos == currentAddress.find("quest-dev")) { + addressManager->handleLookupString("hifi://quest-dev"); } } @@ -217,12 +215,12 @@ bool OculusMobileDisplayPlugin::beginFrameRender(uint32_t frameIndex) { }); } - // static uint32_t count = 0; - // if ((++count % 1000) == 0) { - // AbstractViewStateInterface::instance()->postLambdaEvent([] { - // goToDevMobile(); - // }); - // } + static uint32_t count = 0; + if ((++count % 1000) == 0) { + AbstractViewStateInterface::instance()->postLambdaEvent([] { + goToDevMobile(); + }); + } return result && Parent::beginFrameRender(frameIndex); } From be2d4272da65adc644064abb68a55b7cb395bafd Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 21 Feb 2019 18:11:16 -0800 Subject: [PATCH 153/474] remove the other unnecessary _space->clear() --- libraries/entities-renderer/src/EntityTreeRenderer.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 914c0f97a0..efd1399f30 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -258,8 +258,6 @@ void EntityTreeRenderer::clear() { resetEntitiesScriptEngine(); } // remove all entities from the scene - - _space->clear(); auto scene = _viewState->getMain3DScene(); if (scene) { render::Transaction transaction; From c1786edc2445797631c2c87a9d448c4ff42f7e35 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Thu, 21 Feb 2019 17:47:28 -0800 Subject: [PATCH 154/474] Emulate the old behavior of _lastInputLoudness --- interface/src/scripting/Audio.cpp | 5 +++-- libraries/audio-client/src/AudioClient.cpp | 20 ++++++++++++++------ libraries/audio-client/src/AudioClient.h | 6 ++++-- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index fb64dbe098..2c4c29ff65 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -27,8 +27,9 @@ QString Audio::HMD { "VR" }; Setting::Handle enableNoiseReductionSetting { QStringList { Audio::AUDIO, "NoiseReduction" }, true }; float Audio::loudnessToLevel(float loudness) { - float level = 6.02059991f * fastLog2f(loudness); // level in dBFS - level = (level + 48.0f) * (1/39.0f); // map [-48, -9] dBFS to [0, 1] + float level = loudness * (1/32768.0f); // level in [0, 1] + level = 6.02059991f * fastLog2f(level); // convert to dBFS + level = (level + 48.0f) * (1/42.0f); // map [-48, -6] dBFS to [0, 1] return glm::clamp(level, 0.0f, 1.0f); } diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 60a95ff58a..8c50a195ee 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -175,7 +175,7 @@ static float computeLoudness(int16_t* samples, int numSamples, int numChannels, const int32_t CLIPPING_THRESHOLD = 32392; // -0.1 dBFS const int32_t CLIPPING_DETECTION = 3; // consecutive samples over threshold - float scale = numSamples ? 1.0f / (numSamples * 32768.0f) : 0.0f; + float scale = numSamples ? 1.0f / numSamples : 0.0f; int32_t loudness = 0; isClipping = false; @@ -249,6 +249,8 @@ AudioClient::AudioClient() : _outputBufferSizeFrames("audioOutputBufferFrames", DEFAULT_BUFFER_FRAMES), _sessionOutputBufferSizeFrames(_outputBufferSizeFrames.get()), _outputStarveDetectionEnabled("audioOutputStarveDetectionEnabled", DEFAULT_STARVE_DETECTION_ENABLED), + _lastRawInputLoudness(0.0f), + _lastSmoothedRawInputLoudness(0.0f), _lastInputLoudness(0.0f), _timeSinceLastClip(-1.0f), _muted(false), @@ -1144,6 +1146,9 @@ void AudioClient::handleAudioInput(QByteArray& audioBuffer) { emit inputReceived(audioBuffer); } + // loudness after mute/gate + _lastInputLoudness = (_muted || !audioGateOpen) ? 0.0f : _lastRawInputLoudness; + // detect gate opening and closing bool openedInLastBlock = !_audioGateOpen && audioGateOpen; // the gate just opened bool closedInLastBlock = _audioGateOpen && !audioGateOpen; // the gate just closed @@ -1222,12 +1227,15 @@ void AudioClient::handleMicAudioInput() { // detect loudness and clipping on the raw input bool isClipping = false; - float inputLoudness = computeLoudness(inputAudioSamples.get(), inputSamplesRequired, _inputFormat.channelCount(), isClipping); + float loudness = computeLoudness(inputAudioSamples.get(), inputSamplesRequired, _inputFormat.channelCount(), isClipping); + _lastRawInputLoudness = loudness; - float tc = (inputLoudness > _lastInputLoudness) ? 0.378f : 0.967f; // 10ms attack, 300ms release @ 100Hz - inputLoudness += tc * (_lastInputLoudness - inputLoudness); - _lastInputLoudness = inputLoudness; + // envelope detection + float tc = (loudness > _lastSmoothedRawInputLoudness) ? 0.378f : 0.967f; // 10ms attack, 300ms release @ 100Hz + loudness += tc * (_lastSmoothedRawInputLoudness - loudness); + _lastSmoothedRawInputLoudness = loudness; + // clipping indicator if (isClipping) { _timeSinceLastClip = 0.0f; } else if (_timeSinceLastClip >= 0.0f) { @@ -1235,7 +1243,7 @@ void AudioClient::handleMicAudioInput() { } isClipping = (_timeSinceLastClip >= 0.0f) && (_timeSinceLastClip < 2.0f); // 2 second hold time - emit inputLoudnessChanged(_lastInputLoudness, isClipping); + emit inputLoudnessChanged(_lastSmoothedRawInputLoudness, isClipping); if (!_muted) { possibleResampling(_inputToNetworkResampler, diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 94ed2ce132..29036b7c71 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -127,7 +127,7 @@ public: const QAudioFormat& getOutputFormat() const { return _outputFormat; } - float getLastInputLoudness() const { return _lastInputLoudness; } // TODO: relative to noise floor? + float getLastInputLoudness() const { return _lastInputLoudness; } float getTimeSinceLastClip() const { return _timeSinceLastClip; } float getAudioAverageInputLoudness() const { return _lastInputLoudness; } @@ -355,7 +355,9 @@ private: StDev _stdev; QElapsedTimer _timeSinceLastReceived; - float _lastInputLoudness; + float _lastRawInputLoudness; // before mute/gate + float _lastSmoothedRawInputLoudness; + float _lastInputLoudness; // after mute/gate float _timeSinceLastClip; int _totalInputAudioSamples; From 3072ca43de06bc413c61673ace32ad1ba20ba966 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Fri, 22 Feb 2019 09:24:38 -0700 Subject: [PATCH 155/474] Detailed picking if avatar is not null --- interface/src/avatar/AvatarManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 22f8e7112a..55025b3b23 100755 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -719,7 +719,7 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic } } - if (rayAvatarResult._intersect && pickAgainstMesh) { + if (avatar && rayAvatarResult._intersect && pickAgainstMesh) { glm::vec3 localRayOrigin = avatar->worldToJointPoint(ray.origin, rayAvatarResult._intersectWithJoint); glm::vec3 localRayPoint = avatar->worldToJointPoint(ray.origin + rayAvatarResult._distance * rayDirection, rayAvatarResult._intersectWithJoint); From f04fdb2187a8d30fb50ffaf23d2254ee2d729307 Mon Sep 17 00:00:00 2001 From: amer cerkic Date: Fri, 22 Feb 2019 09:11:24 -0800 Subject: [PATCH 156/474] added esc key event to fire for the away animation when cycling pause/resume. Reverted change on surface destroy that nullified the surface. Forgot to remove that during testin. Removed comment in away.js with syntax error. Setting cpu/gpu levels back to 1/1 after vr mode is released. Noticed that OS stays in same cpu/gpu level after app closes. Hoping this will help reduce battery drain for user --- interface/src/Application.cpp | 12 ++++++++---- libraries/oculusMobile/src/ovr/VrHandler.cpp | 4 ++++ scripts/system/away.js | 3 +-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index a8d0cf6125..730f1007a4 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1756,6 +1756,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo #endif }); + // Setup the _keyboardMouseDevice, _touchscreenDevice, _touchscreenVirtualPadDevice and the user input mapper with the default bindings userInputMapper->registerDevice(_keyboardMouseDevice->getInputDevice()); // if the _touchscreenDevice is not supported it will not be registered @@ -9162,12 +9163,15 @@ void Application::enterForeground() { auto nodeList = DependencyManager::get(); nodeList->setSendDomainServerCheckInEnabled(true); } -#endif + void Application::toggleAwayMode(){ - auto key = QKeyEvent(QEvent::KeyPress,Qt::Key_Escape,Qt::NoModifier); - _keyboardMouseDevice->keyPressEvent(&key); - qDebug()<<"QQQ_ AWAY MODE "; + QKeyEvent event = QKeyEvent (QEvent::KeyPress, Qt::Key_Escape, Qt::NoModifier); + QCoreApplication::sendEvent (this, &event); } + +#endif + + #include "Application.moc" diff --git a/libraries/oculusMobile/src/ovr/VrHandler.cpp b/libraries/oculusMobile/src/ovr/VrHandler.cpp index b3b1416785..3fe3901517 100644 --- a/libraries/oculusMobile/src/ovr/VrHandler.cpp +++ b/libraries/oculusMobile/src/ovr/VrHandler.cpp @@ -140,7 +140,11 @@ struct VrSurface : public TaskQueue { if (vrReady != vrRunning) { if (vrRunning) { __android_log_write(ANDROID_LOG_WARN, "QQQ_OVR", "vrapi_LeaveVrMode"); + vrapi_SetClockLevels(session, 1, 1); + vrapi_SetExtraLatencyMode(session, VRAPI_EXTRA_LATENCY_MODE_OFF); + vrapi_SetDisplayRefreshRate(session, 60); vrapi_LeaveVrMode(session); + session = nullptr; oculusActivity = nullptr; } else { diff --git a/scripts/system/away.js b/scripts/system/away.js index c75d58a240..2407678bb7 100644 --- a/scripts/system/away.js +++ b/scripts/system/away.js @@ -154,7 +154,7 @@ function goAway(fromStartup) { if (!isEnabled || isAway) { return; } - console.warn('QQQ_ JS going away); + // If we're entering away mode from some other state than startup, then we create our move timer immediately. // However if we're just stating up, we need to delay this process so that we don't think the initial teleport // is actually a move. @@ -176,7 +176,6 @@ function goActive() { return; } - console.warn('QQQ_ JS going active); UserActivityLogger.toggledAway(false); MyAvatar.isAway = false; From 9c833f7e64f575912216ee9593e13de6e23331a9 Mon Sep 17 00:00:00 2001 From: amer cerkic Date: Fri, 22 Feb 2019 09:21:51 -0800 Subject: [PATCH 157/474] clean up --- .../java/io/highfidelity/oculus/OculusMobileActivity.java | 5 +---- interface/src/Application.cpp | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/android/libraries/oculus/src/main/java/io/highfidelity/oculus/OculusMobileActivity.java b/android/libraries/oculus/src/main/java/io/highfidelity/oculus/OculusMobileActivity.java index 71ccfa84cd..7672ddf271 100644 --- a/android/libraries/oculus/src/main/java/io/highfidelity/oculus/OculusMobileActivity.java +++ b/android/libraries/oculus/src/main/java/io/highfidelity/oculus/OculusMobileActivity.java @@ -51,15 +51,13 @@ public class OculusMobileActivity extends QtActivity implements SurfaceHolder.Ca nativeOnCreate(); questNativeOnCreate(); } + public void onAppLoadedComplete() { Log.w(TAG, "QQQ Load Completed"); runOnUiThread(() -> { setContentView(mView); questOnAppAfterLoad(); - }); - - } @Override @@ -93,7 +91,6 @@ public class OculusMobileActivity extends QtActivity implements SurfaceHolder.Ca questNativeOnPause(); nativeOnPause(); isPausing=true; - } @Override diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 730f1007a4..e8fed1b2da 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -9166,7 +9166,7 @@ void Application::enterForeground() { void Application::toggleAwayMode(){ - QKeyEvent event = QKeyEvent (QEvent::KeyPress, Qt::Key_Escape, Qt::NoModifier); + QKeyEvent event = QKeyEvent (QEvent::KeyPress, Qt::Key_Escape, Qt::NoModifier); QCoreApplication::sendEvent (this, &event); } From aa8563f3feb96f2a9f7bc48bf7ff9acc0acff786 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Fri, 22 Feb 2019 10:10:34 -0800 Subject: [PATCH 158/474] fix quest ui elements --- .../src/RenderablePolyLineEntityItem.cpp | 15 +++++++- .../src/RenderableShapeEntityItem.cpp | 9 ++++- .../entities-renderer/paintStroke_forward.slp | 1 + .../src/paintStroke_forward.slf | 35 +++++++++++++++++++ .../render-utils/src/RenderForwardTask.cpp | 2 +- 5 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 libraries/entities-renderer/src/entities-renderer/paintStroke_forward.slp create mode 100644 libraries/entities-renderer/src/paintStroke_forward.slf diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp index 64c05b576b..c4ea6a2fea 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp @@ -29,6 +29,13 @@ gpu::PipelinePointer PolyLineEntityRenderer::_glowPipeline = nullptr; static const QUrl DEFAULT_POLYLINE_TEXTURE = PathUtils::resourcesUrl("images/paintStroke.png"); +#if defined(USE_GLES) +static bool DISABLE_DEFERRED = true; +#else +static const QString RENDER_FORWARD{ "HIFI_RENDER_FORWARD" }; +static bool DISABLE_DEFERRED = QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD); +#endif + PolyLineEntityRenderer::PolyLineEntityRenderer(const EntityItemPointer& entity) : Parent(entity) { _texture = DependencyManager::get()->getTexture(DEFAULT_POLYLINE_TEXTURE); @@ -44,7 +51,13 @@ PolyLineEntityRenderer::PolyLineEntityRenderer(const EntityItemPointer& entity) void PolyLineEntityRenderer::buildPipeline() { // FIXME: opaque pipeline - gpu::ShaderPointer program = gpu::Shader::createProgram(shader::entities_renderer::program::paintStroke); + gpu::ShaderPointer program; + if (DISABLE_DEFERRED) { + program = gpu::Shader::createProgram(shader::entities_renderer::program::paintStroke_forward); + } else { + program = gpu::Shader::createProgram(shader::entities_renderer::program::paintStroke); + } + { gpu::StatePointer state = gpu::StatePointer(new gpu::State()); state->setCullMode(gpu::State::CullMode::CULL_NONE); diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index d42a766faa..c3dae762c5 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -30,6 +30,13 @@ using namespace render::entities; // is a half unit sphere. However, the geometry cache renders a UNIT sphere, so we need to scale down. static const float SPHERE_ENTITY_SCALE = 0.5f; +#if defined(USE_GLES) +static bool DISABLE_DEFERRED = true; +#else +static const QString RENDER_FORWARD{ "HIFI_RENDER_FORWARD" }; +static bool DISABLE_DEFERRED = QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD); +#endif + static_assert(shader::render_utils::program::simple != 0, "Validate simple program exists"); static_assert(shader::render_utils::program::simple_transparent != 0, "Validate simple transparent program exists"); @@ -276,7 +283,7 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) { // FIXME, support instanced multi-shape rendering using multidraw indirect outColor.a *= _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; render::ShapePipelinePointer pipeline; - if (_renderLayer == RenderLayer::WORLD) { + if (_renderLayer == RenderLayer::WORLD && !DISABLE_DEFERRED) { pipeline = outColor.a < 1.0f ? geometryCache->getTransparentShapePipeline() : geometryCache->getOpaqueShapePipeline(); } else { pipeline = outColor.a < 1.0f ? geometryCache->getForwardTransparentShapePipeline() : geometryCache->getForwardOpaqueShapePipeline(); diff --git a/libraries/entities-renderer/src/entities-renderer/paintStroke_forward.slp b/libraries/entities-renderer/src/entities-renderer/paintStroke_forward.slp new file mode 100644 index 0000000000..4d49e0d3a4 --- /dev/null +++ b/libraries/entities-renderer/src/entities-renderer/paintStroke_forward.slp @@ -0,0 +1 @@ +VERTEX paintStroke \ No newline at end of file diff --git a/libraries/entities-renderer/src/paintStroke_forward.slf b/libraries/entities-renderer/src/paintStroke_forward.slf new file mode 100644 index 0000000000..b949332826 --- /dev/null +++ b/libraries/entities-renderer/src/paintStroke_forward.slf @@ -0,0 +1,35 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// paintStroke.frag +// fragment shader +// +// Created by Eric Levin on 8/10/2015 +// 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 paintStroke.slh@> +<$declarePolyLineBuffers()$> + +LAYOUT(binding=0) uniform sampler2D _texture; + +layout(location=0) in vec3 _normalWS; +layout(location=1) in vec2 _texCoord; +layout(location=2) in vec4 _color; +layout(location=3) in float _distanceFromCenter; +layout(location=0) out vec4 _fragColor0; + +void main(void) { + vec4 texel = texture(_texture, _texCoord); + int frontCondition = 1 - 2 * int(gl_FrontFacing); + vec3 color = _color.rgb * texel.rgb; + float alpha = texel.a * _color.a; + + alpha *= mix(1.0, pow(1.0 - abs(_distanceFromCenter), 10.0), _polylineData.faceCameraGlow.y); + + _fragColor0 = vec4(color, alpha); +} diff --git a/libraries/render-utils/src/RenderForwardTask.cpp b/libraries/render-utils/src/RenderForwardTask.cpp index df82d4b56d..c6f49bc92a 100755 --- a/libraries/render-utils/src/RenderForwardTask.cpp +++ b/libraries/render-utils/src/RenderForwardTask.cpp @@ -101,7 +101,6 @@ void RenderForwardTask::build(JobModel& task, const render::Varying& input, rend const auto inFrontOpaquesInputs = DrawLayered3D::Inputs(inFrontOpaque, lightingModel, nullJitter).asVarying(); const auto inFrontTransparentsInputs = DrawLayered3D::Inputs(inFrontTransparent, lightingModel, nullJitter).asVarying(); task.addJob("DrawInFrontOpaque", inFrontOpaquesInputs, true); - task.addJob("DrawInFrontTransparent", inFrontTransparentsInputs, false); // Draw opaques forward const auto opaqueInputs = DrawForward::Inputs(opaques, lightingModel).asVarying(); @@ -114,6 +113,7 @@ void RenderForwardTask::build(JobModel& task, const render::Varying& input, rend // Draw transparent objects forward const auto transparentInputs = DrawForward::Inputs(transparents, lightingModel).asVarying(); task.addJob("DrawTransparents", transparentInputs, shapePlumber); + task.addJob("DrawInFrontTransparent", inFrontTransparentsInputs, false); { // Debug the bounds of the rendered items, still look at the zbuffer From 109eb6288682c4116deee1fdfd3741814144f3f9 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Fri, 22 Feb 2019 11:07:40 -0800 Subject: [PATCH 159/474] requested changes --- libraries/render-utils/src/RenderForwardTask.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/libraries/render-utils/src/RenderForwardTask.cpp b/libraries/render-utils/src/RenderForwardTask.cpp index c6f49bc92a..73692b41c2 100755 --- a/libraries/render-utils/src/RenderForwardTask.cpp +++ b/libraries/render-utils/src/RenderForwardTask.cpp @@ -96,12 +96,6 @@ void RenderForwardTask::build(JobModel& task, const render::Varying& input, rend // draw a stencil mask in hidden regions of the framebuffer. task.addJob("PrepareStencil", framebuffer); - // Layered - const auto nullJitter = Varying(glm::vec2(0.0f, 0.0f)); - const auto inFrontOpaquesInputs = DrawLayered3D::Inputs(inFrontOpaque, lightingModel, nullJitter).asVarying(); - const auto inFrontTransparentsInputs = DrawLayered3D::Inputs(inFrontTransparent, lightingModel, nullJitter).asVarying(); - task.addJob("DrawInFrontOpaque", inFrontOpaquesInputs, true); - // Draw opaques forward const auto opaqueInputs = DrawForward::Inputs(opaques, lightingModel).asVarying(); task.addJob("DrawOpaques", opaqueInputs, shapePlumber); @@ -113,6 +107,12 @@ void RenderForwardTask::build(JobModel& task, const render::Varying& input, rend // Draw transparent objects forward const auto transparentInputs = DrawForward::Inputs(transparents, lightingModel).asVarying(); task.addJob("DrawTransparents", transparentInputs, shapePlumber); + + // Layered + const auto nullJitter = Varying(glm::vec2(0.0f, 0.0f)); + const auto inFrontOpaquesInputs = DrawLayered3D::Inputs(inFrontOpaque, lightingModel, nullJitter).asVarying(); + const auto inFrontTransparentsInputs = DrawLayered3D::Inputs(inFrontTransparent, lightingModel, nullJitter).asVarying(); + task.addJob("DrawInFrontOpaque", inFrontOpaquesInputs, true); task.addJob("DrawInFrontTransparent", inFrontTransparentsInputs, false); { // Debug the bounds of the rendered items, still look at the zbuffer From 87d98e5b858828bba6a21d73cb496e7aef0b967b Mon Sep 17 00:00:00 2001 From: amantley Date: Tue, 8 Jan 2019 15:26:46 -0800 Subject: [PATCH 160/474] These are the squashed commits for the ik optimization for the Quest Implmented using a new AnimSplineIK node in the anim graph (cherry picked from commit 4fe03ba238659fee7763991f2499a315482b351f) --- CMakeLists.txt | 8 +- .../avatar-animation_withSplineIKNode.json | 2229 +++++++++++++++++ interface/src/avatar/MyAvatar.cpp | 4 + interface/src/avatar/MySkeletonModel.cpp | 30 + libraries/animation/src/AnimContext.h | 1 + .../animation/src/AnimInverseKinematics.cpp | 5 + libraries/animation/src/AnimNodeLoader.cpp | 51 + .../src/AnimPoleVectorConstraint.cpp | 2 +- libraries/animation/src/AnimSplineIK.cpp | 473 ++++ libraries/animation/src/AnimSplineIK.h | 104 + libraries/animation/src/AnimStateMachine.cpp | 1 - libraries/animation/src/IKTarget.h | 3 +- libraries/animation/src/Rig.cpp | 76 +- .../src/avatars-renderer/Avatar.cpp | 36 + .../src/avatars-renderer/Avatar.h | 9 + libraries/fbx/src/FBXSerializer.cpp | 14 + libraries/shared/src/AvatarConstants.h | 1 + libraries/shared/src/CubicHermiteSpline.h | 41 +- tools/unity-avatar-exporter/Assets/README.txt | 1 + 19 files changed, 3061 insertions(+), 28 deletions(-) create mode 100644 interface/resources/avatar/avatar-animation_withSplineIKNode.json create mode 100644 libraries/animation/src/AnimSplineIK.cpp create mode 100644 libraries/animation/src/AnimSplineIK.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c126dce56a..1ba5e1264f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,6 +52,7 @@ else() set(MOBILE 0) endif() +set(HIFI_USE_OPTIMIZED_IK OFF) set(BUILD_CLIENT_OPTION ON) set(BUILD_SERVER_OPTION ON) set(BUILD_TESTS_OPTION OFF) @@ -115,7 +116,7 @@ if (USE_GLES AND (NOT ANDROID)) set(DISABLE_QML_OPTION ON) endif() - +option(HIFI_USE_OPTIMIZED_IK "USE OPTIMIZED IK" ${HIFI_USE_OPTIMIZED_IK_OPTION}) option(BUILD_CLIENT "Build client components" ${BUILD_CLIENT_OPTION}) option(BUILD_SERVER "Build server components" ${BUILD_SERVER_OPTION}) option(BUILD_TESTS "Build tests" ${BUILD_TESTS_OPTION}) @@ -146,6 +147,7 @@ foreach(PLATFORM_QT_COMPONENT ${PLATFORM_QT_COMPONENTS}) list(APPEND PLATFORM_QT_LIBRARIES "Qt5::${PLATFORM_QT_COMPONENT}") endforeach() +MESSAGE(STATUS "USE OPTIMIZED IK: " ${HIFI_USE_OPTIMIZED_IK}) MESSAGE(STATUS "Build server: " ${BUILD_SERVER}) MESSAGE(STATUS "Build client: " ${BUILD_CLIENT}) MESSAGE(STATUS "Build tests: " ${BUILD_TESTS}) @@ -191,6 +193,10 @@ find_package( Threads ) add_definitions(-DGLM_FORCE_RADIANS) add_definitions(-DGLM_ENABLE_EXPERIMENTAL) add_definitions(-DGLM_FORCE_CTOR_INIT) +if (HIFI_USE_OPTIMIZED_IK) + MESSAGE(STATUS "SET THE USE IK DEFINITION ") + add_definitions(-DHIFI_USE_OPTIMIZED_IK) +endif() set(HIFI_LIBRARY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libraries") set(EXTERNAL_PROJECT_PREFIX "project") diff --git a/interface/resources/avatar/avatar-animation_withSplineIKNode.json b/interface/resources/avatar/avatar-animation_withSplineIKNode.json new file mode 100644 index 0000000000..b1f198c52c --- /dev/null +++ b/interface/resources/avatar/avatar-animation_withSplineIKNode.json @@ -0,0 +1,2229 @@ +{ + "version": "1.1", + "root": { + "id": "userAnimStateMachine", + "type": "stateMachine", + "data": { + "currentState": "userAnimNone", + "states": [ + { + "id": "userAnimNone", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { + "var": "userAnimA", + "state": "userAnimA" + }, + { + "var": "userAnimB", + "state": "userAnimB" + } + ] + }, + { + "id": "userAnimA", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { + "var": "userAnimNone", + "state": "userAnimNone" + }, + { + "var": "userAnimB", + "state": "userAnimB" + } + ] + }, + { + "id": "userAnimB", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { + "var": "userAnimNone", + "state": "userAnimNone" + }, + { + "var": "userAnimA", + "state": "userAnimA" + } + ] + } + ] + }, + "children": [ + { + "id": "userAnimNone", + "type": "poleVectorConstraint", + "data": { + "enabled": false, + "referenceVector": [ 0, 0, 1 ], + "baseJointName": "RightUpLeg", + "midJointName": "RightLeg", + "tipJointName": "RightFoot", + "enabledVar": "rightFootPoleVectorEnabled", + "poleVectorVar": "rightFootPoleVector" + }, + "children": [ + { + "id": "rightFootIK", + "type": "twoBoneIK", + "data": { + "alpha": 1.0, + "enabled": false, + "interpDuration": 15, + "baseJointName": "RightUpLeg", + "midJointName": "RightLeg", + "tipJointName": "RightFoot", + "midHingeAxis": [ -1, 0, 0 ], + "alphaVar": "rightFootIKAlpha", + "enabledVar": "rightFootIKEnabled", + "endEffectorRotationVarVar": "rightFootIKRotationVar", + "endEffectorPositionVarVar": "rightFootIKPositionVar" + }, + "children": [ + { + "id": "leftFootPoleVector", + "type": "poleVectorConstraint", + "data": { + "enabled": false, + "referenceVector": [ 0, 0, 1 ], + "baseJointName": "LeftUpLeg", + "midJointName": "LeftLeg", + "tipJointName": "LeftFoot", + "enabledVar": "leftFootPoleVectorEnabled", + "poleVectorVar": "leftFootPoleVector" + }, + "children": [ + { + "id": "leftFootIK", + "type": "twoBoneIK", + "data": { + "alpha": 1.0, + "enabled": false, + "interpDuration": 15, + "baseJointName": "LeftUpLeg", + "midJointName": "LeftLeg", + "tipJointName": "LeftFoot", + "midHingeAxis": [ -1, 0, 0 ], + "alphaVar": "leftFootIKAlpha", + "enabledVar": "leftFootIKEnabled", + "endEffectorRotationVarVar": "leftFootIKRotationVar", + "endEffectorPositionVarVar": "leftFootIKPositionVar" + }, + "children": [ + { + "id": "rightHandPoleVector", + "type": "poleVectorConstraint", + "data": { + "enabled": false, + "referenceVector": [ -1, 0, 0 ], + "baseJointName": "RightArm", + "midJointName": "RightForeArm", + "tipJointName": "RightHand", + "enabledVar": "rightHandPoleVectorEnabled", + "poleVectorVar": "rightHandPoleVector" + }, + "children": [ + { + "id": "rightHandIK", + "type": "twoBoneIK", + "data": { + "alpha": 1.0, + "enabled": false, + "interpDuration": 15, + "baseJointName": "RightArm", + "midJointName": "RightForeArm", + "tipJointName": "RightHand", + "midHingeAxis": [ 0, 0, -1 ], + "alphaVar": "rightHandIKAlpha", + "enabledVar": "rightHandIKEnabled", + "endEffectorRotationVarVar": "rightHandIKRotationVar", + "endEffectorPositionVarVar": "rightHandIKPositionVar" + }, + "children": [ + { + "id": "leftHandPoleVector", + "type": "poleVectorConstraint", + "data": { + "enabled": false, + "referenceVector": [ 1, 0, 0 ], + "baseJointName": "LeftArm", + "midJointName": "LeftForeArm", + "tipJointName": "LeftHand", + "enabledVar": "leftHandPoleVectorEnabled", + "poleVectorVar": "leftHandPoleVector" + }, + "children": [ + { + "id": "leftHandIK", + "type": "twoBoneIK", + "data": { + "alpha": 1.0, + "enabled": false, + "interpDuration": 15, + "baseJointName": "LeftArm", + "midJointName": "LeftForeArm", + "tipJointName": "LeftHand", + "midHingeAxis": [ 0, 0, 1 ], + "alphaVar": "leftHandIKAlpha", + "enabledVar": "leftHandIKEnabled", + "endEffectorRotationVarVar": "leftHandIKRotationVar", + "endEffectorPositionVarVar": "leftHandIKPositionVar" + }, + "children": [ + { + "id": "userSplineIK", + "type": "splineIK", + "data": { + "alpha": 1.0, + "enabled": false, + "interpDuration": 15, + "baseJointName": "Hips", + "midJointName": "Spine2", + "tipJointName": "Head", + "basePositionVar": "hipsPosition", + "baseRotationVar": "hipsRotation", + "midPositionVar": "spine2Position", + "midRotationVar": "spine2Rotation", + "tipPositionVar": "headPosition", + "tipRotationVar": "headRotation", + "alphaVar": "splineIKAlpha", + "enabledVar": "splineIKEnabled", + "tipTargetFlexCoefficients": [ 1.0, 1.0, 1.0, 1.0, 1.0 ], + "midTargetFlexCoefficients": [ 1.0, 1.0, 1.0 ] + }, + "children": [ + { + "id": "defaultPoseOverlay", + "type": "overlay", + "data": { + "alpha": 0.0, + "alphaVar": "defaultPoseOverlayAlpha", + "boneSet": "fullBody", + "boneSetVar": "defaultPoseOverlayBoneSet" + }, + "children": [ + { + "id": "defaultPose", + "type": "defaultPose", + "data": { + }, + "children": [] + }, + { + "id": "rightHandOverlay", + "type": "overlay", + "data": { + "alpha": 0.0, + "boneSet": "rightHand", + "alphaVar": "rightHandOverlayAlpha" + }, + "children": [ + { + "id": "rightHandStateMachine", + "type": "stateMachine", + "data": { + "currentState": "rightHandGrasp", + "states": [ + { + "id": "rightHandGrasp", + "interpTarget": 3, + "interpDuration": 3, + "transitions": [ + { + "var": "isRightIndexPoint", + "state": "rightIndexPoint" + }, + { + "var": "isRightThumbRaise", + "state": "rightThumbRaise" + }, + { + "var": "isRightIndexPointAndThumbRaise", + "state": "rightIndexPointAndThumbRaise" + } + ] + }, + { + "id": "rightIndexPoint", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { + "var": "isRightHandGrasp", + "state": "rightHandGrasp" + }, + { + "var": "isRightThumbRaise", + "state": "rightThumbRaise" + }, + { + "var": "isRightIndexPointAndThumbRaise", + "state": "rightIndexPointAndThumbRaise" + } + ] + }, + { + "id": "rightThumbRaise", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { + "var": "isRightHandGrasp", + "state": "rightHandGrasp" + }, + { + "var": "isRightIndexPoint", + "state": "rightIndexPoint" + }, + { + "var": "isRightIndexPointAndThumbRaise", + "state": "rightIndexPointAndThumbRaise" + } + ] + }, + { + "id": "rightIndexPointAndThumbRaise", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { + "var": "isRightHandGrasp", + "state": "rightHandGrasp" + }, + { + "var": "isRightIndexPoint", + "state": "rightIndexPoint" + }, + { + "var": "isRightThumbRaise", + "state": "rightThumbRaise" + } + ] + } + ] + }, + "children": [ + { + "id": "rightHandGrasp", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "rightHandGraspAlpha" + }, + "children": [ + { + "id": "rightHandGraspOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/hydra_pose_open_right.fbx", + "startFrame": 0.0, + "endFrame": 0.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "rightHandGraspClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/hydra_pose_closed_right.fbx", + "startFrame": 0.0, + "endFrame": 0.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "rightIndexPoint", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "rightHandGraspAlpha" + }, + "children": [ + { + "id": "rightIndexPointOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_point_open_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "rightIndexPointClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_point_closed_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "rightThumbRaise", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "rightHandGraspAlpha" + }, + "children": [ + { + "id": "rightThumbRaiseOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_open_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "rightThumbRaiseClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_closed_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "rightIndexPointAndThumbRaise", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "rightHandGraspAlpha" + }, + "children": [ + { + "id": "rightIndexPointAndThumbRaiseOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_point_open_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "rightIndexPointAndThumbRaiseClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_point_closed_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + } + ] + }, + { + "id": "leftHandOverlay", + "type": "overlay", + "data": { + "alpha": 0.0, + "boneSet": "leftHand", + "alphaVar": "leftHandOverlayAlpha" + }, + "children": [ + { + "id": "leftHandStateMachine", + "type": "stateMachine", + "data": { + "currentState": "leftHandGrasp", + "states": [ + { + "id": "leftHandGrasp", + "interpTarget": 3, + "interpDuration": 3, + "transitions": [ + { + "var": "isLeftIndexPoint", + "state": "leftIndexPoint" + }, + { + "var": "isLeftThumbRaise", + "state": "leftThumbRaise" + }, + { + "var": "isLeftIndexPointAndThumbRaise", + "state": "leftIndexPointAndThumbRaise" + } + ] + }, + { + "id": "leftIndexPoint", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { + "var": "isLeftHandGrasp", + "state": "leftHandGrasp" + }, + { + "var": "isLeftThumbRaise", + "state": "leftThumbRaise" + }, + { + "var": "isLeftIndexPointAndThumbRaise", + "state": "leftIndexPointAndThumbRaise" + } + ] + }, + { + "id": "leftThumbRaise", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { + "var": "isLeftHandGrasp", + "state": "leftHandGrasp" + }, + { + "var": "isLeftIndexPoint", + "state": "leftIndexPoint" + }, + { + "var": "isLeftIndexPointAndThumbRaise", + "state": "leftIndexPointAndThumbRaise" + } + ] + }, + { + "id": "leftIndexPointAndThumbRaise", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { + "var": "isLeftHandGrasp", + "state": "leftHandGrasp" + }, + { + "var": "isLeftIndexPoint", + "state": "leftIndexPoint" + }, + { + "var": "isLeftThumbRaise", + "state": "leftThumbRaise" + } + ] + } + ] + }, + "children": [ + { + "id": "leftHandGrasp", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "leftHandGraspAlpha" + }, + "children": [ + { + "id": "leftHandGraspOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/hydra_pose_open_left.fbx", + "startFrame": 0.0, + "endFrame": 0.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "leftHandGraspClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/hydra_pose_closed_left.fbx", + "startFrame": 10.0, + "endFrame": 10.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "leftIndexPoint", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "leftHandGraspAlpha" + }, + "children": [ + { + "id": "leftIndexPointOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_point_open_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "leftIndexPointClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_point_closed_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "leftThumbRaise", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "leftHandGraspAlpha" + }, + "children": [ + { + "id": "leftThumbRaiseOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_open_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "leftThumbRaiseClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_closed_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "leftIndexPointAndThumbRaise", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "leftHandGraspAlpha" + }, + "children": [ + { + "id": "leftIndexPointAndThumbRaiseOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_point_open_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "leftIndexPointAndThumbRaiseClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_point_closed_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + } + ] + }, + { + "id": "mainStateMachine", + "type": "stateMachine", + "data": { + "outputJoints": [ "LeftFoot", "RightFoot" ], + "currentState": "idle", + "states": [ + { + "id": "idle", + "interpTarget": 20, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "idleToWalkFwd", + "interpTarget": 12, + "interpDuration": 8, + "transitions": [ + { + "var": "idleToWalkFwdOnDone", + "state": "WALKFWD" + }, + { + "var": "isNotMoving", + "state": "idle" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "idleSettle", + "interpTarget": 15, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "idleSettleOnDone", + "state": "idle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + } + ] + }, + { + "id": "WALKFWD", + "interpTarget": 35, + "interpDuration": 10, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotMoving", + "state": "idleSettle" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "WALKBWD", + "interpTarget": 35, + "interpDuration": 10, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotMoving", + "state": "idleSettle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "STRAFERIGHT", + "interpTarget": 25, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotMoving", + "state": "idleSettle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "STRAFELEFT", + "interpTarget": 25, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotMoving", + "state": "idleSettle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "turnRight", + "interpTarget": 6, + "interpDuration": 8, + "transitions": [ + { + "var": "isNotTurning", + "state": "idle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "turnLeft", + "interpTarget": 6, + "interpDuration": 8, + "transitions": [ + { + "var": "isNotTurning", + "state": "idle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "strafeRightHmd", + "interpTarget": 5, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotMoving", + "state": "idleSettle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + } + ] + }, + { + "id": "strafeLeftHmd", + "interpTarget": 5, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotMoving", + "state": "idleSettle" + }, + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + } + ] + }, + { + "id": "fly", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { + "var": "isNotFlying", + "state": "idleSettle" + } + ] + }, + { + "id": "takeoffStand", + "interpTarget": 2, + "interpDuration": 2, + "transitions": [ + { + "var": "isNotTakeoff", + "state": "inAirStand" + } + ] + }, + { + "id": "TAKEOFFRUN", + "interpTarget": 2, + "interpDuration": 2, + "transitions": [ + { + "var": "isNotTakeoff", + "state": "INAIRRUN" + } + ] + }, + { + "id": "inAirStand", + "interpTarget": 3, + "interpDuration": 3, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotInAir", + "state": "landStandImpact" + } + ] + }, + { + "id": "INAIRRUN", + "interpTarget": 3, + "interpDuration": 3, + "interpType": "snapshotPrev", + "transitions": [ + { + "var": "isNotInAir", + "state": "WALKFWD" + } + ] + }, + { + "id": "landStandImpact", + "interpTarget": 1, + "interpDuration": 1, + "transitions": [ + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "landStandImpactOnDone", + "state": "landStand" + } + ] + }, + { + "id": "landStand", + "interpTarget": 1, + "interpDuration": 1, + "transitions": [ + { + "var": "isMovingForward", + "state": "WALKFWD" + }, + { + "var": "isMovingBackward", + "state": "WALKBWD" + }, + { + "var": "isMovingRight", + "state": "STRAFERIGHT" + }, + { + "var": "isMovingLeft", + "state": "STRAFELEFT" + }, + { + "var": "isTurningRight", + "state": "turnRight" + }, + { + "var": "isTurningLeft", + "state": "turnLeft" + }, + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "isInAirStand", + "state": "inAirStand" + }, + { + "var": "isInAirRun", + "state": "INAIRRUN" + }, + { + "var": "landStandOnDone", + "state": "idle" + }, + { + "var": "isMovingRightHmd", + "state": "strafeRightHmd" + }, + { + "var": "isMovingLeftHmd", + "state": "strafeLeftHmd" + } + ] + }, + { + "id": "LANDRUN", + "interpTarget": 2, + "interpDuration": 2, + "transitions": [ + { + "var": "isFlying", + "state": "fly" + }, + { + "var": "isTakeoffStand", + "state": "takeoffStand" + }, + { + "var": "isTakeoffRun", + "state": "TAKEOFFRUN" + }, + { + "var": "landRunOnDone", + "state": "WALKFWD" + } + ] + } + ] + }, + "children": [ + { + "id": "idle", + "type": "stateMachine", + "data": { + "currentState": "idleStand", + "states": [ + { + "id": "idleStand", + "interpTarget": 6, + "interpDuration": 10, + "transitions": [ + { + "var": "isTalking", + "state": "idleTalk" + } + ] + }, + { + "id": "idleTalk", + "interpTarget": 6, + "interpDuration": 10, + "transitions": [ + { + "var": "notIsTalking", + "state": "idleStand" + } + ] + } + ] + }, + "children": [ + { + "id": "idleStand", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle.fbx", + "startFrame": 0.0, + "endFrame": 300.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "idleTalk", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/talk.fbx", + "startFrame": 0.0, + "endFrame": 800.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "WALKFWD", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [ 0.5, 1.8, 2.3, 3.2, 4.5 ], + "alphaVar": "moveForwardAlpha", + "desiredSpeedVar": "moveForwardSpeed" + }, + "children": [ + { + "id": "walkFwdShort_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_short_fwd.fbx", + "startFrame": 0.0, + "endFrame": 39.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkFwdNormal_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_fwd.fbx", + "startFrame": 0.0, + "endFrame": 30.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkFwdFast_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_fwd_fast.fbx", + "startFrame": 0.0, + "endFrame": 25.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkFwdJog_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jog_fwd.fbx", + "startFrame": 0.0, + "endFrame": 25.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkFwdRun_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/run_fwd.fbx", + "startFrame": 0.0, + "endFrame": 21.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "idleToWalkFwd", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle_to_walk.fbx", + "startFrame": 1.0, + "endFrame": 13.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "idleSettle", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/settle_to_idle.fbx", + "startFrame": 1.0, + "endFrame": 59.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "WALKBWD", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [ 0.6, 1.6, 2.3, 3.1 ], + "alphaVar": "moveBackwardAlpha", + "desiredSpeedVar": "moveBackwardSpeed" + }, + "children": [ + { + "id": "walkBwdShort_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_short_bwd.fbx", + "startFrame": 0.0, + "endFrame": 38.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkBwdFast_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_bwd_fast.fbx", + "startFrame": 0.0, + "endFrame": 27.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "jogBwd_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jog_bwd.fbx", + "startFrame": 0.0, + "endFrame": 24.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "runBwd_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/run_bwd.fbx", + "startFrame": 0.0, + "endFrame": 16.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "turnLeft", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/turn_left.fbx", + "startFrame": 0.0, + "endFrame": 32.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "turnRight", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/turn_left.fbx", + "startFrame": 0.0, + "endFrame": 32.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "STRAFELEFT", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [ 0.1, 0.5, 1.0, 2.6, 3.0 ], + "alphaVar": "moveLateralAlpha", + "desiredSpeedVar": "moveLateralSpeed" + }, + "children": [ + { + "id": "strafeLeftShortStep_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_short_left.fbx", + "startFrame": 0.0, + "endFrame": 29.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftStep_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left.fbx", + "startFrame": 0.0, + "endFrame": 20.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftWalk_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_left.fbx", + "startFrame": 0.0, + "endFrame": 35.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftWalkFast_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_left_fast.fbx", + "startFrame": 0.0, + "endFrame": 21.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftJog_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jog_left.fbx", + "startFrame": 0.0, + "endFrame": 24.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "STRAFERIGHT", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [ 0.1, 0.5, 1.0, 2.6, 3.0 ], + "alphaVar": "moveLateralAlpha", + "desiredSpeedVar": "moveLateralSpeed" + }, + "children": [ + { + "id": "strafeRightShortStep_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_short_left.fbx", + "startFrame": 0.0, + "endFrame": 29.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "strafeRightStep_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left.fbx", + "startFrame": 0.0, + "endFrame": 20.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "strafeRightWalk_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_left.fbx", + "startFrame": 0.0, + "endFrame": 35.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "strafeRightFast_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_left_fast.fbx", + "startFrame": 0.0, + "endFrame": 21.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "strafeRightJog_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jog_left.fbx", + "startFrame": 0.0, + "endFrame": 24.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + } + ] + }, + { + "id": "strafeLeftHmd", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [ 0, 0.5, 2.5 ], + "alphaVar": "moveLateralAlpha", + "desiredSpeedVar": "moveLateralSpeed" + }, + "children": [ + { + "id": "stepLeftShort_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_short_left.fbx", + "startFrame": 0.0, + "endFrame": 29.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "stepLeft_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left.fbx", + "startFrame": 0.0, + "endFrame": 20.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftAnim_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left_fast.fbx", + "startFrame": 0.0, + "endFrame": 16.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "strafeRightHmd", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [ 0, 0.5, 2.5 ], + "alphaVar": "moveLateralAlpha", + "desiredSpeedVar": "moveLateralSpeed" + }, + "children": [ + { + "id": "stepRightShort_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_short_left.fbx", + "startFrame": 0.0, + "endFrame": 29.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "stepRight_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left.fbx", + "startFrame": 0.0, + "endFrame": 20.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "strafeRightAnim_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left_fast.fbx", + "startFrame": 0.0, + "endFrame": 16.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + } + ] + }, + { + "id": "fly", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/fly.fbx", + "startFrame": 1.0, + "endFrame": 80.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "takeoffStand", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_launch.fbx", + "startFrame": 2.0, + "endFrame": 16.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "TAKEOFFRUN", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "startFrame": 4.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirStand", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "inAirAlpha" + }, + "children": [ + { + "id": "inAirStandPreApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_apex.fbx", + "startFrame": 0.0, + "endFrame": 0.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirStandApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_apex.fbx", + "startFrame": 1.0, + "endFrame": 1.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirStandPostApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_apex.fbx", + "startFrame": 2.0, + "endFrame": 2.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + } + ] + }, + { + "id": "INAIRRUN", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "inAirAlpha" + }, + "children": [ + { + "id": "inAirRunPreApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "startFrame": 16.0, + "endFrame": 16.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirRunApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "startFrame": 22.0, + "endFrame": 22.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirRunPostApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "startFrame": 33.0, + "endFrame": 33.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + } + ] + }, + { + "id": "landStandImpact", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_land_settle.fbx", + "startFrame": 1.0, + "endFrame": 6.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "landStand", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_land_settle.fbx", + "startFrame": 6.0, + "endFrame": 68.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "LANDRUN", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "startFrame": 29.0, + "endFrame": 40.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "id": "userAnimA", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle.fbx", + "startFrame": 0.0, + "endFrame": 90.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "userAnimB", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle.fbx", + "startFrame": 0.0, + "endFrame": 90.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + } +} diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index afb7a218f6..ff865172ae 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2951,6 +2951,10 @@ void MyAvatar::initAnimGraph() { graphUrl = _fstAnimGraphOverrideUrl; } else { graphUrl = PathUtils::resourcesUrl("avatar/avatar-animation.json"); + +#if defined(Q_OS_ANDROID) || defined(HIFI_USE_OPTIMIZED_IK) + graphUrl = PathUtils::resourcesUrl("avatar/avatar-animation_withSplineIKNode.json"); +#endif } emit animGraphUrlChanged(graphUrl); diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 26d69841d0..55c29b66c1 100755 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -10,12 +10,14 @@ #include #include +#include #include "Application.h" #include "InterfaceLogging.h" #include "AnimUtil.h" + MySkeletonModel::MySkeletonModel(Avatar* owningAvatar, QObject* parent) : SkeletonModel(owningAvatar, parent) { } @@ -33,6 +35,22 @@ Rig::CharacterControllerState convertCharacterControllerState(CharacterControlle }; } +#if defined(Q_OS_ANDROID) || defined(HIFI_USE_OPTIMIZED_IK) +static glm::vec3 computeSpine2WithHeadHipsSpline(MyAvatar* myAvatar, AnimPose hipsIKTargetPose, AnimPose headIKTargetPose) { + + // the the ik targets to compute the spline with + CubicHermiteSplineFunctorWithArcLength splineFinal(headIKTargetPose.rot(), headIKTargetPose.trans(), hipsIKTargetPose.rot(), hipsIKTargetPose.trans()); + + // measure the total arc length along the spline + float totalArcLength = splineFinal.arcLength(1.0f); + float tFinal = splineFinal.arcLengthInverse(myAvatar->getSpine2SplineRatio() * totalArcLength); + glm::vec3 spine2Translation = splineFinal(tFinal); + + return spine2Translation + myAvatar->getSpine2SplineOffset(); + +} +#endif + static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) { glm::mat4 worldToSensorMat = glm::inverse(myAvatar->getSensorToWorldMatrix()); @@ -233,6 +251,12 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { myAvatar->getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND).isValid() && !(params.primaryControllerFlags[Rig::PrimaryControllerType_Spine2] & (uint8_t)Rig::ControllerFlags::Enabled)) { +#if defined(Q_OS_ANDROID) || defined(HIFI_USE_OPTIMIZED_IK) + AnimPose headAvatarSpace(avatarHeadPose.getRotation(), avatarHeadPose.getTranslation()); + AnimPose headRigSpace = avatarToRigPose * headAvatarSpace; + AnimPose hipsRigSpace = sensorToRigPose * sensorHips; + glm::vec3 spine2TargetTranslation = computeSpine2WithHeadHipsSpline(myAvatar, hipsRigSpace, headRigSpace); +#endif const float SPINE2_ROTATION_FILTER = 0.5f; AnimPose currentSpine2Pose; AnimPose currentHeadPose; @@ -243,6 +267,9 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { if (spine2Exists && headExists && hipsExists) { AnimPose rigSpaceYaw(myAvatar->getSpine2RotationRigSpace()); +#if defined(Q_OS_ANDROID) || defined(HIFI_USE_OPTIMIZED_IK) + rigSpaceYaw.rot() = safeLerp(Quaternions::IDENTITY, rigSpaceYaw.rot(), 0.5f); +#endif glm::vec3 u, v, w; glm::vec3 fwd = rigSpaceYaw.rot() * glm::vec3(0.0f, 0.0f, 1.0f); glm::vec3 up = currentHeadPose.trans() - currentHipsPose.trans(); @@ -253,6 +280,9 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { } generateBasisVectors(up, fwd, u, v, w); AnimPose newSpinePose(glm::mat4(glm::vec4(w, 0.0f), glm::vec4(u, 0.0f), glm::vec4(v, 0.0f), glm::vec4(glm::vec3(0.0f, 0.0f, 0.0f), 1.0f))); +#if defined(Q_OS_ANDROID) || defined(HIFI_USE_OPTIMIZED_IK) + currentSpine2Pose.trans() = spine2TargetTranslation; +#endif currentSpine2Pose.rot() = safeLerp(currentSpine2Pose.rot(), newSpinePose.rot(), SPINE2_ROTATION_FILTER); params.primaryControllerPoses[Rig::PrimaryControllerType_Spine2] = currentSpine2Pose; params.primaryControllerFlags[Rig::PrimaryControllerType_Spine2] = (uint8_t)Rig::ControllerFlags::Enabled | (uint8_t)Rig::ControllerFlags::Estimated; diff --git a/libraries/animation/src/AnimContext.h b/libraries/animation/src/AnimContext.h index c455dd9c8f..e3ab5d9788 100644 --- a/libraries/animation/src/AnimContext.h +++ b/libraries/animation/src/AnimContext.h @@ -28,6 +28,7 @@ enum class AnimNodeType { InverseKinematics, DefaultPose, TwoBoneIK, + SplineIK, PoleVectorConstraint, NumTypes }; diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index d710e9d8ff..37859c939a 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -866,6 +866,11 @@ const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVar //virtual const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut, const AnimPoseVec& underPoses) { +#ifdef Q_OS_ANDROID + // disable IK on android + return underPoses; +#endif + // allows solutionSource to be overridden by an animVar auto solutionSource = animVars.lookup(_solutionSourceVar, (int)_solutionSource); diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index dfa61e9fea..b637d131f8 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -26,6 +26,7 @@ #include "AnimInverseKinematics.h" #include "AnimDefaultPose.h" #include "AnimTwoBoneIK.h" +#include "AnimSplineIK.h" #include "AnimPoleVectorConstraint.h" using NodeLoaderFunc = AnimNode::Pointer (*)(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); @@ -41,6 +42,7 @@ static AnimNode::Pointer loadManipulatorNode(const QJsonObject& jsonObj, const Q static AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer loadDefaultPoseNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer loadTwoBoneIKNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); +static AnimNode::Pointer loadSplineIKNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer loadPoleVectorConstraintNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static const float ANIM_GRAPH_LOAD_PRIORITY = 10.0f; @@ -61,6 +63,7 @@ static const char* animNodeTypeToString(AnimNode::Type type) { case AnimNode::Type::InverseKinematics: return "inverseKinematics"; case AnimNode::Type::DefaultPose: return "defaultPose"; case AnimNode::Type::TwoBoneIK: return "twoBoneIK"; + case AnimNode::Type::SplineIK: return "splineIK"; case AnimNode::Type::PoleVectorConstraint: return "poleVectorConstraint"; case AnimNode::Type::NumTypes: return nullptr; }; @@ -123,6 +126,7 @@ static NodeLoaderFunc animNodeTypeToLoaderFunc(AnimNode::Type type) { case AnimNode::Type::InverseKinematics: return loadInverseKinematicsNode; case AnimNode::Type::DefaultPose: return loadDefaultPoseNode; case AnimNode::Type::TwoBoneIK: return loadTwoBoneIKNode; + case AnimNode::Type::SplineIK: return loadSplineIKNode; case AnimNode::Type::PoleVectorConstraint: return loadPoleVectorConstraintNode; case AnimNode::Type::NumTypes: return nullptr; }; @@ -140,6 +144,7 @@ static NodeProcessFunc animNodeTypeToProcessFunc(AnimNode::Type type) { case AnimNode::Type::InverseKinematics: return processDoNothing; case AnimNode::Type::DefaultPose: return processDoNothing; case AnimNode::Type::TwoBoneIK: return processDoNothing; + case AnimNode::Type::SplineIK: return processDoNothing; case AnimNode::Type::PoleVectorConstraint: return processDoNothing; case AnimNode::Type::NumTypes: return nullptr; }; @@ -574,6 +579,52 @@ static AnimNode::Pointer loadDefaultPoseNode(const QJsonObject& jsonObj, const Q return node; } +static AnimNode::Pointer loadSplineIKNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { + READ_FLOAT(alpha, jsonObj, id, jsonUrl, nullptr); + READ_BOOL(enabled, jsonObj, id, jsonUrl, nullptr); + READ_FLOAT(interpDuration, jsonObj, id, jsonUrl, nullptr); + READ_STRING(baseJointName, jsonObj, id, jsonUrl, nullptr); + READ_STRING(midJointName, jsonObj, id, jsonUrl, nullptr); + READ_STRING(tipJointName, jsonObj, id, jsonUrl, nullptr); + READ_STRING(basePositionVar, jsonObj, id, jsonUrl, nullptr); + READ_STRING(baseRotationVar, jsonObj, id, jsonUrl, nullptr); + READ_STRING(midPositionVar, jsonObj, id, jsonUrl, nullptr); + READ_STRING(midRotationVar, jsonObj, id, jsonUrl, nullptr); + READ_STRING(tipPositionVar, jsonObj, id, jsonUrl, nullptr); + READ_STRING(tipRotationVar, jsonObj, id, jsonUrl, nullptr); + READ_STRING(alphaVar, jsonObj, id, jsonUrl, nullptr); + READ_STRING(enabledVar, jsonObj, id, jsonUrl, nullptr); + + auto tipFlexCoefficientsValue = jsonObj.value("tipTargetFlexCoefficients"); + if (!tipFlexCoefficientsValue.isArray()) { + qCCritical(animation) << "AnimNodeLoader, bad or missing tip flex array"; + return nullptr; + } + auto tipFlexCoefficientsArray = tipFlexCoefficientsValue.toArray(); + std::vector tipTargetFlexCoefficients; + for (const auto& value : tipFlexCoefficientsArray) { + tipTargetFlexCoefficients.push_back((float)value.toDouble()); + } + + auto midFlexCoefficientsValue = jsonObj.value("midTargetFlexCoefficients"); + if (!midFlexCoefficientsValue.isArray()) { + qCCritical(animation) << "AnimNodeLoader, bad or missing mid flex array"; + return nullptr; + } + auto midFlexCoefficientsArray = midFlexCoefficientsValue.toArray(); + std::vector midTargetFlexCoefficients; + for (const auto& midValue : midFlexCoefficientsArray) { + midTargetFlexCoefficients.push_back((float)midValue.toDouble()); + } + + auto node = std::make_shared(id, alpha, enabled, interpDuration, + baseJointName, midJointName, tipJointName, + basePositionVar, baseRotationVar, midPositionVar, midRotationVar, + tipPositionVar, tipRotationVar, alphaVar, enabledVar, + tipTargetFlexCoefficients, midTargetFlexCoefficients); + return node; +} + static AnimNode::Pointer loadTwoBoneIKNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { READ_FLOAT(alpha, jsonObj, id, jsonUrl, nullptr); READ_BOOL(enabled, jsonObj, id, jsonUrl, nullptr); diff --git a/libraries/animation/src/AnimPoleVectorConstraint.cpp b/libraries/animation/src/AnimPoleVectorConstraint.cpp index f017fe2348..c0600ee253 100644 --- a/libraries/animation/src/AnimPoleVectorConstraint.cpp +++ b/libraries/animation/src/AnimPoleVectorConstraint.cpp @@ -117,7 +117,7 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim if (axisLength > MIN_LENGTH && refVectorLength > MIN_LENGTH && sideVectorLength > MIN_LENGTH && refVectorProjLength > MIN_LENGTH && poleVectorProjLength > MIN_LENGTH) { - float dot = glm::clamp(glm::dot(refVectorProj / refVectorProjLength, poleVectorProj / poleVectorProjLength), 0.0f, 1.0f); + float dot = glm::clamp(glm::dot(refVectorProj / refVectorProjLength, poleVectorProj / poleVectorProjLength), -1.0f, 1.0f); float sideDot = glm::dot(poleVector, sideVector); float theta = copysignf(1.0f, sideDot) * acosf(dot); diff --git a/libraries/animation/src/AnimSplineIK.cpp b/libraries/animation/src/AnimSplineIK.cpp new file mode 100644 index 0000000000..cfb34560ff --- /dev/null +++ b/libraries/animation/src/AnimSplineIK.cpp @@ -0,0 +1,473 @@ +// +// AnimSplineIK.cpp +// +// Created by Angus Antley on 1/7/19. +// Copyright (c) 2019 High Fidelity, Inc. All rights reserved. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "AnimSplineIK.h" +#include "AnimationLogging.h" +#include "CubicHermiteSpline.h" +#include +#include "AnimUtil.h" + +static const float FRAMES_PER_SECOND = 30.0f; + +AnimSplineIK::AnimSplineIK(const QString& id, float alpha, bool enabled, float interpDuration, + const QString& baseJointName, + const QString& midJointName, + const QString& tipJointName, + const QString& basePositionVar, + const QString& baseRotationVar, + const QString& midPositionVar, + const QString& midRotationVar, + const QString& tipPositionVar, + const QString& tipRotationVar, + const QString& alphaVar, + const QString& enabledVar, + const std::vector tipTargetFlexCoefficients, + const std::vector midTargetFlexCoefficients) : + AnimNode(AnimNode::Type::SplineIK, id), + _alpha(alpha), + _enabled(enabled), + _interpDuration(interpDuration), + _baseJointName(baseJointName), + _midJointName(midJointName), + _tipJointName(tipJointName), + _basePositionVar(basePositionVar), + _baseRotationVar(baseRotationVar), + _midPositionVar(midPositionVar), + _midRotationVar(midRotationVar), + _tipPositionVar(tipPositionVar), + _tipRotationVar(tipRotationVar), + _alphaVar(alphaVar), + _enabledVar(enabledVar) +{ + + for (int i = 0; i < (int)tipTargetFlexCoefficients.size(); i++) { + if (i < MAX_NUMBER_FLEX_VARIABLES) { + _tipTargetFlexCoefficients[i] = tipTargetFlexCoefficients[i]; + } + } + _numTipTargetFlexCoefficients = std::min((int)tipTargetFlexCoefficients.size(), MAX_NUMBER_FLEX_VARIABLES); + + for (int i = 0; i < (int)midTargetFlexCoefficients.size(); i++) { + if (i < MAX_NUMBER_FLEX_VARIABLES) { + _midTargetFlexCoefficients[i] = midTargetFlexCoefficients[i]; + } + } + _numMidTargetFlexCoefficients = std::min((int)midTargetFlexCoefficients.size(), MAX_NUMBER_FLEX_VARIABLES); + +} + +AnimSplineIK::~AnimSplineIK() { + +} + +const AnimPoseVec& AnimSplineIK::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { + assert(_children.size() == 1); + if (_children.size() != 1) { + return _poses; + } + + const float MIN_ALPHA = 0.0f; + const float MAX_ALPHA = 1.0f; + float alpha = glm::clamp(animVars.lookup(_alphaVar, _alpha), MIN_ALPHA, MAX_ALPHA); + + // evaluate underPoses + AnimPoseVec underPoses = _children[0]->evaluate(animVars, context, dt, triggersOut); + + // if we don't have a skeleton, or jointName lookup failed or the spline alpha is 0 or there are no underposes. + if (!_skeleton || _baseJointIndex == -1 || _midJointIndex == -1 || _tipJointIndex == -1 || alpha < EPSILON || underPoses.size() == 0) { + // pass underPoses through unmodified. + _poses = underPoses; + return _poses; + } + + // guard against size change + if (underPoses.size() != _poses.size()) { + _poses = underPoses; + } + + // determine if we should interpolate + bool enabled = animVars.lookup(_enabledVar, _enabled); + if (enabled != _enabled) { + AnimChain poseChain; + poseChain.buildFromRelativePoses(_skeleton, _poses, _tipJointIndex); + if (enabled) { + beginInterp(InterpType::SnapshotToSolve, poseChain); + } else { + beginInterp(InterpType::SnapshotToUnderPoses, poseChain); + } + } + _enabled = enabled; + + // now that we have saved the previous _poses in _snapshotChain, we can update to the current underposes + _poses = underPoses; + + // don't build chains or do IK if we are disabled & not interping. + if (_interpType == InterpType::None && !enabled) { + return _poses; + } + + // compute under chain for possible interpolation + AnimChain underChain; + underChain.buildFromRelativePoses(_skeleton, underPoses, _tipJointIndex); + + AnimPose baseTargetAbsolutePose; + // if there is a baseJoint ik target in animvars then set the joint to that + // otherwise use the underpose + AnimPose baseJointUnderPose = _skeleton->getAbsolutePose(_baseJointIndex, _poses); + baseTargetAbsolutePose.rot() = animVars.lookupRigToGeometry(_baseRotationVar, baseJointUnderPose.rot()); + baseTargetAbsolutePose.trans() = animVars.lookupRigToGeometry(_basePositionVar, baseJointUnderPose.trans()); + + int baseParentIndex = _skeleton->getParentIndex(_baseJointIndex); + AnimPose baseParentAbsPose(Quaternions::IDENTITY,glm::vec3()); + if (baseParentIndex >= 0) { + baseParentAbsPose = _skeleton->getAbsolutePose(baseParentIndex, _poses); + } + _poses[_baseJointIndex] = baseParentAbsPose.inverse() * baseTargetAbsolutePose; + _poses[_baseJointIndex].scale() = glm::vec3(1.0f); + + // initialize the middle joint target + IKTarget midTarget; + midTarget.setType((int)IKTarget::Type::Spline); + midTarget.setIndex(_midJointIndex); + AnimPose absPoseMid = _skeleton->getAbsolutePose(_midJointIndex, _poses); + glm::quat midTargetRotation = animVars.lookupRigToGeometry(_midRotationVar, absPoseMid.rot()); + glm::vec3 midTargetPosition = animVars.lookupRigToGeometry(_midPositionVar, absPoseMid.trans()); + midTarget.setPose(midTargetRotation, midTargetPosition); + midTarget.setWeight(1.0f); + midTarget.setFlexCoefficients(_numMidTargetFlexCoefficients, _midTargetFlexCoefficients); + + // solve the lower spine spline + AnimChain midJointChain; + AnimPoseVec absolutePosesAfterBaseTipSpline; + absolutePosesAfterBaseTipSpline.resize(_poses.size()); + computeAbsolutePoses(absolutePosesAfterBaseTipSpline); + midJointChain.buildFromRelativePoses(_skeleton, _poses, midTarget.getIndex()); + solveTargetWithSpline(context, _baseJointIndex, midTarget, absolutePosesAfterBaseTipSpline, context.getEnableDebugDrawIKChains(), midJointChain); + midJointChain.outputRelativePoses(_poses); + + // initialize the tip target + IKTarget tipTarget; + tipTarget.setType((int)IKTarget::Type::Spline); + tipTarget.setIndex(_tipJointIndex); + AnimPose absPoseTip = _skeleton->getAbsolutePose(_tipJointIndex, _poses); + glm::quat tipRotation = animVars.lookupRigToGeometry(_tipRotationVar, absPoseTip.rot()); + glm::vec3 tipTranslation = animVars.lookupRigToGeometry(_tipPositionVar, absPoseTip.trans()); + tipTarget.setPose(tipRotation, tipTranslation); + tipTarget.setWeight(1.0f); + tipTarget.setFlexCoefficients(_numTipTargetFlexCoefficients, _tipTargetFlexCoefficients); + + // solve the upper spine spline + AnimChain upperJointChain; + AnimPoseVec finalAbsolutePoses; + finalAbsolutePoses.resize(_poses.size()); + computeAbsolutePoses(finalAbsolutePoses); + upperJointChain.buildFromRelativePoses(_skeleton, _poses, tipTarget.getIndex()); + solveTargetWithSpline(context, _midJointIndex, tipTarget, finalAbsolutePoses, context.getEnableDebugDrawIKChains(), upperJointChain); + upperJointChain.buildDirtyAbsolutePoses(); + upperJointChain.outputRelativePoses(_poses); + + // compute chain + AnimChain ikChain; + ikChain.buildFromRelativePoses(_skeleton, _poses, _tipJointIndex); + // blend with the underChain + ikChain.blend(underChain, alpha); + + // apply smooth interpolation when turning ik on and off + if (_interpType != InterpType::None) { + _interpAlpha += _interpAlphaVel * dt; + + // ease in expo + float easeInAlpha = 1.0f - powf(2.0f, -10.0f * _interpAlpha); + + if (_interpAlpha < 1.0f) { + AnimChain interpChain; + if (_interpType == InterpType::SnapshotToUnderPoses) { + interpChain = underChain; + interpChain.blend(_snapshotChain, easeInAlpha); + } else if (_interpType == InterpType::SnapshotToSolve) { + interpChain = ikChain; + interpChain.blend(_snapshotChain, easeInAlpha); + } + // copy interpChain into _poses + interpChain.outputRelativePoses(_poses); + } else { + // interpolation complete + _interpType = InterpType::None; + } + } + + if (_interpType == InterpType::None) { + if (enabled) { + // copy chain into _poses + ikChain.outputRelativePoses(_poses); + } else { + // copy under chain into _poses + underChain.outputRelativePoses(_poses); + } + } + + // debug render ik targets + if (context.getEnableDebugDrawIKTargets()) { + const vec4 WHITE(1.0f); + const vec4 GREEN(0.0f, 1.0f, 0.0f, 1.0f); + glm::mat4 rigToAvatarMat = createMatFromQuatAndPos(Quaternions::Y_180, glm::vec3()); + + glm::mat4 geomTargetMat = createMatFromQuatAndPos(tipTarget.getRotation(), tipTarget.getTranslation()); + glm::mat4 avatarTargetMat = rigToAvatarMat * context.getGeometryToRigMatrix() * geomTargetMat; + QString name = QString("ikTargetSplineTip"); + DebugDraw::getInstance().addMyAvatarMarker(name, glmExtractRotation(avatarTargetMat), extractTranslation(avatarTargetMat), WHITE); + + glm::mat4 geomTargetMat2 = createMatFromQuatAndPos(midTarget.getRotation(), midTarget.getTranslation()); + glm::mat4 avatarTargetMat2 = rigToAvatarMat * context.getGeometryToRigMatrix() * geomTargetMat2; + QString name2 = QString("ikTargetSplineMid"); + DebugDraw::getInstance().addMyAvatarMarker(name2, glmExtractRotation(avatarTargetMat2), extractTranslation(avatarTargetMat2), WHITE); + + glm::mat4 geomTargetMat3 = createMatFromQuatAndPos(baseTargetAbsolutePose.rot(), baseTargetAbsolutePose.trans()); + glm::mat4 avatarTargetMat3 = rigToAvatarMat * context.getGeometryToRigMatrix() * geomTargetMat3; + QString name3 = QString("ikTargetSplineBase"); + DebugDraw::getInstance().addMyAvatarMarker(name3, glmExtractRotation(avatarTargetMat3), extractTranslation(avatarTargetMat3), WHITE); + + + } else if (context.getEnableDebugDrawIKTargets() != _previousEnableDebugIKTargets) { + + // remove markers if they were added last frame. + QString name = QString("ikTargetSplineTip"); + DebugDraw::getInstance().removeMyAvatarMarker(name); + QString name2 = QString("ikTargetSplineMid"); + DebugDraw::getInstance().removeMyAvatarMarker(name2); + QString name3 = QString("ikTargetSplineBase"); + DebugDraw::getInstance().removeMyAvatarMarker(name3); + } + _previousEnableDebugIKTargets = context.getEnableDebugDrawIKTargets(); + + return _poses; +} + +void AnimSplineIK::lookUpIndices() { + assert(_skeleton); + + // look up bone indices by name + std::vector indices = _skeleton->lookUpJointIndices({ _baseJointName, _tipJointName, _midJointName }); + + // cache the results + _baseJointIndex = indices[0]; + _tipJointIndex = indices[1]; + _midJointIndex = indices[2]; +} + +void AnimSplineIK::computeAbsolutePoses(AnimPoseVec& absolutePoses) const { + int numJoints = (int)_poses.size(); + assert(numJoints <= _skeleton->getNumJoints()); + assert(numJoints == (int)absolutePoses.size()); + for (int i = 0; i < numJoints; ++i) { + int parentIndex = _skeleton->getParentIndex(i); + if (parentIndex < 0) { + absolutePoses[i] = _poses[i]; + } else { + absolutePoses[i] = absolutePoses[parentIndex] * _poses[i]; + } + } +} + +// for AnimDebugDraw rendering +const AnimPoseVec& AnimSplineIK::getPosesInternal() const { + return _poses; +} + +void AnimSplineIK::setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) { + AnimNode::setSkeletonInternal(skeleton); + lookUpIndices(); +} + +void AnimSplineIK::solveTargetWithSpline(const AnimContext& context, int base, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug, AnimChain& chainInfoOut) const { + + // build spline from tip to base + AnimPose tipPose = AnimPose(glm::vec3(1.0f), target.getRotation(), target.getTranslation()); + AnimPose basePose = absolutePoses[base]; + + CubicHermiteSplineFunctorWithArcLength spline; + if (target.getIndex() == _tipJointIndex) { + // set gain factors so that more curvature occurs near the tip of the spline. + const float HIPS_GAIN = 0.5f; + const float HEAD_GAIN = 1.0f; + spline = CubicHermiteSplineFunctorWithArcLength(tipPose.rot(), tipPose.trans(), basePose.rot(), basePose.trans(), HIPS_GAIN, HEAD_GAIN); + } else { + spline = CubicHermiteSplineFunctorWithArcLength(tipPose.rot(),tipPose.trans(), basePose.rot(), basePose.trans()); + } + float totalArcLength = spline.arcLength(1.0f); + + // This prevents the rotation interpolation from rotating the wrong physical way (but correct mathematical way) + // when the head is arched backwards very far. + glm::quat halfRot = safeLerp(basePose.rot(), tipPose.rot(), 0.5f); + if (glm::dot(halfRot * Vectors::UNIT_Z, basePose.rot() * Vectors::UNIT_Z) < 0.0f) { + tipPose.rot() = -tipPose.rot(); + } + + // find or create splineJointInfo for this target + const std::vector* splineJointInfoVec = findOrCreateSplineJointInfo(context, base, target); + + if (splineJointInfoVec && splineJointInfoVec->size() > 0) { + const int baseParentIndex = _skeleton->getParentIndex(base); + AnimPose parentAbsPose = (baseParentIndex >= 0) ? absolutePoses[baseParentIndex] : AnimPose(); + // go thru splineJointInfoVec backwards (base to tip) + for (int i = (int)splineJointInfoVec->size() - 1; i >= 0; i--) { + const SplineJointInfo& splineJointInfo = (*splineJointInfoVec)[i]; + float t = spline.arcLengthInverse(splineJointInfo.ratio * totalArcLength); + glm::vec3 trans = spline(t); + + // for base->tip splines, preform most twist toward the tip by using ease in function. t^2 + float rotT = t; + if (target.getIndex() == _tipJointIndex) { + rotT = t * t; + } + glm::quat twistRot = safeLerp(basePose.rot(), tipPose.rot(), rotT); + + // compute the rotation by using the derivative of the spline as the y-axis, and the twistRot x-axis + glm::vec3 y = glm::normalize(spline.d(t)); + glm::vec3 x = twistRot * Vectors::UNIT_X; + glm::vec3 u, v, w; + generateBasisVectors(y, x, v, u, w); + glm::mat3 m(u, v, glm::cross(u, v)); + glm::quat rot = glm::normalize(glm::quat_cast(m)); + + AnimPose desiredAbsPose = AnimPose(glm::vec3(1.0f), rot, trans) * splineJointInfo.offsetPose; + + // apply flex coefficent + AnimPose flexedAbsPose; + // get the number of flex coeff for this spline + float interpedCoefficient = 1.0f; + int numFlexCoeff = target.getNumFlexCoefficients(); + if (numFlexCoeff == (int)splineJointInfoVec->size()) { + // then do nothing special + interpedCoefficient = target.getFlexCoefficient(i); + } else { + // interp based on ratio of the joint. + if (splineJointInfo.ratio < 1.0f) { + float flexInterp = splineJointInfo.ratio * (float)(numFlexCoeff - 1); + int startCoeff = (int)glm::floor(flexInterp); + float partial = flexInterp - startCoeff; + interpedCoefficient = target.getFlexCoefficient(startCoeff) * (1.0f - partial) + target.getFlexCoefficient(startCoeff + 1) * partial; + } else { + interpedCoefficient = target.getFlexCoefficient(numFlexCoeff - 1); + } + } + ::blend(1, &absolutePoses[splineJointInfo.jointIndex], &desiredAbsPose, interpedCoefficient, &flexedAbsPose); + + AnimPose relPose = parentAbsPose.inverse() * flexedAbsPose; + + if (splineJointInfo.jointIndex != base) { + // constrain the amount the spine can stretch or compress + float length = glm::length(relPose.trans()); + const float EPSILON = 0.0001f; + if (length > EPSILON) { + float defaultLength = glm::length(_skeleton->getRelativeDefaultPose(splineJointInfo.jointIndex).trans()); + const float STRETCH_COMPRESS_PERCENTAGE = 0.15f; + const float MAX_LENGTH = defaultLength * (1.0f + STRETCH_COMPRESS_PERCENTAGE); + const float MIN_LENGTH = defaultLength * (1.0f - STRETCH_COMPRESS_PERCENTAGE); + if (length > MAX_LENGTH) { + relPose.trans() = (relPose.trans() / length) * MAX_LENGTH; + } else if (length < MIN_LENGTH) { + relPose.trans() = (relPose.trans() / length) * MIN_LENGTH; + } + } else { + relPose.trans() = glm::vec3(0.0f); + } + } + + if (!chainInfoOut.setRelativePoseAtJointIndex(splineJointInfo.jointIndex, relPose)) { + qCDebug(animation) << "error: joint not found in spline chain"; + } + + parentAbsPose = flexedAbsPose; + } + } + + if (debug) { + const vec4 CYAN(0.0f, 1.0f, 1.0f, 1.0f); + chainInfoOut.debugDraw(context.getRigToWorldMatrix() * context.getGeometryToRigMatrix(), CYAN); + } +} + +const std::vector* AnimSplineIK::findOrCreateSplineJointInfo(const AnimContext& context, int base, const IKTarget& target) const { + // find or create splineJointInfo for this target + auto iter = _splineJointInfoMap.find(target.getIndex()); + if (iter != _splineJointInfoMap.end()) { + return &(iter->second); + } else { + computeAndCacheSplineJointInfosForIKTarget(context, base, target); + auto iter = _splineJointInfoMap.find(target.getIndex()); + if (iter != _splineJointInfoMap.end()) { + return &(iter->second); + } + } + return nullptr; +} + +// pre-compute information about each joint influenced by this spline IK target. +void AnimSplineIK::computeAndCacheSplineJointInfosForIKTarget(const AnimContext& context, int base, const IKTarget& target) const { + std::vector splineJointInfoVec; + + // build spline between the default poses. + AnimPose tipPose = _skeleton->getAbsoluteDefaultPose(target.getIndex()); + AnimPose basePose = _skeleton->getAbsoluteDefaultPose(base); + + CubicHermiteSplineFunctorWithArcLength spline; + if (target.getIndex() == _tipJointIndex) { + // set gain factors so that more curvature occurs near the tip of the spline. + const float HIPS_GAIN = 0.5f; + const float HEAD_GAIN = 1.0f; + spline = CubicHermiteSplineFunctorWithArcLength(tipPose.rot(), tipPose.trans(), basePose.rot(), basePose.trans(), HIPS_GAIN, HEAD_GAIN); + } else { + spline = CubicHermiteSplineFunctorWithArcLength(tipPose.rot(), tipPose.trans(), basePose.rot(), basePose.trans()); + } + // measure the total arc length along the spline + float totalArcLength = spline.arcLength(1.0f); + + glm::vec3 baseToTip = tipPose.trans() - basePose.trans(); + float baseToTipLength = glm::length(baseToTip); + glm::vec3 baseToTipNormal = baseToTip / baseToTipLength; + + int index = target.getIndex(); + int endIndex = _skeleton->getParentIndex(base); + + while (index != endIndex) { + AnimPose defaultPose = _skeleton->getAbsoluteDefaultPose(index); + glm::vec3 baseToCurrentJoint = defaultPose.trans() - basePose.trans(); + float ratio = glm::dot(baseToCurrentJoint, baseToTipNormal) / baseToTipLength; + + // compute offset from spline to the default pose. + float t = spline.arcLengthInverse(ratio * totalArcLength); + + // compute the rotation by using the derivative of the spline as the y-axis, and the defaultPose x-axis + glm::vec3 y = glm::normalize(spline.d(t)); + glm::vec3 x = defaultPose.rot() * Vectors::UNIT_X; + glm::vec3 u, v, w; + generateBasisVectors(y, x, v, u, w); + glm::mat3 m(u, v, glm::cross(u, v)); + glm::quat rot = glm::normalize(glm::quat_cast(m)); + + AnimPose pose(glm::vec3(1.0f), rot, spline(t)); + AnimPose offsetPose = pose.inverse() * defaultPose; + + SplineJointInfo splineJointInfo = { index, ratio, offsetPose }; + splineJointInfoVec.push_back(splineJointInfo); + index = _skeleton->getParentIndex(index); + } + _splineJointInfoMap[target.getIndex()] = splineJointInfoVec; +} + +void AnimSplineIK::beginInterp(InterpType interpType, const AnimChain& chain) { + // capture the current poses in a snapshot. + _snapshotChain = chain; + + _interpType = interpType; + _interpAlphaVel = FRAMES_PER_SECOND / _interpDuration; + _interpAlpha = 0.0f; +} diff --git a/libraries/animation/src/AnimSplineIK.h b/libraries/animation/src/AnimSplineIK.h new file mode 100644 index 0000000000..a4d8da37ca --- /dev/null +++ b/libraries/animation/src/AnimSplineIK.h @@ -0,0 +1,104 @@ +// +// AnimSplineIK.h +// +// Created by Angus Antley on 1/7/19. +// Copyright (c) 2019 High Fidelity, Inc. All rights reserved. +// +// 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_AnimSplineIK_h +#define hifi_AnimSplineIK_h + +#include "AnimNode.h" +#include "IKTarget.h" +#include "AnimChain.h" + +static const int MAX_NUMBER_FLEX_VARIABLES = 10; + +// Spline IK for the spine +class AnimSplineIK : public AnimNode { +public: + AnimSplineIK(const QString& id, float alpha, bool enabled, float interpDuration, + const QString& baseJointName, const QString& midJointName, const QString& tipJointName, + const QString& basePositionVar, const QString& baseRotationVar, + const QString& midPositionVar, const QString& midRotationVar, + const QString& tipPositionVar, const QString& tipRotationVar, + const QString& alphaVar, const QString& enabledVar, + const std::vector tipTargetFlexCoefficients, + const std::vector midTargetFlexCoefficients); + + virtual ~AnimSplineIK() override; + virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override; + +protected: + + enum class InterpType { + None = 0, + SnapshotToUnderPoses, + SnapshotToSolve, + NumTypes + }; + + void computeAbsolutePoses(AnimPoseVec& absolutePoses) const; + void loadPoses(const AnimPoseVec& poses); + + // for AnimDebugDraw rendering + virtual const AnimPoseVec& getPosesInternal() const override; + virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) override; + + void lookUpIndices(); + void beginInterp(InterpType interpType, const AnimChain& chain); + + AnimPoseVec _poses; + + float _alpha; + bool _enabled; + float _interpDuration; + QString _baseJointName; + QString _midJointName; + QString _tipJointName; + QString _basePositionVar; + QString _baseRotationVar; + QString _midPositionVar; + QString _midRotationVar; + QString _tipPositionVar; + QString _tipRotationVar; + QString _alphaVar; // float - (0, 1) 0 means underPoses only, 1 means IK only. + QString _enabledVar; + + float _tipTargetFlexCoefficients[MAX_NUMBER_FLEX_VARIABLES]; + float _midTargetFlexCoefficients[MAX_NUMBER_FLEX_VARIABLES]; + int _numTipTargetFlexCoefficients { 0 }; + int _numMidTargetFlexCoefficients { 0 }; + + int _baseJointIndex { -1 }; + int _midJointIndex { -1 }; + int _tipJointIndex { -1 }; + + bool _previousEnableDebugIKTargets { false }; + + InterpType _interpType{ InterpType::None }; + float _interpAlphaVel{ 0.0f }; + float _interpAlpha{ 0.0f }; + AnimChain _snapshotChain; + + // used to pre-compute information about each joint influenced by a spline IK target. + struct SplineJointInfo { + int jointIndex; // joint in the skeleton that this information pertains to. + float ratio; // percentage (0..1) along the spline for this joint. + AnimPose offsetPose; // local offset from the spline to the joint. + }; + + void solveTargetWithSpline(const AnimContext& context, int base, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug, AnimChain& chainInfoOut) const; + void computeAndCacheSplineJointInfosForIKTarget(const AnimContext& context, int base, const IKTarget& target) const; + const std::vector* findOrCreateSplineJointInfo(const AnimContext& context, int base, const IKTarget& target) const; + mutable std::map> _splineJointInfoMap; + + // no copies + AnimSplineIK(const AnimSplineIK&) = delete; + AnimSplineIK& operator=(const AnimSplineIK&) = delete; + +}; +#endif // hifi_AnimSplineIK_h diff --git a/libraries/animation/src/AnimStateMachine.cpp b/libraries/animation/src/AnimStateMachine.cpp index fb13b8e71c..2c5d4ad0f3 100644 --- a/libraries/animation/src/AnimStateMachine.cpp +++ b/libraries/animation/src/AnimStateMachine.cpp @@ -22,7 +22,6 @@ AnimStateMachine::~AnimStateMachine() { } const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { - float parentDebugAlpha = context.getDebugAlpha(_id); QString desiredStateID = animVars.lookup(_currentStateVar, _currentState->getID()); diff --git a/libraries/animation/src/IKTarget.h b/libraries/animation/src/IKTarget.h index 325a1b40b6..57eaff9c30 100644 --- a/libraries/animation/src/IKTarget.h +++ b/libraries/animation/src/IKTarget.h @@ -35,6 +35,8 @@ public: bool getPoleVectorEnabled() const { return _poleVectorEnabled; } int getIndex() const { return _index; } Type getType() const { return _type; } + int getNumFlexCoefficients() const { return (int)_numFlexCoefficients; } + float getFlexCoefficient(size_t chainDepth) const; void setPose(const glm::quat& rotation, const glm::vec3& translation); void setPoleVector(const glm::vec3& poleVector) { _poleVector = poleVector; } @@ -43,7 +45,6 @@ public: void setIndex(int index) { _index = index; } void setType(int); void setFlexCoefficients(size_t numFlexCoefficientsIn, const float* flexCoefficientsIn); - float getFlexCoefficient(size_t chainDepth) const; void setWeight(float weight) { _weight = weight; } float getWeight() const { return _weight; } diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index a9c57a4a15..be6240017f 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -34,7 +34,6 @@ #include "IKTarget.h" #include "PathUtils.h" - static int nextRigId = 1; static std::map rigRegistry; static std::mutex rigRegistryMutex; @@ -74,6 +73,20 @@ static const QString RIGHT_FOOT_IK_ROTATION_VAR("rightFootIKRotationVar"); static const QString MAIN_STATE_MACHINE_RIGHT_FOOT_ROTATION("mainStateMachineRightFootRotation"); static const QString MAIN_STATE_MACHINE_RIGHT_FOOT_POSITION("mainStateMachineRightFootPosition"); +static const QString LEFT_HAND_POSITION("leftHandPosition"); +static const QString LEFT_HAND_ROTATION("leftHandRotation"); +static const QString LEFT_HAND_IK_POSITION_VAR("leftHandIKPositionVar"); +static const QString LEFT_HAND_IK_ROTATION_VAR("leftHandIKRotationVar"); +static const QString MAIN_STATE_MACHINE_LEFT_HAND_POSITION("mainStateMachineLeftHandPosition"); +static const QString MAIN_STATE_MACHINE_LEFT_HAND_ROTATION("mainStateMachineLeftHandRotation"); + +static const QString RIGHT_HAND_POSITION("rightHandPosition"); +static const QString RIGHT_HAND_ROTATION("rightHandRotation"); +static const QString RIGHT_HAND_IK_POSITION_VAR("rightHandIKPositionVar"); +static const QString RIGHT_HAND_IK_ROTATION_VAR("rightHandIKRotationVar"); +static const QString MAIN_STATE_MACHINE_RIGHT_HAND_ROTATION("mainStateMachineRightHandRotation"); +static const QString MAIN_STATE_MACHINE_RIGHT_HAND_POSITION("mainStateMachineRightHandPosition"); + Rig::Rig() { // Ensure thread-safe access to the rigRegistry. @@ -1051,16 +1064,29 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos t += deltaTime; - if (_enableInverseKinematics != _lastEnableInverseKinematics) { - if (_enableInverseKinematics) { - _animVars.set("ikOverlayAlpha", 1.0f); - } else { - _animVars.set("ikOverlayAlpha", 0.0f); - } + if (_enableInverseKinematics) { + _animVars.set("ikOverlayAlpha", 1.0f); + _animVars.set("splineIKEnabled", true); + _animVars.set("leftHandIKEnabled", true); + _animVars.set("rightHandIKEnabled", true); + _animVars.set("leftFootIKEnabled", true); + _animVars.set("rightFootIKEnabled", true); + _animVars.set("leftFootPoleVectorEnabled", true); + _animVars.set("rightFootPoleVectorEnabled", true); + } else { + _animVars.set("ikOverlayAlpha", 0.0f); + _animVars.set("splineIKEnabled", false); + _animVars.set("leftHandIKEnabled", false); + _animVars.set("rightHandIKEnabled", false); + _animVars.set("leftFootIKEnabled", false); + _animVars.set("rightFootIKEnabled", false); + _animVars.set("leftHandPoleVectorEnabled", false); + _animVars.set("rightHandPoleVectorEnabled", false); + _animVars.set("leftFootPoleVectorEnabled", false); + _animVars.set("rightFootPoleVectorEnabled", false); } _lastEnableInverseKinematics = _enableInverseKinematics; } - _lastForward = forward; _lastPosition = worldPosition; _lastVelocity = workingVelocity; @@ -1251,6 +1277,7 @@ void Rig::computeHeadFromHMD(const AnimPose& hmdPose, glm::vec3& headPositionOut void Rig::updateHead(bool headEnabled, bool hipsEnabled, const AnimPose& headPose) { if (_animSkeleton) { if (headEnabled) { + _animVars.set("splineIKEnabled", true); _animVars.set("headPosition", headPose.trans()); _animVars.set("headRotation", headPose.rot()); if (hipsEnabled) { @@ -1265,6 +1292,7 @@ void Rig::updateHead(bool headEnabled, bool hipsEnabled, const AnimPose& headPos _animVars.set("headWeight", 8.0f); } } else { + _animVars.set("splineIKEnabled", false); _animVars.unset("headPosition"); _animVars.set("headRotation", headPose.rot()); _animVars.set("headType", (int)IKTarget::Type::RotationOnly); @@ -1396,8 +1424,22 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab const bool ENABLE_POLE_VECTORS = true; + if (headEnabled) { + // always do IK if head is enabled + _animVars.set("leftHandIKEnabled", true); + _animVars.set("rightHandIKEnabled", true); + } else { + // only do IK if we have a valid foot. + _animVars.set("leftHandIKEnabled", leftHandEnabled); + _animVars.set("rightHandIKEnabled", rightHandEnabled); + } + if (leftHandEnabled) { + // we need this for twoBoneIK version of hands. + _animVars.set(LEFT_HAND_IK_POSITION_VAR, LEFT_HAND_POSITION); + _animVars.set(LEFT_HAND_IK_ROTATION_VAR, LEFT_HAND_ROTATION); + glm::vec3 handPosition = leftHandPose.trans(); glm::quat handRotation = leftHandPose.rot(); @@ -1430,8 +1472,11 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab _animVars.set("leftHandPoleVectorEnabled", false); } } else { - _animVars.set("leftHandPoleVectorEnabled", false); + // need this for two bone ik + _animVars.set(LEFT_HAND_IK_POSITION_VAR, MAIN_STATE_MACHINE_LEFT_HAND_POSITION); + _animVars.set(LEFT_HAND_IK_ROTATION_VAR, MAIN_STATE_MACHINE_LEFT_HAND_ROTATION); + _animVars.set("leftHandPoleVectorEnabled", false); _animVars.unset("leftHandPosition"); _animVars.unset("leftHandRotation"); @@ -1445,6 +1490,10 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab if (rightHandEnabled) { + // need this for two bone IK + _animVars.set(RIGHT_HAND_IK_POSITION_VAR, RIGHT_HAND_POSITION); + _animVars.set(RIGHT_HAND_IK_ROTATION_VAR, RIGHT_HAND_ROTATION); + glm::vec3 handPosition = rightHandPose.trans(); glm::quat handRotation = rightHandPose.rot(); @@ -1478,8 +1527,12 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab _animVars.set("rightHandPoleVectorEnabled", false); } } else { - _animVars.set("rightHandPoleVectorEnabled", false); + // need this for two bone IK + _animVars.set(RIGHT_HAND_IK_POSITION_VAR, MAIN_STATE_MACHINE_RIGHT_HAND_POSITION); + _animVars.set(RIGHT_HAND_IK_ROTATION_VAR, MAIN_STATE_MACHINE_RIGHT_HAND_ROTATION); + + _animVars.set("rightHandPoleVectorEnabled", false); _animVars.unset("rightHandPosition"); _animVars.unset("rightHandRotation"); @@ -1697,6 +1750,7 @@ bool Rig::calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex, correctionVector = forwardAmount * frontVector; } poleVector = glm::normalize(attenuationVector + fullPoleVector + correctionVector); + return true; } @@ -1819,7 +1873,7 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo std::shared_ptr ikNode = getAnimInverseKinematicsNode(); for (int i = 0; i < (int)NumSecondaryControllerTypes; i++) { int index = indexOfJoint(secondaryControllerJointNames[i]); - if (index >= 0) { + if ((index >= 0) && (ikNode)) { if (params.secondaryControllerFlags[i] & (uint8_t)ControllerFlags::Enabled) { ikNode->setSecondaryTargetInRigFrame(index, params.secondaryControllerPoses[i]); } else { diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 395fc3b7b4..b842597b88 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -37,6 +37,7 @@ #include "RenderableModelEntityItem.h" #include +#include #include "Logging.h" @@ -1535,11 +1536,13 @@ void Avatar::setModelURLFinished(bool success) { void Avatar::rigReady() { buildUnscaledEyeHeightCache(); computeMultiSphereShapes(); + buildSpine2SplineRatioCache(); } // rig has been reset. void Avatar::rigReset() { clearUnscaledEyeHeightCache(); + clearSpine2SplineRatioCache(); } void Avatar::computeMultiSphereShapes() { @@ -1994,10 +1997,43 @@ void Avatar::buildUnscaledEyeHeightCache() { } } +void Avatar::buildSpine2SplineRatioCache() { + if (_skeletonModel) { + auto& rig = _skeletonModel->getRig(); + AnimPose hipsRigDefaultPose = rig.getAbsoluteDefaultPose(rig.indexOfJoint("Hips")); + AnimPose headRigDefaultPose(rig.getAbsoluteDefaultPose(rig.indexOfJoint("Head"))); + glm::vec3 basePosition = hipsRigDefaultPose.trans(); + glm::vec3 tipPosition = headRigDefaultPose.trans(); + glm::vec3 spine2Position = rig.getAbsoluteDefaultPose(rig.indexOfJoint("Spine2")).trans(); + + glm::vec3 baseToTip = tipPosition - basePosition; + float baseToTipLength = glm::length(baseToTip); + glm::vec3 baseToTipNormal = baseToTip / baseToTipLength; + glm::vec3 baseToSpine2 = spine2Position - basePosition; + + _spine2SplineRatio = glm::dot(baseToSpine2, baseToTipNormal) / baseToTipLength; + + CubicHermiteSplineFunctorWithArcLength defaultSpline(headRigDefaultPose.rot(), headRigDefaultPose.trans(), hipsRigDefaultPose.rot(), hipsRigDefaultPose.trans()); + + // measure the total arc length along the spline + float totalDefaultArcLength = defaultSpline.arcLength(1.0f); + float t = defaultSpline.arcLengthInverse(_spine2SplineRatio * totalDefaultArcLength); + glm::vec3 defaultSplineSpine2Translation = defaultSpline(t); + + _spine2SplineOffset = spine2Position - defaultSplineSpine2Translation; + } + +} + void Avatar::clearUnscaledEyeHeightCache() { _unscaledEyeHeightCache.set(DEFAULT_AVATAR_EYE_HEIGHT); } +void Avatar::clearSpine2SplineRatioCache() { + _spine2SplineRatio = DEFAULT_AVATAR_EYE_HEIGHT; + _spine2SplineOffset = glm::vec3(); +} + float Avatar::getUnscaledEyeHeightFromSkeleton() const { // TODO: if performance becomes a concern we can cache this value rather then computing it everytime. diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 1e6893a410..1acee7439f 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -236,6 +236,7 @@ public: virtual bool setAbsoluteJointRotationInObjectFrame(int index, const glm::quat& rotation) override { return false; } virtual bool setAbsoluteJointTranslationInObjectFrame(int index, const glm::vec3& translation) override { return false; } +<<<<<<< HEAD // world-space to avatar-space rigconversion functions /**jsdoc * @function MyAvatar.worldToJointPoint @@ -285,6 +286,10 @@ public: * @returns {Quat} */ Q_INVOKABLE glm::quat jointToWorldRotation(const glm::quat& rotation, const int jointIndex = -1) const; +======= + virtual glm::vec3 getSpine2SplineOffset() const { return _spine2SplineOffset; } + virtual float getSpine2SplineRatio() const { return _spine2SplineRatio; } +>>>>>>> cache the spine2 spline default offset and ratio virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override; virtual void setAttachmentData(const QVector& attachmentData) override; @@ -563,7 +568,9 @@ public slots: protected: float getUnscaledEyeHeightFromSkeleton() const; void buildUnscaledEyeHeightCache(); + void buildSpine2SplineRatioCache(); void clearUnscaledEyeHeightCache(); + void clearSpine2SplineRatioCache(); 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! @@ -669,6 +676,8 @@ protected: float _displayNameAlpha { 1.0f }; ThreadSafeValueCache _unscaledEyeHeightCache { DEFAULT_AVATAR_EYE_HEIGHT }; + float _spine2SplineRatio { DEFAULT_SPINE2_SPLINE_PROPORTION }; + glm::vec3 _spine2SplineOffset; std::unordered_map _materials; std::mutex _materialsLock; diff --git a/libraries/fbx/src/FBXSerializer.cpp b/libraries/fbx/src/FBXSerializer.cpp index 9e7f422b40..30bd527546 100644 --- a/libraries/fbx/src/FBXSerializer.cpp +++ b/libraries/fbx/src/FBXSerializer.cpp @@ -1288,6 +1288,20 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr } joint.inverseBindRotation = joint.inverseDefaultRotation; joint.name = fbxModel.name; +<<<<<<< HEAD +======= + if (hfmModel.hfmToHifiJointNameMapping.contains(hfmModel.hfmToHifiJointNameMapping.key(joint.name))) { + joint.name = hfmModel.hfmToHifiJointNameMapping.key(fbxModel.name); + } + + foreach (const QString& childID, _connectionChildMap.values(modelID)) { + QString type = typeFlags.value(childID); + if (!type.isEmpty()) { + hfmModel.hasSkeletonJoints |= (joint.isSkeletonJoint = type.toLower().contains("Skeleton")); + break; + } + } +>>>>>>> implemented the splineIK in animSplineIK.cpp, todo: disable animinversekinematic.cpp joint.bindTransformFoundInCluster = false; diff --git a/libraries/shared/src/AvatarConstants.h b/libraries/shared/src/AvatarConstants.h index 103782bd3f..d55a63b960 100644 --- a/libraries/shared/src/AvatarConstants.h +++ b/libraries/shared/src/AvatarConstants.h @@ -20,6 +20,7 @@ const float DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD = 0.11f; // meters const float DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD = 0.185f; // meters const float DEFAULT_AVATAR_NECK_HEIGHT = DEFAULT_AVATAR_HEIGHT - DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD; const float DEFAULT_AVATAR_EYE_HEIGHT = DEFAULT_AVATAR_HEIGHT - DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD; +const float DEFAULT_SPINE2_SPLINE_PROPORTION = 0.71f; const float DEFAULT_AVATAR_SUPPORT_BASE_LEFT = -0.25f; const float DEFAULT_AVATAR_SUPPORT_BASE_RIGHT = 0.25f; const float DEFAULT_AVATAR_SUPPORT_BASE_FRONT = -0.20f; diff --git a/libraries/shared/src/CubicHermiteSpline.h b/libraries/shared/src/CubicHermiteSpline.h index cdbc64308d..c83000996b 100644 --- a/libraries/shared/src/CubicHermiteSpline.h +++ b/libraries/shared/src/CubicHermiteSpline.h @@ -66,19 +66,19 @@ public: memset(_values, 0, sizeof(float) * (NUM_SUBDIVISIONS + 1)); } CubicHermiteSplineFunctorWithArcLength(const glm::vec3& p0, const glm::vec3& m0, const glm::vec3& p1, const glm::vec3& m1) : CubicHermiteSplineFunctor(p0, m0, p1, m1) { - // initialize _values with the accumulated arcLength along the spline. - const float DELTA = 1.0f / NUM_SUBDIVISIONS; - float alpha = 0.0f; - float accum = 0.0f; - _values[0] = 0.0f; - glm::vec3 prevValue = this->operator()(alpha); - for (int i = 1; i < NUM_SUBDIVISIONS + 1; i++) { - glm::vec3 nextValue = this->operator()(alpha + DELTA); - accum += glm::distance(prevValue, nextValue); - alpha += DELTA; - _values[i] = accum; - prevValue = nextValue; - } + + initValues(); + } + + CubicHermiteSplineFunctorWithArcLength(const glm::quat& tipRot, const glm::vec3& tipTrans, const glm::quat& baseRot, const glm::vec3& baseTrans, float baseGain = 1.0f, float tipGain = 1.0f) : CubicHermiteSplineFunctor() { + + float linearDistance = glm::length(baseTrans - tipTrans); + _p0 = baseTrans; + _m0 = baseGain * linearDistance * (baseRot * Vectors::UNIT_Y); + _p1 = tipTrans; + _m1 = tipGain * linearDistance * (tipRot * Vectors::UNIT_Y); + + initValues(); } CubicHermiteSplineFunctorWithArcLength(const CubicHermiteSplineFunctorWithArcLength& orig) : CubicHermiteSplineFunctor(orig) { @@ -110,6 +110,21 @@ public: } protected: float _values[NUM_SUBDIVISIONS + 1]; + + void initValues() { + // initialize _values with the accumulated arcLength along the spline. + const float DELTA = 1.0f / NUM_SUBDIVISIONS; + float alpha = 0.0f; + float accum = 0.0f; + _values[0] = 0.0f; + for (int i = 1; i < NUM_SUBDIVISIONS + 1; i++) { + accum += glm::distance(this->operator()(alpha), + this->operator()(alpha + DELTA)); + alpha += DELTA; + _values[i] = accum; + } + + } }; #endif // hifi_CubicHermiteSpline_h diff --git a/tools/unity-avatar-exporter/Assets/README.txt b/tools/unity-avatar-exporter/Assets/README.txt index b81a620406..c84cec2978 100644 --- a/tools/unity-avatar-exporter/Assets/README.txt +++ b/tools/unity-avatar-exporter/Assets/README.txt @@ -2,6 +2,7 @@ High Fidelity, Inc. Avatar Exporter Version 0.2 + Note: It is recommended to use Unity versions between 2017.4.17f1 and 2018.2.12f1 for this Avatar Exporter. To create a new avatar project: From a6ad53e79e74c5cfeb79264431555c0b2ab9198e Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Fri, 22 Feb 2019 11:30:26 -0800 Subject: [PATCH 161/474] TEST --- tools/CMakeLists.txt | 2 +- tools/nitpick/CMakeLists.txt | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 6cda67db2d..ed66ab1ed1 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -20,7 +20,7 @@ endfunction() if (BUILD_TOOLS) # Allow different tools for stable builds - if (RELEASE_TYPE STREQUAL "PRODUCTION") + if (NOT STABLE_BUILD) set(ALL_TOOLS udt-test vhacd-util diff --git a/tools/nitpick/CMakeLists.txt b/tools/nitpick/CMakeLists.txt index e69b16b866..44eace5e70 100644 --- a/tools/nitpick/CMakeLists.txt +++ b/tools/nitpick/CMakeLists.txt @@ -80,7 +80,9 @@ else () add_executable(${TARGET_NAME} ${NITPICK_SRCS} ${QM}) endif () -add_dependencies(${TARGET_NAME} resources) +if (NOT UNIX) + add_dependencies(${TARGET_NAME} resources) +endif() # disable /OPT:REF and /OPT:ICF for the Debug builds # This will prevent the following linker warnings From 9b73c83ebc048a8a2c384332c61d777b5558b247 Mon Sep 17 00:00:00 2001 From: amantley Date: Fri, 22 Feb 2019 11:50:38 -0800 Subject: [PATCH 162/474] removed merge markers (cherry picked from commit 4286142daac70269de155c99c9bbdb1f951eaff6) --- .../avatars-renderer/src/avatars-renderer/Avatar.h | 3 --- libraries/fbx/src/FBXSerializer.cpp | 14 -------------- 2 files changed, 17 deletions(-) diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 1acee7439f..3d25c275b1 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -236,7 +236,6 @@ public: virtual bool setAbsoluteJointRotationInObjectFrame(int index, const glm::quat& rotation) override { return false; } virtual bool setAbsoluteJointTranslationInObjectFrame(int index, const glm::vec3& translation) override { return false; } -<<<<<<< HEAD // world-space to avatar-space rigconversion functions /**jsdoc * @function MyAvatar.worldToJointPoint @@ -286,10 +285,8 @@ public: * @returns {Quat} */ Q_INVOKABLE glm::quat jointToWorldRotation(const glm::quat& rotation, const int jointIndex = -1) const; -======= virtual glm::vec3 getSpine2SplineOffset() const { return _spine2SplineOffset; } virtual float getSpine2SplineRatio() const { return _spine2SplineRatio; } ->>>>>>> cache the spine2 spline default offset and ratio virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override; virtual void setAttachmentData(const QVector& attachmentData) override; diff --git a/libraries/fbx/src/FBXSerializer.cpp b/libraries/fbx/src/FBXSerializer.cpp index 30bd527546..9e7f422b40 100644 --- a/libraries/fbx/src/FBXSerializer.cpp +++ b/libraries/fbx/src/FBXSerializer.cpp @@ -1288,20 +1288,6 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr } joint.inverseBindRotation = joint.inverseDefaultRotation; joint.name = fbxModel.name; -<<<<<<< HEAD -======= - if (hfmModel.hfmToHifiJointNameMapping.contains(hfmModel.hfmToHifiJointNameMapping.key(joint.name))) { - joint.name = hfmModel.hfmToHifiJointNameMapping.key(fbxModel.name); - } - - foreach (const QString& childID, _connectionChildMap.values(modelID)) { - QString type = typeFlags.value(childID); - if (!type.isEmpty()) { - hfmModel.hasSkeletonJoints |= (joint.isSkeletonJoint = type.toLower().contains("Skeleton")); - break; - } - } ->>>>>>> implemented the splineIK in animSplineIK.cpp, todo: disable animinversekinematic.cpp joint.bindTransformFoundInCluster = false; From 6d6cd42adbdf78fc07f2317b90f7b4941c1a5c38 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Fri, 22 Feb 2019 12:00:00 -0800 Subject: [PATCH 163/474] fix bound scaling --- libraries/entities/src/EntityItem.cpp | 10 +++++----- libraries/entities/src/EntityItemProperties.cpp | 2 +- libraries/render-utils/src/CauterizedModel.cpp | 1 + libraries/render-utils/src/Model.cpp | 1 + 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 9f11b3c018..72246f8229 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -266,7 +266,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet APPEND_ENTITY_PROPERTY(PROP_HREF, getHref()); APPEND_ENTITY_PROPERTY(PROP_DESCRIPTION, getDescription()); APPEND_ENTITY_PROPERTY(PROP_POSITION, getLocalPosition()); - APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, getUnscaledDimensions()); + APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, getScaledDimensions()); APPEND_ENTITY_PROPERTY(PROP_ROTATION, getLocalOrientation()); APPEND_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, getRegistrationPoint()); APPEND_ENTITY_PROPERTY(PROP_CREATED, getCreated()); @@ -818,7 +818,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef }; READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, customUpdatePositionFromNetwork); } - READ_ENTITY_PROPERTY(PROP_DIMENSIONS, glm::vec3, setUnscaledDimensions); + READ_ENTITY_PROPERTY(PROP_DIMENSIONS, glm::vec3, setScaledDimensions); { // See comment above auto customUpdateRotationFromNetwork = [this, shouldUpdate, lastEdited](glm::quat value) { if (shouldUpdate(_lastUpdatedRotationTimestamp, value != _lastUpdatedRotationValue)) { @@ -1315,7 +1315,7 @@ EntityItemProperties EntityItem::getProperties(const EntityPropertyFlags& desire COPY_ENTITY_PROPERTY_TO_PROPERTIES(href, getHref); COPY_ENTITY_PROPERTY_TO_PROPERTIES(description, getDescription); COPY_ENTITY_PROPERTY_TO_PROPERTIES(position, getLocalPosition); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(dimensions, getUnscaledDimensions); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(dimensions, getScaledDimensions); COPY_ENTITY_PROPERTY_TO_PROPERTIES(rotation, getLocalOrientation); COPY_ENTITY_PROPERTY_TO_PROPERTIES(registrationPoint, getRegistrationPoint); COPY_ENTITY_PROPERTY_TO_PROPERTIES(created, getCreated); @@ -1462,7 +1462,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(href, setHref); SET_ENTITY_PROPERTY_FROM_PROPERTIES(description, setDescription); SET_ENTITY_PROPERTY_FROM_PROPERTIES(position, setPosition); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(dimensions, setUnscaledDimensions); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(dimensions, setScaledDimensions); SET_ENTITY_PROPERTY_FROM_PROPERTIES(rotation, setRotation); SET_ENTITY_PROPERTY_FROM_PROPERTIES(registrationPoint, setRegistrationPoint); SET_ENTITY_PROPERTY_FROM_PROPERTIES(created, setCreated); @@ -1872,7 +1872,7 @@ glm::vec3 EntityItem::getScaledDimensions() const { void EntityItem::setScaledDimensions(const glm::vec3& value) { glm::vec3 parentScale = getSNScale(); - setUnscaledDimensions(value * parentScale); + setUnscaledDimensions(value / parentScale); } void EntityItem::setUnscaledDimensions(const glm::vec3& value) { diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 75e2069471..7575763bf9 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -1013,7 +1013,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {Vec3} dimensions=0.1,0.1,0.1 - The dimensions of the entity. When adding an entity, if no dimensions * value is specified then the model is automatically sized to its * {@link Entities.EntityProperties|naturalDimensions}. - * @property {Vec3} modelScale - The scale factor applied to the model's dimensions. + * @property {Vec3} modelScale - The scale factor applied to the model's dimensions. Deprecated. * @property {Color} color=255,255,255 - Currently not used. * @property {string} modelURL="" - The URL of the FBX of OBJ model. Baked FBX models' URLs end in ".baked.fbx".
* @property {string} textures="" - A JSON string of texture name, URL pairs used when rendering the model in place of the diff --git a/libraries/render-utils/src/CauterizedModel.cpp b/libraries/render-utils/src/CauterizedModel.cpp index 81a81c5602..cfb78d6bbc 100644 --- a/libraries/render-utils/src/CauterizedModel.cpp +++ b/libraries/render-utils/src/CauterizedModel.cpp @@ -178,6 +178,7 @@ void CauterizedModel::updateClusterMatrices() { } } } + computeMeshPartLocalBounds(); // post the blender if we're not currently waiting for one to finish auto modelBlender = DependencyManager::get(); diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index b9b294d0e3..02c6562f61 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1385,6 +1385,7 @@ void Model::updateClusterMatrices() { } } } + computeMeshPartLocalBounds(); // post the blender if we're not currently waiting for one to finish auto modelBlender = DependencyManager::get(); From a31b09b204ac32d1cccc3906fdca7e9e9224db55 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Fri, 22 Feb 2019 13:17:13 -0800 Subject: [PATCH 164/474] handle models with no materials --- libraries/graphics/src/graphics/Material.h | 4 ++++ libraries/render-utils/src/RenderPipelines.cpp | 8 ++------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/libraries/graphics/src/graphics/Material.h b/libraries/graphics/src/graphics/Material.h index fdddf3640a..2fce8e1195 100755 --- a/libraries/graphics/src/graphics/Material.h +++ b/libraries/graphics/src/graphics/Material.h @@ -462,6 +462,9 @@ public: void setTexturesLoading(bool value) { _texturesLoading = value; } bool areTexturesLoading() const { return _texturesLoading; } + bool isInitialized() const { return _initialized; } + void setInitialized() { _initialized = true; } + int getTextureCount() const { calculateMaterialInfo(); return _textureCount; } size_t getTextureSize() const { calculateMaterialInfo(); return _textureSize; } bool hasTextureInfo() const { return _hasCalculatedTextureInfo; } @@ -471,6 +474,7 @@ private: gpu::TextureTablePointer _textureTable { std::make_shared() }; bool _needsUpdate { false }; bool _texturesLoading { false }; + bool _initialized { false }; mutable size_t _textureSize { 0 }; mutable int _textureCount { 0 }; diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp index 6f6f2ab856..67c3b526b9 100644 --- a/libraries/render-utils/src/RenderPipelines.cpp +++ b/libraries/render-utils/src/RenderPipelines.cpp @@ -382,11 +382,6 @@ void RenderPipelines::bindMaterial(graphics::MaterialPointer& material, gpu::Bat void RenderPipelines::updateMultiMaterial(graphics::MultiMaterial& multiMaterial) { auto& schemaBuffer = multiMaterial.getSchemaBuffer(); - if (multiMaterial.size() == 0) { - schemaBuffer.edit() = graphics::MultiMaterial::Schema(); - return; - } - auto& drawMaterialTextures = multiMaterial.getTextureTable(); multiMaterial.setTexturesLoading(false); @@ -732,6 +727,7 @@ void RenderPipelines::updateMultiMaterial(graphics::MultiMaterial& multiMaterial schema._key = (uint32_t)schemaKey._flags.to_ulong(); schemaBuffer.edit() = schema; multiMaterial.setNeedsUpdate(false); + multiMaterial.setInitialized(); } void RenderPipelines::bindMaterials(graphics::MultiMaterial& multiMaterial, gpu::Batch& batch, bool enableTextures) { @@ -739,7 +735,7 @@ void RenderPipelines::bindMaterials(graphics::MultiMaterial& multiMaterial, gpu: return; } - if (multiMaterial.needsUpdate() || multiMaterial.areTexturesLoading()) { + if (!multiMaterial.isInitialized() || multiMaterial.needsUpdate() || multiMaterial.areTexturesLoading()) { updateMultiMaterial(multiMaterial); } From dbc3ae3793480bf0021341b33863914fffc98f03 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Fri, 22 Feb 2019 14:18:59 -0800 Subject: [PATCH 165/474] hmmm not working --- .../entities-renderer/src/RenderableShapeEntityItem.cpp | 4 ++-- libraries/graphics/src/graphics/Material.h | 7 ++----- libraries/render-utils/src/MeshPartPayload.cpp | 6 +++--- libraries/render-utils/src/RenderPipelines.cpp | 2 +- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index d42a766faa..5f3f4c9e81 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -53,7 +53,7 @@ bool ShapeEntityRenderer::needsRenderUpdate() const { } auto mat = _materials.find("0"); - if (mat != _materials.end() && (mat->second.needsUpdate() || mat->second.areTexturesLoading())) { + if (mat != _materials.end() && mat->second.shouldUpdate()) { return true; } @@ -188,7 +188,7 @@ bool ShapeEntityRenderer::useMaterialPipeline(const graphics::MultiMaterial& mat ShapeKey ShapeEntityRenderer::getShapeKey() { auto mat = _materials.find("0"); - if (mat != _materials.end() && (mat->second.needsUpdate() || mat->second.areTexturesLoading())) { + if (mat != _materials.end() && mat->second.shouldUpdate()) { RenderPipelines::updateMultiMaterial(mat->second); } diff --git a/libraries/graphics/src/graphics/Material.h b/libraries/graphics/src/graphics/Material.h index 2fce8e1195..d24e906f98 100755 --- a/libraries/graphics/src/graphics/Material.h +++ b/libraries/graphics/src/graphics/Material.h @@ -456,15 +456,12 @@ public: graphics::MaterialKey getMaterialKey() const { return graphics::MaterialKey(_schemaBuffer.get()._key); } const gpu::TextureTablePointer& getTextureTable() const { return _textureTable; } - bool needsUpdate() const { return _needsUpdate; } void setNeedsUpdate(bool needsUpdate) { _needsUpdate = needsUpdate; } - void setTexturesLoading(bool value) { _texturesLoading = value; } - bool areTexturesLoading() const { return _texturesLoading; } - - bool isInitialized() const { return _initialized; } void setInitialized() { _initialized = true; } + bool shouldUpdate() const { return !_initialized || _needsUpdate || _texturesLoading; } + int getTextureCount() const { calculateMaterialInfo(); return _textureCount; } size_t getTextureSize() const { calculateMaterialInfo(); return _textureSize; } bool hasTextureInfo() const { return _hasCalculatedTextureInfo; } diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 6409cdd231..b1104b8aad 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -83,7 +83,7 @@ void MeshPartPayload::updateKey(const render::ItemKey& key) { ItemKey::Builder builder(key); builder.withTypeShape(); - if (_drawMaterials.needsUpdate() || _drawMaterials.areTexturesLoading()) { + if (_drawMaterials.shouldUpdate()) { RenderPipelines::updateMultiMaterial(_drawMaterials); } @@ -329,7 +329,7 @@ void ModelMeshPartPayload::updateKey(const render::ItemKey& key) { builder.withDeformed(); } - if (_drawMaterials.needsUpdate() || _drawMaterials.areTexturesLoading()) { + if (_drawMaterials.shouldUpdate()) { RenderPipelines::updateMultiMaterial(_drawMaterials); } @@ -347,7 +347,7 @@ void ModelMeshPartPayload::setShapeKey(bool invalidateShapeKey, PrimitiveMode pr return; } - if (_drawMaterials.needsUpdate() || _drawMaterials.areTexturesLoading()) { + if (_drawMaterials.shouldUpdate()) { RenderPipelines::updateMultiMaterial(_drawMaterials); } diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp index 67c3b526b9..83216ddf84 100644 --- a/libraries/render-utils/src/RenderPipelines.cpp +++ b/libraries/render-utils/src/RenderPipelines.cpp @@ -735,7 +735,7 @@ void RenderPipelines::bindMaterials(graphics::MultiMaterial& multiMaterial, gpu: return; } - if (!multiMaterial.isInitialized() || multiMaterial.needsUpdate() || multiMaterial.areTexturesLoading()) { + if (multiMaterial.shouldUpdate()) { updateMultiMaterial(multiMaterial); } From 081e62a64758ff2187edad65c73ff32d9d695676 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Fri, 22 Feb 2019 14:26:15 -0800 Subject: [PATCH 166/474] fix parent loop crash --- libraries/shared/src/SpatiallyNestable.cpp | 9 +++++++-- libraries/shared/src/SpatiallyNestable.h | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/libraries/shared/src/SpatiallyNestable.cpp b/libraries/shared/src/SpatiallyNestable.cpp index 19fafdccf4..8d2e5ea45b 100644 --- a/libraries/shared/src/SpatiallyNestable.cpp +++ b/libraries/shared/src/SpatiallyNestable.cpp @@ -1420,11 +1420,16 @@ QUuid SpatiallyNestable::getEditSenderID() { return editSenderID; } -void SpatiallyNestable::bumpAncestorChainRenderableVersion() const { +void SpatiallyNestable::bumpAncestorChainRenderableVersion(int depth) const { + if (depth > MAX_PARENTING_CHAIN_SIZE) { + breakParentingLoop(); + return; + } + _ancestorChainRenderableVersion++; bool success = false; auto parent = getParentPointer(success); if (success && parent) { - parent->bumpAncestorChainRenderableVersion(); + parent->bumpAncestorChainRenderableVersion(depth + 1); } } \ No newline at end of file diff --git a/libraries/shared/src/SpatiallyNestable.h b/libraries/shared/src/SpatiallyNestable.h index 495c941c07..a802a25e89 100644 --- a/libraries/shared/src/SpatiallyNestable.h +++ b/libraries/shared/src/SpatiallyNestable.h @@ -221,7 +221,7 @@ public: bool hasGrabs(); virtual QUuid getEditSenderID(); - void bumpAncestorChainRenderableVersion() const; + void bumpAncestorChainRenderableVersion(int depth = 0) const; protected: QUuid _id; From a87e49bb23ea6c23f979468f624bfe3ec112cb18 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Fri, 22 Feb 2019 15:01:30 -0800 Subject: [PATCH 167/474] start in quest dev --- interface/src/Application.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index a611738445..3864ba30c0 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2414,6 +2414,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo AndroidHelper::instance().notifyLoadComplete(); #endif pauseUntilLoginDetermined(); + +#if defined(Q_OS_ANDROID) + const QString QUEST_DEV = "hifi://quest-dev"; + DependencyManager::get()->handleLookupString(QUEST_DEV); +#endif } void Application::updateVerboseLogging() { From 7cf79c11081209537e74ee123daa42bee963804c Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Fri, 22 Feb 2019 15:06:42 -0800 Subject: [PATCH 168/474] Now WITH installation of nitpick. --- tools/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index ed66ab1ed1..b9ae635a4f 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -20,7 +20,7 @@ endfunction() if (BUILD_TOOLS) # Allow different tools for stable builds - if (NOT STABLE_BUILD) + if (STABLE_BUILD) set(ALL_TOOLS udt-test vhacd-util From 5bf994626ca14723a9cd6cc3104052da48b3c07b Mon Sep 17 00:00:00 2001 From: amer cerkic Date: Fri, 22 Feb 2019 16:28:36 -0800 Subject: [PATCH 169/474] scaling culling projection matrix by 1.5 to help reduce teh ugly effect of stuff getting cut off too early --- .../oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp b/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp index bc8e1a5113..3b8bf50724 100644 --- a/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp +++ b/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp @@ -120,7 +120,10 @@ QRectF OculusMobileDisplayPlugin::getPlayAreaRect() { } glm::mat4 OculusMobileDisplayPlugin::getEyeProjection(Eye eye, const glm::mat4& baseProjection) const { + qDebug()<< "QQQ_ " << __FUNCTION__; + glm::mat4 result = baseProjection; + VrHandler::withOvrMobile([&](ovrMobile* session){ auto trackingState = vrapi_GetPredictedTracking2(session, 0.0); result = ovr::Fov{ trackingState.Eye[eye].ProjectionMatrix }.withZ(baseProjection); @@ -136,9 +139,13 @@ glm::mat4 OculusMobileDisplayPlugin::getCullingProjection(const glm::mat4& baseP for (size_t i = 0; i < 2; ++i) { fovs[i].extract(trackingState.Eye[i].ProjectionMatrix); } + fovs[0].extend(fovs[1]); - return fovs[0].withZ(baseProjection); + result= glm::scale( fovs[0].withZ(baseProjection),glm::vec3(1.5f)); }); + + + return result; } From 5c1e5e0c460f3fc2ce2771bac33d52a7a433b22e Mon Sep 17 00:00:00 2001 From: amer cerkic Date: Fri, 22 Feb 2019 16:30:17 -0800 Subject: [PATCH 170/474] cleanup --- .../oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp b/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp index 3b8bf50724..cf82d7aba5 100644 --- a/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp +++ b/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp @@ -120,8 +120,6 @@ QRectF OculusMobileDisplayPlugin::getPlayAreaRect() { } glm::mat4 OculusMobileDisplayPlugin::getEyeProjection(Eye eye, const glm::mat4& baseProjection) const { - qDebug()<< "QQQ_ " << __FUNCTION__; - glm::mat4 result = baseProjection; VrHandler::withOvrMobile([&](ovrMobile* session){ @@ -142,10 +140,9 @@ glm::mat4 OculusMobileDisplayPlugin::getCullingProjection(const glm::mat4& baseP fovs[0].extend(fovs[1]); result= glm::scale( fovs[0].withZ(baseProjection),glm::vec3(1.5f)); + return result; }); - - return result; } From 30b6b7f21ba7062b274e32a0b2528ac8b36600ea Mon Sep 17 00:00:00 2001 From: Sam Gondelman Date: Fri, 22 Feb 2019 16:30:36 -0800 Subject: [PATCH 171/474] let's try that again --- libraries/shared/src/SpatiallyNestable.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/shared/src/SpatiallyNestable.cpp b/libraries/shared/src/SpatiallyNestable.cpp index 8d2e5ea45b..6d8140a95d 100644 --- a/libraries/shared/src/SpatiallyNestable.cpp +++ b/libraries/shared/src/SpatiallyNestable.cpp @@ -1422,7 +1422,7 @@ QUuid SpatiallyNestable::getEditSenderID() { void SpatiallyNestable::bumpAncestorChainRenderableVersion(int depth) const { if (depth > MAX_PARENTING_CHAIN_SIZE) { - breakParentingLoop(); + // can't break the parent chain here, because it will call setParentID, which calls this return; } @@ -1432,4 +1432,4 @@ void SpatiallyNestable::bumpAncestorChainRenderableVersion(int depth) const { if (success && parent) { parent->bumpAncestorChainRenderableVersion(depth + 1); } -} \ No newline at end of file +} From 37720e9daafc252d75379f0f8d68588f71973ba2 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Fri, 22 Feb 2019 16:53:03 -0800 Subject: [PATCH 172/474] fix nametag rotation --- interface/src/ui/overlays/Overlays.cpp | 36 ++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 660220c731..62a6b88fc0 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -559,6 +559,42 @@ EntityItemProperties Overlays::convertOverlayToEntityProperties(QVariantMap& ove SET_OVERLAY_PROP_DEFAULT(textures, PathUtils::resourcesUrl() + "images/whitePixel.png"); } + { // Overlays did this conversion for rotation + auto iter = overlayProps.find("rotation"); + if (iter != overlayProps.end() && !overlayProps.contains("localRotation")) { + QUuid parentID; + { + auto iter = overlayProps.find("parentID"); + if (iter != overlayProps.end()) { + parentID = iter.value().toUuid(); + } else if (!add) { + EntityPropertyFlags desiredProperties; + desiredProperties += PROP_PARENT_ID; + parentID = DependencyManager::get()->getEntityProperties(id, desiredProperties).getParentID(); + } + } + + int parentJointIndex = -1; + { + auto iter = overlayProps.find("parentJointIndex"); + if (iter != overlayProps.end()) { + parentJointIndex = iter.value().toInt(); + } else if (!add) { + EntityPropertyFlags desiredProperties; + desiredProperties += PROP_PARENT_JOINT_INDEX; + parentJointIndex = DependencyManager::get()->getEntityProperties(id, desiredProperties).getParentJointIndex(); + } + } + + glm::quat rotation = quatFromVariant(iter.value()); + bool success = false; + glm::quat localRotation = SpatiallyNestable::worldToLocal(rotation, parentID, parentJointIndex, false, success); + if (success) { + overlayProps["rotation"] = quatToVariant(localRotation); + } + } + } + if (type == "Text" || type == "Image" || type == "Grid" || type == "Web") { glm::quat originalRotation = ENTITY_ITEM_DEFAULT_ROTATION; { From dab0df1113ffe663f93ff20246353b6bf2408cfe Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Fri, 22 Feb 2019 18:03:23 -0800 Subject: [PATCH 173/474] Trying to fix the damn gamma --- .../display-plugins/OpenGLDisplayPlugin.cpp | 5 ++++- .../src/display-plugins/SrgbToLinear.slf | 3 ++- .../src/OculusMobileDisplayPlugin.cpp | 13 ++++++++++-- .../utilities/render/deferredLighting.qml | 21 +++++++++++++++++++ 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 20fc9a2290..28de13d8c2 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -516,6 +516,7 @@ void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::Textur #ifndef USE_GLES batch.setPipeline(_presentPipeline); #else + //batch.setPipeline(_presentPipeline); batch.setPipeline(_simplePipeline); #endif batch.draw(gpu::TRIANGLE_STRIP, 4); @@ -630,7 +631,8 @@ void OpenGLDisplayPlugin::compositeScene() { batch.setStateScissorRect(ivec4(uvec2(), _compositeFramebuffer->getSize())); batch.resetViewTransform(); batch.setProjectionTransform(mat4()); - batch.setPipeline(_simplePipeline); + // batch.setPipeline(_simplePipeline); + batch.setPipeline(_presentPipeline); batch.setResourceTexture(0, _currentFrame->framebuffer->getRenderBuffer(0)); batch.draw(gpu::TRIANGLE_STRIP, 4); }); @@ -885,6 +887,7 @@ void OpenGLDisplayPlugin::updateCompositeFramebuffer() { auto renderSize = glm::uvec2(getRecommendedRenderSize()); if (!_compositeFramebuffer || _compositeFramebuffer->getSize() != renderSize) { _compositeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("OpenGLDisplayPlugin::composite", gpu::Element::COLOR_RGBA_32, renderSize.x, renderSize.y)); + // _compositeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("OpenGLDisplayPlugin::composite", gpu::Element::COLOR_SRGBA_32, renderSize.x, renderSize.y)); } } diff --git a/libraries/display-plugins/src/display-plugins/SrgbToLinear.slf b/libraries/display-plugins/src/display-plugins/SrgbToLinear.slf index 8b324c81a5..428ec821a6 100644 --- a/libraries/display-plugins/src/display-plugins/SrgbToLinear.slf +++ b/libraries/display-plugins/src/display-plugins/SrgbToLinear.slf @@ -18,5 +18,6 @@ vec3 colorToLinearRGB(vec3 srgb) { void main(void) { outFragColor.a = 1.0; - outFragColor.rgb = colorToLinearRGB(texture(colorMap, varTexCoord0).rgb); + // outFragColor.rgb = colorToLinearRGB(texture(colorMap, varTexCoord0).rgb); + outFragColor.rgb = pow(texture(colorMap, varTexCoord0).rgb, vec3(1.0 / 2.2)); } diff --git a/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp b/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp index bc8e1a5113..ea1a81c4ae 100644 --- a/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp +++ b/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp @@ -6,6 +6,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "OculusMobileDisplayPlugin.h" +#include "../../oculusMobile/src/ovr/Helpers.h" #include #include @@ -58,7 +59,7 @@ void OculusMobileDisplayPlugin::deinit() { bool OculusMobileDisplayPlugin::internalActivate() { _renderTargetSize = { 1024, 512 }; - _cullingProjection = ovr::toGlm(ovrMatrix4f_CreateProjectionFov(90.0f, 90.0f, 0.0f, 0.0f, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP)); + _cullingProjection = ovr::toGlm(ovrMatrix4f_CreateProjectionFov(90.0f, 90.0f, 90.0f, 90.0f, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP)); withOvrJava([&](const ovrJava* java){ @@ -130,6 +131,7 @@ glm::mat4 OculusMobileDisplayPlugin::getEyeProjection(Eye eye, const glm::mat4& glm::mat4 OculusMobileDisplayPlugin::getCullingProjection(const glm::mat4& baseProjection) const { glm::mat4 result = baseProjection; + VrHandler::withOvrMobile([&](ovrMobile* session){ auto trackingState = vrapi_GetPredictedTracking2(session, 0.0); ovr::Fov fovs[2]; @@ -137,7 +139,14 @@ glm::mat4 OculusMobileDisplayPlugin::getCullingProjection(const glm::mat4& baseP fovs[i].extract(trackingState.Eye[i].ProjectionMatrix); } fovs[0].extend(fovs[1]); - return fovs[0].withZ(baseProjection); + float horizontalMargin = 1.1f; + float verticalMargin = 1.5f; + fovs[0].leftRightUpDown[0] *= horizontalMargin; + fovs[0].leftRightUpDown[1] *= horizontalMargin; + fovs[0].leftRightUpDown[2] *= verticalMargin; + fovs[0].leftRightUpDown[3] *= verticalMargin; + + return fovs[0].withZ(baseProjection); }); return result; } diff --git a/scripts/developer/utilities/render/deferredLighting.qml b/scripts/developer/utilities/render/deferredLighting.qml index f5c0b8c5da..d147585212 100644 --- a/scripts/developer/utilities/render/deferredLighting.qml +++ b/scripts/developer/utilities/render/deferredLighting.qml @@ -148,6 +148,27 @@ Rectangle { } } Separator {} + Column { + anchors.left: parent.left + anchors.right: parent.right + spacing: 5 + Repeater { + model: [ "MSAA:PrepareFramebuffer:numSamples:4:1" + ] + ConfigSlider { + label: qsTr(modelData.split(":")[0]) + integral: true + config: render.mainViewTask.getConfig(modelData.split(":")[1]) + property: modelData.split(":")[2] + max: modelData.split(":")[3] + min: modelData.split(":")[4] + + anchors.left: parent.left + anchors.right: parent.right + } + } + } + Separator {} Item { height: childrenRect.height From 16bc667b95860b89f20e25494582cbf18fe9bdc0 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Fri, 22 Feb 2019 18:17:30 -0800 Subject: [PATCH 174/474] Looks like serialization order must be same a flag enum order!? Also resuscitate EntityItem debug dumps. --- assignment-client/src/avatars/AvatarMixer.cpp | 62 ++++++++++++++++++- assignment-client/src/avatars/AvatarMixer.h | 8 +++ .../src/avatars/AvatarMixerClientData.cpp | 54 +++++++++++++++- .../src/avatars/AvatarMixerClientData.h | 2 +- .../src/avatars/AvatarMixerSlave.h | 4 ++ assignment-client/src/octree/OctreeServer.cpp | 3 +- libraries/entities/src/EntityItem.cpp | 13 ++-- libraries/entities/src/EntityItem.h | 2 +- .../entities/src/EntityItemProperties.cpp | 2 +- libraries/entities/src/ZoneEntityItem.cpp | 20 +++++- libraries/entities/src/ZoneEntityItem.h | 2 + 11 files changed, 156 insertions(+), 16 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 500772c1b5..f352e02afa 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -32,6 +33,8 @@ #include #include #include +#include "../AssignmentDynamicFactory.h" +#include "../entities/AssignmentParentFinder.h" const QString AVATAR_MIXER_LOGGING_NAME = "avatar-mixer"; @@ -42,6 +45,9 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) : ThreadedAssignment(message), _slavePool(&_slaveSharedData) { + DependencyManager::registerInheritance(); + DependencyManager::set(); + // make sure we hear about node kills so we can tell the other nodes connect(DependencyManager::get().data(), &NodeList::nodeKilled, this, &AvatarMixer::handleAvatarKilled); @@ -56,6 +62,8 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) : packetReceiver.registerListener(PacketType::RequestsDomainListData, this, "handleRequestsDomainListDataPacket"); packetReceiver.registerListener(PacketType::SetAvatarTraits, this, "queueIncomingPacket"); packetReceiver.registerListener(PacketType::BulkAvatarTraitsAck, this, "queueIncomingPacket"); + packetReceiver.registerListenerForTypes({ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase }, + this, "handleOctreePacket"); packetReceiver.registerListenerForTypes({ PacketType::ReplicatedAvatarIdentity, @@ -227,6 +235,10 @@ void AvatarMixer::start() { int lockWait, nodeTransform, functor; + { + _entityViewer.queryOctree(); + } + // Allow nodes to process any pending/queued packets across our worker threads { auto start = usecTimestampNow(); @@ -847,13 +859,15 @@ AvatarMixerClientData* AvatarMixer::getOrCreateClientData(SharedNodePointer node void AvatarMixer::domainSettingsRequestComplete() { auto nodeList = DependencyManager::get(); nodeList->addSetOfNodeTypesToNodeInterestSet({ - NodeType::Agent, NodeType::EntityScriptServer, + NodeType::Agent, NodeType::EntityScriptServer, NodeType::EntityServer, NodeType::UpstreamAvatarMixer, NodeType::DownstreamAvatarMixer }); // parse the settings to pull out the values we need parseDomainServerSettings(nodeList->getDomainHandler().getSettingsObject()); + setupEntityQuery(); + // start our tight loop... start(); } @@ -941,3 +955,49 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) { qCDebug(avatars) << "Avatars other than" << _slaveSharedData.skeletonURLWhitelist << "will be replaced by" << (_slaveSharedData.skeletonReplacementURL.isEmpty() ? "default" : _slaveSharedData.skeletonReplacementURL.toString()); } } + +void AvatarMixer::setupEntityQuery() { + static char queryJsonString[] = R"({"avatarPriority": true})"; + + _entityViewer.init(); + DependencyManager::registerInheritance(); + DependencyManager::set(_entityViewer.getTree()); + _slaveSharedData.entityTree = _entityViewer.getTree(); + + QJsonParseError jsonParseError; + const QJsonDocument priorityZoneQuery(QJsonDocument::fromJson(queryJsonString, &jsonParseError)); + if (jsonParseError.error != QJsonParseError::NoError) { + qCDebug(avatars) << "Error parsing:" << queryJsonString << " - " << jsonParseError.errorString(); + return; + } + _entityViewer.getOctreeQuery().setJSONParameters(priorityZoneQuery.object()); + +} + +void AvatarMixer::handleOctreePacket(QSharedPointer message, SharedNodePointer senderNode) { + PacketType packetType = message->getType(); + + switch (packetType) { + case PacketType::OctreeStats: + break; + + case PacketType::EntityData: + _entityViewer.processDatagram(*message, senderNode); + break; + + case PacketType::EntityErase: + _entityViewer.processEraseMessage(*message, senderNode); + break; + + default: + qCDebug(avatars) << "Unexpected packet type:" << packetType; + break; + } +} + +void AvatarMixer::aboutToFinish() { + DependencyManager::destroy(); + DependencyManager::destroy(); + + ThreadedAssignment::aboutToFinish(); +} diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index 764656a2d5..6d82172995 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -19,6 +19,7 @@ #include #include +#include "../entities/EntityTreeHeadlessViewer.h" #include "AvatarMixerClientData.h" #include "AvatarMixerSlavePool.h" @@ -28,6 +29,7 @@ class AvatarMixer : public ThreadedAssignment { Q_OBJECT public: AvatarMixer(ReceivedMessage& message); + virtual void aboutToFinish() override; static bool shouldReplicateTo(const Node& from, const Node& to) { return to.getType() == NodeType::DownstreamAvatarMixer && @@ -56,6 +58,7 @@ private slots: void handleReplicatedBulkAvatarPacket(QSharedPointer message); void domainSettingsRequestComplete(); void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID); + void handleOctreePacket(QSharedPointer message, SharedNodePointer senderNode); void start(); private: @@ -70,8 +73,13 @@ private: void optionallyReplicatePacket(ReceivedMessage& message, const Node& node); + void setupEntityQuery(); + p_high_resolution_clock::time_point _lastFrameTimestamp; + // Attach to entity tree for avatar-priority zone info. + EntityTreeHeadlessViewer _entityViewer; + // FIXME - new throttling - use these values somehow float _trailingMixRatio { 0.0f }; float _throttlingRatio { 0.0f }; diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index b7d2f5cdf8..9edbae12d8 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -16,6 +16,8 @@ #include #include +#include +#include #include "AvatarMixerSlave.h" @@ -62,7 +64,7 @@ int AvatarMixerClientData::processPackets(const SlaveSharedData& slaveSharedData switch (packet->getType()) { case PacketType::AvatarData: - parseData(*packet); + parseData(*packet, slaveSharedData); break; case PacketType::SetAvatarTraits: processSetTraitsMessage(*packet, slaveSharedData, *node); @@ -80,7 +82,38 @@ int AvatarMixerClientData::processPackets(const SlaveSharedData& slaveSharedData return packetsProcessed; } -int AvatarMixerClientData::parseData(ReceivedMessage& message) { +namespace { + using std::static_pointer_cast; + + // Operator to find if a point is within an avatar-priority (hero) Zone Entity. + struct FindPriorityZone { + glm::vec3 position; + bool isInPriorityZone { false }; + static bool operation(const OctreeElementPointer& element, void* extraData) { + auto findPriorityZone = static_cast(extraData); + if (element->getAACube().contains(findPriorityZone->position)) { + const EntityTreeElementPointer entityTreeElement = static_pointer_cast(element); + entityTreeElement->forEachEntity([&findPriorityZone](EntityItemPointer item) { + if (item->getType() == EntityTypes::Zone + && item->contains(findPriorityZone->position) + && static_pointer_cast(item)->getAvatarPriority()) { + findPriorityZone->isInPriorityZone = true; + } + }); + if (findPriorityZone->isInPriorityZone) { + return false; // For now, stop at first hit. + } else { + return true; + } + } else { // Position isn't within this subspace, so end recursion. + return false; + } + } + }; + +} // Close anonymous namespace. + +int AvatarMixerClientData::parseData(ReceivedMessage& message, const SlaveSharedData& slaveSharedData) { // pull the sequence number from the data first uint16_t sequenceNumber; @@ -90,9 +123,24 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message) { incrementNumOutOfOrderSends(); } _lastReceivedSequenceNumber = sequenceNumber; + glm::vec3 oldPosition = getPosition(); // compute the offset to the data payload - return _avatar->parseDataFromBuffer(message.readWithoutCopy(message.getBytesLeftToRead())); + if (!_avatar->parseDataFromBuffer(message.readWithoutCopy(message.getBytesLeftToRead()))) { + return false; + } + + auto newPosition = getPosition(); + if (newPosition != oldPosition) { + EntityTree& entityTree = *slaveSharedData.entityTree; + FindPriorityZone findPriorityZone { newPosition, false } ; + entityTree.recurseTreeWithOperation(&FindPriorityZone::operation, &findPriorityZone); + if (findPriorityZone.isInPriorityZone) { + // Tag avatar as hero ... + } + } + + return true; } void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message, diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index 843f19cf22..45d508088c 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -45,7 +45,7 @@ public: using HRCTime = p_high_resolution_clock::time_point; using PerNodeTraitVersions = std::unordered_map; - int parseData(ReceivedMessage& message) override; + int parseData(ReceivedMessage& message, const SlaveSharedData& SlaveSharedData); AvatarData& getAvatar() { return *_avatar; } const AvatarData& getAvatar() const { return *_avatar; } const AvatarData* getConstAvatarData() const { return _avatar.get(); } diff --git a/assignment-client/src/avatars/AvatarMixerSlave.h b/assignment-client/src/avatars/AvatarMixerSlave.h index 91bb02fd55..28d99748fa 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.h +++ b/assignment-client/src/avatars/AvatarMixerSlave.h @@ -90,9 +90,13 @@ public: } }; +class EntityTree; +using EntityTreePointer = std::shared_ptr; + struct SlaveSharedData { QStringList skeletonURLWhitelist; QUrl skeletonReplacementURL; + EntityTreePointer entityTree; }; class AvatarMixerSlave { diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index e993bea358..477d3dd612 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -1203,7 +1203,8 @@ void OctreeServer::beginRunning() { auto nodeList = DependencyManager::get(); // we need to ask the DS about agents so we can ping/reply with them - nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer }); + nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer, + NodeType::AvatarMixer }); beforeRun(); // after payload has been processed diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 5c423b2fe5..25e5893078 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -41,6 +41,7 @@ #include "EntitySimulation.h" #include "EntityDynamicFactoryInterface.h" +//#define WANT_DEBUG Q_DECLARE_METATYPE(EntityItemPointer); int entityItemPointernMetaTypeId = qRegisterMetaType(); @@ -499,6 +500,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef } #ifdef WANT_DEBUG + { quint64 lastEdited = getLastEdited(); float editedAgo = getEditedAgo(); QString agoAsString = formatSecondsElapsed(editedAgo); @@ -512,6 +514,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef qCDebug(entities) << " age=" << getAge() << "seconds - " << ageAsString; qCDebug(entities) << " lastEdited =" << lastEdited; qCDebug(entities) << " ago=" << editedAgo << "seconds - " << agoAsString; + } #endif quint64 lastEditedFromBuffer = 0; @@ -1096,7 +1099,7 @@ void EntityItem::simulate(const quint64& now) { qCDebug(entities) << " hasGravity=" << hasGravity(); qCDebug(entities) << " hasAcceleration=" << hasAcceleration(); qCDebug(entities) << " hasAngularVelocity=" << hasAngularVelocity(); - qCDebug(entities) << " getAngularVelocity=" << getAngularVelocity(); + qCDebug(entities) << " getAngularVelocity=" << getLocalAngularVelocity(); qCDebug(entities) << " isMortal=" << isMortal(); qCDebug(entities) << " getAge()=" << getAge(); qCDebug(entities) << " getLifetime()=" << getLifetime(); @@ -1108,12 +1111,12 @@ void EntityItem::simulate(const quint64& now) { qCDebug(entities) << " hasGravity=" << hasGravity(); qCDebug(entities) << " hasAcceleration=" << hasAcceleration(); qCDebug(entities) << " hasAngularVelocity=" << hasAngularVelocity(); - qCDebug(entities) << " getAngularVelocity=" << getAngularVelocity(); + qCDebug(entities) << " getAngularVelocity=" << getLocalAngularVelocity(); } if (hasAngularVelocity()) { qCDebug(entities) << " CHANGING...="; qCDebug(entities) << " hasAngularVelocity=" << hasAngularVelocity(); - qCDebug(entities) << " getAngularVelocity=" << getAngularVelocity(); + qCDebug(entities) << " getAngularVelocity=" << getLocalAngularVelocity(); } if (isMortal()) { qCDebug(entities) << " MORTAL...="; @@ -2649,13 +2652,13 @@ bool EntityItem::matchesJSONFilters(const QJsonObject& jsonFilters) const { // ALL entity properties. Some work will need to be done to the property system so that it can be more flexible // (to grab the value and default value of a property given the string representation of that property, for example) - // currently the only property filter we handle is '+' for serverScripts + // currently the only property filter we handle in EntityItem is '+' for serverScripts // which means that we only handle a filtered query asking for entities where the serverScripts property is non-default static const QString SERVER_SCRIPTS_PROPERTY = "serverScripts"; foreach(const auto& property, jsonFilters.keys()) { - if (property == SERVER_SCRIPTS_PROPERTY && jsonFilters[property] == EntityQueryFilterSymbol::NonDefault) { + if (property == SERVER_SCRIPTS_PROPERTY && jsonFilters[property] == EntityQueryFilterSymbol::NonDefault) { // check if this entity has a non-default value for serverScripts if (_serverScripts != ENTITY_ITEM_DEFAULT_SERVER_SCRIPTS) { return true; diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index ec7ad78313..87dca3f314 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -516,7 +516,7 @@ public: QUuid getLastEditedBy() const { return _lastEditedBy; } void setLastEditedBy(QUuid value) { _lastEditedBy = value; } - bool matchesJSONFilters(const QJsonObject& jsonFilters) const; + virtual bool matchesJSONFilters(const QJsonObject& jsonFilters) const; virtual bool getMeshes(MeshProxyList& result) { return true; } diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 103f5dbab7..96160ebd93 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -3178,13 +3178,13 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_FLYING_ALLOWED, properties.getFlyingAllowed()); APPEND_ENTITY_PROPERTY(PROP_GHOSTING_ALLOWED, properties.getGhostingAllowed()); APPEND_ENTITY_PROPERTY(PROP_FILTER_URL, properties.getFilterURL()); - APPEND_ENTITY_PROPERTY(PROP_AVATAR_PRIORITY, properties.getAvatarPriority()); APPEND_ENTITY_PROPERTY(PROP_KEY_LIGHT_MODE, (uint32_t)properties.getKeyLightMode()); APPEND_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_MODE, (uint32_t)properties.getAmbientLightMode()); APPEND_ENTITY_PROPERTY(PROP_SKYBOX_MODE, (uint32_t)properties.getSkyboxMode()); APPEND_ENTITY_PROPERTY(PROP_HAZE_MODE, (uint32_t)properties.getHazeMode()); APPEND_ENTITY_PROPERTY(PROP_BLOOM_MODE, (uint32_t)properties.getBloomMode()); + APPEND_ENTITY_PROPERTY(PROP_AVATAR_PRIORITY, properties.getAvatarPriority()); } if (properties.getType() == EntityTypes::PolyVox) { diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 13c7273d94..83619caa3c 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -112,13 +112,13 @@ bool ZoneEntityItem::setSubClassProperties(const EntityItemProperties& propertie SET_ENTITY_PROPERTY_FROM_PROPERTIES(flyingAllowed, setFlyingAllowed); SET_ENTITY_PROPERTY_FROM_PROPERTIES(ghostingAllowed, setGhostingAllowed); SET_ENTITY_PROPERTY_FROM_PROPERTIES(filterURL, setFilterURL); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(avatarPriority, setAvatarPriority); SET_ENTITY_PROPERTY_FROM_PROPERTIES(keyLightMode, setKeyLightMode); SET_ENTITY_PROPERTY_FROM_PROPERTIES(ambientLightMode, setAmbientLightMode); SET_ENTITY_PROPERTY_FROM_PROPERTIES(skyboxMode, setSkyboxMode); SET_ENTITY_PROPERTY_FROM_PROPERTIES(hazeMode, setHazeMode); SET_ENTITY_PROPERTY_FROM_PROPERTIES(bloomMode, setBloomMode); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(avatarPriority, setAvatarPriority); somethingChanged = somethingChanged || _keyLightPropertiesChanged || _ambientLightPropertiesChanged || _stagePropertiesChanged || _skyboxPropertiesChanged || _hazePropertiesChanged || _bloomPropertiesChanged; @@ -188,13 +188,13 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, READ_ENTITY_PROPERTY(PROP_FLYING_ALLOWED, bool, setFlyingAllowed); READ_ENTITY_PROPERTY(PROP_GHOSTING_ALLOWED, bool, setGhostingAllowed); READ_ENTITY_PROPERTY(PROP_FILTER_URL, QString, setFilterURL); - READ_ENTITY_PROPERTY(PROP_AVATAR_PRIORITY, bool, setAvatarPriority); READ_ENTITY_PROPERTY(PROP_KEY_LIGHT_MODE, uint32_t, setKeyLightMode); READ_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_MODE, uint32_t, setAmbientLightMode); READ_ENTITY_PROPERTY(PROP_SKYBOX_MODE, uint32_t, setSkyboxMode); READ_ENTITY_PROPERTY(PROP_HAZE_MODE, uint32_t, setHazeMode); READ_ENTITY_PROPERTY(PROP_BLOOM_MODE, uint32_t, setBloomMode); + READ_ENTITY_PROPERTY(PROP_AVATAR_PRIORITY, bool, setAvatarPriority); return bytesRead; } @@ -254,13 +254,13 @@ void ZoneEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits APPEND_ENTITY_PROPERTY(PROP_FLYING_ALLOWED, getFlyingAllowed()); APPEND_ENTITY_PROPERTY(PROP_GHOSTING_ALLOWED, getGhostingAllowed()); APPEND_ENTITY_PROPERTY(PROP_FILTER_URL, getFilterURL()); - APPEND_ENTITY_PROPERTY(PROP_AVATAR_PRIORITY, getAvatarPriority()); APPEND_ENTITY_PROPERTY(PROP_KEY_LIGHT_MODE, (uint32_t)getKeyLightMode()); APPEND_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_MODE, (uint32_t)getAmbientLightMode()); APPEND_ENTITY_PROPERTY(PROP_SKYBOX_MODE, (uint32_t)getSkyboxMode()); APPEND_ENTITY_PROPERTY(PROP_HAZE_MODE, (uint32_t)getHazeMode()); APPEND_ENTITY_PROPERTY(PROP_BLOOM_MODE, (uint32_t)getBloomMode()); + APPEND_ENTITY_PROPERTY(PROP_AVATAR_PRIORITY, getAvatarPriority()); } void ZoneEntityItem::debugDump() const { @@ -274,6 +274,7 @@ void ZoneEntityItem::debugDump() const { qCDebug(entities) << " _ambientLightMode:" << EntityItemProperties::getComponentModeAsString(_ambientLightMode); qCDebug(entities) << " _skyboxMode:" << EntityItemProperties::getComponentModeAsString(_skyboxMode); qCDebug(entities) << " _bloomMode:" << EntityItemProperties::getComponentModeAsString(_bloomMode); + qCDebug(entities) << " _avatarPriority:" << getAvatarPriority(); _keyLightProperties.debugDump(); _ambientLightProperties.debugDump(); @@ -469,3 +470,16 @@ void ZoneEntityItem::fetchCollisionGeometryResource() { _shapeResource = DependencyManager::get()->getCollisionGeometryResource(hullURL); } } + +bool ZoneEntityItem::matchesJSONFilters(const QJsonObject& jsonFilters) const { + // currently the only property filter we handle in ZoneEntityItem is value of avatarPriority + + static const QString AVATAR_PRIORITY_PROPERTY = "avatarPriority"; + + if (jsonFilters.contains(AVATAR_PRIORITY_PROPERTY)) { + return (jsonFilters[AVATAR_PRIORITY_PROPERTY].toBool() == _avatarPriority); + } + + // Chain to base: + return EntityItem::matchesJSONFilters(jsonFilters); +} diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index 20bab7c710..a3e668b6f6 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -66,6 +66,8 @@ public: QString getCompoundShapeURL() const; virtual void setCompoundShapeURL(const QString& url); + virtual bool matchesJSONFilters(const QJsonObject& jsonFilters) const override; + KeyLightPropertyGroup getKeyLightProperties() const { return resultWithReadLock([&] { return _keyLightProperties; }); } AmbientLightPropertyGroup getAmbientLightProperties() const { return resultWithReadLock([&] { return _ambientLightProperties; }); } From d040803391511f864ee5f116e1e082f92682f168 Mon Sep 17 00:00:00 2001 From: Clement Date: Fri, 22 Feb 2019 18:07:26 -0800 Subject: [PATCH 175/474] Delete old nodes with identical socket --- libraries/networking/src/LimitedNodeList.cpp | 176 +++++++++---------- 1 file changed, 84 insertions(+), 92 deletions(-) diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 8b9e37569c..eaa02f059e 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -645,103 +645,95 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, Node::LocalID localID, bool isReplicated, bool isUpstream, const QUuid& connectionSecret, const NodePermissions& permissions) { - QReadLocker readLocker(&_nodeMutex); - NodeHash::const_iterator it = _nodeHash.find(uuid); + { + QReadLocker readLocker(&_nodeMutex); + NodeHash::const_iterator it = _nodeHash.find(uuid); - if (it != _nodeHash.end()) { - SharedNodePointer& matchingNode = it->second; + if (it != _nodeHash.end()) { + SharedNodePointer& matchingNode = it->second; - matchingNode->setPublicSocket(publicSocket); - matchingNode->setLocalSocket(localSocket); - matchingNode->setPermissions(permissions); - matchingNode->setConnectionSecret(connectionSecret); - matchingNode->setIsReplicated(isReplicated); - matchingNode->setIsUpstream(isUpstream || NodeType::isUpstream(nodeType)); - matchingNode->setLocalID(localID); + matchingNode->setPublicSocket(publicSocket); + matchingNode->setLocalSocket(localSocket); + matchingNode->setPermissions(permissions); + matchingNode->setConnectionSecret(connectionSecret); + matchingNode->setIsReplicated(isReplicated); + matchingNode->setIsUpstream(isUpstream || NodeType::isUpstream(nodeType)); + matchingNode->setLocalID(localID); - return matchingNode; - } else { - auto it = _connectionIDs.find(uuid); - if (it == _connectionIDs.end()) { - _connectionIDs[uuid] = INITIAL_CONNECTION_ID; + return matchingNode; } - - // we didn't have this node, so add them - Node* newNode = new Node(uuid, nodeType, publicSocket, localSocket); - newNode->setIsReplicated(isReplicated); - newNode->setIsUpstream(isUpstream || NodeType::isUpstream(nodeType)); - newNode->setConnectionSecret(connectionSecret); - newNode->setPermissions(permissions); - newNode->setLocalID(localID); - - // move the newly constructed node to the LNL thread - newNode->moveToThread(thread()); - - if (nodeType == NodeType::AudioMixer) { - LimitedNodeList::flagTimeForConnectionStep(LimitedNodeList::AddedAudioMixer); - } - - SharedNodePointer newNodePointer(newNode, &QObject::deleteLater); - - // if this is a solo node type, we assume that the DS has replaced its assignment and we should kill the previous node - if (SOLO_NODE_TYPES.count(newNode->getType())) { - // while we still have the read lock, see if there is a previous solo node we'll need to remove - auto previousSoloIt = std::find_if(_nodeHash.cbegin(), _nodeHash.cend(), [newNode](const UUIDNodePair& nodePair){ - return nodePair.second->getType() == newNode->getType(); - }); - - if (previousSoloIt != _nodeHash.cend()) { - // we have a previous solo node, switch to a write lock so we can remove it - readLocker.unlock(); - - QWriteLocker writeLocker(&_nodeMutex); - - auto oldSoloNode = previousSoloIt->second; - - _localIDMap.unsafe_erase(oldSoloNode->getLocalID()); - _nodeHash.unsafe_erase(previousSoloIt); - handleNodeKill(oldSoloNode); - - // convert the current lock back to a read lock for insertion of new node - writeLocker.unlock(); - readLocker.relock(); - } - } - - // insert the new node and release our read lock -#if defined(Q_OS_ANDROID) || (defined(__clang__) && defined(Q_OS_LINUX)) - _nodeHash.insert(UUIDNodePair(newNode->getUUID(), newNodePointer)); - _localIDMap.insert(std::pair(localID, newNodePointer)); -#else - _nodeHash.emplace(newNode->getUUID(), newNodePointer); - _localIDMap.emplace(localID, newNodePointer); -#endif - readLocker.unlock(); - - qCDebug(networking) << "Added" << *newNode; - - auto weakPtr = newNodePointer.toWeakRef(); // We don't want the lambdas to hold a strong ref - - emit nodeAdded(newNodePointer); - if (newNodePointer->getActiveSocket()) { - emit nodeActivated(newNodePointer); - } else { - connect(newNodePointer.data(), &NetworkPeer::socketActivated, this, [this, weakPtr] { - auto sharedPtr = weakPtr.lock(); - if (sharedPtr) { - emit nodeActivated(sharedPtr); - disconnect(sharedPtr.data(), &NetworkPeer::socketActivated, this, 0); - } - }); - } - - // Signal when a socket changes, so we can start the hole punch over. - connect(newNodePointer.data(), &NetworkPeer::socketUpdated, this, [this, weakPtr] { - emit nodeSocketUpdated(weakPtr); - }); - - return newNodePointer; } + + auto removeOldNode = [&](auto node) { + if (node) { + QWriteLocker writeLocker(&_nodeMutex); + _localIDMap.unsafe_erase(node->getLocalID()); + _nodeHash.unsafe_erase(node->getUUID()); + handleNodeKill(node); + } + }; + + // if this is a solo node type, we assume that the DS has replaced its assignment and we should kill the previous node + if (SOLO_NODE_TYPES.count(nodeType)) { + removeOldNode(soloNodeOfType(nodeType)); + } + // If there is a new node with the same socket, this is a reconnection, kill the old node + removeOldNode(findNodeWithAddr(publicSocket)); + removeOldNode(findNodeWithAddr(localSocket)); + + auto it = _connectionIDs.find(uuid); + if (it == _connectionIDs.end()) { + _connectionIDs[uuid] = INITIAL_CONNECTION_ID; + } + + // we didn't have this node, so add them + Node* newNode = new Node(uuid, nodeType, publicSocket, localSocket); + newNode->setIsReplicated(isReplicated); + newNode->setIsUpstream(isUpstream || NodeType::isUpstream(nodeType)); + newNode->setConnectionSecret(connectionSecret); + newNode->setPermissions(permissions); + newNode->setLocalID(localID); + + // move the newly constructed node to the LNL thread + newNode->moveToThread(thread()); + + if (nodeType == NodeType::AudioMixer) { + LimitedNodeList::flagTimeForConnectionStep(LimitedNodeList::AddedAudioMixer); + } + + SharedNodePointer newNodePointer(newNode, &QObject::deleteLater); + + + { + QReadLocker readLocker(&_nodeMutex); + // insert the new node and release our read lock + _nodeHash.insert({ newNode->getUUID(), newNodePointer }); + _localIDMap.insert({ localID, newNodePointer }); + } + + qCDebug(networking) << "Added" << *newNode; + + auto weakPtr = newNodePointer.toWeakRef(); // We don't want the lambdas to hold a strong ref + + emit nodeAdded(newNodePointer); + if (newNodePointer->getActiveSocket()) { + emit nodeActivated(newNodePointer); + } else { + connect(newNodePointer.data(), &NetworkPeer::socketActivated, this, [this, weakPtr] { + auto sharedPtr = weakPtr.lock(); + if (sharedPtr) { + emit nodeActivated(sharedPtr); + disconnect(sharedPtr.data(), &NetworkPeer::socketActivated, this, 0); + } + }); + } + + // Signal when a socket changes, so we can start the hole punch over. + connect(newNodePointer.data(), &NetworkPeer::socketUpdated, this, [this, weakPtr] { + emit nodeSocketUpdated(weakPtr); + }); + + return newNodePointer; } std::unique_ptr LimitedNodeList::constructPingPacket(const QUuid& nodeId, PingType_t pingType) { From 4e9d162172c7c28c3d813373e1d8d4566ec164a3 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sun, 24 Feb 2019 13:23:20 +1300 Subject: [PATCH 176/474] AccountServices, HifiAbout, and WalletScriptingInterface JSDoc review --- interface/src/AboutUtil.h | 14 ++++---- interface/src/commerce/Wallet.h | 8 ++--- .../AccountServicesScriptingInterface.cpp | 4 +-- .../AccountServicesScriptingInterface.h | 32 +++++++++---------- .../src/scripting/WalletScriptingInterface.h | 22 ++++++------- 5 files changed, 40 insertions(+), 40 deletions(-) diff --git a/interface/src/AboutUtil.h b/interface/src/AboutUtil.h index c06255aaa5..4a5074857d 100644 --- a/interface/src/AboutUtil.h +++ b/interface/src/AboutUtil.h @@ -16,8 +16,8 @@ #include /**jsdoc - * The HifiAbout API provides information about the version of Interface that is currently running. It also - * provides the ability to open a Web page in an Interface browser window. + * The HifiAbout API provides information about the version of Interface that is currently running. It also + * has the functionality to open a web page in an Interface browser window. * * @namespace HifiAbout * @@ -30,9 +30,9 @@ * @property {string} qtVersion - The Qt version used in Interface that is currently running. Read-only. * * @example Report build information for the version of Interface currently running. - * print("HiFi build date: " + HifiAbout.buildDate); // 11 Feb 2019 - * print("HiFi version: " + HifiAbout.buildVersion); // 0.78.0 - * print("Qt version: " + HifiAbout.qtVersion); // 5.10.1 + * print("HiFi build date: " + HifiAbout.buildDate); // Returns the build date of the version of Interface currently running on your machine. + * print("HiFi version: " + HifiAbout.buildVersion); // Returns the build version of Interface currently running on your machine. + * print("Qt version: " + HifiAbout.qtVersion); // Returns the Qt version details of the version of Interface currently running on your machine. */ class AboutUtil : public QObject { @@ -52,9 +52,9 @@ public: public slots: /**jsdoc - * Display a Web page in an Interface browser window. + * Display a web page in an Interface browser window. * @function HifiAbout.openUrl - * @param {string} url - The URL of the Web page to display. + * @param {string} url - The URL of the web page you want to view in Interface. */ void openUrl(const QString &url) const; private: diff --git a/interface/src/commerce/Wallet.h b/interface/src/commerce/Wallet.h index 5e5e6c9b4f..fdd6b5e2a6 100644 --- a/interface/src/commerce/Wallet.h +++ b/interface/src/commerce/Wallet.h @@ -62,8 +62,8 @@ public: * ValueMeaningDescription * * - * 0Not logged inThe user isn't logged in. - * 1Not set upThe user's wallet isn't set up. + * 0Not logged inThe user is not logged in. + * 1Not set upThe user's wallet has not been set up. * 2Pre-existingThere is a wallet present on the server but not one * locally. * 3ConflictingThere is a wallet present on the server plus one present locally, @@ -73,8 +73,8 @@ public: * 5ReadyThe wallet is ready for use. * * - *

Wallets used to be stored locally but now they're stored on the server, unless the computer once had a wallet stored - * locally in which case the wallet may be present in both places.

+ *

Wallets used to be stored locally but now they're only stored on the server. A wallet is present in both places if + * your computer previously stored its information locally.

* @typedef {number} WalletScriptingInterface.WalletStatus */ enum WalletStatus { diff --git a/interface/src/scripting/AccountServicesScriptingInterface.cpp b/interface/src/scripting/AccountServicesScriptingInterface.cpp index a3597886e9..5ede7c7a98 100644 --- a/interface/src/scripting/AccountServicesScriptingInterface.cpp +++ b/interface/src/scripting/AccountServicesScriptingInterface.cpp @@ -115,8 +115,8 @@ DownloadInfoResult::DownloadInfoResult() : /**jsdoc * Information on the assets currently being downloaded and pending download. * @typedef {object} AccountServices.DownloadInfoResult - * @property {number[]} downloading - The percentage complete for each asset currently being downloaded. - * @property {number} pending - The number of assets waiting to be download. + * @property {number[]} downloading - The download percentage of each asset currently downloading. + * @property {number} pending - The number of assets pending download. */ QScriptValue DownloadInfoResultToScriptValue(QScriptEngine* engine, const DownloadInfoResult& result) { QScriptValue object = engine->newObject(); diff --git a/interface/src/scripting/AccountServicesScriptingInterface.h b/interface/src/scripting/AccountServicesScriptingInterface.h index c08181d7c9..b5c4a6e0df 100644 --- a/interface/src/scripting/AccountServicesScriptingInterface.h +++ b/interface/src/scripting/AccountServicesScriptingInterface.h @@ -38,19 +38,19 @@ class AccountServicesScriptingInterface : public QObject { Q_OBJECT /**jsdoc - * The AccountServices API provides functions related to user connectivity, visibility, and asset download - * progress. + * The AccountServices API provides functions that give information on user connectivity, visibility, and + * asset download progress. * * @hifi-interface * @hifi-client-entity * @hifi-avatar * * @namespace AccountServices - * @property {string} username - The user name if the user is logged in, otherwise "Unknown user". - * Read-only. + * @property {string} username - The user name of the user logged in. If there is no user logged in, it is + * "Unknown user". Read-only. * @property {boolean} loggedIn - true if the user is logged in, otherwise false. * Read-only. - * @property {string} findableBy - The user's visibility to other people:
+ * @property {string} findableBy - The user's visibility to other users:
* "none" - user appears offline.
* "friends" - user is visible only to friends.
* "connections" - user is visible to friends and connections.
@@ -74,23 +74,23 @@ public: public slots: /**jsdoc - * Get information on the progress of downloading assets in the domain. + * Gets information on the download progress of assets in the domain. * @function AccountServices.getDownloadInfo - * @returns {AccountServices.DownloadInfoResult} Information on the progress of assets download. + * @returns {AccountServices.DownloadInfoResult} Information on the download progress of assets. */ DownloadInfoResult getDownloadInfo(); /**jsdoc - * Cause a {@link AccountServices.downloadInfoChanged|downloadInfoChanged} signal to be triggered with information on the - * current progress of the download of assets in the domain. + * Triggers a {@link AccountServices.downloadInfoChanged|downloadInfoChanged} signal with information on the current + * download progress of the assets in the domain. * @function AccountServices.updateDownloadInfo */ void updateDownloadInfo(); /**jsdoc - * Check whether the user is logged in. + * Checks whether the user is logged in. * @function AccountServices.isLoggedIn - * @returns {boolean} true if the user is logged in, false otherwise. + * @returns {boolean} true if the user is logged in, false if not. * @example Report whether you are logged in. * var isLoggedIn = AccountServices.isLoggedIn(); * print("You are logged in: " + isLoggedIn); // true or false @@ -100,7 +100,7 @@ public slots: /**jsdoc * Prompts the user to log in (the login dialog is displayed) if they're not already logged in. * @function AccountServices.checkAndSignalForAccessToken - * @returns {boolean} true if the user is already logged in, false otherwise. + * @returns {boolean} true if the user is logged in, false if not. */ bool checkAndSignalForAccessToken(); @@ -140,7 +140,7 @@ signals: /**jsdoc * Triggered when the username logged in with changes, i.e., when the user logs in or out. * @function AccountServices.myUsernameChanged - * @param {string} username - The username logged in with if the user is logged in, otherwise "". + * @param {string} username - The user name of the user logged in. If there is no user logged in, it is "". * @returns {Signal} * @example Report when your username changes. * AccountServices.myUsernameChanged.connect(function (username) { @@ -150,9 +150,9 @@ signals: void myUsernameChanged(const QString& username); /**jsdoc - * Triggered when the progress of the download of assets for the domain changes. + * Triggered when the download progress of the assets in the domain changes. * @function AccountServices.downloadInfoChanged - * @param {AccountServices.DownloadInfoResult} downloadInfo - Information on the progress of assets download. + * @param {AccountServices.DownloadInfoResult} downloadInfo - Information on the download progress of assets. * @returns {Signal} */ void downloadInfoChanged(DownloadInfoResult info); @@ -186,7 +186,7 @@ signals: /**jsdoc * Triggered when the login status of the user changes. * @function AccountServices.loggedInChanged - * @param {boolean} loggedIn - true if the user is logged in, otherwise false. + * @param {boolean} loggedIn - true if the user is logged in, false if not. * @returns {Signal} * @example Report when your login status changes. * AccountServices.loggedInChanged.connect(function(loggedIn) { diff --git a/interface/src/scripting/WalletScriptingInterface.h b/interface/src/scripting/WalletScriptingInterface.h index 7482b8be00..3ef9c7953a 100644 --- a/interface/src/scripting/WalletScriptingInterface.h +++ b/interface/src/scripting/WalletScriptingInterface.h @@ -41,8 +41,8 @@ public: * * @property {WalletScriptingInterface.WalletStatus} walletStatus - The status of the user's wallet. Read-only. * @property {boolean} limitedCommerce - true if Interface is running in limited commerce mode. In limited commerce - * mode, certain Interface functionality is disabled, e.g., users can't buy non-free items from the Marketplace. The Oculus - * Store version of Interface runs in limited commerce mode. Read-only. + * mode, certain Interface functionalities are disabled, e.g., users can't buy items that are not free from the Marketplace. + * The Oculus Store version of Interface runs in limited commerce mode. Read-only. */ class WalletScriptingInterface : public QObject, public Dependency { Q_OBJECT @@ -55,16 +55,16 @@ public: WalletScriptingInterface(); /**jsdoc - * Check and update the user's wallet status. + * Checks and updates the user's wallet status. * @function WalletScriptingInterface.refreshWalletStatus */ Q_INVOKABLE void refreshWalletStatus(); /**jsdoc - * Get the current status of the user's wallet. + * Gets the current status of the user's wallet. * @function WalletScriptingInterface.getWalletStatus * @returns {WalletScriptingInterface.WalletStatus} - * @example Two ways to report your wallet status. + * @example Use two methods to report your wallet's status. * print("Wallet status: " + WalletScriptingInterface.walletStatus); // Same value as next line. * print("Wallet status: " + WalletScriptingInterface.getWalletStatus()); */ @@ -74,11 +74,11 @@ public: * Check that a certified avatar entity is owned by the avatar whose entity it is. The result of the check is provided via * the {@link WalletScriptingInterface.ownershipVerificationSuccess|ownershipVerificationSuccess} and * {@link WalletScriptingInterface.ownershipVerificationFailed|ownershipVerificationFailed} signals.
- * Warning: Neither of these signals fire if the entity is not an avatar entity or it's not a certified - * entity. + * Warning: Neither of these signals are triggered if the entity is not an avatar entity or is not + * certified. * @function WalletScriptingInterface.proveAvatarEntityOwnershipVerification - * @param {Uuid} entityID - The ID of the avatar entity to check. - * @example Check ownership of all nearby certified avatar entities. + * @param {Uuid} entityID - The avatar entity's ID. + * @example Check the ownership of all nearby certified avatar entities. * // Set up response handling. * function ownershipSuccess(entityID) { * print("Ownership test succeeded for: " + entityID); @@ -118,7 +118,7 @@ public: signals: /**jsdoc - * Triggered when the status of the user's wallet changes. + * Triggered when the user's wallet status changes. * @function WalletScriptingInterface.walletStatusChanged * @returns {Signal} * @example Report when your wallet status changes, e.g., when you log in and out. @@ -136,7 +136,7 @@ signals: void limitedCommerceChanged(); /**jsdoc - * Triggered when the user rezzes a certified entity but the user's wallet is not ready and so the certified location of the + * Triggered when the user rezzes a certified entity but the user's wallet is not ready. So the certified location of the * entity cannot be updated in the metaverse. * @function WalletScriptingInterface.walletNotSetup * @returns {Signal} From 36c66f019d1fcf79bdf7a49b90f789470da293b3 Mon Sep 17 00:00:00 2001 From: ingerjm0 Date: Sat, 23 Feb 2019 21:10:40 -0800 Subject: [PATCH 177/474] Add @signal tag to jsdoc; use custom jsdoc templates --- tools/jsdoc/config.json | 13 + tools/jsdoc/hifi-jsdoc-template/LICENSE.md | 61 ++ tools/jsdoc/hifi-jsdoc-template/README.md | 42 + tools/jsdoc/hifi-jsdoc-template/package.json | 59 ++ tools/jsdoc/hifi-jsdoc-template/publish.js | 772 ++++++++++++++++++ .../static/fonts/Cairo-Bold.ttf | Bin 0 -> 170996 bytes .../static/fonts/proximanova-regular.otf | Bin 0 -> 62892 bytes .../static/images/white-logo.png | Bin 0 -> 31280 bytes .../static/scripts/collapse.js | 11 + .../static/scripts/jquery-3.1.1.min.js | 4 + .../static/scripts/linenumber.js | 25 + .../scripts/prettify/Apache-License-2.0.txt | 202 +++++ .../static/scripts/prettify/lang-css.js | 2 + .../static/scripts/prettify/prettify.js | 28 + .../static/scripts/search.js | 42 + .../static/styles/jsdoc.css | 731 +++++++++++++++++ .../static/styles/prettify.css | 132 +++ .../hifi-jsdoc-template/tmpl/augments.tmpl | 10 + .../hifi-jsdoc-template/tmpl/container.tmpl | 283 +++++++ .../hifi-jsdoc-template/tmpl/details.tmpl | 143 ++++ .../hifi-jsdoc-template/tmpl/example.tmpl | 141 ++++ .../hifi-jsdoc-template/tmpl/examples.tmpl | 13 + .../hifi-jsdoc-template/tmpl/exceptions.tmpl | 32 + .../hifi-jsdoc-template/tmpl/layout.tmpl | 73 ++ .../hifi-jsdoc-template/tmpl/mainpage.tmpl | 10 + .../hifi-jsdoc-template/tmpl/members.tmpl | 41 + .../hifi-jsdoc-template/tmpl/method.tmpl | 119 +++ .../hifi-jsdoc-template/tmpl/methodList.tmpl | 107 +++ .../hifi-jsdoc-template/tmpl/paramList.tmpl | 67 ++ .../hifi-jsdoc-template/tmpl/params.tmpl | 115 +++ .../hifi-jsdoc-template/tmpl/properties.tmpl | 103 +++ .../hifi-jsdoc-template/tmpl/returns.tmpl | 8 + .../hifi-jsdoc-template/tmpl/returnsSimp.tmpl | 6 + .../hifi-jsdoc-template/tmpl/signal.tmpl | 123 +++ .../hifi-jsdoc-template/tmpl/signalList.tmpl | 92 +++ .../hifi-jsdoc-template/tmpl/source.tmpl | 8 + .../hifi-jsdoc-template/tmpl/tutorial.tmpl | 19 + .../jsdoc/hifi-jsdoc-template/tmpl/type.tmpl | 7 + tools/jsdoc/plugins/hifi.js | 35 +- 39 files changed, 3664 insertions(+), 15 deletions(-) create mode 100644 tools/jsdoc/hifi-jsdoc-template/LICENSE.md create mode 100644 tools/jsdoc/hifi-jsdoc-template/README.md create mode 100644 tools/jsdoc/hifi-jsdoc-template/package.json create mode 100644 tools/jsdoc/hifi-jsdoc-template/publish.js create mode 100644 tools/jsdoc/hifi-jsdoc-template/static/fonts/Cairo-Bold.ttf create mode 100644 tools/jsdoc/hifi-jsdoc-template/static/fonts/proximanova-regular.otf create mode 100644 tools/jsdoc/hifi-jsdoc-template/static/images/white-logo.png create mode 100644 tools/jsdoc/hifi-jsdoc-template/static/scripts/collapse.js create mode 100644 tools/jsdoc/hifi-jsdoc-template/static/scripts/jquery-3.1.1.min.js create mode 100644 tools/jsdoc/hifi-jsdoc-template/static/scripts/linenumber.js create mode 100644 tools/jsdoc/hifi-jsdoc-template/static/scripts/prettify/Apache-License-2.0.txt create mode 100644 tools/jsdoc/hifi-jsdoc-template/static/scripts/prettify/lang-css.js create mode 100644 tools/jsdoc/hifi-jsdoc-template/static/scripts/prettify/prettify.js create mode 100644 tools/jsdoc/hifi-jsdoc-template/static/scripts/search.js create mode 100644 tools/jsdoc/hifi-jsdoc-template/static/styles/jsdoc.css create mode 100644 tools/jsdoc/hifi-jsdoc-template/static/styles/prettify.css create mode 100644 tools/jsdoc/hifi-jsdoc-template/tmpl/augments.tmpl create mode 100644 tools/jsdoc/hifi-jsdoc-template/tmpl/container.tmpl create mode 100644 tools/jsdoc/hifi-jsdoc-template/tmpl/details.tmpl create mode 100644 tools/jsdoc/hifi-jsdoc-template/tmpl/example.tmpl create mode 100644 tools/jsdoc/hifi-jsdoc-template/tmpl/examples.tmpl create mode 100644 tools/jsdoc/hifi-jsdoc-template/tmpl/exceptions.tmpl create mode 100644 tools/jsdoc/hifi-jsdoc-template/tmpl/layout.tmpl create mode 100644 tools/jsdoc/hifi-jsdoc-template/tmpl/mainpage.tmpl create mode 100644 tools/jsdoc/hifi-jsdoc-template/tmpl/members.tmpl create mode 100644 tools/jsdoc/hifi-jsdoc-template/tmpl/method.tmpl create mode 100644 tools/jsdoc/hifi-jsdoc-template/tmpl/methodList.tmpl create mode 100644 tools/jsdoc/hifi-jsdoc-template/tmpl/paramList.tmpl create mode 100644 tools/jsdoc/hifi-jsdoc-template/tmpl/params.tmpl create mode 100644 tools/jsdoc/hifi-jsdoc-template/tmpl/properties.tmpl create mode 100644 tools/jsdoc/hifi-jsdoc-template/tmpl/returns.tmpl create mode 100644 tools/jsdoc/hifi-jsdoc-template/tmpl/returnsSimp.tmpl create mode 100644 tools/jsdoc/hifi-jsdoc-template/tmpl/signal.tmpl create mode 100644 tools/jsdoc/hifi-jsdoc-template/tmpl/signalList.tmpl create mode 100644 tools/jsdoc/hifi-jsdoc-template/tmpl/source.tmpl create mode 100644 tools/jsdoc/hifi-jsdoc-template/tmpl/tutorial.tmpl create mode 100644 tools/jsdoc/hifi-jsdoc-template/tmpl/type.tmpl diff --git a/tools/jsdoc/config.json b/tools/jsdoc/config.json index a24e248661..5074362225 100644 --- a/tools/jsdoc/config.json +++ b/tools/jsdoc/config.json @@ -1,4 +1,17 @@ { + "opts": { + "template": "hifi-jsdoc-template" + }, + "docdash": { + "meta": { + "title": "", + "description": "", + "keyword": "" + }, + "search": [true], + "collapse": [true], + "typedefs": [false] + }, "templates": { "default": { "outputSourceFiles": false diff --git a/tools/jsdoc/hifi-jsdoc-template/LICENSE.md b/tools/jsdoc/hifi-jsdoc-template/LICENSE.md new file mode 100644 index 0000000000..ff66af581b --- /dev/null +++ b/tools/jsdoc/hifi-jsdoc-template/LICENSE.md @@ -0,0 +1,61 @@ +# License + +Docdash is free software, licensed under the Apache License, Version 2.0 (the +"License"). Commercial and non-commercial use are permitted in compliance with +the License. + +Copyright (c) 2016 Clement Moron and the +[contributors to docdash](https://github.com/clenemt/docdash/graphs/contributors). +All rights reserved. + +You may obtain a copy of the License at: +http://www.apache.org/licenses/LICENSE-2.0 + +In addition, a copy of the License is included with this distribution. + +As stated in Section 7, "Disclaimer of Warranty," of the License: + +> Licensor provides the Work (and each Contributor provides its Contributions) +> on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +> express or implied, including, without limitation, any warranties or +> conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +> PARTICULAR PURPOSE. You are solely responsible for determining the +> appropriateness of using or redistributing the Work and assume any risks +> associated with Your exercise of permissions under this License. + +The source code for docdash is available at: +https://github.com/clenemt/docdash + +# Third-Party Software + +Docdash includes or depends upon the following third-party software, either in +whole or in part. Each third-party software package is provided under its own +license. + +## JSDoc 3 + +JSDoc 3 is free software, licensed under the Apache License, Version 2.0 (the +"License"). Commercial and non-commercial use are permitted in compliance with +the License. + +Copyright (c) 2011-2016 Michael Mathews and the +[contributors to JSDoc](https://github.com/jsdoc3/jsdoc/graphs/contributors). +All rights reserved. + +You may obtain a copy of the License at: +http://www.apache.org/licenses/LICENSE-2.0 + +In addition, a copy of the License is included with this distribution. + +As stated in Section 7, "Disclaimer of Warranty," of the License: + +> Licensor provides the Work (and each Contributor provides its Contributions) +> on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +> express or implied, including, without limitation, any warranties or +> conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +> PARTICULAR PURPOSE. You are solely responsible for determining the +> appropriateness of using or redistributing the Work and assume any risks +> associated with Your exercise of permissions under this License. + +The source code for JSDoc 3 is available at: +https://github.com/jsdoc3/jsdoc diff --git a/tools/jsdoc/hifi-jsdoc-template/README.md b/tools/jsdoc/hifi-jsdoc-template/README.md new file mode 100644 index 0000000000..797beaa79a --- /dev/null +++ b/tools/jsdoc/hifi-jsdoc-template/README.md @@ -0,0 +1,42 @@ +# hifi-jsdoc-template +The hifi-jsdoc-template is based on the [DocDash](https://github.com/clenemt/docdash) template. + +## Usage +Clone repository to your designated `jsdoc/node_modules` template directory. + +In your `config.json` file, add a template option. + +```json +"opts": { + "template": "node_modules/hifi-jsdoc-template" +} +``` + +## Sample `config.json` + +```json +{ + "opts": { + "template": "node_modules/hifi-jsdoc-template" + }, + "docdash": { + "meta": { + "title": "", + "description": "", + "keyword": "" + }, + "search": [true], + "collapse": [true], + "typedefs": [false] + }, + "templates": { + "default": { + "outputSourceFiles": false + } + }, + "plugins": [ + "plugins/hifi", + "plugins/hifiJSONExport" + ] +} +``` diff --git a/tools/jsdoc/hifi-jsdoc-template/package.json b/tools/jsdoc/hifi-jsdoc-template/package.json new file mode 100644 index 0000000000..f011adc2ba --- /dev/null +++ b/tools/jsdoc/hifi-jsdoc-template/package.json @@ -0,0 +1,59 @@ +{ + "_from": "docdash", + "_id": "docdash@1.0.0", + "_inBundle": false, + "_integrity": "sha512-HhK72PT4z55og8FDqskO/tTYXxU+LovRz+9pCDHLnUoPchkxjdIJidS+96LqW3CLrRdBmnkDRrcVrDFGLIluTw==", + "_location": "/docdash", + "_phantomChildren": {}, + "_requested": { + "type": "tag", + "registry": true, + "raw": "docdash", + "name": "docdash", + "escapedName": "docdash", + "rawSpec": "", + "saveSpec": null, + "fetchSpec": "latest" + }, + "_requiredBy": [ + "#USER", + "/" + ], + "_resolved": "https://registry.npmjs.org/docdash/-/docdash-1.0.0.tgz", + "_shasum": "5b7df10fed3d341fc4416a8978c65ad561869d18", + "_spec": "docdash", + "_where": "D:\\hifi\\tools\\jsdoc", + "author": { + "name": "Clement Moron", + "email": "clement.moron@gmail.com" + }, + "bugs": { + "url": "https://github.com/clenemt/docdash/issues" + }, + "bundleDependencies": false, + "deprecated": false, + "description": "A clean, responsive documentation template theme for JSDoc 3 inspired by lodash and minami", + "devDependencies": { + "browser-sync": "latest", + "jsdoc": "latest", + "watch-run": "latest" + }, + "homepage": "https://github.com/clenemt/docdash#readme", + "keywords": [ + "jsdoc", + "template" + ], + "license": "Apache-2.0", + "main": "publish.js", + "name": "docdash", + "repository": { + "type": "git", + "url": "git+https://github.com/clenemt/docdash.git" + }, + "scripts": { + "sync": "browser-sync start -s ../fixtures-doc -f ../fixtures-doc --reload-delay 1000 --no-ui --no-notify", + "test": "jsdoc -c fixtures/fixtures.conf.json", + "watch": "watch-run -d 1000 -p tmpl/**,static/** \"npm run test\"" + }, + "version": "1.0.0" +} diff --git a/tools/jsdoc/hifi-jsdoc-template/publish.js b/tools/jsdoc/hifi-jsdoc-template/publish.js new file mode 100644 index 0000000000..9cd428bbbb --- /dev/null +++ b/tools/jsdoc/hifi-jsdoc-template/publish.js @@ -0,0 +1,772 @@ +/*global env: true */ +'use strict'; + +var doop = require('jsdoc/util/doop'); +var fs = require('jsdoc/fs'); +var helper = require('jsdoc/util/templateHelper'); +var logger = require('jsdoc/util/logger'); +var path = require('jsdoc/path'); +var taffy = require('taffydb').taffy; +var template = require('jsdoc/template'); +var util = require('util'); + +var htmlsafe = helper.htmlsafe; +var linkto = helper.linkto; +var resolveAuthorLinks = helper.resolveAuthorLinks; +var scopeToPunc = helper.scopeToPunc; +var hasOwnProp = Object.prototype.hasOwnProperty; + +var data; +var view; + +var outdir = path.normalize(env.opts.destination); + +function copyFile(source, target, cb) { + var cbCalled = false; + + var rd = fs.createReadStream(source); + rd.on("error", function(err) { + done(err); + }); + var wr = fs.createWriteStream(target); + wr.on("error", function(err) { + done(err); + }); + wr.on("close", function(ex) { + done(); + }); + rd.pipe(wr); + + function done(err) { + if (!cbCalled) { + cb(err); + cbCalled = true; + } + } +} + +function find(spec) { + return helper.find(data, spec); +} + +function tutoriallink(tutorial) { + return helper.toTutorial(tutorial, null, { tag: 'em', classname: 'disabled', prefix: 'Tutorial: ' }); +} + +function getAncestorLinks(doclet) { + return helper.getAncestorLinks(data, doclet); +} + +function hashToLink(doclet, hash) { + if ( !/^(#.+)/.test(hash) ) { return hash; } + + var url = helper.createLink(doclet); + + url = url.replace(/(#.+|$)/, hash); + return '' + hash + ''; +} + +function needsSignature(doclet) { + var needsSig = false; + + // function and class definitions always get a signature + if (doclet.kind === 'function' || doclet.kind === 'class' && !doclet.hideconstructor) { + needsSig = true; + } + // typedefs that contain functions get a signature, too + else if (doclet.kind === 'typedef' && doclet.type && doclet.type.names && + doclet.type.names.length) { + for (var i = 0, l = doclet.type.names.length; i < l; i++) { + if (doclet.type.names[i].toLowerCase() === 'function') { + needsSig = true; + break; + } + } + } + + return needsSig; +} + +function getSignatureAttributes(item) { + var attributes = []; + + if (item.optional) { + attributes.push('opt'); + } + + if (item.nullable === true) { + attributes.push('nullable'); + } + else if (item.nullable === false) { + attributes.push('non-null'); + } + + return attributes; +} + +function updateItemName(item) { + var attributes = getSignatureAttributes(item); + var itemName = item.name || ''; + + if (item.variable) { + itemName = '…' + itemName; + } + + if (attributes && attributes.length) { + itemName = util.format( '%s%s', itemName, + attributes.join(', ') ); + } + + return itemName; +} + +function addParamAttributes(params) { + return params.filter(function(param) { + return param.name && param.name.indexOf('.') === -1; + }).map(updateItemName); +} + +function buildItemTypeStrings(item) { + var types = []; + + if (item && item.type && item.type.names) { + item.type.names.forEach(function(name) { + types.push( linkto(name, htmlsafe(name)) ); + }); + } + + return types; +} + +function buildAttribsString(attribs) { + var attribsString = ''; + + if (attribs && attribs.length) { + attribsString = htmlsafe( util.format('(%s) ', attribs.join(', ')) ); + } + + return attribsString; +} + +function addNonParamAttributes(items) { + var types = []; + + items.forEach(function(item) { + types = types.concat( buildItemTypeStrings(item) ); + }); + + return types; +} + +function addSignatureParams(f) { + var params = f.params ? addParamAttributes(f.params) : []; + f.signature = util.format( '%s( %s )', (f.signature || ''), params.join(', ') ); +} + +function addSignatureReturns(f) { + var attribs = []; + var attribsString = ''; + var returnTypes = []; + var returnTypesString = ''; + + // jam all the return-type attributes into an array. this could create odd results (for example, + // if there are both nullable and non-nullable return types), but let's assume that most people + // who use multiple @return tags aren't using Closure Compiler type annotations, and vice-versa. + if (f.returns) { + f.returns.forEach(function(item) { + helper.getAttribs(item).forEach(function(attrib) { + if (attribs.indexOf(attrib) === -1) { + attribs.push(attrib); + } + }); + }); + + attribsString = buildAttribsString(attribs); + } + + if (f.returns) { + returnTypes = addNonParamAttributes(f.returns); + } + if (returnTypes.length) { + returnTypesString = util.format( ' → %s{%s}', attribsString, returnTypes.join('|') ); + } + + f.signature = '' + (f.signature || '') + '' + + '' + returnTypesString + ''; +} + +function addSignatureTypes(f) { + var types = f.type ? buildItemTypeStrings(f) : []; + + f.signature = (f.signature || '') + '' + + (types.length ? ' :' + types.join('|') : '') + ''; +} + +function addAttribs(f) { + var attribs = helper.getAttribs(f); + var attribsString = buildAttribsString(attribs); + + f.attribs = util.format('%s', attribsString); +} + +function shortenPaths(files, commonPrefix) { + Object.keys(files).forEach(function(file) { + files[file].shortened = files[file].resolved.replace(commonPrefix, '') + // always use forward slashes + .replace(/\\/g, '/'); + }); + + return files; +} + +function getPathFromDoclet(doclet) { + if (!doclet.meta) { + return null; + } + + return doclet.meta.path && doclet.meta.path !== 'null' ? + path.join(doclet.meta.path, doclet.meta.filename) : + doclet.meta.filename; +} + +function generate(type, title, docs, filename, resolveLinks) { + resolveLinks = resolveLinks === false ? false : true; + + var docData = { + type: type, + title: title, + docs: docs + }; + + var outpath = path.join(outdir, filename), + html = view.render('container.tmpl', docData); + + if (resolveLinks) { + html = helper.resolveLinks(html); // turn {@link foo} into foo + } + + fs.writeFileSync(outpath, html, 'utf8'); +} + +function generateSourceFiles(sourceFiles, encoding) { + encoding = encoding || 'utf8'; + Object.keys(sourceFiles).forEach(function(file) { + var source; + // links are keyed to the shortened path in each doclet's `meta.shortpath` property + var sourceOutfile = helper.getUniqueFilename(sourceFiles[file].shortened); + helper.registerLink(sourceFiles[file].shortened, sourceOutfile); + + try { + source = { + kind: 'source', + code: helper.htmlsafe( fs.readFileSync(sourceFiles[file].resolved, encoding) ) + }; + } + catch(e) { + logger.error('Error while generating source file %s: %s', file, e.message); + } + + generate('Source', sourceFiles[file].shortened, [source], sourceOutfile, false); + }); +} + +/** + * Look for classes or functions with the same name as modules (which indicates that the module + * exports only that class or function), then attach the classes or functions to the `module` + * property of the appropriate module doclets. The name of each class or function is also updated + * for display purposes. This function mutates the original arrays. + * + * @private + * @param {Array.} doclets - The array of classes and functions to + * check. + * @param {Array.} modules - The array of module doclets to search. + */ +function attachModuleSymbols(doclets, modules) { + var symbols = {}; + + // build a lookup table + doclets.forEach(function(symbol) { + symbols[symbol.longname] = symbols[symbol.longname] || []; + symbols[symbol.longname].push(symbol); + }); + + return modules.map(function(module) { + if (symbols[module.longname]) { + module.modules = symbols[module.longname] + // Only show symbols that have a description. Make an exception for classes, because + // we want to show the constructor-signature heading no matter what. + .filter(function(symbol) { + return symbol.description || symbol.kind === 'class'; + }) + .map(function(symbol) { + symbol = doop(symbol); + + if (symbol.kind === 'class' || symbol.kind === 'function' && !symbol.hideconstructor) { + symbol.name = symbol.name.replace('module:', '(require("') + '"))'; + } + + return symbol; + }); + } + }); +} + +function buildMemberNav(items, itemHeading, itemsSeen, linktoFn) { + var nav = ''; + + if (items && items.length) { + var itemsNav = ''; + + items.forEach(function(item) { + var displayName; + var methods = find({kind:'function', memberof: item.longname}); + var signals = find({kind:'signal', memberof: item.longname}); + var members = find({kind:'member', memberof: item.longname}); + var docdash = env && env.conf && env.conf.docdash || {}; + var conf = env && env.conf || {}; + if ( !hasOwnProp.call(item, 'longname') ) { + itemsNav += '
  • ' + linktoFn('', item.name); + itemsNav += '
  • '; + } else if ( !hasOwnProp.call(itemsSeen, item.longname) ) { + if (conf.templates.default.useLongnameInNav) { + displayName = item.longname; + } else { + displayName = item.name; + } + itemsNav += '
  • ' + linktoFn(item.longname, displayName.replace(/\b(module|event):/g, '')); + + if (docdash.static && members.find(function (m) { return m.scope === 'static'; } )) { + itemsNav += "
      "; + + members.forEach(function (member) { + if (!member.scope === 'static') return; + itemsNav += "
    • '; + } + } + + return nav; +} + +function linktoTutorial(longName, name) { + return tutoriallink(name); +} + +function linktoExternal(longName, name) { + return linkto(longName, name.replace(/(^"|"$)/g, '')); +} + +/** + * Create the navigation sidebar. + * @param {object} members The members that will be used to create the sidebar. + * @param {array} members.classes + * @param {array} members.externals + * @param {array} members.globals + * @param {array} members.mixins + * @param {array} members.modules + * @param {array} members.namespaces + * @param {array} members.tutorials + * @param {array} members.events + * @param {array} members.interfaces + * @return {s + ring} The HTML for the navigation sidebar. + */ + +function buildNav(members) { + var nav = '

      Home

      '; + var seen = {}; + var seenTutorials = {}; + var docdash = env && env.conf && env.conf.docdash || {}; + if(docdash.menu){ + for(var menu in docdash.menu){ + nav += '

      '; + } + } + var defaultOrder = [ + 'Namespaces', 'Classes', 'Modules', 'Externals', 'Events', 'Mixins', 'Tutorials', 'Interfaces' + ]; + var order = docdash.sectionOrder || defaultOrder; + var sections = { + Namespaces: buildMemberNav(members.namespaces, 'Namespaces', seen, linkto), + Classes: buildMemberNav(members.classes, 'Classes', seen, linkto), + Modules: buildMemberNav(members.modules, 'Modules', {}, linkto), + Externals: buildMemberNav(members.externals, 'Externals', seen, linktoExternal), + Events: buildMemberNav(members.events, 'Events', seen, linkto), + Mixins: buildMemberNav(members.mixins, 'Mixins', seen, linkto), + Tutorials: buildMemberNav(members.tutorials, 'Tutorials', seenTutorials, linktoTutorial), + Interfaces: buildMemberNav(members.interfaces, 'Interfaces', seen, linkto), + }; + order.forEach(member => nav += sections[member]); + + if (members.globals.length) { + var globalNav = ''; + + members.globals.forEach(function(g) { + if ( (docdash.typedefs || g.kind !== 'typedef') && !hasOwnProp.call(seen, g.longname) ) { + globalNav += '
    • ' + linkto(g.longname, g.name) + '
    • '; + } + seen[g.longname] = true; + }); + + if (!globalNav) { + // turn the heading into a link so you can actually get to the global page + nav += '

      ' + linkto('global', 'Global') + '

      '; + } + else { + nav += '

      Globals

        ' + globalNav + '
      '; + } + } + + return nav; +} + +/** + @param {TAFFY} taffyData See . + @param {object} opts + @param {Tutorial} tutorials + */ +exports.publish = function(taffyData, opts, tutorials) { + var docdash = env && env.conf && env.conf.docdash || {}; + data = taffyData; + + var conf = env.conf.templates || {}; + conf.default = conf.default || {}; + + var templatePath = path.normalize(opts.template); + view = new template.Template( path.join(templatePath, 'tmpl') ); + + // claim some special filenames in advance, so the All-Powerful Overseer of Filename Uniqueness + // doesn't try to hand them out later + var indexUrl = helper.getUniqueFilename('index'); + // don't call registerLink() on this one! 'index' is also a valid longname + + var globalUrl = helper.getUniqueFilename('global'); + helper.registerLink('global', globalUrl); + + // set up templating + view.layout = conf.default.layoutFile ? + path.getResourcePath(path.dirname(conf.default.layoutFile), + path.basename(conf.default.layoutFile) ) : + 'layout.tmpl'; + + // set up tutorials for helper + helper.setTutorials(tutorials); + + data = helper.prune(data); + + docdash.sort !== false && data.sort('longname, version, since'); + helper.addEventListeners(data); + + var sourceFiles = {}; + var sourceFilePaths = []; + data().each(function(doclet) { + if(docdash.removeQuotes){ + if(docdash.removeQuotes === "all"){ + if(doclet.name){ + doclet.name = doclet.name.replace(/"/g, ''); + doclet.name = doclet.name.replace(/'/g, ''); + } + if(doclet.longname){ + doclet.longname = doclet.longname.replace(/"/g, ''); + doclet.longname = doclet.longname.replace(/'/g, ''); + } + } + else if(docdash.removeQuotes === "trim"){ + if(doclet.name){ + doclet.name = doclet.name.replace(/^"(.*)"$/, '$1'); + doclet.name = doclet.name.replace(/^'(.*)'$/, '$1'); + } + if(doclet.longname){ + doclet.longname = doclet.longname.replace(/^"(.*)"$/, '$1'); + doclet.longname = doclet.longname.replace(/^'(.*)'$/, '$1'); + } + } + } + doclet.attribs = ''; + + if (doclet.examples) { + doclet.examples = doclet.examples.map(function(example) { + var caption, code; + + if (example && example.match(/^\s*([\s\S]+?)<\/caption>(\s*[\n\r])([\s\S]+)$/i)) { + caption = RegExp.$1; + code = RegExp.$3; + } + + return { + caption: caption || '', + code: code || example || '' + }; + }); + } + if (doclet.see) { + doclet.see.forEach(function(seeItem, i) { + doclet.see[i] = hashToLink(doclet, seeItem); + }); + } + + // build a list of source files + var sourcePath; + if (doclet.meta) { + sourcePath = getPathFromDoclet(doclet); + sourceFiles[sourcePath] = { + resolved: sourcePath, + shortened: null + }; + if (sourceFilePaths.indexOf(sourcePath) === -1) { + sourceFilePaths.push(sourcePath); + } + } + }); + + // update outdir if necessary, then create outdir + var packageInfo = ( find({kind: 'package'}) || [] ) [0]; + if (packageInfo && packageInfo.name) { + outdir = path.join( outdir, packageInfo.name, (packageInfo.version || '') ); + } + fs.mkPath(outdir); + + // copy the template's static files to outdir + var fromDir = path.join(templatePath, 'static'); + var staticFiles = fs.ls(fromDir, 3); + + staticFiles.forEach(function(fileName) { + var toDir = fs.toDir( fileName.replace(fromDir, outdir) ); + fs.mkPath(toDir); + copyFile(fileName, path.join(toDir, path.basename(fileName)), function(err){if(err) console.err(err);}); + }); + + // copy user-specified static files to outdir + var staticFilePaths; + var staticFileFilter; + var staticFileScanner; + if (conf.default.staticFiles) { + // The canonical property name is `include`. We accept `paths` for backwards compatibility + // with a bug in JSDoc 3.2.x. + staticFilePaths = conf.default.staticFiles.include || + conf.default.staticFiles.paths || + []; + staticFileFilter = new (require('jsdoc/src/filter')).Filter(conf.default.staticFiles); + staticFileScanner = new (require('jsdoc/src/scanner')).Scanner(); + + staticFilePaths.forEach(function(filePath) { + var extraStaticFiles = staticFileScanner.scan([filePath], 10, staticFileFilter); + + extraStaticFiles.forEach(function(fileName) { + var sourcePath = fs.toDir(filePath); + var toDir = fs.toDir( fileName.replace(sourcePath, outdir) ); + fs.mkPath(toDir); + copyFile(fileName, path.join(toDir, path.basename(fileName)), function(err){if(err) console.err(err);}); + }); + }); + } + + if (sourceFilePaths.length) { + sourceFiles = shortenPaths( sourceFiles, path.commonPrefix(sourceFilePaths) ); + } + data().each(function(doclet) { + var url = helper.createLink(doclet); + helper.registerLink(doclet.longname, url); + + // add a shortened version of the full path + var docletPath; + if (doclet.meta) { + docletPath = getPathFromDoclet(doclet); + docletPath = sourceFiles[docletPath].shortened; + if (docletPath) { + doclet.meta.shortpath = docletPath; + } + } + }); + + data().each(function(doclet) { + var url = helper.longnameToUrl[doclet.longname]; + + if (url.indexOf('#') > -1) { + doclet.id = helper.longnameToUrl[doclet.longname].split(/#/).pop(); + } + else { + doclet.id = doclet.name; + } + + if ( needsSignature(doclet) ) { + addSignatureParams(doclet); + addSignatureReturns(doclet); + addAttribs(doclet); + } + }); + + // do this after the urls have all been generated + data().each(function(doclet) { + doclet.ancestors = getAncestorLinks(doclet); + + if (doclet.kind === 'member') { + addSignatureTypes(doclet); + addAttribs(doclet); + } + + if (doclet.kind === 'constant') { + addSignatureTypes(doclet); + addAttribs(doclet); + doclet.kind = 'member'; + } + }); + + var members = helper.getMembers(data); + members.tutorials = tutorials.children; + + // output pretty-printed source files by default + var outputSourceFiles = conf.default && conf.default.outputSourceFiles !== false + ? true + : false; + + // add template helpers + view.find = find; + view.linkto = linkto; + view.resolveAuthorLinks = resolveAuthorLinks; + view.tutoriallink = tutoriallink; + view.htmlsafe = htmlsafe; + view.outputSourceFiles = outputSourceFiles; + + // once for all + view.nav = buildNav(members); + attachModuleSymbols( find({ longname: {left: 'module:'} }), members.modules ); + + // generate the pretty-printed source files first so other pages can link to them + if (outputSourceFiles) { + generateSourceFiles(sourceFiles, opts.encoding); + } + + if (members.globals.length) { + generate('', 'Global', [{kind: 'globalobj'}], globalUrl); + } + + // index page displays information from package.json and lists files + var files = find({kind: 'file'}); + var packages = find({kind: 'package'}); + + generate('', 'High Fidelity API Reference', + packages.concat( + [{kind: 'mainpage', readme: opts.readme, longname: (opts.mainpagetitle) ? opts.mainpagetitle : 'Main Page'}] + ).concat(files), + indexUrl); + + // set up the lists that we'll use to generate pages + var classes = taffy(members.classes); + var modules = taffy(members.modules); + var namespaces = taffy(members.namespaces); + var mixins = taffy(members.mixins); + var externals = taffy(members.externals); + var interfaces = taffy(members.interfaces); + + Object.keys(helper.longnameToUrl).forEach(function(longname) { + var myModules = helper.find(modules, {longname: longname}); + if (myModules.length) { + generate('Module', myModules[0].name, myModules, helper.longnameToUrl[longname]); + } + + var myClasses = helper.find(classes, {longname: longname}); + if (myClasses.length) { + generate('Class', myClasses[0].name, myClasses, helper.longnameToUrl[longname]); + } + + var myNamespaces = helper.find(namespaces, {longname: longname}); + if (myNamespaces.length) { + generate('Namespace', myNamespaces[0].name, myNamespaces, helper.longnameToUrl[longname]); + } + + var myMixins = helper.find(mixins, {longname: longname}); + if (myMixins.length) { + generate('Mixin', myMixins[0].name, myMixins, helper.longnameToUrl[longname]); + } + + var myExternals = helper.find(externals, {longname: longname}); + if (myExternals.length) { + generate('External', myExternals[0].name, myExternals, helper.longnameToUrl[longname]); + } + + var myInterfaces = helper.find(interfaces, {longname: longname}); + if (myInterfaces.length) { + generate('Interface', myInterfaces[0].name, myInterfaces, helper.longnameToUrl[longname]); + } + }); + + // TODO: move the tutorial functions to templateHelper.js + function generateTutorial(title, tutorial, filename) { + var tutorialData = { + title: title, + header: tutorial.title, + content: tutorial.parse(), + children: tutorial.children + }; + + var tutorialPath = path.join(outdir, filename); + var html = view.render('tutorial.tmpl', tutorialData); + + // yes, you can use {@link} in tutorials too! + html = helper.resolveLinks(html); // turn {@link foo} into
      foo + fs.writeFileSync(tutorialPath, html, 'utf8'); + } + + // tutorials can have only one parent so there is no risk for loops + function saveChildren(node) { + node.children.forEach(function(child) { + generateTutorial('Tutorial: ' + child.title, child, helper.tutorialToUrl(child.name)); + saveChildren(child); + }); + } + + saveChildren(tutorials); +}; diff --git a/tools/jsdoc/hifi-jsdoc-template/static/fonts/Cairo-Bold.ttf b/tools/jsdoc/hifi-jsdoc-template/static/fonts/Cairo-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..ad884391d78a4c9012f51b6681746bb51cb7f7c0 GIT binary patch literal 170996 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 z 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�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 literal 0 HcmV?d00001 diff --git a/tools/jsdoc/hifi-jsdoc-template/static/fonts/proximanova-regular.otf b/tools/jsdoc/hifi-jsdoc-template/static/fonts/proximanova-regular.otf new file mode 100644 index 0000000000000000000000000000000000000000..27c8d8f7bf9e823f3d2b89ed8efaf401baf4b942 GIT binary patch literal 62892 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 WU`Gn&ro$r5k-bSr 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 literal 0 HcmV?d00001 diff --git a/tools/jsdoc/hifi-jsdoc-template/static/images/white-logo.png b/tools/jsdoc/hifi-jsdoc-template/static/images/white-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..18cd2d88ddc2816a0d7f4b8b8554943037094b8e GIT binary patch literal 31280 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 NqW-1x#kvlU{s*yk?ZW^7 literal 0 HcmV?d00001 diff --git a/tools/jsdoc/hifi-jsdoc-template/static/scripts/collapse.js b/tools/jsdoc/hifi-jsdoc-template/static/scripts/collapse.js new file mode 100644 index 0000000000..4525c1756e --- /dev/null +++ b/tools/jsdoc/hifi-jsdoc-template/static/scripts/collapse.js @@ -0,0 +1,11 @@ +function hideAllButCurrent(){ + //by default all submenut items are hidden + $("nav > ul > li > ul li").hide(); + + //only current page (if it exists) should be opened + var file = window.location.pathname.split("/").pop(); + $("nav > ul > li > a[href^='"+file+"']").parent().find("> ul li").show(); +} +$( document ).ready(function() { + hideAllButCurrent(); +}); \ No newline at end of file diff --git a/tools/jsdoc/hifi-jsdoc-template/static/scripts/jquery-3.1.1.min.js b/tools/jsdoc/hifi-jsdoc-template/static/scripts/jquery-3.1.1.min.js new file mode 100644 index 0000000000..4c5be4c0fb --- /dev/null +++ b/tools/jsdoc/hifi-jsdoc-template/static/scripts/jquery-3.1.1.min.js @@ -0,0 +1,4 @@ +/*! jQuery v3.1.1 | (c) jQuery Foundation | jquery.org/license */ +!function(a,b){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){"use strict";var c=[],d=a.document,e=Object.getPrototypeOf,f=c.slice,g=c.concat,h=c.push,i=c.indexOf,j={},k=j.toString,l=j.hasOwnProperty,m=l.toString,n=m.call(Object),o={};function p(a,b){b=b||d;var c=b.createElement("script");c.text=a,b.head.appendChild(c).parentNode.removeChild(c)}var q="3.1.1",r=function(a,b){return new r.fn.init(a,b)},s=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,t=/^-ms-/,u=/-([a-z])/g,v=function(a,b){return b.toUpperCase()};r.fn=r.prototype={jquery:q,constructor:r,length:0,toArray:function(){return f.call(this)},get:function(a){return null==a?f.call(this):a<0?this[a+this.length]:this[a]},pushStack:function(a){var b=r.merge(this.constructor(),a);return b.prevObject=this,b},each:function(a){return r.each(this,a)},map:function(a){return this.pushStack(r.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(f.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(a<0?b:0);return this.pushStack(c>=0&&c0&&b-1 in a)}var x=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=function(a,b){for(var c=0,d=a.length;c+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(N),U=new RegExp("^"+L+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+N),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),aa=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:d<0?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ba=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ca=function(a,b){return b?"\0"===a?"\ufffd":a.slice(0,-1)+"\\"+a.charCodeAt(a.length-1).toString(16)+" ":"\\"+a},da=function(){m()},ea=ta(function(a){return a.disabled===!0&&("form"in a||"label"in a)},{dir:"parentNode",next:"legend"});try{G.apply(D=H.call(v.childNodes),v.childNodes),D[v.childNodes.length].nodeType}catch(fa){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s=b&&b.ownerDocument,w=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==w&&9!==w&&11!==w)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==w&&(l=Z.exec(a)))if(f=l[1]){if(9===w){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(s&&(j=s.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(l[2])return G.apply(d,b.getElementsByTagName(a)),d;if((f=l[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==w)s=b,r=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(ba,ca):b.setAttribute("id",k=u),o=g(a),h=o.length;while(h--)o[h]="#"+k+" "+sa(o[h]);r=o.join(","),s=$.test(a)&&qa(b.parentNode)||b}if(r)try{return G.apply(d,s.querySelectorAll(r)),d}catch(x){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(P,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("fieldset");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&a.sourceIndex-b.sourceIndex;if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return function(b){return"form"in b?b.parentNode&&b.disabled===!1?"label"in b?"label"in b.parentNode?b.parentNode.disabled===a:b.disabled===a:b.isDisabled===a||b.isDisabled!==!a&&ea(b)===a:b.disabled===a:"label"in b&&b.disabled===a}}function pa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function qa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return!!b&&"HTML"!==b.nodeName},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),v!==n&&(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(n.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){return a.getAttribute("id")===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}}):(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c,d,e,f=b.getElementById(a);if(f){if(c=f.getAttributeNode("id"),c&&c.value===a)return[f];e=b.getElementsByName(a),d=0;while(f=e[d++])if(c=f.getAttributeNode("id"),c&&c.value===a)return[f]}return[]}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){if("undefined"!=typeof b.getElementsByClassName&&p)return b.getElementsByClassName(a)},r=[],q=[],(c.qsa=Y.test(n.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){a.innerHTML="";var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+K+"*[*^$|!~]?="),2!==a.querySelectorAll(":enabled").length&&q.push(":enabled",":disabled"),o.appendChild(a).disabled=!0,2!==a.querySelectorAll(":disabled").length&&q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Y.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"*"),s.call(a,"[s!='']:x"),r.push("!=",N)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Y.test(o.compareDocumentPosition),t=b||Y.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?I(k,a)-I(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?I(k,a)-I(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?la(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(S,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.escape=function(a){return(a+"").replace(ba,ca)},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(_,aa),a[3]=(a[3]||a[4]||a[5]||"").replace(_,aa),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return V.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&T.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(_,aa).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:!b||(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(O," ")+" ").indexOf(c)>-1:"|="===b&&(e===c||e.slice(0,c.length+1)===c+"-"))}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(P,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(_,aa),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return U.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(_,aa).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:oa(!1),disabled:oa(!0),checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:pa(function(){return[0]}),last:pa(function(a,b){return[b-1]}),eq:pa(function(a,b,c){return[c<0?c+b:c]}),even:pa(function(a,b){for(var c=0;c=0;)a.push(d);return a}),gt:pa(function(a,b,c){for(var d=c<0?c+b:c;++d1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function va(a,b,c){for(var d=0,e=b.length;d-1&&(f[j]=!(g[j]=l))}}else r=wa(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ya(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ta(function(a){return a===b},h,!0),l=ta(function(a){return I(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];i1&&ua(m),i>1&&sa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(P,"$1"),c,i0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=E.call(i));u=wa(u)}G.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&ga.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=ya(b[c]),f[u]?d.push(f):e.push(f);f=A(a,za(e,d)),f.selector=a}return f},i=ga.select=function(a,b,c,e){var f,i,j,k,l,m="function"==typeof a&&a,n=!e&&g(a=m.selector||a);if(c=c||[],1===n.length){if(i=n[0]=n[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&9===b.nodeType&&p&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(_,aa),b)||[])[0],!b)return c;m&&(b=b.parentNode),a=a.slice(i.shift().value.length)}f=V.needsContext.test(a)?0:i.length;while(f--){if(j=i[f],d.relative[k=j.type])break;if((l=d.find[k])&&(e=l(j.matches[0].replace(_,aa),$.test(i[0].type)&&qa(b.parentNode)||b))){if(i.splice(f,1),a=e.length&&sa(i),!a)return G.apply(c,e),c;break}}}return(m||h(a,n))(e,b,!p,c,!b||$.test(a)&&qa(b.parentNode)||b),c},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("fieldset"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){if(!c)return a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){if(!c&&"input"===a.nodeName.toLowerCase())return a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(J,function(a,b,c){var d;if(!c)return a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);r.find=x,r.expr=x.selectors,r.expr[":"]=r.expr.pseudos,r.uniqueSort=r.unique=x.uniqueSort,r.text=x.getText,r.isXMLDoc=x.isXML,r.contains=x.contains,r.escapeSelector=x.escape;var y=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&r(a).is(c))break;d.push(a)}return d},z=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},A=r.expr.match.needsContext,B=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i,C=/^.[^:#\[\.,]*$/;function D(a,b,c){return r.isFunction(b)?r.grep(a,function(a,d){return!!b.call(a,d,a)!==c}):b.nodeType?r.grep(a,function(a){return a===b!==c}):"string"!=typeof b?r.grep(a,function(a){return i.call(b,a)>-1!==c}):C.test(b)?r.filter(b,a,c):(b=r.filter(b,a),r.grep(a,function(a){return i.call(b,a)>-1!==c&&1===a.nodeType}))}r.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?r.find.matchesSelector(d,a)?[d]:[]:r.find.matches(a,r.grep(b,function(a){return 1===a.nodeType}))},r.fn.extend({find:function(a){var b,c,d=this.length,e=this;if("string"!=typeof a)return this.pushStack(r(a).filter(function(){for(b=0;b1?r.uniqueSort(c):c},filter:function(a){return this.pushStack(D(this,a||[],!1))},not:function(a){return this.pushStack(D(this,a||[],!0))},is:function(a){return!!D(this,"string"==typeof a&&A.test(a)?r(a):a||[],!1).length}});var E,F=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,G=r.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||E,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:F.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof r?b[0]:b,r.merge(this,r.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),B.test(e[1])&&r.isPlainObject(b))for(e in b)r.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&(this[0]=f,this.length=1),this}return a.nodeType?(this[0]=a,this.length=1,this):r.isFunction(a)?void 0!==c.ready?c.ready(a):a(r):r.makeArray(a,this)};G.prototype=r.fn,E=r(d);var H=/^(?:parents|prev(?:Until|All))/,I={children:!0,contents:!0,next:!0,prev:!0};r.fn.extend({has:function(a){var b=r(a,this),c=b.length;return this.filter(function(){for(var a=0;a-1:1===c.nodeType&&r.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?r.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?i.call(r(a),this[0]):i.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(r.uniqueSort(r.merge(this.get(),r(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function J(a,b){while((a=a[b])&&1!==a.nodeType);return a}r.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return y(a,"parentNode")},parentsUntil:function(a,b,c){return y(a,"parentNode",c)},next:function(a){return J(a,"nextSibling")},prev:function(a){return J(a,"previousSibling")},nextAll:function(a){return y(a,"nextSibling")},prevAll:function(a){return y(a,"previousSibling")},nextUntil:function(a,b,c){return y(a,"nextSibling",c)},prevUntil:function(a,b,c){return y(a,"previousSibling",c)},siblings:function(a){return z((a.parentNode||{}).firstChild,a)},children:function(a){return z(a.firstChild)},contents:function(a){return a.contentDocument||r.merge([],a.childNodes)}},function(a,b){r.fn[a]=function(c,d){var e=r.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=r.filter(d,e)),this.length>1&&(I[a]||r.uniqueSort(e),H.test(a)&&e.reverse()),this.pushStack(e)}});var K=/[^\x20\t\r\n\f]+/g;function L(a){var b={};return r.each(a.match(K)||[],function(a,c){b[c]=!0}),b}r.Callbacks=function(a){a="string"==typeof a?L(a):r.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),c<=h&&h--}),this},has:function(a){return a?r.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||b||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j};function M(a){return a}function N(a){throw a}function O(a,b,c){var d;try{a&&r.isFunction(d=a.promise)?d.call(a).done(b).fail(c):a&&r.isFunction(d=a.then)?d.call(a,b,c):b.call(void 0,a)}catch(a){c.call(void 0,a)}}r.extend({Deferred:function(b){var c=[["notify","progress",r.Callbacks("memory"),r.Callbacks("memory"),2],["resolve","done",r.Callbacks("once memory"),r.Callbacks("once memory"),0,"resolved"],["reject","fail",r.Callbacks("once memory"),r.Callbacks("once memory"),1,"rejected"]],d="pending",e={state:function(){return d},always:function(){return f.done(arguments).fail(arguments),this},"catch":function(a){return e.then(null,a)},pipe:function(){var a=arguments;return r.Deferred(function(b){r.each(c,function(c,d){var e=r.isFunction(a[d[4]])&&a[d[4]];f[d[1]](function(){var a=e&&e.apply(this,arguments);a&&r.isFunction(a.promise)?a.promise().progress(b.notify).done(b.resolve).fail(b.reject):b[d[0]+"With"](this,e?[a]:arguments)})}),a=null}).promise()},then:function(b,d,e){var f=0;function g(b,c,d,e){return function(){var h=this,i=arguments,j=function(){var a,j;if(!(b=f&&(d!==N&&(h=void 0,i=[a]),c.rejectWith(h,i))}};b?k():(r.Deferred.getStackHook&&(k.stackTrace=r.Deferred.getStackHook()),a.setTimeout(k))}}return r.Deferred(function(a){c[0][3].add(g(0,a,r.isFunction(e)?e:M,a.notifyWith)),c[1][3].add(g(0,a,r.isFunction(b)?b:M)),c[2][3].add(g(0,a,r.isFunction(d)?d:N))}).promise()},promise:function(a){return null!=a?r.extend(a,e):e}},f={};return r.each(c,function(a,b){var g=b[2],h=b[5];e[b[1]]=g.add,h&&g.add(function(){d=h},c[3-a][2].disable,c[0][2].lock),g.add(b[3].fire),f[b[0]]=function(){return f[b[0]+"With"](this===f?void 0:this,arguments),this},f[b[0]+"With"]=g.fireWith}),e.promise(f),b&&b.call(f,f),f},when:function(a){var b=arguments.length,c=b,d=Array(c),e=f.call(arguments),g=r.Deferred(),h=function(a){return function(c){d[a]=this,e[a]=arguments.length>1?f.call(arguments):c,--b||g.resolveWith(d,e)}};if(b<=1&&(O(a,g.done(h(c)).resolve,g.reject),"pending"===g.state()||r.isFunction(e[c]&&e[c].then)))return g.then();while(c--)O(e[c],h(c),g.reject);return g.promise()}});var P=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;r.Deferred.exceptionHook=function(b,c){a.console&&a.console.warn&&b&&P.test(b.name)&&a.console.warn("jQuery.Deferred exception: "+b.message,b.stack,c)},r.readyException=function(b){a.setTimeout(function(){throw b})};var Q=r.Deferred();r.fn.ready=function(a){return Q.then(a)["catch"](function(a){r.readyException(a)}),this},r.extend({isReady:!1,readyWait:1,holdReady:function(a){a?r.readyWait++:r.ready(!0)},ready:function(a){(a===!0?--r.readyWait:r.isReady)||(r.isReady=!0,a!==!0&&--r.readyWait>0||Q.resolveWith(d,[r]))}}),r.ready.then=Q.then;function R(){d.removeEventListener("DOMContentLoaded",R), +a.removeEventListener("load",R),r.ready()}"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(r.ready):(d.addEventListener("DOMContentLoaded",R),a.addEventListener("load",R));var S=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===r.type(c)){e=!0;for(h in c)S(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,r.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(r(a),c)})),b))for(;h1,null,!0)},removeData:function(a){return this.each(function(){W.remove(this,a)})}}),r.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=V.get(a,b),c&&(!d||r.isArray(c)?d=V.access(a,b,r.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=r.queue(a,b),d=c.length,e=c.shift(),f=r._queueHooks(a,b),g=function(){r.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return V.get(a,c)||V.access(a,c,{empty:r.Callbacks("once memory").add(function(){V.remove(a,[b+"queue",c])})})}}),r.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length\x20\t\r\n\f]+)/i,ka=/^$|\/(?:java|ecma)script/i,la={option:[1,""],thead:[1,"","
      "],col:[2,"","
      "],tr:[2,"","
      "],td:[3,"","
      "],_default:[0,"",""]};la.optgroup=la.option,la.tbody=la.tfoot=la.colgroup=la.caption=la.thead,la.th=la.td;function ma(a,b){var c;return c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[],void 0===b||b&&r.nodeName(a,b)?r.merge([a],c):c}function na(a,b){for(var c=0,d=a.length;c-1)e&&e.push(f);else if(j=r.contains(f.ownerDocument,f),g=ma(l.appendChild(f),"script"),j&&na(g),c){k=0;while(f=g[k++])ka.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),o.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="",o.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var qa=d.documentElement,ra=/^key/,sa=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,ta=/^([^.]*)(?:\.(.+)|)/;function ua(){return!0}function va(){return!1}function wa(){try{return d.activeElement}catch(a){}}function xa(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)xa(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=va;else if(!e)return a;return 1===f&&(g=e,e=function(a){return r().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=r.guid++)),a.each(function(){r.event.add(this,b,e,d,c)})}r.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=V.get(a);if(q){c.handler&&(f=c,c=f.handler,e=f.selector),e&&r.find.matchesSelector(qa,e),c.guid||(c.guid=r.guid++),(i=q.events)||(i=q.events={}),(g=q.handle)||(g=q.handle=function(b){return"undefined"!=typeof r&&r.event.triggered!==b.type?r.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(K)||[""],j=b.length;while(j--)h=ta.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n&&(l=r.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=r.event.special[n]||{},k=r.extend({type:n,origType:p,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&r.expr.match.needsContext.test(e),namespace:o.join(".")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,o,g)!==!1||a.addEventListener&&a.addEventListener(n,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),r.event.global[n]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=V.hasData(a)&&V.get(a);if(q&&(i=q.events)){b=(b||"").match(K)||[""],j=b.length;while(j--)if(h=ta.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n){l=r.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp("(^|\\.)"+o.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&p!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,o,q.handle)!==!1||r.removeEvent(a,n,q.handle),delete i[n])}else for(n in i)r.event.remove(a,n+b[j],c,d,!0);r.isEmptyObject(i)&&V.remove(a,"handle events")}},dispatch:function(a){var b=r.event.fix(a),c,d,e,f,g,h,i=new Array(arguments.length),j=(V.get(this,"events")||{})[b.type]||[],k=r.event.special[b.type]||{};for(i[0]=b,c=1;c=1))for(;j!==this;j=j.parentNode||this)if(1===j.nodeType&&("click"!==a.type||j.disabled!==!0)){for(f=[],g={},c=0;c-1:r.find(e,this,null,[j]).length),g[e]&&f.push(d);f.length&&h.push({elem:j,handlers:f})}return j=this,i\x20\t\r\n\f]*)[^>]*)\/>/gi,za=/\s*$/g;function Da(a,b){return r.nodeName(a,"table")&&r.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a:a}function Ea(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function Fa(a){var b=Ba.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Ga(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(V.hasData(a)&&(f=V.access(a),g=V.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;c1&&"string"==typeof q&&!o.checkClone&&Aa.test(q))return a.each(function(e){var f=a.eq(e);s&&(b[0]=q.call(this,e,f.html())),Ia(f,b,c,d)});if(m&&(e=pa(b,a[0].ownerDocument,!1,a,d),f=e.firstChild,1===e.childNodes.length&&(e=f),f||d)){for(h=r.map(ma(e,"script"),Ea),i=h.length;l")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=r.contains(a.ownerDocument,a);if(!(o.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||r.isXMLDoc(a)))for(g=ma(h),f=ma(a),d=0,e=f.length;d0&&na(g,!i&&ma(a,"script")),h},cleanData:function(a){for(var b,c,d,e=r.event.special,f=0;void 0!==(c=a[f]);f++)if(T(c)){if(b=c[V.expando]){if(b.events)for(d in b.events)e[d]?r.event.remove(c,d):r.removeEvent(c,d,b.handle);c[V.expando]=void 0}c[W.expando]&&(c[W.expando]=void 0)}}}),r.fn.extend({detach:function(a){return Ja(this,a,!0)},remove:function(a){return Ja(this,a)},text:function(a){return S(this,function(a){return void 0===a?r.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return Ia(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Da(this,a);b.appendChild(a)}})},prepend:function(){return Ia(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Da(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ia(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ia(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(r.cleanData(ma(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null!=a&&a,b=null==b?a:b,this.map(function(){return r.clone(this,a,b)})},html:function(a){return S(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!za.test(a)&&!la[(ja.exec(a)||["",""])[1].toLowerCase()]){a=r.htmlPrefilter(a);try{for(;c1)}});function Ya(a,b,c,d,e){return new Ya.prototype.init(a,b,c,d,e)}r.Tween=Ya,Ya.prototype={constructor:Ya,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||r.easing._default,this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(r.cssNumber[c]?"":"px")},cur:function(){var a=Ya.propHooks[this.prop];return a&&a.get?a.get(this):Ya.propHooks._default.get(this)},run:function(a){var b,c=Ya.propHooks[this.prop];return this.options.duration?this.pos=b=r.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Ya.propHooks._default.set(this),this}},Ya.prototype.init.prototype=Ya.prototype,Ya.propHooks={_default:{get:function(a){var b;return 1!==a.elem.nodeType||null!=a.elem[a.prop]&&null==a.elem.style[a.prop]?a.elem[a.prop]:(b=r.css(a.elem,a.prop,""),b&&"auto"!==b?b:0)},set:function(a){r.fx.step[a.prop]?r.fx.step[a.prop](a):1!==a.elem.nodeType||null==a.elem.style[r.cssProps[a.prop]]&&!r.cssHooks[a.prop]?a.elem[a.prop]=a.now:r.style(a.elem,a.prop,a.now+a.unit)}}},Ya.propHooks.scrollTop=Ya.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},r.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},_default:"swing"},r.fx=Ya.prototype.init,r.fx.step={};var Za,$a,_a=/^(?:toggle|show|hide)$/,ab=/queueHooks$/;function bb(){$a&&(a.requestAnimationFrame(bb),r.fx.tick())}function cb(){return a.setTimeout(function(){Za=void 0}),Za=r.now()}function db(a,b){var c,d=0,e={height:a};for(b=b?1:0;d<4;d+=2-b)c=ba[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function eb(a,b,c){for(var d,e=(hb.tweeners[b]||[]).concat(hb.tweeners["*"]),f=0,g=e.length;f1)},removeAttr:function(a){return this.each(function(){r.removeAttr(this,a)})}}),r.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return"undefined"==typeof a.getAttribute?r.prop(a,b,c):(1===f&&r.isXMLDoc(a)||(e=r.attrHooks[b.toLowerCase()]||(r.expr.match.bool.test(b)?ib:void 0)), +void 0!==c?null===c?void r.removeAttr(a,b):e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:(a.setAttribute(b,c+""),c):e&&"get"in e&&null!==(d=e.get(a,b))?d:(d=r.find.attr(a,b),null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!o.radioValue&&"radio"===b&&r.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d=0,e=b&&b.match(K);if(e&&1===a.nodeType)while(c=e[d++])a.removeAttribute(c)}}),ib={set:function(a,b,c){return b===!1?r.removeAttr(a,c):a.setAttribute(c,c),c}},r.each(r.expr.match.bool.source.match(/\w+/g),function(a,b){var c=jb[b]||r.find.attr;jb[b]=function(a,b,d){var e,f,g=b.toLowerCase();return d||(f=jb[g],jb[g]=e,e=null!=c(a,b,d)?g:null,jb[g]=f),e}});var kb=/^(?:input|select|textarea|button)$/i,lb=/^(?:a|area)$/i;r.fn.extend({prop:function(a,b){return S(this,r.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[r.propFix[a]||a]})}}),r.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&r.isXMLDoc(a)||(b=r.propFix[b]||b,e=r.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=r.find.attr(a,"tabindex");return b?parseInt(b,10):kb.test(a.nodeName)||lb.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),o.optSelected||(r.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),r.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){r.propFix[this.toLowerCase()]=this});function mb(a){var b=a.match(K)||[];return b.join(" ")}function nb(a){return a.getAttribute&&a.getAttribute("class")||""}r.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).addClass(a.call(this,b,nb(this)))});if("string"==typeof a&&a){b=a.match(K)||[];while(c=this[i++])if(e=nb(c),d=1===c.nodeType&&" "+mb(e)+" "){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=mb(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).removeClass(a.call(this,b,nb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(K)||[];while(c=this[i++])if(e=nb(c),d=1===c.nodeType&&" "+mb(e)+" "){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=mb(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):r.isFunction(a)?this.each(function(c){r(this).toggleClass(a.call(this,c,nb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=r(this),f=a.match(K)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=nb(this),b&&V.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":V.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+mb(nb(c))+" ").indexOf(b)>-1)return!0;return!1}});var ob=/\r/g;r.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=r.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,r(this).val()):a,null==e?e="":"number"==typeof e?e+="":r.isArray(e)&&(e=r.map(e,function(a){return null==a?"":a+""})),b=r.valHooks[this.type]||r.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=r.valHooks[e.type]||r.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(ob,""):null==c?"":c)}}}),r.extend({valHooks:{option:{get:function(a){var b=r.find.attr(a,"value");return null!=b?b:mb(r.text(a))}},select:{get:function(a){var b,c,d,e=a.options,f=a.selectedIndex,g="select-one"===a.type,h=g?null:[],i=g?f+1:e.length;for(d=f<0?i:g?f:0;d-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),r.each(["radio","checkbox"],function(){r.valHooks[this]={set:function(a,b){if(r.isArray(b))return a.checked=r.inArray(r(a).val(),b)>-1}},o.checkOn||(r.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var pb=/^(?:focusinfocus|focusoutblur)$/;r.extend(r.event,{trigger:function(b,c,e,f){var g,h,i,j,k,m,n,o=[e||d],p=l.call(b,"type")?b.type:b,q=l.call(b,"namespace")?b.namespace.split("."):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!pb.test(p+r.event.triggered)&&(p.indexOf(".")>-1&&(q=p.split("."),p=q.shift(),q.sort()),k=p.indexOf(":")<0&&"on"+p,b=b[r.expando]?b:new r.Event(p,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=q.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:r.makeArray(c,[b]),n=r.event.special[p]||{},f||!n.trigger||n.trigger.apply(e,c)!==!1)){if(!f&&!n.noBubble&&!r.isWindow(e)){for(j=n.delegateType||p,pb.test(j+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),i=h;i===(e.ownerDocument||d)&&o.push(i.defaultView||i.parentWindow||a)}g=0;while((h=o[g++])&&!b.isPropagationStopped())b.type=g>1?j:n.bindType||p,m=(V.get(h,"events")||{})[b.type]&&V.get(h,"handle"),m&&m.apply(h,c),m=k&&h[k],m&&m.apply&&T(h)&&(b.result=m.apply(h,c),b.result===!1&&b.preventDefault());return b.type=p,f||b.isDefaultPrevented()||n._default&&n._default.apply(o.pop(),c)!==!1||!T(e)||k&&r.isFunction(e[p])&&!r.isWindow(e)&&(i=e[k],i&&(e[k]=null),r.event.triggered=p,e[p](),r.event.triggered=void 0,i&&(e[k]=i)),b.result}},simulate:function(a,b,c){var d=r.extend(new r.Event,c,{type:a,isSimulated:!0});r.event.trigger(d,null,b)}}),r.fn.extend({trigger:function(a,b){return this.each(function(){r.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];if(c)return r.event.trigger(a,b,c,!0)}}),r.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(a,b){r.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),r.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),o.focusin="onfocusin"in a,o.focusin||r.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){r.event.simulate(b,a.target,r.event.fix(a))};r.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=V.access(d,b);e||d.addEventListener(a,c,!0),V.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=V.access(d,b)-1;e?V.access(d,b,e):(d.removeEventListener(a,c,!0),V.remove(d,b))}}});var qb=a.location,rb=r.now(),sb=/\?/;r.parseXML=function(b){var c;if(!b||"string"!=typeof b)return null;try{c=(new a.DOMParser).parseFromString(b,"text/xml")}catch(d){c=void 0}return c&&!c.getElementsByTagName("parsererror").length||r.error("Invalid XML: "+b),c};var tb=/\[\]$/,ub=/\r?\n/g,vb=/^(?:submit|button|image|reset|file)$/i,wb=/^(?:input|select|textarea|keygen)/i;function xb(a,b,c,d){var e;if(r.isArray(b))r.each(b,function(b,e){c||tb.test(a)?d(a,e):xb(a+"["+("object"==typeof e&&null!=e?b:"")+"]",e,c,d)});else if(c||"object"!==r.type(b))d(a,b);else for(e in b)xb(a+"["+e+"]",b[e],c,d)}r.param=function(a,b){var c,d=[],e=function(a,b){var c=r.isFunction(b)?b():b;d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(null==c?"":c)};if(r.isArray(a)||a.jquery&&!r.isPlainObject(a))r.each(a,function(){e(this.name,this.value)});else for(c in a)xb(c,a[c],b,e);return d.join("&")},r.fn.extend({serialize:function(){return r.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=r.prop(this,"elements");return a?r.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!r(this).is(":disabled")&&wb.test(this.nodeName)&&!vb.test(a)&&(this.checked||!ia.test(a))}).map(function(a,b){var c=r(this).val();return null==c?null:r.isArray(c)?r.map(c,function(a){return{name:b.name,value:a.replace(ub,"\r\n")}}):{name:b.name,value:c.replace(ub,"\r\n")}}).get()}});var yb=/%20/g,zb=/#.*$/,Ab=/([?&])_=[^&]*/,Bb=/^(.*?):[ \t]*([^\r\n]*)$/gm,Cb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Db=/^(?:GET|HEAD)$/,Eb=/^\/\//,Fb={},Gb={},Hb="*/".concat("*"),Ib=d.createElement("a");Ib.href=qb.href;function Jb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(K)||[];if(r.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Kb(a,b,c,d){var e={},f=a===Gb;function g(h){var i;return e[h]=!0,r.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Lb(a,b){var c,d,e=r.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&r.extend(!0,a,d),a}function Mb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}if(f)return f!==i[0]&&i.unshift(f),c[f]}function Nb(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}r.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:qb.href,type:"GET",isLocal:Cb.test(qb.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Hb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":r.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Lb(Lb(a,r.ajaxSettings),b):Lb(r.ajaxSettings,a)},ajaxPrefilter:Jb(Fb),ajaxTransport:Jb(Gb),ajax:function(b,c){"object"==typeof b&&(c=b,b=void 0),c=c||{};var e,f,g,h,i,j,k,l,m,n,o=r.ajaxSetup({},c),p=o.context||o,q=o.context&&(p.nodeType||p.jquery)?r(p):r.event,s=r.Deferred(),t=r.Callbacks("once memory"),u=o.statusCode||{},v={},w={},x="canceled",y={readyState:0,getResponseHeader:function(a){var b;if(k){if(!h){h={};while(b=Bb.exec(g))h[b[1].toLowerCase()]=b[2]}b=h[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return k?g:null},setRequestHeader:function(a,b){return null==k&&(a=w[a.toLowerCase()]=w[a.toLowerCase()]||a,v[a]=b),this},overrideMimeType:function(a){return null==k&&(o.mimeType=a),this},statusCode:function(a){var b;if(a)if(k)y.always(a[y.status]);else for(b in a)u[b]=[u[b],a[b]];return this},abort:function(a){var b=a||x;return e&&e.abort(b),A(0,b),this}};if(s.promise(y),o.url=((b||o.url||qb.href)+"").replace(Eb,qb.protocol+"//"),o.type=c.method||c.type||o.method||o.type,o.dataTypes=(o.dataType||"*").toLowerCase().match(K)||[""],null==o.crossDomain){j=d.createElement("a");try{j.href=o.url,j.href=j.href,o.crossDomain=Ib.protocol+"//"+Ib.host!=j.protocol+"//"+j.host}catch(z){o.crossDomain=!0}}if(o.data&&o.processData&&"string"!=typeof o.data&&(o.data=r.param(o.data,o.traditional)),Kb(Fb,o,c,y),k)return y;l=r.event&&o.global,l&&0===r.active++&&r.event.trigger("ajaxStart"),o.type=o.type.toUpperCase(),o.hasContent=!Db.test(o.type),f=o.url.replace(zb,""),o.hasContent?o.data&&o.processData&&0===(o.contentType||"").indexOf("application/x-www-form-urlencoded")&&(o.data=o.data.replace(yb,"+")):(n=o.url.slice(f.length),o.data&&(f+=(sb.test(f)?"&":"?")+o.data,delete o.data),o.cache===!1&&(f=f.replace(Ab,"$1"),n=(sb.test(f)?"&":"?")+"_="+rb++ +n),o.url=f+n),o.ifModified&&(r.lastModified[f]&&y.setRequestHeader("If-Modified-Since",r.lastModified[f]),r.etag[f]&&y.setRequestHeader("If-None-Match",r.etag[f])),(o.data&&o.hasContent&&o.contentType!==!1||c.contentType)&&y.setRequestHeader("Content-Type",o.contentType),y.setRequestHeader("Accept",o.dataTypes[0]&&o.accepts[o.dataTypes[0]]?o.accepts[o.dataTypes[0]]+("*"!==o.dataTypes[0]?", "+Hb+"; q=0.01":""):o.accepts["*"]);for(m in o.headers)y.setRequestHeader(m,o.headers[m]);if(o.beforeSend&&(o.beforeSend.call(p,y,o)===!1||k))return y.abort();if(x="abort",t.add(o.complete),y.done(o.success),y.fail(o.error),e=Kb(Gb,o,c,y)){if(y.readyState=1,l&&q.trigger("ajaxSend",[y,o]),k)return y;o.async&&o.timeout>0&&(i=a.setTimeout(function(){y.abort("timeout")},o.timeout));try{k=!1,e.send(v,A)}catch(z){if(k)throw z;A(-1,z)}}else A(-1,"No Transport");function A(b,c,d,h){var j,m,n,v,w,x=c;k||(k=!0,i&&a.clearTimeout(i),e=void 0,g=h||"",y.readyState=b>0?4:0,j=b>=200&&b<300||304===b,d&&(v=Mb(o,y,d)),v=Nb(o,v,y,j),j?(o.ifModified&&(w=y.getResponseHeader("Last-Modified"),w&&(r.lastModified[f]=w),w=y.getResponseHeader("etag"),w&&(r.etag[f]=w)),204===b||"HEAD"===o.type?x="nocontent":304===b?x="notmodified":(x=v.state,m=v.data,n=v.error,j=!n)):(n=x,!b&&x||(x="error",b<0&&(b=0))),y.status=b,y.statusText=(c||x)+"",j?s.resolveWith(p,[m,x,y]):s.rejectWith(p,[y,x,n]),y.statusCode(u),u=void 0,l&&q.trigger(j?"ajaxSuccess":"ajaxError",[y,o,j?m:n]),t.fireWith(p,[y,x]),l&&(q.trigger("ajaxComplete",[y,o]),--r.active||r.event.trigger("ajaxStop")))}return y},getJSON:function(a,b,c){return r.get(a,b,c,"json")},getScript:function(a,b){return r.get(a,void 0,b,"script")}}),r.each(["get","post"],function(a,b){r[b]=function(a,c,d,e){return r.isFunction(c)&&(e=e||d,d=c,c=void 0),r.ajax(r.extend({url:a,type:b,dataType:e,data:c,success:d},r.isPlainObject(a)&&a))}}),r._evalUrl=function(a){return r.ajax({url:a,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},r.fn.extend({wrapAll:function(a){var b;return this[0]&&(r.isFunction(a)&&(a=a.call(this[0])),b=r(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this},wrapInner:function(a){return r.isFunction(a)?this.each(function(b){r(this).wrapInner(a.call(this,b))}):this.each(function(){var b=r(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=r.isFunction(a);return this.each(function(c){r(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(a){return this.parent(a).not("body").each(function(){r(this).replaceWith(this.childNodes)}),this}}),r.expr.pseudos.hidden=function(a){return!r.expr.pseudos.visible(a)},r.expr.pseudos.visible=function(a){return!!(a.offsetWidth||a.offsetHeight||a.getClientRects().length)},r.ajaxSettings.xhr=function(){try{return new a.XMLHttpRequest}catch(b){}};var Ob={0:200,1223:204},Pb=r.ajaxSettings.xhr();o.cors=!!Pb&&"withCredentials"in Pb,o.ajax=Pb=!!Pb,r.ajaxTransport(function(b){var c,d;if(o.cors||Pb&&!b.crossDomain)return{send:function(e,f){var g,h=b.xhr();if(h.open(b.type,b.url,b.async,b.username,b.password),b.xhrFields)for(g in b.xhrFields)h[g]=b.xhrFields[g];b.mimeType&&h.overrideMimeType&&h.overrideMimeType(b.mimeType),b.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest");for(g in e)h.setRequestHeader(g,e[g]);c=function(a){return function(){c&&(c=d=h.onload=h.onerror=h.onabort=h.onreadystatechange=null,"abort"===a?h.abort():"error"===a?"number"!=typeof h.status?f(0,"error"):f(h.status,h.statusText):f(Ob[h.status]||h.status,h.statusText,"text"!==(h.responseType||"text")||"string"!=typeof h.responseText?{binary:h.response}:{text:h.responseText},h.getAllResponseHeaders()))}},h.onload=c(),d=h.onerror=c("error"),void 0!==h.onabort?h.onabort=d:h.onreadystatechange=function(){4===h.readyState&&a.setTimeout(function(){c&&d()})},c=c("abort");try{h.send(b.hasContent&&b.data||null)}catch(i){if(c)throw i}},abort:function(){c&&c()}}}),r.ajaxPrefilter(function(a){a.crossDomain&&(a.contents.script=!1)}),r.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(a){return r.globalEval(a),a}}}),r.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),r.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(e,f){b=r(" + + + + + + + + + + + +
      +

      + + + + +
      + + + +
      + +
      + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/jsdoc/hifi-jsdoc-template/tmpl/mainpage.tmpl b/tools/jsdoc/hifi-jsdoc-template/tmpl/mainpage.tmpl new file mode 100644 index 0000000000..b38a185c0f --- /dev/null +++ b/tools/jsdoc/hifi-jsdoc-template/tmpl/mainpage.tmpl @@ -0,0 +1,10 @@ + + + +
      +
      +
      + diff --git a/tools/jsdoc/hifi-jsdoc-template/tmpl/members.tmpl b/tools/jsdoc/hifi-jsdoc-template/tmpl/members.tmpl new file mode 100644 index 0000000000..eef64c1f3f --- /dev/null +++ b/tools/jsdoc/hifi-jsdoc-template/tmpl/members.tmpl @@ -0,0 +1,41 @@ + + + + + + + + + + + + + +
      +
      + Type: +
      + +

      + +

       

      + + + + +
      Example 1? 's':'' ?>
      + + + +
      + + +
      Fires:
      +
        +
      • +
      + + diff --git a/tools/jsdoc/hifi-jsdoc-template/tmpl/method.tmpl b/tools/jsdoc/hifi-jsdoc-template/tmpl/method.tmpl new file mode 100644 index 0000000000..153459f475 --- /dev/null +++ b/tools/jsdoc/hifi-jsdoc-template/tmpl/method.tmpl @@ -0,0 +1,119 @@ + + + + + + + + + + + + + +
      + +
      + Returns: + + + +
      + +

      + + +

      + +

       

      + + + +

      Throws:

      + 1) { ?>
        +
      • +
      +

      + + + + + +

      Parameters

      + + + +
      Example 1? 's':'' ?>
      + + + +
      + + + +
      Extends:
      + + + + +
      Type:
      +
        +
      • + +
      • +
      + + + +
      This:
      +
      + + + +
      Requires:
      +
        +
      • +
      + + + +
      Fires:
      +
        +
      • +
      + + + +
      Listens to Events:
      +
        +
      • +
      + + + +
      Listeners of This Event:
      +
        +
      • +
      + + + +
      Yields:
      + 1) { ?>
        +
      • +
      + + + diff --git a/tools/jsdoc/hifi-jsdoc-template/tmpl/methodList.tmpl b/tools/jsdoc/hifi-jsdoc-template/tmpl/methodList.tmpl new file mode 100644 index 0000000000..7d88cd5e51 --- /dev/null +++ b/tools/jsdoc/hifi-jsdoc-template/tmpl/methodList.tmpl @@ -0,0 +1,107 @@ + + + +

      Constructor

      + + + + + + + + + + None + + 1) { ?>
        +
      • +
      + + + + + + + + + + + + + + + + + +
      Extends:
      + + + + +
      Type:
      +
        +
      • + +
      • +
      + + + +
      This:
      +
      + + + + +
      Requires:
      +
        +
      • +
      + + + +
      Fires:
      +
        +
      • +
      + + + +
      Listens to Events:
      +
        +
      • +
      + + + +
      Listeners of This Event:
      +
        +
      • +
      + + + +
      Yields:
      + 1) { ?>
        +
      • +
      + + + + \ No newline at end of file diff --git a/tools/jsdoc/hifi-jsdoc-template/tmpl/paramList.tmpl b/tools/jsdoc/hifi-jsdoc-template/tmpl/paramList.tmpl new file mode 100644 index 0000000000..5b5164b771 --- /dev/null +++ b/tools/jsdoc/hifi-jsdoc-template/tmpl/paramList.tmpl @@ -0,0 +1,67 @@ + + + + + + + , + + + diff --git a/tools/jsdoc/hifi-jsdoc-template/tmpl/params.tmpl b/tools/jsdoc/hifi-jsdoc-template/tmpl/params.tmpl new file mode 100644 index 0000000000..17f58b1677 --- /dev/null +++ b/tools/jsdoc/hifi-jsdoc-template/tmpl/params.tmpl @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameTypeAttributesDefaultDescription
      + + + + + + Default Value: + + +
      Properties
      + +
      \ No newline at end of file diff --git a/tools/jsdoc/hifi-jsdoc-template/tmpl/properties.tmpl b/tools/jsdoc/hifi-jsdoc-template/tmpl/properties.tmpl new file mode 100644 index 0000000000..5c83a7d587 --- /dev/null +++ b/tools/jsdoc/hifi-jsdoc-template/tmpl/properties.tmpl @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameTypeAttributesSummary
      + + + + + + <optional>
      + + + + <nullable>
      + +
      + + + +

      Default Value:

      + + + +
      Properties
      + +
      diff --git a/tools/jsdoc/hifi-jsdoc-template/tmpl/returns.tmpl b/tools/jsdoc/hifi-jsdoc-template/tmpl/returns.tmpl new file mode 100644 index 0000000000..3958e7018e --- /dev/null +++ b/tools/jsdoc/hifi-jsdoc-template/tmpl/returns.tmpl @@ -0,0 +1,8 @@ + + + + + diff --git a/tools/jsdoc/hifi-jsdoc-template/tmpl/returnsSimp.tmpl b/tools/jsdoc/hifi-jsdoc-template/tmpl/returnsSimp.tmpl new file mode 100644 index 0000000000..b93aa50ad4 --- /dev/null +++ b/tools/jsdoc/hifi-jsdoc-template/tmpl/returnsSimp.tmpl @@ -0,0 +1,6 @@ + + + + + diff --git a/tools/jsdoc/hifi-jsdoc-template/tmpl/signal.tmpl b/tools/jsdoc/hifi-jsdoc-template/tmpl/signal.tmpl new file mode 100644 index 0000000000..20743fe052 --- /dev/null +++ b/tools/jsdoc/hifi-jsdoc-template/tmpl/signal.tmpl @@ -0,0 +1,123 @@ + + + + + + + + + + + + + +
      + ( + + + + ) +
      + Returns: + + + +
      + +

      + + +

      + +

       

      + + + +

      Throws:

      + 1) { ?>
        +
      • +
      +

      + + + + + +

      Parameters

      + + + +
      Example 1? 's':'' ?>
      + + + +
      + + + +
      Extends:
      + + + + +
      Type:
      +
        +
      • + +
      • +
      + + + +
      This:
      +
      + + + +
      Requires:
      +
        +
      • +
      + + + +
      Fires:
      +
        +
      • +
      + + + +
      Listens to Events:
      +
        +
      • +
      + + + +
      Listeners of This Event:
      +
        +
      • +
      + + + +
      Yields:
      + 1) { ?>
        +
      • +
      + + + diff --git a/tools/jsdoc/hifi-jsdoc-template/tmpl/signalList.tmpl b/tools/jsdoc/hifi-jsdoc-template/tmpl/signalList.tmpl new file mode 100644 index 0000000000..b9a0e0ca86 --- /dev/null +++ b/tools/jsdoc/hifi-jsdoc-template/tmpl/signalList.tmpl @@ -0,0 +1,92 @@ + + + +

      Constructor

      + + + + + + + + + + + + + + + + + + + + +
      Extends:
      + + + + +
      Type:
      +
        +
      • + +
      • +
      + + + +
      This:
      +
      + + + + +
      Requires:
      +
        +
      • +
      + + + +
      Fires:
      +
        +
      • +
      + + + +
      Listens to Events:
      +
        +
      • +
      + + + +
      Listeners of This Event:
      +
        +
      • +
      + + + +
      Yields:
      + 1) { ?>
        +
      • +
      + + + + \ No newline at end of file diff --git a/tools/jsdoc/hifi-jsdoc-template/tmpl/source.tmpl b/tools/jsdoc/hifi-jsdoc-template/tmpl/source.tmpl new file mode 100644 index 0000000000..e559b5d103 --- /dev/null +++ b/tools/jsdoc/hifi-jsdoc-template/tmpl/source.tmpl @@ -0,0 +1,8 @@ + +
      +
      +
      +
      +
      \ No newline at end of file diff --git a/tools/jsdoc/hifi-jsdoc-template/tmpl/tutorial.tmpl b/tools/jsdoc/hifi-jsdoc-template/tmpl/tutorial.tmpl new file mode 100644 index 0000000000..88a0ad52aa --- /dev/null +++ b/tools/jsdoc/hifi-jsdoc-template/tmpl/tutorial.tmpl @@ -0,0 +1,19 @@ +
      + +
      + 0) { ?> +
        +
      • +
      + + +

      +
      + +
      + +
      + +
      diff --git a/tools/jsdoc/hifi-jsdoc-template/tmpl/type.tmpl b/tools/jsdoc/hifi-jsdoc-template/tmpl/type.tmpl new file mode 100644 index 0000000000..8f8d89369d --- /dev/null +++ b/tools/jsdoc/hifi-jsdoc-template/tmpl/type.tmpl @@ -0,0 +1,7 @@ + + +| + \ No newline at end of file diff --git a/tools/jsdoc/plugins/hifi.js b/tools/jsdoc/plugins/hifi.js index a525093965..b61c80284c 100644 --- a/tools/jsdoc/plugins/hifi.js +++ b/tools/jsdoc/plugins/hifi.js @@ -107,9 +107,6 @@ exports.handlers = { if (e.doclet.hifiClientEntity) { rows.push("Client Entity Scripts"); } - if (e.doclet.hifiAvatar) { - rows.push("Avatar Scripts"); - } if (e.doclet.hifiServerEntity) { rows.push("Server Entity Scripts"); } @@ -117,15 +114,31 @@ exports.handlers = { rows.push("Assignment Client Scripts"); } - // Append an Available In: table at the end of the namespace description. + // Append an Available In: sentence at the beginning of the namespace description. if (rows.length > 0) { - var table = "
      Available in:" + rows.join("") + "

      "; - e.doclet.description = table + (e.doclet.description ? e.doclet.description : ""); - } + var availableIn = "

      Supported Script Types: " + rows.join(" • ") + "

      "; + + e.doclet.description = (e.doclet.description ? e.doclet.description : "") + availableIn; + } } } }; +// Functions for adding @signal custom tag +/** @private */ +function setDocletKindToTitle(doclet, tag) { + doclet.addTag( 'kind', tag.title ); +} + +function setDocletNameToValue(doclet, tag) { + if (tag.value && tag.value.description) { // as in a long tag + doclet.addTag('name', tag.value.description); + } + else if (tag.text) { // or a short tag + doclet.addTag('name', tag.text); + } +} + // Define custom hifi tags here exports.defineTags = function (dictionary) { @@ -143,14 +156,6 @@ exports.defineTags = function (dictionary) { } }); - // @hifi-avatar-script - dictionary.defineTag("hifi-avatar", { - onTagged: function (doclet, tag) { - doclet.hifiAvatar = true; - } - }); - - // @hifi-client-entity dictionary.defineTag("hifi-client-entity", { onTagged: function (doclet, tag) { From 75000d9a798dea718fd001ba727896a30fb41916 Mon Sep 17 00:00:00 2001 From: ingerjm0 Date: Sat, 23 Feb 2019 21:28:00 -0800 Subject: [PATCH 178/474] Add @signal tag to hifi plugin --- tools/jsdoc/plugins/hifi.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tools/jsdoc/plugins/hifi.js b/tools/jsdoc/plugins/hifi.js index b61c80284c..16969b18fe 100644 --- a/tools/jsdoc/plugins/hifi.js +++ b/tools/jsdoc/plugins/hifi.js @@ -169,4 +169,14 @@ exports.defineTags = function (dictionary) { doclet.hifiServerEntity = true; } }); + + // @signal + dictionary.defineTag("signal", { + mustHaveValue: true, + onTagged: function(doclet, tag) { + setDocletKindToTitle(doclet, tag); + setDocletNameToValue(doclet, tag); + } + }); + }; \ No newline at end of file From 26ad42b9657e9c348f4bd3f637a5da3bef57a79c Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Sun, 24 Feb 2019 19:07:56 -0800 Subject: [PATCH 179/474] FIxing the gamma correction for Quest, intoducing simple gpu lib conversion shaders --- .../display-plugins/OpenGLDisplayPlugin.cpp | 22 +++++++++++----- libraries/gpu/src/gpu/Color.slh | 21 ++++++++++++--- .../src/gpu/DrawTextureGammaLinearToSRGB.slf | 26 +++++++++++++++++++ .../src/gpu/DrawTextureGammaLinearToSRGB.slp | 1 + .../src/gpu/DrawTextureGammaSRGBToLinear.slf | 26 +++++++++++++++++++ .../src/gpu/DrawTextureGammaSRGBToLinear.slp | 1 + .../oculusMobile/src/ovr/Framebuffer.cpp | 11 ++++---- 7 files changed, 93 insertions(+), 15 deletions(-) create mode 100644 libraries/gpu/src/gpu/DrawTextureGammaLinearToSRGB.slf create mode 100644 libraries/gpu/src/gpu/DrawTextureGammaLinearToSRGB.slp create mode 100644 libraries/gpu/src/gpu/DrawTextureGammaSRGBToLinear.slf create mode 100644 libraries/gpu/src/gpu/DrawTextureGammaSRGBToLinear.slp diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 28de13d8c2..c536e6b6e2 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -380,16 +380,26 @@ void OpenGLDisplayPlugin::customizeContext() { scissorState->setScissorEnable(true); { +#ifdef Q_OS_ANDROID + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTextureGammaLinearToSRGB); +#else gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTexture); - _simplePipeline = gpu::Pipeline::create(program, scissorState); - _hudPipeline = gpu::Pipeline::create(program, blendState); +#endif + _simplePipeline = gpu::Pipeline::create(program, scissorState); } - { - gpu::ShaderPointer program = gpu::Shader::createProgram(shader::display_plugins::program::SrgbToLinear); +#ifdef Q_OS_ANDROID + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTextureGammaLinearToSRGB); +#else + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTextureGammaSRGBToLinear); +#endif _presentPipeline = gpu::Pipeline::create(program, scissorState); } + { + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTexture); + _hudPipeline = gpu::Pipeline::create(program, blendState); + } { gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTextureMirroredX); _mirrorHUDPipeline = gpu::Pipeline::create(program, blendState); @@ -516,7 +526,6 @@ void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::Textur #ifndef USE_GLES batch.setPipeline(_presentPipeline); #else - //batch.setPipeline(_presentPipeline); batch.setPipeline(_simplePipeline); #endif batch.draw(gpu::TRIANGLE_STRIP, 4); @@ -631,8 +640,7 @@ void OpenGLDisplayPlugin::compositeScene() { batch.setStateScissorRect(ivec4(uvec2(), _compositeFramebuffer->getSize())); batch.resetViewTransform(); batch.setProjectionTransform(mat4()); - // batch.setPipeline(_simplePipeline); - batch.setPipeline(_presentPipeline); + batch.setPipeline(_simplePipeline); batch.setResourceTexture(0, _currentFrame->framebuffer->getRenderBuffer(0)); batch.draw(gpu::TRIANGLE_STRIP, 4); }); diff --git a/libraries/gpu/src/gpu/Color.slh b/libraries/gpu/src/gpu/Color.slh index 65ddc0b01e..af61e5a34b 100644 --- a/libraries/gpu/src/gpu/Color.slh +++ b/libraries/gpu/src/gpu/Color.slh @@ -16,10 +16,10 @@ // YCoCg =====> Luma (Y) chrominance green (Cg) and chrominance orange (Co) // https://software.intel.com/en-us/node/503873 +// sRGB ====> Linear float color_scalar_sRGBToLinear(float value) { - const float SRGB_ELBOW = 0.04045; - - return mix(pow((value + 0.055) / 1.055, 2.4), value / 12.92, float(value <= SRGB_ELBOW)); + // Same as pow(value, 2.2) + return mix(pow((value + 0.055) / 1.055, 2.4), value / 12.92, float(value <= 0.04045)); } vec3 color_sRGBToLinear(vec3 srgb) { @@ -30,6 +30,21 @@ vec4 color_sRGBAToLinear(vec4 srgba) { return vec4(color_sRGBToLinear(srgba.xyz), srgba.w); } +// Linear ====> sRGB +float color_scalar_LinearTosRGB(float value) { + // Same as return pow(value, 1/2.2) + return mix(1.055 * pow(value, 0.41666) - 0.055, value * 12.92, float(value < 0.0031308)); +} + +vec3 color_LinearTosRGB(vec3 lrgb) { + // Same as return pow(lrgb, 1/2.2) + return vec3(color_scalar_LinearTosRGB(lrgb.r), color_scalar_LinearTosRGB(lrgb.g), color_scalar_LinearTosRGB(lrgb.b)); +} + +vec4 color_LinearTosRGBA(vec4 lrgba) { + return vec4(color_LinearTosRGB(lrgba.xyz), lrgba.w); +} + vec3 color_LinearToYCoCg(vec3 rgb) { // Y = R/4 + G/2 + B/4 // Co = R/2 - B/2 diff --git a/libraries/gpu/src/gpu/DrawTextureGammaLinearToSRGB.slf b/libraries/gpu/src/gpu/DrawTextureGammaLinearToSRGB.slf new file mode 100644 index 0000000000..017df1d88d --- /dev/null +++ b/libraries/gpu/src/gpu/DrawTextureGammaLinearToSRGB.slf @@ -0,0 +1,26 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// DrawTexture.frag +// +// Draw texture 0 fetched at texcoord.xy +// +// Created by Sam Gateau on 6/22/2015 +// 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 gpu/Color.slh@> + + +LAYOUT(binding=0) uniform sampler2D colorMap; + +layout(location=0) in vec2 varTexCoord0; +layout(location=0) out vec4 outFragColor; + +void main(void) { + outFragColor = color_LinearTosRGBA(texture(colorMap, varTexCoord0)); +} diff --git a/libraries/gpu/src/gpu/DrawTextureGammaLinearToSRGB.slp b/libraries/gpu/src/gpu/DrawTextureGammaLinearToSRGB.slp new file mode 100644 index 0000000000..f922364b75 --- /dev/null +++ b/libraries/gpu/src/gpu/DrawTextureGammaLinearToSRGB.slp @@ -0,0 +1 @@ +VERTEX DrawUnitQuadTexcoord diff --git a/libraries/gpu/src/gpu/DrawTextureGammaSRGBToLinear.slf b/libraries/gpu/src/gpu/DrawTextureGammaSRGBToLinear.slf new file mode 100644 index 0000000000..048384fe6c --- /dev/null +++ b/libraries/gpu/src/gpu/DrawTextureGammaSRGBToLinear.slf @@ -0,0 +1,26 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// DrawTexture.frag +// +// Draw texture 0 fetched at texcoord.xy +// +// Created by Sam Gateau on 6/22/2015 +// 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 gpu/Color.slh@> + + +LAYOUT(binding=0) uniform sampler2D colorMap; + +layout(location=0) in vec2 varTexCoord0; +layout(location=0) out vec4 outFragColor; + +void main(void) { + outFragColor = color_sRGBAToLinear(texture(colorMap, varTexCoord0)); +} diff --git a/libraries/gpu/src/gpu/DrawTextureGammaSRGBToLinear.slp b/libraries/gpu/src/gpu/DrawTextureGammaSRGBToLinear.slp new file mode 100644 index 0000000000..f922364b75 --- /dev/null +++ b/libraries/gpu/src/gpu/DrawTextureGammaSRGBToLinear.slp @@ -0,0 +1 @@ +VERTEX DrawUnitQuadTexcoord diff --git a/libraries/oculusMobile/src/ovr/Framebuffer.cpp b/libraries/oculusMobile/src/ovr/Framebuffer.cpp index 4c4fd2a983..0f59eef614 100644 --- a/libraries/oculusMobile/src/ovr/Framebuffer.cpp +++ b/libraries/oculusMobile/src/ovr/Framebuffer.cpp @@ -32,18 +32,19 @@ void Framebuffer::create(const glm::uvec2& size) { _validTexture = false; // Depth renderbuffer - glGenRenderbuffers(1, &_depth); + /* glGenRenderbuffers(1, &_depth); glBindRenderbuffer(GL_RENDERBUFFER, _depth); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, _size.x, _size.y); glBindRenderbuffer(GL_RENDERBUFFER, 0); - +*/ // Framebuffer glGenFramebuffers(1, &_fbo); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo); - glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depth); - glBindFramebuffer(GL_FRAMEBUFFER, 0); + // glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo); + // glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depth); + // glBindFramebuffer(GL_FRAMEBUFFER, 0); _swapChain = vrapi_CreateTextureSwapChain3(VRAPI_TEXTURE_TYPE_2D, GL_RGBA8, _size.x, _size.y, 1, 3); + _length = vrapi_GetTextureSwapChainLength(_swapChain); if (!_length) { __android_log_write(ANDROID_LOG_WARN, "QQQ_OVR", "Unable to count swap chain textures"); From 4ee2b4322951e46f20355107758d6aea96c58245 Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Sun, 24 Feb 2019 20:49:51 -0800 Subject: [PATCH 180/474] FIxing the gamma correction for Quest, intoducing simple gpu lib conversion shaders --- .../src/display-plugins/SrgbToLinear.slf | 23 ------------------- .../src/display-plugins/SrgbToLinear.slp | 1 - libraries/gpu/src/gpu/Color.slh | 8 +++++-- 3 files changed, 6 insertions(+), 26 deletions(-) delete mode 100644 libraries/display-plugins/src/display-plugins/SrgbToLinear.slf delete mode 100644 libraries/display-plugins/src/display-plugins/SrgbToLinear.slp diff --git a/libraries/display-plugins/src/display-plugins/SrgbToLinear.slf b/libraries/display-plugins/src/display-plugins/SrgbToLinear.slf deleted file mode 100644 index 428ec821a6..0000000000 --- a/libraries/display-plugins/src/display-plugins/SrgbToLinear.slf +++ /dev/null @@ -1,23 +0,0 @@ -// OpenGLDisplayPlugin_present.frag - -LAYOUT(binding=0) uniform sampler2D colorMap; - -layout(location=0) in vec2 varTexCoord0; - -layout(location=0) out vec4 outFragColor; - -float sRGBFloatToLinear(float value) { - const float SRGB_ELBOW = 0.04045; - - return mix(pow((value + 0.055) / 1.055, 2.4), value / 12.92, float(value <= SRGB_ELBOW)); -} - -vec3 colorToLinearRGB(vec3 srgb) { - return vec3(sRGBFloatToLinear(srgb.r), sRGBFloatToLinear(srgb.g), sRGBFloatToLinear(srgb.b)); -} - -void main(void) { - outFragColor.a = 1.0; - // outFragColor.rgb = colorToLinearRGB(texture(colorMap, varTexCoord0).rgb); - outFragColor.rgb = pow(texture(colorMap, varTexCoord0).rgb, vec3(1.0 / 2.2)); -} diff --git a/libraries/display-plugins/src/display-plugins/SrgbToLinear.slp b/libraries/display-plugins/src/display-plugins/SrgbToLinear.slp deleted file mode 100644 index c2c4bfbebd..0000000000 --- a/libraries/display-plugins/src/display-plugins/SrgbToLinear.slp +++ /dev/null @@ -1 +0,0 @@ -VERTEX gpu::vertex::DrawUnitQuadTexcoord diff --git a/libraries/gpu/src/gpu/Color.slh b/libraries/gpu/src/gpu/Color.slh index af61e5a34b..c676e66c6c 100644 --- a/libraries/gpu/src/gpu/Color.slh +++ b/libraries/gpu/src/gpu/Color.slh @@ -23,7 +23,10 @@ float color_scalar_sRGBToLinear(float value) { } vec3 color_sRGBToLinear(vec3 srgb) { - return vec3(color_scalar_sRGBToLinear(srgb.r), color_scalar_sRGBToLinear(srgb.g), color_scalar_sRGBToLinear(srgb.b)); + // return vec3(color_scalar_sRGBToLinear(srgb.r), color_scalar_sRGBToLinear(srgb.g), color_scalar_sRGBToLinear(srgb.b)); + // Same as pow(value, 2.2) + return mix(pow((srgb + vec3(0.055)) / vec3(1.055), vec3(2.4)), srgb / vec3(12.92), vec3(lessThanEqual(srgb, vec3(0.04045)))); + } vec4 color_sRGBAToLinear(vec4 srgba) { @@ -38,7 +41,8 @@ float color_scalar_LinearTosRGB(float value) { vec3 color_LinearTosRGB(vec3 lrgb) { // Same as return pow(lrgb, 1/2.2) - return vec3(color_scalar_LinearTosRGB(lrgb.r), color_scalar_LinearTosRGB(lrgb.g), color_scalar_LinearTosRGB(lrgb.b)); +// return vec3(color_scalar_LinearTosRGB(lrgb.r), color_scalar_LinearTosRGB(lrgb.g), color_scalar_LinearTosRGB(lrgb.b)); + return mix(vec3(1.055) * pow(vec3(lrgb), vec3(0.41666)) - vec3(0.055), vec3(lrgb) * vec3(12.92), vec3(lessThan(lrgb, vec3(0.0031308)))); } vec4 color_LinearTosRGBA(vec4 lrgba) { From df6d8f3be00c621023405f6b9bbfe5d3ab333755 Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Sun, 24 Feb 2019 21:31:28 -0800 Subject: [PATCH 181/474] FIxing the gamma correction for Quest, intoducing simple gpu lib conversion shaders --- libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp b/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp index 7943d058bd..a63b954f02 100644 --- a/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp +++ b/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp @@ -6,7 +6,6 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "OculusMobileDisplayPlugin.h" -#include "../../oculusMobile/src/ovr/Helpers.h" #include #include From 2bcee4e454f4a727800e3d6081a9367079b319ae Mon Sep 17 00:00:00 2001 From: nimisha20 Date: Mon, 25 Feb 2019 08:54:47 -0800 Subject: [PATCH 182/474] Update AccountServicesScriptingInterface.cpp Changes according to ctrlaltdavid's comments. --- interface/src/scripting/AccountServicesScriptingInterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/scripting/AccountServicesScriptingInterface.cpp b/interface/src/scripting/AccountServicesScriptingInterface.cpp index 5ede7c7a98..5f8fb065ff 100644 --- a/interface/src/scripting/AccountServicesScriptingInterface.cpp +++ b/interface/src/scripting/AccountServicesScriptingInterface.cpp @@ -115,7 +115,7 @@ DownloadInfoResult::DownloadInfoResult() : /**jsdoc * Information on the assets currently being downloaded and pending download. * @typedef {object} AccountServices.DownloadInfoResult - * @property {number[]} downloading - The download percentage of each asset currently downloading. + * @property {number[]} downloading - The download percentage remaining of each asset currently downloading. * @property {number} pending - The number of assets pending download. */ QScriptValue DownloadInfoResultToScriptValue(QScriptEngine* engine, const DownloadInfoResult& result) { From e20f13f0ae8a17aa01ed0911a86228e94a905dea Mon Sep 17 00:00:00 2001 From: nimisha20 Date: Mon, 25 Feb 2019 09:28:56 -0800 Subject: [PATCH 183/474] Update AccountServicesScriptingInterface.h Adding ctrlaltdavid's changes --- interface/src/scripting/AccountServicesScriptingInterface.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/scripting/AccountServicesScriptingInterface.h b/interface/src/scripting/AccountServicesScriptingInterface.h index b5c4a6e0df..415b405e53 100644 --- a/interface/src/scripting/AccountServicesScriptingInterface.h +++ b/interface/src/scripting/AccountServicesScriptingInterface.h @@ -98,7 +98,7 @@ public slots: bool isLoggedIn(); /**jsdoc - * Prompts the user to log in (the login dialog is displayed) if they're not already logged in. + * Prompts the user to log in (with a login dialog) if they're not already logged in. The function returns if the user is logged in or not before the user completes the login dialog. * @function AccountServices.checkAndSignalForAccessToken * @returns {boolean} true if the user is logged in, false if not. */ From b563f21037878bb28812c9100d0cbedd5871809e Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Mon, 25 Feb 2019 09:32:27 -0800 Subject: [PATCH 184/474] Fix comments --- libraries/gpu/src/gpu/DrawTextureGammaLinearToSRGB.slf | 8 ++++---- libraries/gpu/src/gpu/DrawTextureGammaSRGBToLinear.slf | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/libraries/gpu/src/gpu/DrawTextureGammaLinearToSRGB.slf b/libraries/gpu/src/gpu/DrawTextureGammaLinearToSRGB.slf index 017df1d88d..3ca3a92f01 100644 --- a/libraries/gpu/src/gpu/DrawTextureGammaLinearToSRGB.slf +++ b/libraries/gpu/src/gpu/DrawTextureGammaLinearToSRGB.slf @@ -2,12 +2,12 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// DrawTexture.frag +// DrawTextureGammaLinearToSRGB.frag // -// Draw texture 0 fetched at texcoord.xy +// Draw texture 0 fetched at texcoord.xy, and apply linear to sRGB color space conversion // -// Created by Sam Gateau on 6/22/2015 -// Copyright 2015 High Fidelity, Inc. +// Created by Sam Gateau on 2/24/2019 +// Copyright 2019 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 diff --git a/libraries/gpu/src/gpu/DrawTextureGammaSRGBToLinear.slf b/libraries/gpu/src/gpu/DrawTextureGammaSRGBToLinear.slf index 048384fe6c..870967ec3a 100644 --- a/libraries/gpu/src/gpu/DrawTextureGammaSRGBToLinear.slf +++ b/libraries/gpu/src/gpu/DrawTextureGammaSRGBToLinear.slf @@ -2,12 +2,12 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// DrawTexture.frag +// DrawTextureGammaSRGBToLinear.frag // -// Draw texture 0 fetched at texcoord.xy +// Draw texture 0 fetched at texcoord.xy, and apply sRGB to Linear color space conversion // -// Created by Sam Gateau on 6/22/2015 -// Copyright 2015 High Fidelity, Inc. +// Created by Sam Gateau on 2/24/2019 +// Copyright 2019 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 From d27d624c3f652011dea0ba4459680154846426e3 Mon Sep 17 00:00:00 2001 From: Clement Date: Fri, 22 Feb 2019 18:05:42 -0800 Subject: [PATCH 185/474] Don't send a KillAvatar packet on kick The DS already takes cares of removing nodes no longer allowed when the permissions are updated and KillAvatar was never meant to be used from the DS. --- domain-server/src/DomainServerSettingsManager.cpp | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 780fad15f2..4e833f6b77 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -25,13 +25,13 @@ #include #include +#include #include #include #include #include #include #include -#include //for KillAvatarReason #include #include "DomainServerNodeData.h" @@ -870,14 +870,6 @@ void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointerwrite(nodeUUID.toRfc4122()); - packet->writePrimitive(KillAvatarReason::NoReason); - - // send to avatar mixer, it sends the kill to everyone else - limitedNodeList->broadcastToNodes(std::move(packet), NodeSet() << NodeType::AvatarMixer); - if (newPermissions) { qDebug() << "Removing connect permission for node" << uuidStringWithoutCurlyBraces(matchingNode->getUUID()) << "after kick request from" << uuidStringWithoutCurlyBraces(sendingNode->getUUID()); From d3b3dfd76d14b82f11596f677b0be3e072399ac5 Mon Sep 17 00:00:00 2001 From: Clement Date: Fri, 22 Feb 2019 18:05:42 -0800 Subject: [PATCH 186/474] Don't send a KillAvatar packet on kick The DS already takes cares of removing nodes no longer allowed when the permissions are updated and KillAvatar was never meant to be used from the DS. --- domain-server/src/DomainServerSettingsManager.cpp | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 780fad15f2..4e833f6b77 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -25,13 +25,13 @@ #include #include +#include #include #include #include #include #include #include -#include //for KillAvatarReason #include #include "DomainServerNodeData.h" @@ -870,14 +870,6 @@ void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointerwrite(nodeUUID.toRfc4122()); - packet->writePrimitive(KillAvatarReason::NoReason); - - // send to avatar mixer, it sends the kill to everyone else - limitedNodeList->broadcastToNodes(std::move(packet), NodeSet() << NodeType::AvatarMixer); - if (newPermissions) { qDebug() << "Removing connect permission for node" << uuidStringWithoutCurlyBraces(matchingNode->getUUID()) << "after kick request from" << uuidStringWithoutCurlyBraces(sendingNode->getUUID()); From 50035a198366ad46ffc61fd42d2847302f501b6b Mon Sep 17 00:00:00 2001 From: Clement Date: Fri, 22 Feb 2019 18:07:26 -0800 Subject: [PATCH 187/474] Delete old nodes with identical socket --- libraries/networking/src/LimitedNodeList.cpp | 176 +++++++++---------- 1 file changed, 84 insertions(+), 92 deletions(-) diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 8b9e37569c..eaa02f059e 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -645,103 +645,95 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, Node::LocalID localID, bool isReplicated, bool isUpstream, const QUuid& connectionSecret, const NodePermissions& permissions) { - QReadLocker readLocker(&_nodeMutex); - NodeHash::const_iterator it = _nodeHash.find(uuid); + { + QReadLocker readLocker(&_nodeMutex); + NodeHash::const_iterator it = _nodeHash.find(uuid); - if (it != _nodeHash.end()) { - SharedNodePointer& matchingNode = it->second; + if (it != _nodeHash.end()) { + SharedNodePointer& matchingNode = it->second; - matchingNode->setPublicSocket(publicSocket); - matchingNode->setLocalSocket(localSocket); - matchingNode->setPermissions(permissions); - matchingNode->setConnectionSecret(connectionSecret); - matchingNode->setIsReplicated(isReplicated); - matchingNode->setIsUpstream(isUpstream || NodeType::isUpstream(nodeType)); - matchingNode->setLocalID(localID); + matchingNode->setPublicSocket(publicSocket); + matchingNode->setLocalSocket(localSocket); + matchingNode->setPermissions(permissions); + matchingNode->setConnectionSecret(connectionSecret); + matchingNode->setIsReplicated(isReplicated); + matchingNode->setIsUpstream(isUpstream || NodeType::isUpstream(nodeType)); + matchingNode->setLocalID(localID); - return matchingNode; - } else { - auto it = _connectionIDs.find(uuid); - if (it == _connectionIDs.end()) { - _connectionIDs[uuid] = INITIAL_CONNECTION_ID; + return matchingNode; } - - // we didn't have this node, so add them - Node* newNode = new Node(uuid, nodeType, publicSocket, localSocket); - newNode->setIsReplicated(isReplicated); - newNode->setIsUpstream(isUpstream || NodeType::isUpstream(nodeType)); - newNode->setConnectionSecret(connectionSecret); - newNode->setPermissions(permissions); - newNode->setLocalID(localID); - - // move the newly constructed node to the LNL thread - newNode->moveToThread(thread()); - - if (nodeType == NodeType::AudioMixer) { - LimitedNodeList::flagTimeForConnectionStep(LimitedNodeList::AddedAudioMixer); - } - - SharedNodePointer newNodePointer(newNode, &QObject::deleteLater); - - // if this is a solo node type, we assume that the DS has replaced its assignment and we should kill the previous node - if (SOLO_NODE_TYPES.count(newNode->getType())) { - // while we still have the read lock, see if there is a previous solo node we'll need to remove - auto previousSoloIt = std::find_if(_nodeHash.cbegin(), _nodeHash.cend(), [newNode](const UUIDNodePair& nodePair){ - return nodePair.second->getType() == newNode->getType(); - }); - - if (previousSoloIt != _nodeHash.cend()) { - // we have a previous solo node, switch to a write lock so we can remove it - readLocker.unlock(); - - QWriteLocker writeLocker(&_nodeMutex); - - auto oldSoloNode = previousSoloIt->second; - - _localIDMap.unsafe_erase(oldSoloNode->getLocalID()); - _nodeHash.unsafe_erase(previousSoloIt); - handleNodeKill(oldSoloNode); - - // convert the current lock back to a read lock for insertion of new node - writeLocker.unlock(); - readLocker.relock(); - } - } - - // insert the new node and release our read lock -#if defined(Q_OS_ANDROID) || (defined(__clang__) && defined(Q_OS_LINUX)) - _nodeHash.insert(UUIDNodePair(newNode->getUUID(), newNodePointer)); - _localIDMap.insert(std::pair(localID, newNodePointer)); -#else - _nodeHash.emplace(newNode->getUUID(), newNodePointer); - _localIDMap.emplace(localID, newNodePointer); -#endif - readLocker.unlock(); - - qCDebug(networking) << "Added" << *newNode; - - auto weakPtr = newNodePointer.toWeakRef(); // We don't want the lambdas to hold a strong ref - - emit nodeAdded(newNodePointer); - if (newNodePointer->getActiveSocket()) { - emit nodeActivated(newNodePointer); - } else { - connect(newNodePointer.data(), &NetworkPeer::socketActivated, this, [this, weakPtr] { - auto sharedPtr = weakPtr.lock(); - if (sharedPtr) { - emit nodeActivated(sharedPtr); - disconnect(sharedPtr.data(), &NetworkPeer::socketActivated, this, 0); - } - }); - } - - // Signal when a socket changes, so we can start the hole punch over. - connect(newNodePointer.data(), &NetworkPeer::socketUpdated, this, [this, weakPtr] { - emit nodeSocketUpdated(weakPtr); - }); - - return newNodePointer; } + + auto removeOldNode = [&](auto node) { + if (node) { + QWriteLocker writeLocker(&_nodeMutex); + _localIDMap.unsafe_erase(node->getLocalID()); + _nodeHash.unsafe_erase(node->getUUID()); + handleNodeKill(node); + } + }; + + // if this is a solo node type, we assume that the DS has replaced its assignment and we should kill the previous node + if (SOLO_NODE_TYPES.count(nodeType)) { + removeOldNode(soloNodeOfType(nodeType)); + } + // If there is a new node with the same socket, this is a reconnection, kill the old node + removeOldNode(findNodeWithAddr(publicSocket)); + removeOldNode(findNodeWithAddr(localSocket)); + + auto it = _connectionIDs.find(uuid); + if (it == _connectionIDs.end()) { + _connectionIDs[uuid] = INITIAL_CONNECTION_ID; + } + + // we didn't have this node, so add them + Node* newNode = new Node(uuid, nodeType, publicSocket, localSocket); + newNode->setIsReplicated(isReplicated); + newNode->setIsUpstream(isUpstream || NodeType::isUpstream(nodeType)); + newNode->setConnectionSecret(connectionSecret); + newNode->setPermissions(permissions); + newNode->setLocalID(localID); + + // move the newly constructed node to the LNL thread + newNode->moveToThread(thread()); + + if (nodeType == NodeType::AudioMixer) { + LimitedNodeList::flagTimeForConnectionStep(LimitedNodeList::AddedAudioMixer); + } + + SharedNodePointer newNodePointer(newNode, &QObject::deleteLater); + + + { + QReadLocker readLocker(&_nodeMutex); + // insert the new node and release our read lock + _nodeHash.insert({ newNode->getUUID(), newNodePointer }); + _localIDMap.insert({ localID, newNodePointer }); + } + + qCDebug(networking) << "Added" << *newNode; + + auto weakPtr = newNodePointer.toWeakRef(); // We don't want the lambdas to hold a strong ref + + emit nodeAdded(newNodePointer); + if (newNodePointer->getActiveSocket()) { + emit nodeActivated(newNodePointer); + } else { + connect(newNodePointer.data(), &NetworkPeer::socketActivated, this, [this, weakPtr] { + auto sharedPtr = weakPtr.lock(); + if (sharedPtr) { + emit nodeActivated(sharedPtr); + disconnect(sharedPtr.data(), &NetworkPeer::socketActivated, this, 0); + } + }); + } + + // Signal when a socket changes, so we can start the hole punch over. + connect(newNodePointer.data(), &NetworkPeer::socketUpdated, this, [this, weakPtr] { + emit nodeSocketUpdated(weakPtr); + }); + + return newNodePointer; } std::unique_ptr LimitedNodeList::constructPingPacket(const QUuid& nodeId, PingType_t pingType) { From 8058ad884e343d685ff72e5dff479a6afeb9cfd3 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 25 Feb 2019 09:48:41 -0800 Subject: [PATCH 188/474] trying to fix overlay rotation --- interface/src/ui/overlays/Overlays.cpp | 60 +++++++------------------- interface/src/ui/overlays/Overlays.h | 2 +- 2 files changed, 17 insertions(+), 45 deletions(-) diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 62a6b88fc0..6d04c4d53a 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -295,14 +295,14 @@ QString Overlays::overlayToEntityType(const QString& type) { } \ } -static QHash savedRotations = QHash(); +static QHash> savedRotations = QHash>(); EntityItemProperties Overlays::convertOverlayToEntityProperties(QVariantMap& overlayProps, const QString& type, bool add, const QUuid& id) { - glm::quat rotation; + std::pair rotation; return convertOverlayToEntityProperties(overlayProps, rotation, type, add, id); } -EntityItemProperties Overlays::convertOverlayToEntityProperties(QVariantMap& overlayProps, glm::quat& rotationToSave, const QString& type, bool add, const QUuid& id) { +EntityItemProperties Overlays::convertOverlayToEntityProperties(QVariantMap& overlayProps, std::pair& rotationToSave, const QString& type, bool add, const QUuid& id) { overlayProps["type"] = type; SET_OVERLAY_PROP_DEFAULT(alpha, 0.7); @@ -559,65 +559,33 @@ EntityItemProperties Overlays::convertOverlayToEntityProperties(QVariantMap& ove SET_OVERLAY_PROP_DEFAULT(textures, PathUtils::resourcesUrl() + "images/whitePixel.png"); } - { // Overlays did this conversion for rotation - auto iter = overlayProps.find("rotation"); - if (iter != overlayProps.end() && !overlayProps.contains("localRotation")) { - QUuid parentID; - { - auto iter = overlayProps.find("parentID"); - if (iter != overlayProps.end()) { - parentID = iter.value().toUuid(); - } else if (!add) { - EntityPropertyFlags desiredProperties; - desiredProperties += PROP_PARENT_ID; - parentID = DependencyManager::get()->getEntityProperties(id, desiredProperties).getParentID(); - } - } - - int parentJointIndex = -1; - { - auto iter = overlayProps.find("parentJointIndex"); - if (iter != overlayProps.end()) { - parentJointIndex = iter.value().toInt(); - } else if (!add) { - EntityPropertyFlags desiredProperties; - desiredProperties += PROP_PARENT_JOINT_INDEX; - parentJointIndex = DependencyManager::get()->getEntityProperties(id, desiredProperties).getParentJointIndex(); - } - } - - glm::quat rotation = quatFromVariant(iter.value()); - bool success = false; - glm::quat localRotation = SpatiallyNestable::worldToLocal(rotation, parentID, parentJointIndex, false, success); - if (success) { - overlayProps["rotation"] = quatToVariant(localRotation); - } - } - } - if (type == "Text" || type == "Image" || type == "Grid" || type == "Web") { glm::quat originalRotation = ENTITY_ITEM_DEFAULT_ROTATION; + bool local = false; { auto iter = overlayProps.find("rotation"); if (iter != overlayProps.end()) { originalRotation = quatFromVariant(iter.value()); + local = false; } else { iter = overlayProps.find("localRotation"); if (iter != overlayProps.end()) { originalRotation = quatFromVariant(iter.value()); + local = true; } else if (!add) { auto iter2 = savedRotations.find(id); if (iter2 != savedRotations.end()) { - originalRotation = iter2.value(); + originalRotation = iter2.value().first; + local = iter2.value().second; } } } } if (!add) { - savedRotations[id] = originalRotation; + savedRotations[id] = { originalRotation, local }; } else { - rotationToSave = originalRotation; + rotationToSave = { originalRotation, local }; } glm::vec3 dimensions = ENTITY_ITEM_DEFAULT_DIMENSIONS; @@ -648,7 +616,11 @@ EntityItemProperties Overlays::convertOverlayToEntityProperties(QVariantMap& ove rotation = glm::angleAxis((float)M_PI, rotation * Vectors::UP) * rotation; } - overlayProps["localRotation"] = quatToVariant(rotation); + if (local) { + overlayProps["localRotation"] = quatToVariant(rotation); + } else { + overlayProps["rotation"] = quatToVariant(rotation); + } overlayProps["dimensions"] = vec3toVariant(glm::abs(dimensions)); } } @@ -826,7 +798,7 @@ QUuid Overlays::addOverlay(const QString& type, const QVariant& properties) { if (type == "rectangle3d") { propertyMap["shape"] = "Quad"; } - glm::quat rotationToSave; + std::pair rotationToSave; QUuid id = DependencyManager::get()->addEntityInternal(convertOverlayToEntityProperties(propertyMap, rotationToSave, entityType, true), entity::HostType::LOCAL); if (entityType == "Text" || entityType == "Image" || entityType == "Grid" || entityType == "Web") { savedRotations[id] = rotationToSave; diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index 838a38eb54..93efc2bc0b 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -729,7 +729,7 @@ private: QVariantMap convertEntityToOverlayProperties(const EntityItemProperties& entityProps); EntityItemProperties convertOverlayToEntityProperties(QVariantMap& overlayProps, const QString& type, bool add, const QUuid& id); - EntityItemProperties convertOverlayToEntityProperties(QVariantMap& overlayProps, glm::quat& rotationToSave, const QString& type, bool add, const QUuid& id = QUuid()); + EntityItemProperties convertOverlayToEntityProperties(QVariantMap& overlayProps, std::pair& rotationToSave, const QString& type, bool add, const QUuid& id = QUuid()); private slots: void mousePressPointerEvent(const QUuid& id, const PointerEvent& event); From 79d0a0a0a8a82e8a6ac1e482b18936f81786c11a Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Mon, 25 Feb 2019 19:28:38 +0100 Subject: [PATCH 189/474] avatar doctor project status --- .../qml/hifi/avatarPackager/AvatarProject.qml | 82 ++++++++++++++++++- interface/src/avatar/AvatarProject.h | 5 +- 2 files changed, 85 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/avatarPackager/AvatarProject.qml b/interface/resources/qml/hifi/avatarPackager/AvatarProject.qml index 85ef821a4a..a92739cf8a 100644 --- a/interface/resources/qml/hifi/avatarPackager/AvatarProject.qml +++ b/interface/resources/qml/hifi/avatarPackager/AvatarProject.qml @@ -213,6 +213,63 @@ Item { popup.open(); } + HiFiGlyphs { + id: errorsGlyph + visible: !AvatarPackagerCore.currentAvatarProject || AvatarPackagerCore.currentAvatarProject.hasErrors + text: hifi.glyphs.alert + size: 315 + color: "#EA4C5F" + anchors { + top: parent.top + topMargin: -30 + horizontalCenter: parent.horizontalCenter + } + } + + Image { + id: successGlyph + visible: AvatarPackagerCore.currentAvatarProject && !AvatarPackagerCore.currentAvatarProject.hasErrors + anchors { + top: parent.top + topMargin: 52 + horizontalCenter: parent.horizontalCenter + } + width: 149.6 + height: 149 + source: "../../../icons/checkmark-stroke.svg" + } + + RalewayRegular { + id: doctorStatusMessage + + states: [ + State { + when: AvatarPackagerCore.currentAvatarProject && !AvatarPackagerCore.currentAvatarProject.hasErrors + name: "noErrors" + PropertyChanges { + target: doctorStatusMessage + text: "Your avatar looks fine." + } + }, + State { + when: !AvatarPackagerCore.currentAvatarProject || AvatarPackagerCore.currentAvatarProject.hasErrors + name: "errors" + PropertyChanges { + target: doctorStatusMessage + text: "It seems your project has a few issues that will affect how it works in High Fidelity. " + } + } + ] + color: 'white' + size: 20 + + anchors.left: parent.left + anchors.right: parent.right + anchors.top: errorsGlyph.bottom + + wrapMode: Text.Wrap + } + RalewayRegular { id: infoMessage @@ -240,7 +297,7 @@ Item { anchors.left: parent.left anchors.right: parent.right - anchors.top: parent.top + anchors.top: doctorStatusMessage.bottom anchors.bottomMargin: 24 @@ -249,6 +306,29 @@ Item { text: "You can upload your files to our servers to always access them, and to make your avatar visible to other users." } + RalewayRegular { + id: showErrorsLink + + color: 'white' + linkColor: '#00B4EF' + + visible: AvatarPackagerCore.currentAvatarProject && AvatarPackagerCore.currentAvatarProject.hasErrors + + anchors { + top: infoMessage.bottom + topMargin: 28 + horizontalCenter: parent.horizontalCenter + } + + size: 28 + + text: "View all errors" + + onLinkActivated: { + avatarPackager.state = AvatarPackagerState.avatarDoctorErrorReport; + } + } + HifiControls.Button { id: openFolderButton diff --git a/interface/src/avatar/AvatarProject.h b/interface/src/avatar/AvatarProject.h index f11547bdca..0a63290051 100644 --- a/interface/src/avatar/AvatarProject.h +++ b/interface/src/avatar/AvatarProject.h @@ -77,7 +77,10 @@ public: return QDir::cleanPath(QDir(_projectPath).absoluteFilePath(_fst->getModelPath())); } Q_INVOKABLE bool getHasErrors() const { return _hasErrors; } - Q_INVOKABLE void setHasErrors(bool hasErrors) { _hasErrors = hasErrors; } + Q_INVOKABLE void setHasErrors(bool hasErrors) { + _hasErrors = hasErrors; + emit hasErrorsChanged(); + } /** * returns the AvatarProject or a nullptr on failure. From 761a9229511efce10c68a77f00805b8220af0a05 Mon Sep 17 00:00:00 2001 From: ingerjm0 Date: Mon, 25 Feb 2019 10:43:14 -0800 Subject: [PATCH 190/474] Fixed per code review --- tools/jsdoc/plugins/hifi.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tools/jsdoc/plugins/hifi.js b/tools/jsdoc/plugins/hifi.js index 16969b18fe..b4350ddbdb 100644 --- a/tools/jsdoc/plugins/hifi.js +++ b/tools/jsdoc/plugins/hifi.js @@ -133,8 +133,7 @@ function setDocletKindToTitle(doclet, tag) { function setDocletNameToValue(doclet, tag) { if (tag.value && tag.value.description) { // as in a long tag doclet.addTag('name', tag.value.description); - } - else if (tag.text) { // or a short tag + } else if (tag.text) { // or a short tag doclet.addTag('name', tag.text); } } From db821487d9ec82f7a051699fc841cea41eceabb9 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 25 Feb 2019 11:01:21 -0800 Subject: [PATCH 191/474] don't allow others' grabs to move locked or ungrabbable things --- interface/src/avatar/GrabManager.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/interface/src/avatar/GrabManager.cpp b/interface/src/avatar/GrabManager.cpp index db1337b64d..8273def490 100644 --- a/interface/src/avatar/GrabManager.cpp +++ b/interface/src/avatar/GrabManager.cpp @@ -32,6 +32,17 @@ void GrabManager::simulateGrabs() { bool success; SpatiallyNestablePointer grabbedThing = SpatiallyNestable::findByID(grabbedThingID, success); if (success && grabbedThing) { + auto entity = std::dynamic_pointer_cast(grabbedThing); + if (entity) { + if (entity->getLocked()) { + continue; // even if someone else claims to be grabbing it, don't move a locked thing + } + const GrabPropertyGroup& grabProps = entity->getGrabProperties(); + if (!grabProps.getGrabbable()) { + continue; // even if someone else claims to be grabbing it, don't move non-grabbable + } + } + glm::vec3 finalPosition = acc.finalizePosition(); glm::quat finalOrientation = acc.finalizeOrientation(); grabbedThing->setTransform(createMatFromQuatAndPos(finalOrientation, finalPosition)); From 292ac88c961b2762acc6919749132963fba884e3 Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Mon, 25 Feb 2019 11:36:45 -0800 Subject: [PATCH 192/474] Update with new standalone-optimized icon --- .../resources/icons/standalone-optimized.svg | 27 ++++++++ interface/resources/qml/hifi/Card.qml | 28 ++++---- .../hifi/commerce/marketplace/Marketplace.qml | 20 +++--- .../commerce/marketplace/MarketplaceItem.qml | 38 ++++++----- .../marketplace/MarketplaceListItem.qml | 66 ++++++++++--------- .../hifi/commerce/purchases/PurchasedItem.qml | 18 +++-- 6 files changed, 123 insertions(+), 74 deletions(-) create mode 100644 interface/resources/icons/standalone-optimized.svg diff --git a/interface/resources/icons/standalone-optimized.svg b/interface/resources/icons/standalone-optimized.svg new file mode 100644 index 0000000000..f721be9ebb --- /dev/null +++ b/interface/resources/icons/standalone-optimized.svg @@ -0,0 +1,27 @@ + + + + + + diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml index 9beb108b36..1c0424a691 100644 --- a/interface/resources/qml/hifi/Card.qml +++ b/interface/resources/qml/hifi/Card.qml @@ -273,22 +273,28 @@ Item { } } - HiFiGlyphs { + Image { id: standaloneOptomizedBadge anchors { right: actionIcon.left - bottom: parent.bottom; + top: actionIcon.top + topMargin: 2 + rightMargin: 3 } - height: (root.isConcurrency && root.standaloneOptimized) ? 34 : 0 - - visible: root.isConcurrency && root.standaloneOptimized - - text: hifi.glyphs.hmd - size: 34 - horizontalAlignment: Text.AlignHCenter - color: messageColor - } + height: root.standaloneOptimized ? 25 : 0 + width: 25 + + visible: root.standaloneOptimized && isConcurrency + fillMode: Image.PreserveAspectFit + source: "../../icons/standalone-optimized.svg" + } + ColorOverlay { + anchors.fill: standaloneOptomizedBadge + source: standaloneOptomizedBadge + color: hifi.colors.blueHighlight + visible: root.standaloneOptimized && isConcurrency + } StateImage { id: actionIcon; diff --git a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml index 5f97e64e6c..2206dfcb99 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml @@ -196,16 +196,16 @@ Rectangle { visible: true Image { - id: marketplaceHeaderImage; - source: "../common/images/marketplaceHeaderImage.png"; - anchors.top: parent.top; - anchors.topMargin: 2; - anchors.bottom: parent.bottom; - anchors.bottomMargin: 0; - anchors.left: parent.left; - anchors.leftMargin: 8; - width: 140; - fillMode: Image.PreserveAspectFit; + id: marketplaceHeaderImage + source: "../common/images/marketplaceHeaderImage.png" + anchors.top: parent.top + anchors.topMargin: 2 + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + anchors.left: parent.left + anchors.leftMargin: 8 + width: 140 + fillMode: Image.PreserveAspectFit MouseArea { anchors.fill: parent; diff --git a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml index ecd0c8b422..1579d70500 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml @@ -15,6 +15,7 @@ import Hifi 1.0 as Hifi import QtQuick 2.9 import QtQuick.Controls 2.2 import stylesUit 1.0 +import QtGraphicalEffects 1.0 import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls import "../common" as HifiCommerceCommon @@ -248,28 +249,33 @@ Rectangle { id: badges anchors { - left: parent.left + right: buyButton.left top: parent.top - right: parent.right + rightMargin: 10 } height: childrenRect.height - HiFiGlyphs { + Image { id: standaloneOptomizedBadge anchors { - left: parent.left - verticalCenter: parent.verticalCenter + topMargin: 15 + right: parent.right + top: parent.top } - height: root.standaloneOptimized ? 34 : 0 + height: root.standaloneOptimized ? 50 : 0 + width: 50 visible: root.standaloneOptimized - - text: hifi.glyphs.hmd - size: 34 - horizontalAlignment: Text.AlignHCenter + fillMode: Image.PreserveAspectFit + source: "../../../../icons/standalone-optimized.svg" + } + ColorOverlay { + anchors.fill: standaloneOptomizedBadge + source: standaloneOptomizedBadge color: hifi.colors.blueHighlight - } + visible: root.standaloneOptimized + } } HifiControlsUit.Button { @@ -277,11 +283,11 @@ Rectangle { anchors { right: parent.right - top: badges.bottom - left: parent.left + top: parent.top topMargin: 15 } - height: 50 + height: 50 + width: 180 property bool isNFS: availability === "not for sale" // Note: server will say "sold out" or "invalidated" before it says NFS property bool isMine: creator === Account.username @@ -301,11 +307,11 @@ Rectangle { id: creatorItem anchors { - top: buyButton.bottom + top: parent.top leftMargin: 15 topMargin: 15 } - width: parent.width + width: paintedWidth height: childrenRect.height RalewaySemiBold { diff --git a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml index 4fbed690f6..d0632ce8c3 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml @@ -240,16 +240,18 @@ Rectangle { id: creatorText anchors { - top: creatorLabel.top; - left: creatorLabel.right; - leftMargin: 15; + top: creatorLabel.top + left: creatorLabel.right + leftMargin: 15 + right: badges.left } - width: paintedWidth; + width: paintedWidth - text: root.creator; - size: 14; - color: hifi.colors.lightGray; - verticalAlignment: Text.AlignVCenter; + text: root.creator + size: 14 + elide: Text.ElideRight + color: hifi.colors.lightGray + verticalAlignment: Text.AlignVCenter } RalewaySemiBold { @@ -260,12 +262,12 @@ Rectangle { left: parent.left leftMargin: 15 } - width: paintedWidth; + width: paintedWidth - text: "IN:"; - size: 14; - color: hifi.colors.lightGrayText; - verticalAlignment: Text.AlignVCenter; + text: "IN:" + size: 14 + color: hifi.colors.lightGrayText + verticalAlignment: Text.AlignVCenter } RalewaySemiBold { @@ -275,14 +277,15 @@ Rectangle { top: categoryLabel.top left: categoryLabel.right leftMargin: 15 + right: badges.left } width: paintedWidth text: root.category size: 14 - color: hifi.colors.blueHighlight; - verticalAlignment: Text.AlignVCenter; - + color: hifi.colors.blueHighlight + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight MouseArea { anchors.fill: parent @@ -293,30 +296,33 @@ Rectangle { id: badges anchors { - left: parent.left - top: categoryLabel.bottom right: buyButton.left + top: parent.top + topMargin: 10 + rightMargin: 10 } - height: childrenRect.height + height: 50 - HiFiGlyphs { + Image { id: standaloneOptomizedBadge anchors { - left: parent.left - leftMargin: 15 - verticalCenter: parent.verticalCenter - + right: parent.right + top: parent.top } - height: 24 + height: root.standaloneOptimized ? 40 : 0 + width: 40 visible: root.standaloneOptimized - - text: hifi.glyphs.hmd - size: 24 - horizontalAlignment: Text.AlignHCenter + fillMode: Image.PreserveAspectFit + source: "../../../../icons/standalone-optimized.svg" + } + ColorOverlay { + anchors.fill: standaloneOptomizedBadge + source: standaloneOptomizedBadge color: hifi.colors.blueHighlight - } + visible: root.standaloneOptimized + } } HifiControlsUit.Button { diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index 22792e5727..87bf7afbe4 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -13,6 +13,7 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 +import QtGraphicalEffects 1.0 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 import stylesUit 1.0 @@ -848,8 +849,7 @@ Item { } } } - - HiFiGlyphs { + Image { id: standaloneOptomizedBadge anchors { @@ -858,14 +858,18 @@ Item { rightMargin: 15 bottomMargin:12 } - height: root.standaloneOptimized ? 34 : 0 + height: root.standaloneOptimized ? 36 : 0 + width: 36 visible: root.standaloneOptimized - - text: hifi.glyphs.hmd - size: 34 - horizontalAlignment: Text.AlignHCenter + fillMode: Image.PreserveAspectFit + source: "../../../../icons/standalone-optimized.svg" + } + ColorOverlay { + anchors.fill: standaloneOptomizedBadge + source: standaloneOptomizedBadge color: hifi.colors.blueHighlight + visible: root.standaloneOptimized } } From e102ad073eca78e1598e35ff4d1d057febe67c2b Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 25 Feb 2019 11:49:38 -0800 Subject: [PATCH 193/474] allow flying in HMD if you would otherwise fall forever --- interface/src/avatar/MyAvatar.cpp | 3 ++- libraries/physics/src/CharacterController.cpp | 22 ++++++++----------- libraries/physics/src/CharacterController.h | 16 ++++++++------ 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index af261f490b..faa9f88ae9 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -932,7 +932,8 @@ void MyAvatar::simulate(float deltaTime, bool inView) { bool isPhysicsEnabled = qApp->isPhysicsEnabled(); bool zoneAllowsFlying = zoneInteractionProperties.first; bool collisionlessAllowed = zoneInteractionProperties.second; - _characterController.setFlyingAllowed((zoneAllowsFlying && _enableFlying) || !isPhysicsEnabled); + _characterController.setZoneFlyingAllowed(zoneAllowsFlying || !isPhysicsEnabled); + _characterController.setComfortFlyingAllowed(_enableFlying); _characterController.setCollisionlessAllowed(collisionlessAllowed); } diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index 66ce5f32bf..a5f1a0598f 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -781,18 +781,18 @@ void CharacterController::updateState() { const float jumpSpeed = sqrtf(2.0f * -DEFAULT_AVATAR_GRAVITY * jumpHeight); if ((velocity.dot(_currentUp) <= (jumpSpeed / 2.0f)) && ((_floorDistance < FLY_TO_GROUND_THRESHOLD) || _hasSupport)) { SET_STATE(State::Ground, "hit ground"); - } else if (_flyingAllowed) { + } else if (_zoneFlyingAllowed) { btVector3 desiredVelocity = _targetVelocity; if (desiredVelocity.length2() < MIN_TARGET_SPEED_SQUARED) { desiredVelocity = btVector3(0.0f, 0.0f, 0.0f); } bool vertTargetSpeedIsNonZero = desiredVelocity.dot(_currentUp) > MIN_TARGET_SPEED; - if ((jumpButtonHeld || vertTargetSpeedIsNonZero) && (_takeoffJumpButtonID != _jumpButtonDownCount)) { + if (_comfortFlyingAllowed && (jumpButtonHeld || vertTargetSpeedIsNonZero) && (_takeoffJumpButtonID != _jumpButtonDownCount)) { SET_STATE(State::Hover, "double jump button"); - } else if ((jumpButtonHeld || vertTargetSpeedIsNonZero) && (now - _jumpButtonDownStartTime) > JUMP_TO_HOVER_PERIOD) { + } else if (_comfortFlyingAllowed && (jumpButtonHeld || vertTargetSpeedIsNonZero) && (now - _jumpButtonDownStartTime) > JUMP_TO_HOVER_PERIOD) { SET_STATE(State::Hover, "jump button held"); - } else if (_floorDistance > _scaleFactor * DEFAULT_AVATAR_FALL_HEIGHT) { - // Transition to hover if we are above the fall threshold + } else if ((!rayHasHit && !_hasSupport) || _floorDistance > _scaleFactor * DEFAULT_AVATAR_FALL_HEIGHT) { + // Transition to hover if there's no ground beneath us or we are above the fall threshold, regardless of _comfortFlyingAllowed SET_STATE(State::Hover, "above fall threshold"); } } @@ -801,8 +801,10 @@ void CharacterController::updateState() { case State::Hover: btScalar horizontalSpeed = (velocity - velocity.dot(_currentUp) * _currentUp).length(); bool flyingFast = horizontalSpeed > (MAX_WALKING_SPEED * 0.75f); - if (!_flyingAllowed) { - SET_STATE(State::InAir, "flying not allowed"); + if (!_zoneFlyingAllowed) { + SET_STATE(State::InAir, "zone flying not allowed"); + } else if (!_comfortFlyingAllowed && (rayHasHit || _hasSupport || _floorDistance < FLY_TO_GROUND_THRESHOLD)) { + SET_STATE(State::InAir, "comfort flying not allowed"); } else if ((_floorDistance < MIN_HOVER_HEIGHT) && !jumpButtonHeld && !flyingFast) { SET_STATE(State::InAir, "near ground"); } else if (((_floorDistance < FLY_TO_GROUND_THRESHOLD) || _hasSupport) && !flyingFast) { @@ -847,12 +849,6 @@ bool CharacterController::getRigidBodyLocation(glm::vec3& avatarRigidBodyPositio return true; } -void CharacterController::setFlyingAllowed(bool value) { - if (value != _flyingAllowed) { - _flyingAllowed = value; - } -} - void CharacterController::setCollisionlessAllowed(bool value) { if (value != _collisionlessAllowed) { _collisionlessAllowed = value; diff --git a/libraries/physics/src/CharacterController.h b/libraries/physics/src/CharacterController.h index d59374a94a..c46c9c8361 100755 --- a/libraries/physics/src/CharacterController.h +++ b/libraries/physics/src/CharacterController.h @@ -65,10 +65,10 @@ public: // overrides from btCharacterControllerInterface virtual void setWalkDirection(const btVector3 &walkDirection) override { assert(false); } virtual void setVelocityForTimeInterval(const btVector3 &velocity, btScalar timeInterval) override { assert(false); } - virtual void reset(btCollisionWorld* collisionWorld) override { } - virtual void warp(const btVector3& origin) override { } - virtual void debugDraw(btIDebugDraw* debugDrawer) override { } - virtual void setUpInterpolate(bool value) override { } + virtual void reset(btCollisionWorld* collisionWorld) override {} + virtual void warp(const btVector3& origin) override {} + virtual void debugDraw(btIDebugDraw* debugDrawer) override {} + virtual void setUpInterpolate(bool value) override {} virtual void updateAction(btCollisionWorld* collisionWorld, btScalar deltaTime) override; virtual void preStep(btCollisionWorld *collisionWorld) override; virtual void playerStep(btCollisionWorld *collisionWorld, btScalar dt) override; @@ -90,7 +90,7 @@ public: void preSimulation(); void postSimulation(); - void setPositionAndOrientation( const glm::vec3& position, const glm::quat& orientation); + void setPositionAndOrientation(const glm::vec3& position, const glm::quat& orientation); void getPositionAndOrientation(glm::vec3& position, glm::quat& rotation) const; void setParentVelocity(const glm::vec3& parentVelocity); @@ -129,7 +129,8 @@ public: bool getRigidBodyLocation(glm::vec3& avatarRigidBodyPosition, glm::quat& avatarRigidBodyRotation); - void setFlyingAllowed(bool value); + void setZoneFlyingAllowed(bool value) { _zoneFlyingAllowed = value; } + void setComfortFlyingAllowed(bool value) { _comfortFlyingAllowed = value; } void setCollisionlessAllowed(bool value); void setPendingFlagsUpdateCollisionMask(){ _pendingFlags |= PENDING_FLAG_UPDATE_COLLISION_MASK; } @@ -212,7 +213,8 @@ protected: uint32_t _pendingFlags { 0 }; uint32_t _previousFlags { 0 }; - bool _flyingAllowed { true }; + bool _zoneFlyingAllowed { true }; + bool _comfortFlyingAllowed { true }; bool _collisionlessAllowed { true }; bool _collisionless { false }; From cff0fd470a43020148f0baf5fa8dc92d4a74a198 Mon Sep 17 00:00:00 2001 From: amantley Date: Mon, 25 Feb 2019 12:23:03 -0800 Subject: [PATCH 194/474] re-enabled ik off by default on Desktop --- libraries/animation/src/Rig.cpp | 44 +++++++++++++++++---------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index fcf2cd28a3..372f03c163 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1064,28 +1064,30 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos t += deltaTime; - if (_enableInverseKinematics) { - _animVars.set("ikOverlayAlpha", 1.0f); - _animVars.set("splineIKEnabled", true); - _animVars.set("leftHandIKEnabled", true); - _animVars.set("rightHandIKEnabled", true); - _animVars.set("leftFootIKEnabled", true); - _animVars.set("rightFootIKEnabled", true); - _animVars.set("leftFootPoleVectorEnabled", true); - _animVars.set("rightFootPoleVectorEnabled", true); - } else { - _animVars.set("ikOverlayAlpha", 0.0f); - _animVars.set("splineIKEnabled", false); - _animVars.set("leftHandIKEnabled", false); - _animVars.set("rightHandIKEnabled", false); - _animVars.set("leftFootIKEnabled", false); - _animVars.set("rightFootIKEnabled", false); - _animVars.set("leftHandPoleVectorEnabled", false); - _animVars.set("rightHandPoleVectorEnabled", false); - _animVars.set("leftFootPoleVectorEnabled", false); - _animVars.set("rightFootPoleVectorEnabled", false); + if (_enableInverseKinematics != _lastEnableInverseKinematics) { + if (_enableInverseKinematics) { + _animVars.set("ikOverlayAlpha", 1.0f); + _animVars.set("splineIKEnabled", true); + _animVars.set("leftHandIKEnabled", true); + _animVars.set("rightHandIKEnabled", true); + _animVars.set("leftFootIKEnabled", true); + _animVars.set("rightFootIKEnabled", true); + _animVars.set("leftFootPoleVectorEnabled", true); + _animVars.set("rightFootPoleVectorEnabled", true); + } else { + _animVars.set("ikOverlayAlpha", 0.0f); + _animVars.set("splineIKEnabled", false); + _animVars.set("leftHandIKEnabled", false); + _animVars.set("rightHandIKEnabled", false); + _animVars.set("leftFootIKEnabled", false); + _animVars.set("rightFootIKEnabled", false); + _animVars.set("leftHandPoleVectorEnabled", false); + _animVars.set("rightHandPoleVectorEnabled", false); + _animVars.set("leftFootPoleVectorEnabled", false); + _animVars.set("rightFootPoleVectorEnabled", false); + } + _lastEnableInverseKinematics = _enableInverseKinematics; } - _lastEnableInverseKinematics = _enableInverseKinematics; } _lastForward = forward; _lastPosition = worldPosition; From 5831db30891244ebdf9658e502080517a7d5030d Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 22 Feb 2019 13:35:52 -0800 Subject: [PATCH 195/474] Build quest demo apk on Jenkins --- android/apps/questInterface/build.gradle | 19 +++++++------ android/apps/questInterface/keystore.jks | Bin 0 -> 2223 bytes android/build_android.sh | 33 ++++++++++++++++++----- android/containerized_build.sh | 2 ++ 4 files changed, 38 insertions(+), 16 deletions(-) create mode 100644 android/apps/questInterface/keystore.jks diff --git a/android/apps/questInterface/build.gradle b/android/apps/questInterface/build.gradle index 3d13877607..bf600b5df1 100644 --- a/android/apps/questInterface/build.gradle +++ b/android/apps/questInterface/build.gradle @@ -44,10 +44,15 @@ android { } signingConfigs { release { - storeFile project.hasProperty("HIFI_ANDROID_KEYSTORE") ? file(HIFI_ANDROID_KEYSTORE) : null - storePassword project.hasProperty("HIFI_ANDROID_KEYSTORE_PASSWORD") ? HIFI_ANDROID_KEYSTORE_PASSWORD : '' - keyAlias project.hasProperty("HIFI_ANDROID_KEY_ALIAS") ? HIFI_ANDROID_KEY_ALIAS : '' - keyPassword project.hasProperty("HIFI_ANDROID_KEY_PASSWORD") ? HIFI_ANDROID_KEY_PASSWORD : '' + // Hack for Quest demo + storeFile file("keystore.jks") + storePassword "password" + keyAlias "key0" + keyPassword "password" + // storeFile project.hasProperty("HIFI_ANDROID_KEYSTORE") ? file(HIFI_ANDROID_KEYSTORE) : null + // storePassword project.hasProperty("HIFI_ANDROID_KEYSTORE_PASSWORD") ? HIFI_ANDROID_KEYSTORE_PASSWORD : '' + // keyAlias project.hasProperty("HIFI_ANDROID_KEY_ALIAS") ? HIFI_ANDROID_KEY_ALIAS : '' + // keyPassword project.hasProperty("HIFI_ANDROID_KEY_PASSWORD") ? HIFI_ANDROID_KEY_PASSWORD : '' v2SigningEnabled false } } @@ -133,12 +138,6 @@ android { assetList.each { file -> out.println(file) } } } - - variant.outputs.all { - if (RELEASE_NUMBER != '0') { - outputFileName = "app_" + RELEASE_NUMBER + "_" + RELEASE_TYPE + ".apk" - } - } } } diff --git a/android/apps/questInterface/keystore.jks b/android/apps/questInterface/keystore.jks new file mode 100644 index 0000000000000000000000000000000000000000..4b646122f6f20c60a69d27f15590bc69a757ef60 GIT binary patch literal 2223 zcmcgs={wX51O3fvX1rrx=GM4~5QE=bW0`ntS)(isLbC5-EMaVA(ny7CEs;T}s4SI2 zg|SDLNJ7XyGE|oA?o-eEKF|FN-Vf))Ip@nc=fm0O>~jDBfC2yj`3<3P=bXA`j(81FAhsS$ zAO5f&X%HvyB`iGj4 zwp#gCx-pp1C~MYb8jor+@}K(fqOz+(FEKiPyzx-2$z8qH zqHbD3rM+OPGOmC1O0m%KOxBp-dbsEAp5ORo$}*c$cKZgXxv-~aT!o*u5%*Vs-HdwU zW^#h@vSv*Y=de+XCtsBLVdL3YdheCo#+lu3yXwJnW8rPr@YKqv#!Df+u2*6?<%co~ z{SrGP@KZs&Yp2f>e0;N{dVM+@<@86K`v$P&JGgt3Zc)$7)lV90uYoNae;B5vLc_9$ zp5G~ZpfF{B9&jt_A^5K`vn`NZ)WCDLr$u}tYDMF6er$P^8dEVx?Wrw#7TQzN!3m`p zbf87ffQf>Dchzji;kkaR4K(%@jV6*!x{8RP^+-Aj3ncL8Pd}gP15)(Se)6$vPJSAC9mo!F%IGAkG>ld9d zIbuym8VlrW80aFhv8U!G^IZyBJ!!3BFW7x3G~SPRU$l70bkJXY*Dq6ZvO+jE8(;2? z*2OWXac@dgchXXm%24@D8QVy1N|l{Hdxg99RG3Lhy~3TMYAbxsa9wL{ez%L1?DqC4 z+Ff{mHK3Mf-V;hOmkW&H%N6bET1>yQw(xdq5-->6ec+h4sOG%Ys|Wl_}+^*VD$)aXn5q+T~o>mG9@H_Wmhd&p(1eT;KbWYuk*>1tN$q-W5g zVaF8fEN)NQMDz0${lMCs{QaaPe~IV7rfet+ehKc&=E03;@@a%cn`TX5=N!F--Om+m zbLcb2V&Bai{eeKLT62tsSH2ECI;a=$NFd!ssfX~g4LUp?fKS$kyL!_y0@`nB_DyFJ ztQRO+@3#b+_K(ALKea<1Q!)_wx{#PH8_q))C!r$DXj1QSmy9>x9Y^11QuFq51|1(8 zseax~9#zJZIW?Tr>6<&Qou4(Wj+aQ_7nby0El(<;`+B}%%KmgNtEDkyecPTnZk%WzDjZ4xGSkm#hUp+)Z0t7 zl(2Yc-H{TY(G|{oy_FlMY54T|sW@pS@9;FU6(Mp%DxPy1qJ;*;JiXfMy3E4kDi)H` zANLnVwY|{UADj{BYNG}F+eM_zSpO7EX3wnJc}K){#T8%86Ffro$TFhL>x5Lb@?Pnv|@#n3R{ zKnVlaaNkhyPf;vUgGl@f1VJL1r0yV!C8-dJMACoh|0e_;a^&CTAqW6QhwuU*9fE<< zArPQUqHp`-n-Mu(TzgUXg*)#2_ghBAwht9Jm9hful2-XWB9h0GzE;tmk)^x#?Wa3` z&We}lbI!Mf)LT~Zl%u|Fl4tIju4KtGj}Du{kPeE}5oltSRs3@0Q=^p--GIjzS|d6= zZ1F+#d|ZCkTD>bepd;0w%xGDsxc5(vE0>^Of04vMyK8lvh~>Mnc`euBmm5AGqUQ-S z@@jhf-Vnl=)^mT^HA5K}QQRcsnpciEJGBh=6swNKoUg zDRfp<_S%C4YRgoO8cq=2G#*8Y`o78#+c9Xvh1V~^R%D6R^H`^IG6&)#&8Riy)9;`# z2mrY!35tVaNSKWn4_pK;l#qF$NwhjkZ?C)Wc_~X>7?eSo|9eF4-y=c*X37vNCh^J) z>1d$^^&-k4*##0_Bik?*LCSTH+ptz!r)s;&1|6teZ)2C*i$CbtX|WVCYQ$fbbGw>7u_XWd7XkpJi?(T?Zp0@ zqJ4=#o%meLIYxha<-wpXv0cs5oTA7lIfdJXkJvf3#q5|RIn^va2$^hR??~2;1a>j+ zPGpc-d&SrWgOE+R+TQ;Dcz_U-z5};j!t84AFtb|uk8nS_z|QU~EV(Fh{;3obuLvzU KB59rbJmw!9zT}$# literal 0 HcmV?d00001 diff --git a/android/build_android.sh b/android/build_android.sh index a066332f9a..106a295750 100755 --- a/android/build_android.sh +++ b/android/build_android.sh @@ -1,11 +1,32 @@ #!/usr/bin/env bash set -xeuo pipefail + +ANDROID_BUILD_TYPE=release +ANDROID_BUILD_TARGET=assembleRelease + +case "$RELEASE_TYPE" in + PR) ANDROID_APK_SUFFIX=PR${RELEASE_NUMBER}-${SHA7}.apk ;; + *) ANDROID_APK_SUFFIX=${RELEASE_NUMBER}-${SHA7}.apk ;; +esac + + +# Interface build +ANDROID_APP=interface +ANDROID_OUTPUT_DIR=./apps/${ANDROID_APP}/build/outputs/apk/${ANDROID_BUILD_TYPE} +ANDROID_OUTPUT_FILE=${ANDROID_APP}-${ANDROID_BUILD_TYPE}-unsigned.apk +ANDROID_APK_NAME=HighFidelity-Beta-${ANDROID_APK_SUFFIX} ./gradlew -PHIFI_ANDROID_PRECOMPILED=${HIFI_ANDROID_PRECOMPILED} -PVERSION_CODE=${VERSION_CODE} -PRELEASE_NUMBER=${RELEASE_NUMBER} -PRELEASE_TYPE=${RELEASE_TYPE} ${ANDROID_APP}:${ANDROID_BUILD_TARGET} +cp ${ANDROID_OUTPUT_DIR}/${ANDROID_OUTPUT_FILE} ./${ANDROID_APK_NAME} + +# Quest Interface build +ANDROID_APP=questInterface +ANDROID_OUTPUT_DIR=./apps/${ANDROID_APP}/build/outputs/apk/${ANDROID_BUILD_TYPE} +ANDROID_OUTPUT_FILE=${ANDROID_APP}-${ANDROID_BUILD_TYPE}.apk +ANDROID_APK_NAME=HighFidelity-Quest-Beta-${ANDROID_APK_SUFFIX} +./gradlew -PHIFI_ANDROID_PRECOMPILED=${HIFI_ANDROID_PRECOMPILED} -PVERSION_CODE=${VERSION_CODE} -PRELEASE_NUMBER=${RELEASE_NUMBER} -PRELEASE_TYPE=${RELEASE_TYPE} ${ANDROID_APP}:${ANDROID_BUILD_TARGET} +cp ${ANDROID_OUTPUT_DIR}/${ANDROID_OUTPUT_FILE} ./${ANDROID_APK_NAME} + + + -# This is the actual output from gradle, which no longer attempts to muck with the naming of the APK -OUTPUT_APK=./apps/${ANDROID_APP}/build/outputs/apk/${ANDROID_BUILD_DIR}/${ANDROID_BUILT_APK_NAME} -# This is the APK name requested by Jenkins -TARGET_APK=./${ANDROID_APK_NAME} -# Make sure this matches up with the new ARTIFACT_EXPRESSION for jenkins builds, which should be "android/*.apk" -cp ${OUTPUT_APK} ${TARGET_APK} diff --git a/android/containerized_build.sh b/android/containerized_build.sh index 8b2f26cb50..34e620ad2e 100755 --- a/android/containerized_build.sh +++ b/android/containerized_build.sh @@ -9,6 +9,7 @@ docker build --build-arg BUILD_UID=`id -u` -t "${DOCKER_IMAGE_NAME}" -f docker/D # So make sure we use VERSION_CODE consistently test -z "$VERSION_CODE" && export VERSION_CODE=$VERSION +# FIXME figure out which of these actually need to be forwarded and which can be eliminated docker run \ --rm \ --security-opt seccomp:unconfined \ @@ -27,6 +28,7 @@ docker run \ -e OAUTH_CLIENT_SECRET \ -e OAUTH_CLIENT_ID \ -e OAUTH_REDIRECT_URI \ + -e SHA7 \ -e VERSION_CODE \ "${DOCKER_IMAGE_NAME}" \ sh -c "./build_android.sh" From 363c0cc26f55a7573f997cc8c0dfe91459b3d87b Mon Sep 17 00:00:00 2001 From: amantley Date: Mon, 25 Feb 2019 13:03:28 -0800 Subject: [PATCH 196/474] moved the update of last ik to outside the if changed statement --- libraries/animation/src/Rig.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 372f03c163..fb7bb15341 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1086,8 +1086,8 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _animVars.set("leftFootPoleVectorEnabled", false); _animVars.set("rightFootPoleVectorEnabled", false); } - _lastEnableInverseKinematics = _enableInverseKinematics; } + _lastEnableInverseKinematics = _enableInverseKinematics; } _lastForward = forward; _lastPosition = worldPosition; From 6e13203fecc0e94853fd76fcdcf6e2a72a7329d5 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 25 Feb 2019 13:52:46 -0800 Subject: [PATCH 197/474] avoid nullptr deref --- interface/src/avatar/AvatarProject.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/interface/src/avatar/AvatarProject.cpp b/interface/src/avatar/AvatarProject.cpp index b020cdb627..260ff33db7 100644 --- a/interface/src/avatar/AvatarProject.cpp +++ b/interface/src/avatar/AvatarProject.cpp @@ -254,11 +254,15 @@ void AvatarProject::openInInventory() const { DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system")); tablet->loadQMLSource("hifi/commerce/wallet/Wallet.qml"); DependencyManager::get()->openTablet(); - tablet->getTabletRoot()->forceActiveFocus(); - auto name = getProjectName(); // I'm not a fan of this, but it's the only current option. + auto name = getProjectName(); QTimer::singleShot(TIME_TO_WAIT_FOR_INVENTORY_TO_OPEN_MS, [name, tablet]() { tablet->sendToQml(QVariantMap({ { "method", "updatePurchases" }, { "filterText", name } })); }); + + QQuickItem* root = tablet->getTabletRoot(); + if (root) { + root->forceActiveFocus(); + } } From 07753927590ef3af2d1aa51f22dd09ed1850f5f7 Mon Sep 17 00:00:00 2001 From: amer cerkic Date: Mon, 25 Feb 2019 14:05:54 -0800 Subject: [PATCH 198/474] adding market place back to the default scripts --- scripts/+android_questInterface/defaultScripts.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/+android_questInterface/defaultScripts.js b/scripts/+android_questInterface/defaultScripts.js index e996f71908..c294537419 100644 --- a/scripts/+android_questInterface/defaultScripts.js +++ b/scripts/+android_questInterface/defaultScripts.js @@ -25,6 +25,7 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/notifications.js", "system/commerce/wallet.js", "system/dialTone.js", + "system/marketplaces/marketplaces.js", "system/quickGoto.js", "system/firstPersonHMD.js", "system/tablet-ui/tabletUI.js", From 12dbaa0ea0698ebe4798605956e15695f0896e74 Mon Sep 17 00:00:00 2001 From: amantley Date: Mon, 25 Feb 2019 14:56:03 -0800 Subject: [PATCH 199/474] changed the condition so that you can turn off ik in hmd mode if you want to for debug purposes --- libraries/animation/src/Rig.cpp | 35 ++++++++++++--------------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index fb7bb15341..25f154e9fd 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1064,28 +1064,19 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos t += deltaTime; - if (_enableInverseKinematics != _lastEnableInverseKinematics) { - if (_enableInverseKinematics) { - _animVars.set("ikOverlayAlpha", 1.0f); - _animVars.set("splineIKEnabled", true); - _animVars.set("leftHandIKEnabled", true); - _animVars.set("rightHandIKEnabled", true); - _animVars.set("leftFootIKEnabled", true); - _animVars.set("rightFootIKEnabled", true); - _animVars.set("leftFootPoleVectorEnabled", true); - _animVars.set("rightFootPoleVectorEnabled", true); - } else { - _animVars.set("ikOverlayAlpha", 0.0f); - _animVars.set("splineIKEnabled", false); - _animVars.set("leftHandIKEnabled", false); - _animVars.set("rightHandIKEnabled", false); - _animVars.set("leftFootIKEnabled", false); - _animVars.set("rightFootIKEnabled", false); - _animVars.set("leftHandPoleVectorEnabled", false); - _animVars.set("rightHandPoleVectorEnabled", false); - _animVars.set("leftFootPoleVectorEnabled", false); - _animVars.set("rightFootPoleVectorEnabled", false); - } + if (_enableInverseKinematics) { + _animVars.set("ikOverlayAlpha", 1.0f); + } else { + _animVars.set("ikOverlayAlpha", 0.0f); + _animVars.set("splineIKEnabled", false); + _animVars.set("leftHandIKEnabled", false); + _animVars.set("rightHandIKEnabled", false); + _animVars.set("leftFootIKEnabled", false); + _animVars.set("rightFootIKEnabled", false); + _animVars.set("leftHandPoleVectorEnabled", false); + _animVars.set("rightHandPoleVectorEnabled", false); + _animVars.set("leftFootPoleVectorEnabled", false); + _animVars.set("rightFootPoleVectorEnabled", false); } _lastEnableInverseKinematics = _enableInverseKinematics; } From f235d046013de90509d629f7542c97423e6f559c Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 25 Feb 2019 15:06:11 -0800 Subject: [PATCH 200/474] trying to fix web crashes --- .../src/RenderableWebEntityItem.cpp | 142 ++++++++++-------- .../src/RenderableWebEntityItem.h | 1 + 2 files changed, 79 insertions(+), 64 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index ccd815b74a..0f2c708d17 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -101,21 +101,15 @@ bool WebEntityRenderer::isTransparent() const { } bool WebEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const { - if (_contextPosition != entity->getWorldPosition()) { - return true; - } - - { - QSharedPointer webSurface; - withReadLock([&] { - webSurface = _webSurface; - }); - if (webSurface && uvec2(getWindowSize(entity)) != toGlm(webSurface->size())) { + if (resultWithReadLock([&] { + if (_webSurface && uvec2(getWindowSize(entity)) != toGlm(_webSurface->size())) { + return true; + } + + if (_contextPosition != entity->getWorldPosition()) { return true; } - } - if(resultWithReadLock([&] { if (_color != entity->getColor()) { return true; } @@ -194,7 +188,7 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene auto newContentType = getContentType(newSourceURL); ContentType currentContentType; withReadLock([&] { - urlChanged = _sourceURL != newSourceURL; + urlChanged = newSourceURL.isEmpty() || newSourceURL != _tryingToBuildURL; }); currentContentType = _contentType; @@ -206,7 +200,6 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene } } - withWriteLock([&] { _inputMode = entity->getInputMode(); _dpi = entity->getDPI(); @@ -216,6 +209,8 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene _billboardMode = entity->getBillboardMode(); if (_contentType == ContentType::NoContent) { + _tryingToBuildURL = newSourceURL; + _sourceURL = newSourceURL; return; } @@ -226,10 +221,12 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene if (_webSurface) { if (_webSurface->getRootItem()) { - if (_contentType == ContentType::HtmlContent && urlChanged) { + if (_contentType == ContentType::HtmlContent && _sourceURL != newSourceURL) { _webSurface->getRootItem()->setProperty(URL_PROPERTY, newSourceURL); + _sourceURL = newSourceURL; + } else if (_contentType != ContentType::HtmlContent) { + _sourceURL = newSourceURL; } - _sourceURL = newSourceURL; { auto scriptURL = entity->getScriptURL(); @@ -294,20 +291,21 @@ void WebEntityRenderer::doRender(RenderArgs* args) { }); // Try to update the texture - { - QSharedPointer webSurface; - withReadLock([&] { - webSurface = _webSurface; - }); - if (!webSurface) { - return; + OffscreenQmlSurface::TextureAndFence newTextureAndFence; + bool newTextureAvailable = false; + if (!resultWithReadLock([&] { + if (!_webSurface) { + return false; } - OffscreenQmlSurface::TextureAndFence newTextureAndFence; - bool newTextureAvailable = webSurface->fetchTexture(newTextureAndFence); - if (newTextureAvailable) { - _texture->setExternalTexture(newTextureAndFence.first, newTextureAndFence.second); - } + newTextureAvailable = _webSurface->fetchTexture(newTextureAndFence); + return true; + })) { + return; + } + + if (newTextureAvailable) { + _texture->setExternalTexture(newTextureAndFence.first, newTextureAndFence.second); } static const glm::vec2 texMin(0.0f), texMax(1.0f), topLeft(-0.5f), bottomRight(0.5f); @@ -351,6 +349,8 @@ void WebEntityRenderer::buildWebSurface(const EntityItemPointer& entity, const Q _connections.push_back(QObject::connect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, this, [entityItemID](const QVariant& message) { emit DependencyManager::get()->webEventReceived(entityItemID, message); })); + + _tryingToBuildURL = newSourceURL; } void WebEntityRenderer::destroyWebSurface() { @@ -383,11 +383,16 @@ glm::vec2 WebEntityRenderer::getWindowSize(const TypedEntityPointer& entity) con void WebEntityRenderer::hoverEnterEntity(const PointerEvent& event) { if (_inputMode == WebInputMode::MOUSE) { handlePointerEvent(event); - } else if (_webSurface) { - PointerEvent webEvent = event; - webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _dpi)); - _webSurface->hoverBeginEvent(webEvent, _touchDevice); + return; } + + withReadLock([&] { + if (_webSurface) { + PointerEvent webEvent = event; + webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _dpi)); + _webSurface->hoverBeginEvent(webEvent, _touchDevice); + } + }); } void WebEntityRenderer::hoverLeaveEntity(const PointerEvent& event) { @@ -398,34 +403,39 @@ void WebEntityRenderer::hoverLeaveEntity(const PointerEvent& event) { // QML onReleased is only triggered if a click has happened first. We need to send this "fake" mouse move event to properly trigger an onExited. PointerEvent endMoveEvent(PointerEvent::Move, event.getID()); handlePointerEvent(endMoveEvent); - } else if (_webSurface) { - PointerEvent webEvent = event; - webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _dpi)); - _webSurface->hoverEndEvent(webEvent, _touchDevice); - } -} - -void WebEntityRenderer::handlePointerEvent(const PointerEvent& event) { - if (_inputMode == WebInputMode::TOUCH) { - handlePointerEventAsTouch(event); - } else { - handlePointerEventAsMouse(event); - } -} - -void WebEntityRenderer::handlePointerEventAsTouch(const PointerEvent& event) { - if (_webSurface) { - PointerEvent webEvent = event; - webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _dpi)); - _webSurface->handlePointerEvent(webEvent, _touchDevice); - } -} - -void WebEntityRenderer::handlePointerEventAsMouse(const PointerEvent& event) { - if (!_webSurface) { return; } + withReadLock([&] { + if (_webSurface) { + PointerEvent webEvent = event; + webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _dpi)); + _webSurface->hoverEndEvent(webEvent, _touchDevice); + } + }); +} + +void WebEntityRenderer::handlePointerEvent(const PointerEvent& event) { + withReadLock([&] { + if (!_webSurface) { + return; + } + + if (_inputMode == WebInputMode::TOUCH) { + handlePointerEventAsTouch(event); + } else { + handlePointerEventAsMouse(event); + } + }); +} + +void WebEntityRenderer::handlePointerEventAsTouch(const PointerEvent& event) { + PointerEvent webEvent = event; + webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _dpi)); + _webSurface->handlePointerEvent(webEvent, _touchDevice); +} + +void WebEntityRenderer::handlePointerEventAsMouse(const PointerEvent& event) { glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * _dpi); QPointF windowPoint(windowPos.x, windowPos.y); @@ -459,16 +469,20 @@ void WebEntityRenderer::handlePointerEventAsMouse(const PointerEvent& event) { } void WebEntityRenderer::setProxyWindow(QWindow* proxyWindow) { - if (_webSurface) { - _webSurface->setProxyWindow(proxyWindow); - } + withReadLock([&] { + if (_webSurface) { + _webSurface->setProxyWindow(proxyWindow); + } + }); } QObject* WebEntityRenderer::getEventHandler() { - if (!_webSurface) { - return nullptr; - } - return _webSurface->getEventHandler(); + return resultWithReadLock([&]() -> QObject* { + if (!_webSurface) { + return nullptr; + } + return _webSurface->getEventHandler(); + }); } void WebEntityRenderer::emitScriptEvent(const QVariant& message) { diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h index 30b63a72df..0345898b62 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.h +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h @@ -82,6 +82,7 @@ private: QSharedPointer _webSurface { nullptr }; bool _cachedWebSurface { false }; gpu::TexturePointer _texture; + QString _tryingToBuildURL; glm::u8vec3 _color; float _alpha { 1.0f }; From e5091e7f59d6e078b9f2a2ddbd42aac7241f1d98 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Mon, 25 Feb 2019 15:44:52 -0800 Subject: [PATCH 201/474] Fixes for EntityItem contains test, add MixerAvatar class and use in sort --- .../src/avatars/AvatarMixerClientData.cpp | 4 +- .../src/avatars/AvatarMixerClientData.h | 12 +-- .../src/avatars/AvatarMixerSlave.cpp | 80 +++++++++++++------ assignment-client/src/avatars/MixerAvatar.h | 31 +++++++ libraries/entities/src/EntityItem.cpp | 2 +- 5 files changed, 94 insertions(+), 35 deletions(-) create mode 100644 assignment-client/src/avatars/MixerAvatar.h diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index 9edbae12d8..9ba1ea82ca 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -135,9 +135,7 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message, const SlaveShared EntityTree& entityTree = *slaveSharedData.entityTree; FindPriorityZone findPriorityZone { newPosition, false } ; entityTree.recurseTreeWithOperation(&FindPriorityZone::operation, &findPriorityZone); - if (findPriorityZone.isInPriorityZone) { - // Tag avatar as hero ... - } + _avatar->setPriorityAvatar(findPriorityZone.isInPriorityZone); } return true; diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index 45d508088c..a11f218a7b 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -21,7 +21,7 @@ #include #include -#include +#include "MixerAvatar.h" #include #include #include @@ -46,10 +46,10 @@ public: using PerNodeTraitVersions = std::unordered_map; int parseData(ReceivedMessage& message, const SlaveSharedData& SlaveSharedData); - AvatarData& getAvatar() { return *_avatar; } - const AvatarData& getAvatar() const { return *_avatar; } - const AvatarData* getConstAvatarData() const { return _avatar.get(); } - AvatarSharedPointer getAvatarSharedPointer() const { return _avatar; } + MixerAvatar& getAvatar() { return *_avatar; } + const MixerAvatar& getAvatar() const { return *_avatar; } + const MixerAvatar* getConstAvatarData() const { return _avatar.get(); } + MixerAvatarSharedPointer getAvatarSharedPointer() const { return _avatar; } uint16_t getLastBroadcastSequenceNumber(NLPacket::LocalID nodeID) const; void setLastBroadcastSequenceNumber(NLPacket::LocalID nodeID, uint16_t sequenceNumber) @@ -163,7 +163,7 @@ private: }; PacketQueue _packetQueue; - AvatarSharedPointer _avatar { new AvatarData() }; + MixerAvatarSharedPointer _avatar { new MixerAvatar() }; uint16_t _lastReceivedSequenceNumber { 0 }; std::unordered_map _lastBroadcastSequenceNumbers; diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 6b039e2c03..7d25268a6b 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -281,7 +281,51 @@ AABox computeBubbleBox(const AvatarData& avatar, float bubbleExpansionFactor) { return box; } +namespace { + class SortableAvatar : public PrioritySortUtil::Sortable { + public: + SortableAvatar() = delete; + SortableAvatar(const MixerAvatar* avatar, const Node* avatarNode, uint64_t lastEncodeTime) + : _avatar(avatar), _node(avatarNode), _lastEncodeTime(lastEncodeTime) { + } + glm::vec3 getPosition() const override { return _avatar->getClientGlobalPosition(); } + float getRadius() const override { + glm::vec3 nodeBoxScale = _avatar->getGlobalBoundingBox().getScale(); + return 0.5f * glm::max(nodeBoxScale.x, glm::max(nodeBoxScale.y, nodeBoxScale.z)); + } + uint64_t getTimestamp() const override { + return _lastEncodeTime; + } + const Node* getNode() const { return _node; } + const MixerAvatar* getAvatar() const { return _avatar; } + + private: + const MixerAvatar* _avatar; + const Node* _node; + uint64_t _lastEncodeTime; + }; + +} // Close anonymous namespace. + +// Specialize computePriority() for avatars: +template<> float PrioritySortUtil::PriorityQueue::computePriority(const SortableAvatar& thing) const { + static constexpr float AVATAR_HERO_BONUS { 25.0f }; // Higher than any normal priority. + + float priority = std::numeric_limits::min(); + + for (const auto& view : _views) { + priority = std::max(priority, computePriority(view, thing)); + } + + if (thing.getAvatar()->getPriorityAvatar()) { + priority += AVATAR_HERO_BONUS; + } + + return priority; +} + void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) { + const float AVATAR_HERO_FRACTION { 0.4f }; const Node* destinationNode = node.data(); auto nodeList = DependencyManager::get(); @@ -314,8 +358,9 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) int identityBytesSent = 0; int traitBytesSent = 0; - // max number of avatarBytes per frame - int maxAvatarBytesPerFrame = int(_maxKbpsPerNode * BYTES_PER_KILOBIT / AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND); + // max number of avatarBytes per frame (13 900, typical) + const int maxAvatarBytesPerFrame = int(_maxKbpsPerNode * BYTES_PER_KILOBIT / AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND); + const int maxHeroBytesPerFrame = int(maxAvatarBytesPerFrame * AVATAR_HERO_FRACTION); // 5555, typical // keep track of the number of other avatars held back in this frame int numAvatarsHeldBack = 0; @@ -339,27 +384,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) const float MY_AVATAR_BUBBLE_EXPANSION_FACTOR = 4.0f; // magic number determined emperically AABox nodeBox = computeBubbleBox(avatar, MY_AVATAR_BUBBLE_EXPANSION_FACTOR); - class SortableAvatar: public PrioritySortUtil::Sortable { - public: - SortableAvatar() = delete; - SortableAvatar(const AvatarData* avatar, const Node* avatarNode, uint64_t lastEncodeTime) - : _avatar(avatar), _node(avatarNode), _lastEncodeTime(lastEncodeTime) {} - glm::vec3 getPosition() const override { return _avatar->getClientGlobalPosition(); } - float getRadius() const override { - glm::vec3 nodeBoxScale = _avatar->getGlobalBoundingBox().getScale(); - return 0.5f * glm::max(nodeBoxScale.x, glm::max(nodeBoxScale.y, nodeBoxScale.z)); - } - uint64_t getTimestamp() const override { - return _lastEncodeTime; - } - const Node* getNode() const { return _node; } - - private: - const AvatarData* _avatar; - const Node* _node; - uint64_t _lastEncodeTime; - }; - // prepare to sort const auto& cameraViews = nodeData->getViewFrustums(); PrioritySortUtil::PriorityQueue sortedAvatars(cameraViews, @@ -446,7 +470,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) if (!shouldIgnore) { // sort this one for later - const AvatarData* avatarNodeData = avatarClientNodeData->getConstAvatarData(); + const MixerAvatar* avatarNodeData = avatarClientNodeData->getConstAvatarData(); auto lastEncodeTime = nodeData->getLastOtherAvatarEncodeTime(avatarNode->getLocalID()); sortedAvatars.push(SortableAvatar(avatarNodeData, avatarNode, lastEncodeTime)); @@ -508,10 +532,16 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) } } + bool overHeroBudget = frameByteEstimate > maxHeroBytesPerFrame; + auto startAvatarDataPacking = chrono::high_resolution_clock::now(); const AvatarMixerClientData* otherNodeData = reinterpret_cast(otherNode->getLinkedData()); - const AvatarData* otherAvatar = otherNodeData->getConstAvatarData(); + const MixerAvatar* otherAvatar = otherNodeData->getConstAvatarData(); + + if (overHeroBudget && otherAvatar->getPriorityAvatar()) { + continue; // No more heroes (this frame). + } // Typically all out-of-view avatars but such avatars' priorities will rise with time: bool isLowerPriority = sortedAvatar.getPriority() <= OUT_OF_VIEW_THRESHOLD; diff --git a/assignment-client/src/avatars/MixerAvatar.h b/assignment-client/src/avatars/MixerAvatar.h new file mode 100644 index 0000000000..4781fdee1b --- /dev/null +++ b/assignment-client/src/avatars/MixerAvatar.h @@ -0,0 +1,31 @@ +// +// MixerAvatar.h +// assignment-client/src/avatars +// +// Created by Simon Walton Feb 2019. +// Copyright 2019 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 +// + +// Avatar class for use within the avatar mixer - encapsulates data required only for +// sorting priorities within the mixer. + +#ifndef hifi_MixerAvatar_h +#define hifi_MixerAvatar_h + +#include + +class MixerAvatar : public AvatarData { +public: + bool getPriorityAvatar() const { return _bPriorityAvatar; } + void setPriorityAvatar(bool bPriorityAvatar) { _bPriorityAvatar = bPriorityAvatar; } + +private: + bool _bPriorityAvatar { false }; +}; + +using MixerAvatarSharedPointer = std::shared_ptr; + +#endif // hifi_MixerAvatar_h diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 25e5893078..3ecbdf497a 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1738,7 +1738,7 @@ bool EntityItem::contains(const glm::vec3& point) const { // the above cases not yet supported --> fall through to BOX case case SHAPE_TYPE_BOX: { localPoint = glm::abs(localPoint); - return glm::any(glm::lessThanEqual(localPoint, glm::vec3(NORMALIZED_HALF_SIDE))); + return glm::all(glm::lessThanEqual(localPoint, glm::vec3(NORMALIZED_HALF_SIDE))); } case SHAPE_TYPE_ELLIPSOID: { // since we've transformed into the normalized space this is just a sphere-point intersection test From d6f755955d44721be003b0dfb203fe57592d6308 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Mon, 25 Feb 2019 16:24:12 -0800 Subject: [PATCH 202/474] Add _avatarPriorityChanged to EntityItemProperties::markAllChanged() --- libraries/entities/src/EntityItemProperties.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 96160ebd93..a7cba157ae 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -4022,6 +4022,7 @@ void EntityItemProperties::markAllChanged() { _bloom.markAllChanged(); _flyingAllowedChanged = true; _ghostingAllowedChanged = true; + _avatarPriorityChanged = true; _filterURLChanged = true; _keyLightModeChanged = true; _ambientLightModeChanged = true; From a8dd7b7e1fb124f2380102b835658eb91aca80d2 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Mon, 25 Feb 2019 17:14:22 -0800 Subject: [PATCH 203/474] Fix gcc error about defining templates outside their namespace Also warning about hiding virtual function. --- assignment-client/src/avatars/AvatarMixerClientData.h | 1 + assignment-client/src/avatars/AvatarMixerSlave.cpp | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index a11f218a7b..98c8d7e15b 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -45,6 +45,7 @@ public: using HRCTime = p_high_resolution_clock::time_point; using PerNodeTraitVersions = std::unordered_map; + using NodeData::parseData; // Avoid clang warning about hiding. int parseData(ReceivedMessage& message, const SlaveSharedData& SlaveSharedData); MixerAvatar& getAvatar() { return *_avatar; } const MixerAvatar& getAvatar() const { return *_avatar; } diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 7d25268a6b..9ad3cff7e1 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -308,7 +308,8 @@ namespace { } // Close anonymous namespace. // Specialize computePriority() for avatars: -template<> float PrioritySortUtil::PriorityQueue::computePriority(const SortableAvatar& thing) const { +namespace PrioritySortUtil { +template<> float PriorityQueue::computePriority(const SortableAvatar& thing) const { static constexpr float AVATAR_HERO_BONUS { 25.0f }; // Higher than any normal priority. float priority = std::numeric_limits::min(); @@ -323,6 +324,7 @@ template<> float PrioritySortUtil::PriorityQueue::computePriorit return priority; } +} void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) { const float AVATAR_HERO_FRACTION { 0.4f }; From 6e4ec15c9de9e19a808191fd11e0aafd58cd63c1 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 25 Feb 2019 17:14:51 -0800 Subject: [PATCH 204/474] there we go --- libraries/render-utils/src/RenderPipelines.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp index 83216ddf84..c4a7368f39 100644 --- a/libraries/render-utils/src/RenderPipelines.cpp +++ b/libraries/render-utils/src/RenderPipelines.cpp @@ -731,10 +731,6 @@ void RenderPipelines::updateMultiMaterial(graphics::MultiMaterial& multiMaterial } void RenderPipelines::bindMaterials(graphics::MultiMaterial& multiMaterial, gpu::Batch& batch, bool enableTextures) { - if (multiMaterial.size() == 0) { - return; - } - if (multiMaterial.shouldUpdate()) { updateMultiMaterial(multiMaterial); } From 650b34c463fb46da3912f6db146d5f965fdb8f84 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Mon, 25 Feb 2019 18:00:56 -0800 Subject: [PATCH 205/474] adding clock --- interface/resources/fonts/rawline-500.ttf | Bin 0 -> 262160 bytes .../resources/qml/hifi/tablet/TabletHome.qml | 143 ++++++++++++++---- .../resources/qml/styles-uit/Rawline.qml | 20 +++ interface/resources/qml/stylesUit/Rawline.qml | 20 +++ interface/src/Application.cpp | 1 + 5 files changed, 156 insertions(+), 28 deletions(-) create mode 100644 interface/resources/fonts/rawline-500.ttf create mode 100644 interface/resources/qml/styles-uit/Rawline.qml create mode 100644 interface/resources/qml/stylesUit/Rawline.qml diff --git a/interface/resources/fonts/rawline-500.ttf b/interface/resources/fonts/rawline-500.ttf new file mode 100644 index 0000000000000000000000000000000000000000..a0e18c7364f49252d59226d0959b994a4156b3ba GIT binary patch literal 262160 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%0DJ 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

      G#Mh;)ywUqNlIcrQ=}Lr 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 GKls0o2tK3$ literal 0 HcmV?d00001 diff --git a/interface/resources/qml/hifi/tablet/TabletHome.qml b/interface/resources/qml/hifi/tablet/TabletHome.qml index 4d7883522e..94a65e70a0 100644 --- a/interface/resources/qml/hifi/tablet/TabletHome.qml +++ b/interface/resources/qml/hifi/tablet/TabletHome.qml @@ -6,7 +6,7 @@ import QtQuick.Layouts 1.3 import TabletScriptingInterface 1.0 import "." -import stylesUit 1.0 +import stylesUit 1.0 as HifiStylesUit import "../audio" as HifiAudio Item { @@ -49,44 +49,131 @@ Item { } Item { - width: 150 - height: 50 + id: rightContainer + width: clockItem.width > loginItem.width ? clockItem.width + clockAmPmTextMetrics.width : + loginItem.width + clockAmPmTextMetrics.width + height: parent.height + anchors.top: parent.top + anchors.topMargin: 15 anchors.right: parent.right - anchors.rightMargin: 30 - anchors.verticalCenter: parent.verticalCenter + anchors.rightMargin: 20 + anchors.bottom: parent.bottom - ColumnLayout { - anchors.fill: parent + function timeChanged() { + var date = new Date(); + clockTime.text = date.toLocaleTimeString(Qt.locale("en_US"), "h:mm ap"); + var regex = /[\sa-zA-z]+/; + clockTime.text = clockTime.text.replace(regex, ""); + clockAmPm.text = date.toLocaleTimeString(Qt.locale("en_US"), "ap"); + } - RalewaySemiBold { - text: Account.loggedIn ? qsTr("Log out") : qsTr("Log in") - horizontalAlignment: Text.AlignRight - Layout.alignment: Qt.AlignRight - font.pixelSize: 20 - color: "#afafaf" + Timer { + interval: 1000; running: true; repeat: true; + onTriggered: rightContainer.timeChanged(); + } + + Item { + id: clockAmPmItem + width: clockAmPmTextMetrics.width + height: clockAmPmTextMetrics.height + + anchors.top: parent.top + anchors.right: parent.right + TextMetrics { + id: clockAmPmTextMetrics + text: clockAmPm.text + font: clockAmPm.font } - - RalewaySemiBold { - visible: Account.loggedIn - height: Account.loggedIn ? parent.height/2 - parent.spacing/2 : 0 - text: Account.loggedIn ? "[" + tabletRoot.usernameShort + "]" : "" - horizontalAlignment: Text.AlignRight - Layout.alignment: Qt.AlignRight - font.pixelSize: 20 + Text { + anchors.left: parent.left + id: clockAmPm + anchors.right: parent.right + font.capitalization: Font.AllUppercase + font.pixelSize: 12 + font.family: "Rawline" color: "#afafaf" } } - MouseArea { - anchors.fill: parent - onClicked: { - if (!Account.loggedIn) { - DialogsManager.showLoginDialog() - } else { - Account.logOut() + Item { + id: clockItem + width: clockTimeTextMetrics.width + height: clockTimeTextMetrics.height + anchors { + top: parent.top + topMargin: -10 + right: clockAmPmItem.left + rightMargin: 5 + } + TextMetrics { + id: clockTimeTextMetrics + text: clockTime.text + font: clockTime.font + } + Text { + anchors.top: parent.top + anchors.right: parent.right + id: clockTime + font.bold: false + font.pixelSize: 36 + font.family: "Rawline" + color: "#afafaf" + } + } + + Item { + id: loginItem + width: loginTextMetrics.width + height: loginTextMetrics.height + anchors { + bottom: parent.bottom + bottomMargin: 10 + right: clockAmPmItem.left + rightMargin: 5 + } + Text { + id: loginText + anchors.right: parent.right + visible: !Account.loggedIn + text: qsTr("Log in") + horizontalAlignment: Text.AlignRight + Layout.alignment: Qt.AlignRight + font.pixelSize: 18 + font.family: "Rawline" + color: "#afafaf" + } + TextMetrics { + id: loginTextMetrics + text: Account.loggedIn ? tabletRoot.usernameShort : loginText.text + font: loginText.font + } + + HifiStylesUit.ShortcutText { + anchors.right: parent.right + visible: Account.loggedIn + text: "" + tabletRoot.usernameShort + "" + horizontalAlignment: Text.AlignRight + Layout.alignment: Qt.AlignRight + font.pixelSize: 18 + font.bold: true + font.family: "Rawline" + linkColor: hifi.colors.blueAccent + } + + MouseArea { + anchors.fill: parent + onClicked: { + if (!Account.loggedIn) { + DialogsManager.showLoginDialog(); + } else { + Account.logOut(); + } } } } + Component.onCompleted: { + rightContainer.timeChanged(); + } } } diff --git a/interface/resources/qml/styles-uit/Rawline.qml b/interface/resources/qml/styles-uit/Rawline.qml new file mode 100644 index 0000000000..50c6544739 --- /dev/null +++ b/interface/resources/qml/styles-uit/Rawline.qml @@ -0,0 +1,20 @@ +// +// Rawline.qml +// +// Created by Wayne Chen on 25 Feb 2019 +// Copyright 2019 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 +// + +import QtQuick 2.7 + +Text { + id: root + property real size: 32 + font.pixelSize: size + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignLeft + font.family: "Rawline" +} diff --git a/interface/resources/qml/stylesUit/Rawline.qml b/interface/resources/qml/stylesUit/Rawline.qml new file mode 100644 index 0000000000..50c6544739 --- /dev/null +++ b/interface/resources/qml/stylesUit/Rawline.qml @@ -0,0 +1,20 @@ +// +// Rawline.qml +// +// Created by Wayne Chen on 25 Feb 2019 +// Copyright 2019 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 +// + +import QtQuick 2.7 + +Text { + id: root + property real size: 32 + font.pixelSize: size + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignLeft + font.family: "Rawline" +} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 83b287b7ae..a850cdfe12 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1075,6 +1075,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/FiraSans-SemiBold.ttf"); QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Raleway-Light.ttf"); QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Raleway-Regular.ttf"); + QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/rawline-500.ttf"); QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Raleway-Bold.ttf"); QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Raleway-SemiBold.ttf"); QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Cairo-SemiBold.ttf"); From 4825ecdbf35c78b624c739128a790f147530e52c Mon Sep 17 00:00:00 2001 From: nimisha20 Date: Mon, 25 Feb 2019 22:44:16 -0800 Subject: [PATCH 206/474] Update AccountServicesScriptingInterface.h --- interface/src/scripting/AccountServicesScriptingInterface.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/scripting/AccountServicesScriptingInterface.h b/interface/src/scripting/AccountServicesScriptingInterface.h index 415b405e53..1555caba3c 100644 --- a/interface/src/scripting/AccountServicesScriptingInterface.h +++ b/interface/src/scripting/AccountServicesScriptingInterface.h @@ -98,7 +98,7 @@ public slots: bool isLoggedIn(); /**jsdoc - * Prompts the user to log in (with a login dialog) if they're not already logged in. The function returns if the user is logged in or not before the user completes the login dialog. + * Prompts the user to log in (with a login dialog) if they're not already logged in. The function returns a value before the user completes the login dialog. * @function AccountServices.checkAndSignalForAccessToken * @returns {boolean} true if the user is logged in, false if not. */ From 736a050e6808ff0913e819558937d3b06d8d9e3c Mon Sep 17 00:00:00 2001 From: nimisha20 Date: Mon, 25 Feb 2019 22:55:56 -0800 Subject: [PATCH 207/474] Update AccountServicesScriptingInterface.h --- interface/src/scripting/AccountServicesScriptingInterface.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/scripting/AccountServicesScriptingInterface.h b/interface/src/scripting/AccountServicesScriptingInterface.h index 1555caba3c..b5bee6a7e4 100644 --- a/interface/src/scripting/AccountServicesScriptingInterface.h +++ b/interface/src/scripting/AccountServicesScriptingInterface.h @@ -98,7 +98,7 @@ public slots: bool isLoggedIn(); /**jsdoc - * Prompts the user to log in (with a login dialog) if they're not already logged in. The function returns a value before the user completes the login dialog. + * The function returns the log in status of the user and prompts the user to log in (with a login dialog) if they're not already logged in. * @function AccountServices.checkAndSignalForAccessToken * @returns {boolean} true if the user is logged in, false if not. */ From ba3895efdd942ed40d24ad5134b23d5bc05c4756 Mon Sep 17 00:00:00 2001 From: nimisha20 Date: Mon, 25 Feb 2019 22:56:36 -0800 Subject: [PATCH 208/474] Update AccountServicesScriptingInterface.h Minor copy-edit --- interface/src/scripting/AccountServicesScriptingInterface.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/scripting/AccountServicesScriptingInterface.h b/interface/src/scripting/AccountServicesScriptingInterface.h index b5bee6a7e4..b188b4e63b 100644 --- a/interface/src/scripting/AccountServicesScriptingInterface.h +++ b/interface/src/scripting/AccountServicesScriptingInterface.h @@ -98,7 +98,7 @@ public slots: bool isLoggedIn(); /**jsdoc - * The function returns the log in status of the user and prompts the user to log in (with a login dialog) if they're not already logged in. + * The function returns the login status of the user and prompts the user to log in (with a login dialog) if they're not already logged in. * @function AccountServices.checkAndSignalForAccessToken * @returns {boolean} true if the user is logged in, false if not. */ From 7912c1b2fdaa17acb14b0c452209913921405aba Mon Sep 17 00:00:00 2001 From: Flame Soulis Date: Tue, 26 Feb 2019 08:38:07 -0500 Subject: [PATCH 209/474] Adds Ubuntu 16.04 friendly cmake instructions --- BUILD_LINUX.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/BUILD_LINUX.md b/BUILD_LINUX.md index 1559ece191..58368a168f 100644 --- a/BUILD_LINUX.md +++ b/BUILD_LINUX.md @@ -37,8 +37,14 @@ sudo apt-get -y install libpulse0 libnss3 libnspr4 libfontconfig1 libxcursor1 li Install build tools: ```bash +# For Ubuntu 18.04 sudo apt-get install cmake ``` +```bash +# For Ubuntu 16.04 +wget https://cmake.org/files/v3.9/cmake-3.9.5-Linux-x86_64.sh +sudo sh cmake-3.9.5-Linux-x86_64.sh --prefix=/usr/local --exclude-subdir +``` Install Python 3: ```bash From 79c5cfee53928b559c04b89a49a00583e54d1e8f Mon Sep 17 00:00:00 2001 From: Flame Soulis Date: Tue, 26 Feb 2019 08:38:28 -0500 Subject: [PATCH 210/474] Updates checkout tag to the latest (0.79) --- BUILD_LINUX.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BUILD_LINUX.md b/BUILD_LINUX.md index 58368a168f..8820bda8f6 100644 --- a/BUILD_LINUX.md +++ b/BUILD_LINUX.md @@ -67,7 +67,7 @@ git tags Then checkout last tag with: ```bash -git checkout tags/v0.71.0 +git checkout tags/v0.79.0 ``` ### Compiling From db1c78246fc640971ea55f273a1132ac4966e2ce Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Tue, 26 Feb 2019 11:32:43 -0700 Subject: [PATCH 211/474] Read and apply the FBX upVector parameter --- libraries/fbx/src/FBXSerializer.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/libraries/fbx/src/FBXSerializer.cpp b/libraries/fbx/src/FBXSerializer.cpp index 9e7f422b40..3c8aa8f799 100644 --- a/libraries/fbx/src/FBXSerializer.cpp +++ b/libraries/fbx/src/FBXSerializer.cpp @@ -167,7 +167,6 @@ glm::mat4 getGlobalTransform(const QMultiMap& _connectionParen } } } - return globalTransform; } @@ -436,6 +435,8 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr hfmModel.originalURL = url; float unitScaleFactor = 1.0f; + glm::quat upAxisZRotation; + bool applyUpAxisZRotation = false; glm::vec3 ambientColor; QString hifiGlobalNodeID; unsigned int meshIndex = 0; @@ -473,11 +474,20 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr if (subobject.name == propertyName) { static const QVariant UNIT_SCALE_FACTOR = QByteArray("UnitScaleFactor"); static const QVariant AMBIENT_COLOR = QByteArray("AmbientColor"); + static const QVariant UP_AXIS = QByteArray("UpAxis"); const auto& subpropName = subobject.properties.at(0); if (subpropName == UNIT_SCALE_FACTOR) { unitScaleFactor = subobject.properties.at(index).toFloat(); } else if (subpropName == AMBIENT_COLOR) { ambientColor = getVec3(subobject.properties, index); + } else if (subpropName == UP_AXIS) { + constexpr int UP_AXIS_Y = 1; + constexpr int UP_AXIS_Z = 2; + int upAxis = subobject.properties.at(index).toInt(); + if (upAxis == UP_AXIS_Z) { + upAxisZRotation = glm::angleAxis(glm::radians(-90.0f), glm::vec3(1.0f, 0.0f, 0.0f)); + applyUpAxisZRotation = true; + } } } } @@ -1271,7 +1281,6 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr hfmModel.hasSkeletonJoints = (hfmModel.hasSkeletonJoints || joint.isSkeletonJoint); glm::quat combinedRotation = joint.preRotation * joint.rotation * joint.postRotation; - if (joint.parentIndex == -1) { joint.transform = hfmModel.offset * glm::translate(joint.translation) * joint.preTransform * glm::mat4_cast(combinedRotation) * joint.postTransform; @@ -1664,6 +1673,9 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr } } + if (applyUpAxisZRotation) { + hfmModelPtr->bindExtents.rotate(upAxisZRotation); + } return hfmModelPtr; } From 0b080471ff975c89d7108844f554527caa900ef7 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Tue, 26 Feb 2019 10:57:35 -0800 Subject: [PATCH 212/474] Filter on scriptValue to reduce EntityItem load; bump ping timeouts for testing --- assignment-client/src/avatars/AvatarMixer.cpp | 2 +- libraries/networking/src/DomainHandler.h | 2 +- libraries/networking/src/ThreadedAssignment.cpp | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index f352e02afa..df89194247 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -957,7 +957,7 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) { } void AvatarMixer::setupEntityQuery() { - static char queryJsonString[] = R"({"avatarPriority": true})"; + static char queryJsonString[] = R"({"avatarPriority": true, "serverScripts": "+"})"; _entityViewer.init(); DependencyManager::registerInheritance(); diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 620ffb9641..219af3338c 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -35,7 +35,7 @@ const unsigned short DEFAULT_DOMAIN_SERVER_DTLS_PORT = 40103; const quint16 DOMAIN_SERVER_HTTP_PORT = 40100; const quint16 DOMAIN_SERVER_HTTPS_PORT = 40101; -const int MAX_SILENT_DOMAIN_SERVER_CHECK_INS = 5; +const int MAX_SILENT_DOMAIN_SERVER_CHECK_INS = 10; // XXX class DomainHandler : public QObject { Q_OBJECT diff --git a/libraries/networking/src/ThreadedAssignment.cpp b/libraries/networking/src/ThreadedAssignment.cpp index bdba47f0ed..62aec4a2a1 100644 --- a/libraries/networking/src/ThreadedAssignment.cpp +++ b/libraries/networking/src/ThreadedAssignment.cpp @@ -123,6 +123,7 @@ void ThreadedAssignment::checkInWithDomainServerOrExit() { // increase the number of queued check ins _numQueuedCheckIns++; + qCDebug(networking) << "Number of queued checkins = " << _numQueuedCheckIns; } } From b7cc6ecca4519626eac7035b53090cc94dd11989 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Tue, 26 Feb 2019 10:59:31 -0800 Subject: [PATCH 213/474] trigger for 79 hero branch From 028cce53949035ebb7611cbf1327426acb8803ef Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Tue, 26 Feb 2019 12:26:50 -0800 Subject: [PATCH 214/474] change short username to 14 characters, displaying as normal text --- .../resources/qml/hifi/tablet/TabletHome.qml | 19 ++----------------- .../resources/qml/hifi/tablet/TabletRoot.qml | 6 +++--- 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/interface/resources/qml/hifi/tablet/TabletHome.qml b/interface/resources/qml/hifi/tablet/TabletHome.qml index 94a65e70a0..a1da69a44a 100644 --- a/interface/resources/qml/hifi/tablet/TabletHome.qml +++ b/interface/resources/qml/hifi/tablet/TabletHome.qml @@ -134,8 +134,7 @@ Item { Text { id: loginText anchors.right: parent.right - visible: !Account.loggedIn - text: qsTr("Log in") + text: Account.loggedIn ? tabletRoot.usernameShort : qsTr("Log in") horizontalAlignment: Text.AlignRight Layout.alignment: Qt.AlignRight font.pixelSize: 18 @@ -144,29 +143,15 @@ Item { } TextMetrics { id: loginTextMetrics - text: Account.loggedIn ? tabletRoot.usernameShort : loginText.text + text: loginText.text font: loginText.font } - HifiStylesUit.ShortcutText { - anchors.right: parent.right - visible: Account.loggedIn - text: "" + tabletRoot.usernameShort + "" - horizontalAlignment: Text.AlignRight - Layout.alignment: Qt.AlignRight - font.pixelSize: 18 - font.bold: true - font.family: "Rawline" - linkColor: hifi.colors.blueAccent - } - MouseArea { anchors.fill: parent onClicked: { if (!Account.loggedIn) { DialogsManager.showLoginDialog(); - } else { - Account.logOut(); } } } diff --git a/interface/resources/qml/hifi/tablet/TabletRoot.qml b/interface/resources/qml/hifi/tablet/TabletRoot.qml index 93a23f1b9d..8d237d146a 100644 --- a/interface/resources/qml/hifi/tablet/TabletRoot.qml +++ b/interface/resources/qml/hifi/tablet/TabletRoot.qml @@ -178,10 +178,10 @@ Rectangle { function setUsername(newUsername) { username = newUsername; - usernameShort = newUsername.substring(0, 8); + usernameShort = newUsername.substring(0, 14); - if (newUsername.length > 8) { - usernameShort = usernameShort + "..." + if (newUsername.length > 14) { + usernameShort = usernameShort + "..." } } From f47ec099273548a1300be6699571727bc29aaf32 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Tue, 26 Feb 2019 14:01:30 -0700 Subject: [PATCH 215/474] use UP_AXIS_Y --- libraries/fbx/src/FBXSerializer.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/fbx/src/FBXSerializer.cpp b/libraries/fbx/src/FBXSerializer.cpp index 3c8aa8f799..0b31eda94b 100644 --- a/libraries/fbx/src/FBXSerializer.cpp +++ b/libraries/fbx/src/FBXSerializer.cpp @@ -484,7 +484,9 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr constexpr int UP_AXIS_Y = 1; constexpr int UP_AXIS_Z = 2; int upAxis = subobject.properties.at(index).toInt(); - if (upAxis == UP_AXIS_Z) { + if (upAxis == UP_AXIS_Y) { + // No update necessary, y up is the default + } else if (upAxis == UP_AXIS_Z) { upAxisZRotation = glm::angleAxis(glm::radians(-90.0f), glm::vec3(1.0f, 0.0f, 0.0f)); applyUpAxisZRotation = true; } From 7039ee471d217a3812586025c2e71498bddcc5de Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Tue, 26 Feb 2019 13:49:55 -0800 Subject: [PATCH 216/474] Move newly-created Connection to NodeList thread --- libraries/networking/src/udt/Connection.cpp | 1 - libraries/networking/src/udt/Socket.cpp | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/libraries/networking/src/udt/Connection.cpp b/libraries/networking/src/udt/Connection.cpp index 7ab2296935..418dc8f417 100644 --- a/libraries/networking/src/udt/Connection.cpp +++ b/libraries/networking/src/udt/Connection.cpp @@ -31,7 +31,6 @@ using namespace udt; using namespace std::chrono; Connection::Connection(Socket* parentSocket, HifiSockAddr destination, std::unique_ptr congestionControl) : - QObject(parentSocket), _parentSocket(parentSocket), _destination(destination), _congestionControl(move(congestionControl)) diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index 358acce694..7829e3727c 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -251,7 +251,10 @@ Connection* Socket::findOrCreateConnection(const HifiSockAddr& sockAddr, bool fi auto congestionControl = _ccFactory->create(); congestionControl->setMaxBandwidth(_maxBandwidth); auto connection = std::unique_ptr(new Connection(this, sockAddr, std::move(congestionControl))); - + if (QThread::currentThread() != thread()) { + qCDebug(networking) << "Moving new Connection to NodeList thread"; + connection->moveToThread(thread()); + } // allow higher-level classes to find out when connections have completed a handshake QObject::connect(connection.get(), &Connection::receiverHandshakeRequestComplete, this, &Socket::clientHandshakeRequestComplete); From 4c6bea48b2a713fce07934df97ed77b3ebd316a7 Mon Sep 17 00:00:00 2001 From: amer cerkic Date: Tue, 26 Feb 2019 14:48:34 -0800 Subject: [PATCH 217/474] fixed a case where _widths = 0 and widths-1 is not valid. --- .../src/RenderablePolyLineEntityItem.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp index c4ea6a2fea..0ea8db7c15 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp @@ -184,8 +184,11 @@ void PolyLineEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPo void PolyLineEntityRenderer::updateGeometry() { int maxNumVertices = std::min(_points.length(), _normals.length()); + qDebug()<<"QQQ_ widths size: "<< _widths.size(); + qDebug()<<"QQQ_ MaxNumbVertices: "<= 0) { + if (_widths.size() > 0) { for (int i = 1; i < maxNumVertices; i++) { float width = PolyLineEntityItem::DEFAULT_LINE_WIDTH; if (i < _widths.length()) { @@ -206,12 +209,15 @@ void PolyLineEntityRenderer::updateGeometry() { std::vector vertices; vertices.reserve(maxNumVertices); + for (int i = 0; i < maxNumVertices; i++) { // Position glm::vec3 point = _points[i]; - // uCoord - float width = i < _widths.size() ? _widths[i] : PolyLineEntityItem::DEFAULT_LINE_WIDTH; + float width=PolyLineEntityItem::DEFAULT_LINE_WIDTH; + if(_widths.size()>0 && i < _widths.size()) + width = _widths[i]; + if (i > 0) { // First uCoord is 0.0f if (!_isUVModeStretch) { accumulatedDistance += glm::distance(point, _points[i - 1]); From 5a7c8c312640c4975a5b94dc6662056b8be1ea0a Mon Sep 17 00:00:00 2001 From: amer cerkic Date: Tue, 26 Feb 2019 14:53:44 -0800 Subject: [PATCH 218/474] removed debugg helpers --- .../entities-renderer/src/RenderablePolyLineEntityItem.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp index 0ea8db7c15..ec8444ff35 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp @@ -183,10 +183,6 @@ void PolyLineEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPo void PolyLineEntityRenderer::updateGeometry() { int maxNumVertices = std::min(_points.length(), _normals.length()); - - qDebug()<<"QQQ_ widths size: "<< _widths.size(); - qDebug()<<"QQQ_ MaxNumbVertices: "< 0) { for (int i = 1; i < maxNumVertices; i++) { @@ -215,8 +211,9 @@ void PolyLineEntityRenderer::updateGeometry() { glm::vec3 point = _points[i]; float width=PolyLineEntityItem::DEFAULT_LINE_WIDTH; - if(_widths.size()>0 && i < _widths.size()) + if(_widths.size()>0 && i < _widths.size()) { width = _widths[i]; + } if (i > 0) { // First uCoord is 0.0f if (!_isUVModeStretch) { From f5852139f75238a3b4b79eebfb63d22ae8ec6da4 Mon Sep 17 00:00:00 2001 From: amer cerkic Date: Tue, 26 Feb 2019 15:05:58 -0800 Subject: [PATCH 219/474] fixing comment since the original check was correct --- .../entities-renderer/src/RenderablePolyLineEntityItem.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp index ec8444ff35..44b368d827 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp @@ -209,11 +209,7 @@ void PolyLineEntityRenderer::updateGeometry() { for (int i = 0; i < maxNumVertices; i++) { // Position glm::vec3 point = _points[i]; - - float width=PolyLineEntityItem::DEFAULT_LINE_WIDTH; - if(_widths.size()>0 && i < _widths.size()) { - width = _widths[i]; - } + float width = i < _widths.size() ? _widths[i] : PolyLineEntityItem::DEFAULT_LINE_WIDTH; if (i > 0) { // First uCoord is 0.0f if (!_isUVModeStretch) { From 19d584f0af1a6ebc2910edfe301319b322bdbad0 Mon Sep 17 00:00:00 2001 From: amer cerkic Date: Tue, 26 Feb 2019 15:28:28 -0800 Subject: [PATCH 220/474] added Sams suggestions to the logic --- .../src/RenderablePolyLineEntityItem.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp index 44b368d827..454e8b136a 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp @@ -185,15 +185,17 @@ void PolyLineEntityRenderer::updateGeometry() { int maxNumVertices = std::min(_points.length(), _normals.length()); bool doesStrokeWidthVary = false; if (_widths.size() > 0) { + float prevWidth = _widths[0]; for (int i = 1; i < maxNumVertices; i++) { - float width = PolyLineEntityItem::DEFAULT_LINE_WIDTH; - if (i < _widths.length()) { - width = _widths[i]; - } - if (width != _widths[i - 1]) { + float width = i < _widths.length() ? _widths[i] : PolyLineEntityItem::DEFAULT_LINE_WIDTH; + if (width != prevWidth) { doesStrokeWidthVary = true; break; } + if (i > _widths.length() + 1) { + break; + } + prevWidth = width; } } @@ -209,6 +211,7 @@ void PolyLineEntityRenderer::updateGeometry() { for (int i = 0; i < maxNumVertices; i++) { // Position glm::vec3 point = _points[i]; + // uCoord float width = i < _widths.size() ? _widths[i] : PolyLineEntityItem::DEFAULT_LINE_WIDTH; if (i > 0) { // First uCoord is 0.0f From 4d591e0624081af7ff589f64bb0cb0c3d956f5a1 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 26 Feb 2019 16:12:12 -0800 Subject: [PATCH 221/474] fix web interaction and debug print filling logs --- .../entities/src/MovingEntitiesOperator.cpp | 6 +- .../system/libraries/entitySelectionTool.js | 107 ++++++++++-------- 2 files changed, 62 insertions(+), 51 deletions(-) diff --git a/libraries/entities/src/MovingEntitiesOperator.cpp b/libraries/entities/src/MovingEntitiesOperator.cpp index 4b908745e0..9dd5a4d206 100644 --- a/libraries/entities/src/MovingEntitiesOperator.cpp +++ b/libraries/entities/src/MovingEntitiesOperator.cpp @@ -55,13 +55,11 @@ void MovingEntitiesOperator::addEntityToMoveList(EntityItemPointer entity, const qCDebug(entities) << " oldContainingElement->bestFitBounds(newCubeClamped):" << oldContainingElement->bestFitBounds(newCubeClamped); } else { - qCDebug(entities) << " WARNING NO OLD CONTAINING ELEMENT!!!"; + qCDebug(entities) << " WARNING NO OLD CONTAINING ELEMENT for entity" << entity->getEntityItemID(); } } - + if (!oldContainingElement) { - qCDebug(entities) << "UNEXPECTED!!!! attempting to move entity "<< entity->getEntityItemID() - << "that has no containing element. "; return; // bail without adding. } diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 767128ca16..269283ea6d 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -708,7 +708,7 @@ SelectionDisplay = (function() { shape: "Cone", solid: true, visible: false, - ignoreRayIntersection: false, + ignorePickIntersection: true, drawInFront: true }; var handlePropertiesTranslateArrowCylinders = { @@ -716,7 +716,7 @@ SelectionDisplay = (function() { shape: "Cylinder", solid: true, visible: false, - ignoreRayIntersection: false, + ignorePickIntersection: true, drawInFront: true }; var handleTranslateXCone = Overlays.addOverlay("shape", handlePropertiesTranslateArrowCones); @@ -741,7 +741,7 @@ SelectionDisplay = (function() { majorTickMarksAngle: ROTATE_DEFAULT_TICK_MARKS_ANGLE, majorTickMarksLength: 0.1, visible: false, - ignoreRayIntersection: false, + ignorePickIntersection: true, drawInFront: true }; var handleRotatePitchRing = Overlays.addOverlay("circle3d", handlePropertiesRotateRings); @@ -766,7 +766,7 @@ SelectionDisplay = (function() { solid: true, innerRadius: 0.9, visible: false, - ignoreRayIntersection: true, + ignorePickIntersection: true, drawInFront: true }); @@ -779,7 +779,7 @@ SelectionDisplay = (function() { visible: false, isFacingAvatar: true, drawInFront: true, - ignoreRayIntersection: true, + ignorePickIntersection: true, dimensions: { x: 0, y: 0 }, lineHeight: 0.0, topMargin: 0, @@ -791,7 +791,7 @@ SelectionDisplay = (function() { var handlePropertiesStretchCubes = { solid: true, visible: false, - ignoreRayIntersection: false, + ignorePickIntersection: true, drawInFront: true }; var handleStretchXCube = Overlays.addOverlay("cube", handlePropertiesStretchCubes); @@ -802,18 +802,17 @@ SelectionDisplay = (function() { Overlays.editOverlay(handleStretchZCube, { color: COLOR_BLUE }); var handlePropertiesStretchPanel = { - shape: "Quad", alpha: 0.5, solid: true, visible: false, - ignoreRayIntersection: true, + ignorePickIntersection: true, drawInFront: true }; - var handleStretchXPanel = Overlays.addOverlay("shape", handlePropertiesStretchPanel); + var handleStretchXPanel = Overlays.addOverlay("cube", handlePropertiesStretchPanel); Overlays.editOverlay(handleStretchXPanel, { color: COLOR_RED }); - var handleStretchYPanel = Overlays.addOverlay("shape", handlePropertiesStretchPanel); + var handleStretchYPanel = Overlays.addOverlay("cube", handlePropertiesStretchPanel); Overlays.editOverlay(handleStretchYPanel, { color: COLOR_GREEN }); - var handleStretchZPanel = Overlays.addOverlay("shape", handlePropertiesStretchPanel); + var handleStretchZPanel = Overlays.addOverlay("cube", handlePropertiesStretchPanel); Overlays.editOverlay(handleStretchZPanel, { color: COLOR_BLUE }); var handleScaleCube = Overlays.addOverlay("cube", { @@ -821,7 +820,7 @@ SelectionDisplay = (function() { color: COLOR_SCALE_CUBE, solid: true, visible: false, - ignoreRayIntersection: false, + ignorePickIntersection: true, drawInFront: true, borderSize: 1.4 }); @@ -841,7 +840,7 @@ SelectionDisplay = (function() { color: COLOR_GREEN, solid: true, visible: false, - ignoreRayIntersection: false, + ignorePickIntersection: true, drawInFront: true, borderSize: 1.4 }); @@ -854,6 +853,7 @@ SelectionDisplay = (function() { alpha: 0, solid: false, visible: false, + ignorePickIntersection: true, dashed: false }); @@ -865,6 +865,7 @@ SelectionDisplay = (function() { alpha: 0, solid: false, visible: false, + ignorePickIntersection: true, dashed: false }); @@ -877,7 +878,7 @@ SelectionDisplay = (function() { green: 0, blue: 0 }, - ignoreRayIntersection: true // always ignore this + ignorePickIntersection: true // always ignore this }); var yRailOverlay = Overlays.addOverlay("line3d", { visible: false, @@ -888,7 +889,7 @@ SelectionDisplay = (function() { green: 255, blue: 0 }, - ignoreRayIntersection: true // always ignore this + ignorePickIntersection: true // always ignore this }); var zRailOverlay = Overlays.addOverlay("line3d", { visible: false, @@ -899,7 +900,7 @@ SelectionDisplay = (function() { green: 0, blue: 255 }, - ignoreRayIntersection: true // always ignore this + ignorePickIntersection: true // always ignore this }); var allOverlays = [ @@ -972,7 +973,7 @@ SelectionDisplay = (function() { color: COLOR_DEBUG_PICK_PLANE, solid: true, visible: false, - ignoreRayIntersection: true, + ignorePickIntersection: true, drawInFront: false }); var debugPickPlaneHits = []; @@ -1802,6 +1803,7 @@ SelectionDisplay = (function() { isActiveTool(handleRotateYawRing) || isActiveTool(handleRotateRollRing); selectionBoxGeometry.visible = !inModeRotate && !isCameraInsideBox; + selectionBoxGeometry.ignorePickIntersection = !selectionBoxGeometry.visible; Overlays.editOverlay(selectionBox, selectionBoxGeometry); // UPDATE ICON TRANSLATE HANDLE @@ -1811,9 +1813,13 @@ SelectionDisplay = (function() { rotation: rotation }; iconSelectionBoxGeometry.visible = !inModeRotate && isCameraInsideBox; + iconSelectionBoxGeometry.ignorePickIntersection = !iconSelectionBoxGeometry.visible; Overlays.editOverlay(iconSelectionBox, iconSelectionBoxGeometry); } else { - Overlays.editOverlay(iconSelectionBox, { visible: false }); + Overlays.editOverlay(iconSelectionBox, { + visible: false, + ignorePickIntersection: true + }); } // UPDATE DUPLICATOR (CURRENTLY HIDDEN FOR NOW) @@ -1882,7 +1888,7 @@ SelectionDisplay = (function() { // FUNCTION: SET OVERLAYS VISIBLE that.setOverlaysVisible = function(isVisible) { for (var i = 0, length = allOverlays.length; i < length; i++) { - Overlays.editOverlay(allOverlays[i], { visible: isVisible }); + Overlays.editOverlay(allOverlays[i], { visible: isVisible, ignorePickIntersection: !isVisible }); } }; @@ -1894,18 +1900,18 @@ SelectionDisplay = (function() { }; that.setHandleTranslateXVisible = function(isVisible) { - Overlays.editOverlay(handleTranslateXCone, { visible: isVisible }); - Overlays.editOverlay(handleTranslateXCylinder, { visible: isVisible }); + Overlays.editOverlay(handleTranslateXCone, { visible: isVisible, ignorePickIntersection: !isVisible }); + Overlays.editOverlay(handleTranslateXCylinder, { visible: isVisible, ignorePickIntersection: !isVisible }); }; that.setHandleTranslateYVisible = function(isVisible) { - Overlays.editOverlay(handleTranslateYCone, { visible: isVisible }); - Overlays.editOverlay(handleTranslateYCylinder, { visible: isVisible }); + Overlays.editOverlay(handleTranslateYCone, { visible: isVisible, ignorePickIntersection: !isVisible }); + Overlays.editOverlay(handleTranslateYCylinder, { visible: isVisible, ignorePickIntersection: !isVisible }); }; that.setHandleTranslateZVisible = function(isVisible) { - Overlays.editOverlay(handleTranslateZCone, { visible: isVisible }); - Overlays.editOverlay(handleTranslateZCylinder, { visible: isVisible }); + Overlays.editOverlay(handleTranslateZCone, { visible: isVisible, ignorePickIntersection: !isVisible }); + Overlays.editOverlay(handleTranslateZCylinder, { visible: isVisible, ignorePickIntersection: !isVisible }); }; // FUNCTION: SET HANDLE ROTATE VISIBLE @@ -1916,15 +1922,15 @@ SelectionDisplay = (function() { }; that.setHandleRotatePitchVisible = function(isVisible) { - Overlays.editOverlay(handleRotatePitchRing, { visible: isVisible }); + Overlays.editOverlay(handleRotatePitchRing, { visible: isVisible, ignorePickIntersection: !isVisible }); }; that.setHandleRotateYawVisible = function(isVisible) { - Overlays.editOverlay(handleRotateYawRing, { visible: isVisible }); + Overlays.editOverlay(handleRotateYawRing, { visible: isVisible, ignorePickIntersection: !isVisible }); }; that.setHandleRotateRollVisible = function(isVisible) { - Overlays.editOverlay(handleRotateRollRing, { visible: isVisible }); + Overlays.editOverlay(handleRotateRollRing, { visible: isVisible, ignorePickIntersection: !isVisible }); }; // FUNCTION: SET HANDLE STRETCH VISIBLE @@ -1935,15 +1941,15 @@ SelectionDisplay = (function() { }; that.setHandleStretchXVisible = function(isVisible) { - Overlays.editOverlay(handleStretchXCube, { visible: isVisible }); + Overlays.editOverlay(handleStretchXCube, { visible: isVisible, ignorePickIntersection: !isVisible }); }; that.setHandleStretchYVisible = function(isVisible) { - Overlays.editOverlay(handleStretchYCube, { visible: isVisible }); + Overlays.editOverlay(handleStretchYCube, { visible: isVisible, ignorePickIntersection: !isVisible }); }; that.setHandleStretchZVisible = function(isVisible) { - Overlays.editOverlay(handleStretchZCube, { visible: isVisible }); + Overlays.editOverlay(handleStretchZCube, { visible: isVisible, ignorePickIntersection: !isVisible }); }; // FUNCTION: SET HANDLE SCALE VISIBLE @@ -1953,16 +1959,16 @@ SelectionDisplay = (function() { }; that.setHandleScaleVisible = function(isVisible) { - Overlays.editOverlay(handleScaleCube, { visible: isVisible }); + Overlays.editOverlay(handleScaleCube, { visible: isVisible, ignorePickIntersection: !isVisible }); }; that.setHandleBoundingBoxVisible = function(isVisible) { - Overlays.editOverlay(handleBoundingBox, { visible: isVisible }); + Overlays.editOverlay(handleBoundingBox, { visible: isVisible, ignorePickIntersection: true }); }; // FUNCTION: SET HANDLE DUPLICATOR VISIBLE that.setHandleDuplicatorVisible = function(isVisible) { - Overlays.editOverlay(handleDuplicator, { visible: isVisible }); + Overlays.editOverlay(handleDuplicator, { visible: isVisible, ignorePickIntersection: !isVisible }); }; // FUNCTION: DEBUG PICK PLANE @@ -1975,7 +1981,7 @@ SelectionDisplay = (function() { position: pickPlanePosition, rotation: rotation, dimensions: dimensions, - visible: true + visible: true }); }; @@ -1986,7 +1992,7 @@ SelectionDisplay = (function() { shape: "Sphere", solid: true, visible: true, - ignoreRayIntersection: true, + ignorePickIntersection: true, drawInFront: false, color: COLOR_DEBUG_PICK_PLANE_HIT, position: pickHitPosition, @@ -2082,10 +2088,12 @@ SelectionDisplay = (function() { pushCommandForSelections(duplicatedEntityIDs); if (isConstrained) { Overlays.editOverlay(xRailOverlay, { - visible: false + visible: false, + ignorePickIntersection: true }); Overlays.editOverlay(zRailOverlay, { - visible: false + visible: false, + ignorePickIntersection: true }); } }, @@ -2174,22 +2182,26 @@ SelectionDisplay = (function() { Overlays.editOverlay(xRailOverlay, { start: xStart, end: xEnd, - visible: true + visible: true, + ignorePickIntersection: true }); Overlays.editOverlay(zRailOverlay, { start: zStart, end: zEnd, - visible: true + visible: true, + ignorePickIntersection: true }); isConstrained = true; } } else { if (isConstrained) { Overlays.editOverlay(xRailOverlay, { - visible: false + visible: false, + ignorePickIntersection: true }); Overlays.editOverlay(zRailOverlay, { - visible: false + visible: false, + ignorePickIntersection: true }); isConstrained = false; } @@ -2460,7 +2472,7 @@ SelectionDisplay = (function() { } if (stretchPanel !== null) { - Overlays.editOverlay(stretchPanel, { visible: true }); + Overlays.editOverlay(stretchPanel, { visible: true, ignorePickIntersection: false }); } var stretchCubePosition = Overlays.getProperty(handleStretchCube, "position"); var stretchPanelPosition = Overlays.getProperty(stretchPanel, "position"); @@ -2481,7 +2493,7 @@ SelectionDisplay = (function() { } if (stretchPanel !== null) { - Overlays.editOverlay(stretchPanel, { visible: false }); + Overlays.editOverlay(stretchPanel, { visible: false, ignorePickIntersection: true }); } activeStretchCubePanelOffset = null; @@ -2775,7 +2787,8 @@ SelectionDisplay = (function() { rotation: worldRotation, startAt: 0, endAt: 0, - visible: true + visible: true, + ignorePickIntersection: false }); // editOverlays may not have committed rotation changes. @@ -2805,13 +2818,13 @@ SelectionDisplay = (function() { if (wantDebug) { print("================== " + getMode() + "(addHandleRotateTool onEnd) -> ======================="); } - Overlays.editOverlay(rotationDegreesDisplay, { visible: false }); + Overlays.editOverlay(rotationDegreesDisplay, { visible: false, ignorePickIntersection: true }); Overlays.editOverlay(selectedHandle, { hasTickMarks: false, solid: true, innerRadius: ROTATE_RING_IDLE_INNER_RADIUS }); - Overlays.editOverlay(handleRotateCurrentRing, { visible: false }); + Overlays.editOverlay(handleRotateCurrentRing, { visible: false, ignorePickIntersection: true }); pushCommandForSelections(); if (wantDebug) { print("================== " + getMode() + "(addHandleRotateTool onEnd) <- ======================="); From 08d6a2ce7f0724b99bc0d8686a4d84de3acf8de6 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Tue, 26 Feb 2019 16:40:16 -0800 Subject: [PATCH 222/474] Avatar Hero debugging & logging --- assignment-client/src/avatars/AvatarMixer.cpp | 1 + .../src/avatars/AvatarMixerClientData.cpp | 13 +++++++++++++ assignment-client/src/avatars/AvatarMixerSlave.cpp | 3 +++ assignment-client/src/avatars/AvatarMixerSlave.h | 3 +++ libraries/networking/src/ThreadedAssignment.cpp | 4 +++- 5 files changed, 23 insertions(+), 1 deletion(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index df89194247..3064a5cd14 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -752,6 +752,7 @@ void AvatarMixer::sendStatsPacket() { slavesAggregatObject["sent_4_averageDataBytes"] = TIGHT_LOOP_STAT(aggregateStats.numDataBytesSent); slavesAggregatObject["sent_5_averageTraitsBytes"] = TIGHT_LOOP_STAT(aggregateStats.numTraitsBytesSent); slavesAggregatObject["sent_6_averageIdentityBytes"] = TIGHT_LOOP_STAT(aggregateStats.numIdentityBytesSent); + slavesAggregatObject["sent_7_averageHeroAvatars"] = TIGHT_LOOP_STAT(aggregateStats.numHeroesIncluded); slavesAggregatObject["timing_1_processIncomingPackets"] = TIGHT_LOOP_STAT_UINT64(aggregateStats.processIncomingPacketsElapsedTime); slavesAggregatObject["timing_2_ignoreCalculation"] = TIGHT_LOOP_STAT_UINT64(aggregateStats.ignoreCalculationElapsedTime); diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index 9ba1ea82ca..1fe6d7e13c 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -19,6 +19,8 @@ #include #include +#include "AvatarLogging.h" + #include "AvatarMixerSlave.h" AvatarMixerClientData::AvatarMixerClientData(const QUuid& nodeID, Node::LocalID nodeLocalID) : @@ -132,10 +134,21 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message, const SlaveShared auto newPosition = getPosition(); if (newPosition != oldPosition) { +//#define AVATAR_HERO_TEST_HACK +#ifdef AVATAR_HERO_TEST_HACK + { + const static QString heroKey { "HERO" }; + _avatar->setPriorityAvatar(_avatar->getDisplayName().contains(heroKey)); + } +#else EntityTree& entityTree = *slaveSharedData.entityTree; FindPriorityZone findPriorityZone { newPosition, false } ; entityTree.recurseTreeWithOperation(&FindPriorityZone::operation, &findPriorityZone); _avatar->setPriorityAvatar(findPriorityZone.isInPriorityZone); + if (findPriorityZone.isInPriorityZone) { + qCWarning(avatars) << "Avatar" << _avatar->getDisplayName() << "in hero zone"; + } +#endif } return true; diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 9ad3cff7e1..5d2efdf830 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -596,6 +596,9 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) if (detail != AvatarData::NoData) { _stats.numOthersIncluded++; + if (otherAvatar->getPriorityAvatar()) { + _stats.numHeroesIncluded++; + } // increment the number of avatars sent to this receiver nodeData->incrementNumAvatarsSentLastFrame(); diff --git a/assignment-client/src/avatars/AvatarMixerSlave.h b/assignment-client/src/avatars/AvatarMixerSlave.h index 28d99748fa..8c5ad6b181 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.h +++ b/assignment-client/src/avatars/AvatarMixerSlave.h @@ -32,6 +32,7 @@ public: int numIdentityPacketsSent { 0 }; int numOthersIncluded { 0 }; int overBudgetAvatars { 0 }; + int numHeroesIncluded { 0 }; quint64 ignoreCalculationElapsedTime { 0 }; quint64 avatarDataPackingElapsedTime { 0 }; @@ -57,6 +58,7 @@ public: numIdentityPacketsSent = 0; numOthersIncluded = 0; overBudgetAvatars = 0; + numHeroesIncluded = 0; ignoreCalculationElapsedTime = 0; avatarDataPackingElapsedTime = 0; @@ -80,6 +82,7 @@ public: numIdentityPacketsSent += rhs.numIdentityPacketsSent; numOthersIncluded += rhs.numOthersIncluded; overBudgetAvatars += rhs.overBudgetAvatars; + numHeroesIncluded += rhs.numHeroesIncluded; ignoreCalculationElapsedTime += rhs.ignoreCalculationElapsedTime; avatarDataPackingElapsedTime += rhs.avatarDataPackingElapsedTime; diff --git a/libraries/networking/src/ThreadedAssignment.cpp b/libraries/networking/src/ThreadedAssignment.cpp index 62aec4a2a1..012ac242ab 100644 --- a/libraries/networking/src/ThreadedAssignment.cpp +++ b/libraries/networking/src/ThreadedAssignment.cpp @@ -123,7 +123,9 @@ void ThreadedAssignment::checkInWithDomainServerOrExit() { // increase the number of queued check ins _numQueuedCheckIns++; - qCDebug(networking) << "Number of queued checkins = " << _numQueuedCheckIns; + if (_numQueuedCheckIns > 1) { + qCDebug(networking) << "Number of queued checkins = " << _numQueuedCheckIns; + } } } From ff7995ae18a9e6262fbb58167912ce9240f9b586 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Tue, 26 Feb 2019 18:29:55 -0700 Subject: [PATCH 223/474] Right fix --- libraries/fbx/src/FBXSerializer.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libraries/fbx/src/FBXSerializer.cpp b/libraries/fbx/src/FBXSerializer.cpp index 0b31eda94b..e022ca8921 100644 --- a/libraries/fbx/src/FBXSerializer.cpp +++ b/libraries/fbx/src/FBXSerializer.cpp @@ -1281,7 +1281,9 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr joint.geometricScaling = fbxModel.geometricScaling; joint.isSkeletonJoint = fbxModel.isLimbNode; hfmModel.hasSkeletonJoints = (hfmModel.hasSkeletonJoints || joint.isSkeletonJoint); - + if (applyUpAxisZRotation && joint.parentIndex == -1 && !joint.isSkeletonJoint) { + joint.rotation *= upAxisZRotation; + } glm::quat combinedRotation = joint.preRotation * joint.rotation * joint.postRotation; if (joint.parentIndex == -1) { joint.transform = hfmModel.offset * glm::translate(joint.translation) * joint.preTransform * @@ -1676,6 +1678,7 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr } if (applyUpAxisZRotation) { + hfmModelPtr->meshExtents.rotate(upAxisZRotation); hfmModelPtr->bindExtents.rotate(upAxisZRotation); } return hfmModelPtr; From 9559f9ffd39ac80fdb7bb6f76c946ee15d5586be Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Tue, 26 Feb 2019 17:52:18 -0800 Subject: [PATCH 224/474] Add off-hand controller manipulation to fargrab. --- .../controllerModules/farGrabEntity.js | 94 +++++++++++++++++-- 1 file changed, 88 insertions(+), 6 deletions(-) diff --git a/scripts/system/controllers/controllerModules/farGrabEntity.js b/scripts/system/controllers/controllerModules/farGrabEntity.js index 197a809e91..d2d8af9dc4 100644 --- a/scripts/system/controllers/controllerModules/farGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farGrabEntity.js @@ -60,6 +60,14 @@ Script.include("/~/system/libraries/controllers.js"); this.reticleMaxY = 0; this.endedGrab = 0; this.MIN_HAPTIC_PULSE_INTERVAL = 500; // ms + this.disabled = false; + var _this = this; + this.leftTrigger = 0.0; + this.rightTrigger = 0.0; + this.initialControllerRotation = Quat.IDENTITY; + this.currentControllerRotation = Quat.IDENTITY; + this.manipulating = false; + this.wasManipulating = false; var FAR_GRAB_JOINTS = [65527, 65528]; // FARGRAB_LEFTHAND_INDEX, FARGRAB_RIGHTHAND_INDEX @@ -75,6 +83,46 @@ Script.include("/~/system/libraries/controllers.js"); 100, makeLaserParams(this.hand, false)); + //enableDispatcherModule("LeftFarGrabEntity", leftFarGrabEntity); + //enableDispatcherModule("RightFarGrabEntity", rightFarGrabEntity); + + this.getOtherModule = function () { + // Used to fetch other module. + return getEnabledModuleByName(this.hand === RIGHT_HAND ? ("RightFarGrabEntity") : ("LeftFarGrabEntity")); + }; + + this.getTargetRotation = function () { + if (this.targetIsNull()) { + return null; + } else { + var props = Entities.getEntityProperties(this.targetEntityID, ["rotation"]); + return props.rotation; + } + }; + + this.getOffhand = function () { + return (this.hand === RIGHT_HAND ? LEFT_HAND : RIGHT_HAND); + } + + this.getOffhandTrigger = function () { + return (_this.hand === RIGHT_HAND ? _this.rightTrigger : _this.leftTrigger); + } + + this.shouldManipulateTarget = function () { + return (_this.getOffhandTrigger() > TRIGGER_ON_VALUE) ? true : false; + }; + + this.calculateEntityRotationManipulation = function (controllerRotation) { + return Quat.multiply(controllerRotation, Quat.inverse(this.initialControllerRotation)); + }; + + this.setJointTranslation = function (newTargetPosLocal) { + MyAvatar.setJointTranslation(FAR_GRAB_JOINTS[this.hand], newTargetPosLocal); + }; + + this.setJointRotation = function (newTargetRotLocal) { + MyAvatar.setJointRotation(FAR_GRAB_JOINTS[this.hand], newTargetRotLocal); + }; this.handToController = function() { return (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; @@ -142,8 +190,9 @@ Script.include("/~/system/libraries/controllers.js"); Messages.sendLocalMessage('Hifi-unhighlight-entity', JSON.stringify(message)); var newTargetPosLocal = MyAvatar.worldToJointPoint(targetProps.position); - MyAvatar.setJointTranslation(FAR_GRAB_JOINTS[this.hand], newTargetPosLocal); - MyAvatar.setJointRotation(FAR_GRAB_JOINTS[this.hand], { x: 0, y: 0, z: 0, w: 1 }); + var newTargetRotLocal = targetProps.rotation; + this.setJointTranslation(newTargetPosLocal); + this.setJointRotation(newTargetRotLocal); var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; Entities.callEntityMethod(targetProps.id, "startDistanceGrab", args); @@ -231,8 +280,31 @@ Script.include("/~/system/libraries/controllers.js"); // var newTargetPosLocal = Mat4.transformPoint(MyAvatar.getSensorToWorldMatrix(), newTargetPosition); var newTargetPosLocal = MyAvatar.worldToJointPoint(newTargetPosition); - MyAvatar.setJointTranslation(FAR_GRAB_JOINTS[this.hand], newTargetPosLocal); - MyAvatar.setJointRotation(FAR_GRAB_JOINTS[this.hand], { x: 0, y: 0, z: 0, w: 1 }); + if (this.shouldManipulateTarget()) { + var pose = Controller.getPoseValue((this.getOffhand() ? Controller.Standard.RightHand : Controller.Standard.LeftHand)); + if (pose.valid) { + if (!this.manipulating) { + if (!this.wasManipulating) { + this.initialEntityRotation = this.getTargetRotation(); // Worldframe. + } + this.initialControllerRotation = Quat.multiply(pose.rotation, MyAvatar.orientation); // Worldframe. + this.manipulating = true; + } + } + + var rot = Quat.multiply(pose.rotation, MyAvatar.orientation); + var rotBetween = this.calculateEntityRotationManipulation(rot); + this.lastJointRotation = Quat.multiply(rotBetween, this.initialEntityRotation); + this.setJointRotation(this.lastJointRotation); + } else { + if (this.manipulating) { + this.initialEntityRotation = this.lastJointRotation; + this.wasManipulating = true; + } + this.manipulating = false; + this.initialControllerRotation = Quat.IDENTITY; + } + this.setJointTranslation(newTargetPosLocal); this.previousRoomControllerPosition = roomControllerPosition; }; @@ -254,9 +326,15 @@ Script.include("/~/system/libraries/controllers.js"); })); unhighlightTargetEntity(this.targetEntityID); this.grabbing = false; - this.targetEntityID = null; this.potentialEntityWithContextOverlay = false; MyAvatar.clearJointData(FAR_GRAB_JOINTS[this.hand]); + this.initialEntityRotation = Quat.IDENTITY; + this.initialControllerRotation = Quat.IDENTITY; + this.targetEntityID = null; + this.manipulating = false; + this.wasManipulating = false; + var otherModule = this.getOtherModule(); + otherModule.disabled = false; }; this.updateRecommendedArea = function() { @@ -326,7 +404,9 @@ Script.include("/~/system/libraries/controllers.js"); this.distanceHolding = false; - if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { + if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE && !this.disabled) { + var otherModule = this.getOtherModule(); + otherModule.disabled = true; return makeRunningValues(true, [], []); } else { this.destroyContextOverlay(); @@ -336,6 +416,8 @@ Script.include("/~/system/libraries/controllers.js"); }; this.run = function (controllerData) { + this.leftTrigger = controllerData.triggerValues[LEFT_HAND]; + this.rightTrigger = controllerData.triggerValues[RIGHT_HAND]; if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE || this.targetIsNull()) { this.endFarGrabEntity(controllerData); return makeRunningValues(false, [], []); From 52d80476a8fda0d32abff288815ff75a5f9bae32 Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Tue, 26 Feb 2019 17:55:16 -0800 Subject: [PATCH 225/474] Fix activation criteria. --- scripts/system/controllers/controllerModules/farGrabEntity.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/system/controllers/controllerModules/farGrabEntity.js b/scripts/system/controllers/controllerModules/farGrabEntity.js index d2d8af9dc4..6e08542bda 100644 --- a/scripts/system/controllers/controllerModules/farGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farGrabEntity.js @@ -88,7 +88,7 @@ Script.include("/~/system/libraries/controllers.js"); this.getOtherModule = function () { // Used to fetch other module. - return getEnabledModuleByName(this.hand === RIGHT_HAND ? ("RightFarGrabEntity") : ("LeftFarGrabEntity")); + return getEnabledModuleByName(this.hand === RIGHT_HAND ? ("LeftFarGrabEntity") : ("RightFarGrabEntity")); }; this.getTargetRotation = function () { @@ -105,7 +105,7 @@ Script.include("/~/system/libraries/controllers.js"); } this.getOffhandTrigger = function () { - return (_this.hand === RIGHT_HAND ? _this.rightTrigger : _this.leftTrigger); + return (_this.hand === RIGHT_HAND ? _this.leftTrigger : _this.rightTrigger); } this.shouldManipulateTarget = function () { From 167ddbecff127d7a33f0546744f1b5c2fef97968 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Tue, 26 Feb 2019 18:35:01 -0800 Subject: [PATCH 226/474] Use session display-name for debugging --- assignment-client/src/avatars/AvatarMixerClientData.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index 1fe6d7e13c..a63a76829b 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -146,7 +146,7 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message, const SlaveShared entityTree.recurseTreeWithOperation(&FindPriorityZone::operation, &findPriorityZone); _avatar->setPriorityAvatar(findPriorityZone.isInPriorityZone); if (findPriorityZone.isInPriorityZone) { - qCWarning(avatars) << "Avatar" << _avatar->getDisplayName() << "in hero zone"; + qCWarning(avatars) << "Avatar" << _avatar->getSessionDisplayName() << "in hero zone"; } #endif } From ca740c6254229112e3657dd02f2179883ca06ed1 Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Tue, 26 Feb 2019 18:44:00 -0800 Subject: [PATCH 227/474] Add a 2x multiplier for controller movement to entity rotation when manipulating with fargrab. --- .../controllerModules/farGrabEntity.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/scripts/system/controllers/controllerModules/farGrabEntity.js b/scripts/system/controllers/controllerModules/farGrabEntity.js index 6e08542bda..5e621809b2 100644 --- a/scripts/system/controllers/controllerModules/farGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farGrabEntity.js @@ -276,25 +276,31 @@ Script.include("/~/system/libraries/controllers.js"); newTargetPosition = Vec3.sum(newTargetPosition, worldControllerPosition); newTargetPosition = Vec3.sum(newTargetPosition, this.offsetPosition); - // MyAvatar.setJointTranslation(FAR_GRAB_JOINTS[this.hand], MyAvatar.worldToJointPoint(newTargetPosition)); - - // var newTargetPosLocal = Mat4.transformPoint(MyAvatar.getSensorToWorldMatrix(), newTargetPosition); var newTargetPosLocal = MyAvatar.worldToJointPoint(newTargetPosition); + + // This block handles the user's ability to rotate the object they're FarGrabbing if (this.shouldManipulateTarget()) { + // Get the pose of the controller that is not grabbing. var pose = Controller.getPoseValue((this.getOffhand() ? Controller.Standard.RightHand : Controller.Standard.LeftHand)); if (pose.valid) { + // If we weren't manipulating the object yet, initialize the entity's original position. if (!this.manipulating) { + // This will only be triggered if we've let go of the off-hand trigger and pulled it again without ending a grab. + // Need to poll the entity's rotation again here. if (!this.wasManipulating) { - this.initialEntityRotation = this.getTargetRotation(); // Worldframe. + this.initialEntityRotation = this.getTargetRotation(); } - this.initialControllerRotation = Quat.multiply(pose.rotation, MyAvatar.orientation); // Worldframe. + // Save the original controller orientation, we only care about the delta between this rotation and wherever + // the controller rotates, so that we can apply it to the entity's rotation. + this.initialControllerRotation = Quat.multiply(pose.rotation, MyAvatar.orientation); this.manipulating = true; } } var rot = Quat.multiply(pose.rotation, MyAvatar.orientation); var rotBetween = this.calculateEntityRotationManipulation(rot); - this.lastJointRotation = Quat.multiply(rotBetween, this.initialEntityRotation); + var doubleRot = Quat.multiply(rotBetween, rotBetween); + this.lastJointRotation = Quat.multiply(doubleRot, this.initialEntityRotation); this.setJointRotation(this.lastJointRotation); } else { if (this.manipulating) { From 2fd94c6bbcf61d24b047bad5fc0bd35357f35850 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Tue, 26 Feb 2019 15:37:31 -0800 Subject: [PATCH 228/474] fixing login dialog --- interface/src/Application.cpp | 16 ++++++++++++++-- interface/src/Application.h | 1 + 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 83b287b7ae..ebc1176ee1 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4979,6 +4979,15 @@ void Application::idle() { } } + { + if (_keyboardFocusWaitingOnRenderable && getEntities()->renderableForEntityId(_keyboardFocusedEntity.get())) { + _keyboardFocusWaitingOnRenderable = false; + QUuid entityId = _keyboardFocusedEntity.get(); + setKeyboardFocusEntity(UNKNOWN_ENTITY_ID); + setKeyboardFocusEntity(entityId); + } + } + { PerformanceTimer perfTimer("pluginIdle"); PerformanceWarning warn(showWarnings, "Application::idle()... pluginIdle()"); @@ -5807,7 +5816,7 @@ void Application::setKeyboardFocusEntity(const QUuid& id) { if (qApp->getLoginDialogPoppedUp() && !_loginDialogID.isNull()) { if (id == _loginDialogID) { emit loginDialogFocusEnabled(); - } else { + } else if (!_keyboardFocusWaitingOnRenderable) { // that's the only entity we want in focus; return; } @@ -5824,7 +5833,10 @@ void Application::setKeyboardFocusEntity(const QUuid& id) { if (properties.getVisible()) { auto entities = getEntities(); auto entityId = _keyboardFocusedEntity.get(); - if (entities->wantsKeyboardFocus(entityId)) { + auto entityItemRenderable = entities->renderableForEntityId(entityId); + if (!entityItemRenderable) { + _keyboardFocusWaitingOnRenderable = true; + } else if (entityItemRenderable->wantsKeyboardFocus()) { entities->setProxyWindow(entityId, _window->windowHandle()); if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->pluginFocusOutEvent(); diff --git a/interface/src/Application.h b/interface/src/Application.h index afd9f5f12f..c16f260192 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -732,6 +732,7 @@ private: bool _failedToConnectToEntityServer { false }; bool _reticleClickPressed { false }; + bool _keyboardFocusWaitingOnRenderable { false }; int _avatarAttachmentRequest = 0; From bf9e5b9550ce622e39ddbaff655efacb952f73a8 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 27 Feb 2019 10:14:28 -0800 Subject: [PATCH 229/474] comment out another log --- interface/src/octree/SafeLanding.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/octree/SafeLanding.cpp b/interface/src/octree/SafeLanding.cpp index 9efad22d09..e56ca984e0 100644 --- a/interface/src/octree/SafeLanding.cpp +++ b/interface/src/octree/SafeLanding.cpp @@ -72,7 +72,7 @@ void SafeLanding::addTrackedEntity(const EntityItemID& entityID) { Locker lock(_lock); EntityItemPointer entity = _entityTree->findEntityByID(entityID); - if (entity && entity->getCreated() < _startTime) { + if (entity && !entity->isLocalEntity() && entity->getCreated() < _startTime) { _trackedEntities.emplace(entityID, entity); int trackedEntityCount = (int)_trackedEntities.size(); @@ -81,7 +81,7 @@ void SafeLanding::addTrackedEntity(const EntityItemID& entityID) { _maxTrackedEntityCount = trackedEntityCount; _trackedEntityStabilityCount = 0; } - qCDebug(interfaceapp) << "Safe Landing: Tracking entity " << entity->getItemName(); + //qCDebug(interfaceapp) << "Safe Landing: Tracking entity " << entity->getItemName(); } } } From 92cfa49bfb55fc780a1f2a34a437ef6d1001379f Mon Sep 17 00:00:00 2001 From: amer cerkic Date: Wed, 27 Feb 2019 11:42:22 -0800 Subject: [PATCH 230/474] adding and testing command line parameter passing to application --- .../questInterface/src/main/AndroidManifest.xml | 2 +- .../questInterface/InterfaceActivity.java | 15 +++++++++++++++ .../questInterface/PermissionsChecker.java | 16 +++++++++++++++- .../oculus/OculusMobileActivity.java | 11 +++++++++++ interface/src/Application.cpp | 8 ++++---- 5 files changed, 46 insertions(+), 6 deletions(-) diff --git a/android/apps/questInterface/src/main/AndroidManifest.xml b/android/apps/questInterface/src/main/AndroidManifest.xml index a5de47bdce..0f3616612a 100644 --- a/android/apps/questInterface/src/main/AndroidManifest.xml +++ b/android/apps/questInterface/src/main/AndroidManifest.xml @@ -28,7 +28,7 @@ android:excludeFromRecents="true"> - + ()->handleLookupString(QUEST_DEV); +// / const QString QUEST_DEV = "hifi://quest-dev"; + // DependencyManager::get()->handleLookupString(QUEST_DEV); #endif } @@ -3669,8 +3669,8 @@ void Application::handleSandboxStatus(QNetworkReply* reply) { // If this is a first run we short-circuit the address passed in if (_firstRun.get()) { #if !defined(Q_OS_ANDROID) - DependencyManager::get()->goToEntry(); - sentTo = SENT_TO_ENTRY; + // DependencyManager::get()->goToEntry(); + // sentTo = SENT_TO_ENTRY; #endif _firstRun.set(false); From 13cda8d212154e16565553d661da07275d35dde5 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Wed, 27 Feb 2019 13:14:08 -0800 Subject: [PATCH 231/474] Use separate priority-queues for hero & other avatars --- .../src/avatars/AvatarMixerSlave.cpp | 271 ++++++++++-------- 1 file changed, 146 insertions(+), 125 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 5d2efdf830..7627f6a2e5 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -307,24 +307,24 @@ namespace { } // Close anonymous namespace. -// Specialize computePriority() for avatars: -namespace PrioritySortUtil { -template<> float PriorityQueue::computePriority(const SortableAvatar& thing) const { - static constexpr float AVATAR_HERO_BONUS { 25.0f }; // Higher than any normal priority. - - float priority = std::numeric_limits::min(); - - for (const auto& view : _views) { - priority = std::max(priority, computePriority(view, thing)); - } - - if (thing.getAvatar()->getPriorityAvatar()) { - priority += AVATAR_HERO_BONUS; - } - - return priority; -} -} +//// Specialize computePriority() for avatars: +//namespace PrioritySortUtil { +//template<> float PriorityQueue::computePriority(const SortableAvatar& thing) const { +// static constexpr float AVATAR_HERO_BONUS { 25.0f }; // Higher than any normal priority. +// +// float priority = std::numeric_limits::min(); +// +// for (const auto& view : _views) { +// priority = std::max(priority, computePriority(view, thing)); +// } +// +// if (thing.getAvatar()->getPriorityAvatar()) { +// priority += AVATAR_HERO_BONUS; +// } +// +// return priority; +//} +//} void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) { const float AVATAR_HERO_FRACTION { 0.4f }; @@ -388,11 +388,23 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // prepare to sort const auto& cameraViews = nodeData->getViewFrustums(); - PrioritySortUtil::PriorityQueue sortedAvatars(cameraViews, - AvatarData::_avatarSortCoefficientSize, - AvatarData::_avatarSortCoefficientCenter, - AvatarData::_avatarSortCoefficientAge); - sortedAvatars.reserve(_end - _begin); + + using AvatarPriorityQueue = PrioritySortUtil::PriorityQueue; + // Keep two independent queues, one for heroes and one for the riff-raff. + enum PriorityVariants { kHero, kNonhero }; + AvatarPriorityQueue avatarPriorityQueues[2] = { {cameraViews, + AvatarData::_avatarSortCoefficientSize, AvatarData::_avatarSortCoefficientCenter, AvatarData::_avatarSortCoefficientAge}, + {cameraViews, + AvatarData::_avatarSortCoefficientSize, AvatarData::_avatarSortCoefficientCenter, AvatarData::_avatarSortCoefficientAge} + }; + + //PrioritySortUtil::PriorityQueue sortedAvatars(cameraViews, + // AvatarData::_avatarSortCoefficientSize, + // AvatarData::_avatarSortCoefficientCenter, + // AvatarData::_avatarSortCoefficientAge); + //sortedAvatars.reserve(_end - _begin); + + avatarPriorityQueues[kNonhero].reserve(_end - _begin); for (auto listedNode = _begin; listedNode != _end; ++listedNode) { Node* otherNodeRaw = (*listedNode).data(); @@ -475,7 +487,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) const MixerAvatar* avatarNodeData = avatarClientNodeData->getConstAvatarData(); auto lastEncodeTime = nodeData->getLastOtherAvatarEncodeTime(avatarNode->getLocalID()); - sortedAvatars.push(SortableAvatar(avatarNodeData, avatarNode, lastEncodeTime)); + avatarPriorityQueues[avatarNodeData->getPriorityAvatar() ? kHero : kNonhero].push( + SortableAvatar(avatarNodeData, avatarNode, lastEncodeTime)); } // If Avatar A's PAL WAS open but is no longer open, AND @@ -501,124 +514,132 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // loop through our sorted avatars and allocate our bandwidth to them accordingly - int remainingAvatars = (int)sortedAvatars.size(); + int remainingAvatars = (int)avatarPriorityQueues[kHero].size() + (int)avatarPriorityQueues[kNonhero].size(); auto traitsPacketList = NLPacketList::create(PacketType::BulkAvatarTraits, QByteArray(), true, true); auto avatarPacket = NLPacket::create(PacketType::BulkAvatarData); const int avatarPacketCapacity = avatarPacket->getPayloadCapacity(); int avatarSpaceAvailable = avatarPacketCapacity; int numPacketsSent = 0; + int numAvatarsSent = 0; auto identityPacketList = NLPacketList::create(PacketType::AvatarIdentity, QByteArray(), true, true); - const auto& sortedAvatarVector = sortedAvatars.getSortedVector(numToSendEst); - for (const auto& sortedAvatar : sortedAvatarVector) { - const Node* otherNode = sortedAvatar.getNode(); - auto lastEncodeForOther = sortedAvatar.getTimestamp(); + for (PriorityVariants currentVariant = kHero; currentVariant <= kNonhero; ++((int&)currentVariant)) { + const auto& sortedAvatarVector = avatarPriorityQueues[currentVariant].getSortedVector(numToSendEst); + for (const auto& sortedAvatar : sortedAvatarVector) { + const Node* otherNode = sortedAvatar.getNode(); + auto lastEncodeForOther = sortedAvatar.getTimestamp(); - assert(otherNode); // we can't have gotten here without the avatarData being a valid key in the map + assert(otherNode); // we can't have gotten here without the avatarData being a valid key in the map - AvatarData::AvatarDataDetail detail = AvatarData::NoData; + AvatarData::AvatarDataDetail detail = AvatarData::NoData; - // NOTE: Here's where we determine if we are over budget and drop remaining avatars, - // or send minimal avatar data in uncommon case of PALIsOpen. - int minimRemainingAvatarBytes = minimumBytesPerAvatar * remainingAvatars; - auto frameByteEstimate = identityBytesSent + traitBytesSent + numAvatarDataBytes + minimRemainingAvatarBytes; - bool overBudget = frameByteEstimate > maxAvatarBytesPerFrame; - if (overBudget) { - if (PALIsOpen) { - _stats.overBudgetAvatars++; - detail = AvatarData::PALMinimum; - } else { - _stats.overBudgetAvatars += remainingAvatars; - break; - } - } - - bool overHeroBudget = frameByteEstimate > maxHeroBytesPerFrame; - - auto startAvatarDataPacking = chrono::high_resolution_clock::now(); - - const AvatarMixerClientData* otherNodeData = reinterpret_cast(otherNode->getLinkedData()); - const MixerAvatar* otherAvatar = otherNodeData->getConstAvatarData(); - - if (overHeroBudget && otherAvatar->getPriorityAvatar()) { - continue; // No more heroes (this frame). - } - - // Typically all out-of-view avatars but such avatars' priorities will rise with time: - bool isLowerPriority = sortedAvatar.getPriority() <= OUT_OF_VIEW_THRESHOLD; - - if (isLowerPriority) { - detail = PALIsOpen ? AvatarData::PALMinimum : AvatarData::MinimumData; - nodeData->incrementAvatarOutOfView(); - } else if (!overBudget) { - detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO ? AvatarData::SendAllData : AvatarData::CullSmallData; - nodeData->incrementAvatarInView(); - - // If the time that the mixer sent AVATAR DATA about Avatar B to Avatar A is BEFORE OR EQUAL TO - // the time that Avatar B flagged an IDENTITY DATA change, send IDENTITY DATA about Avatar B to Avatar A. - if (otherAvatar->hasProcessedFirstIdentity() - && nodeData->getLastBroadcastTime(otherNode->getLocalID()) <= otherNodeData->getIdentityChangeTimestamp()) { - identityBytesSent += sendIdentityPacket(*identityPacketList, otherNodeData, *destinationNode); - - // remember the last time we sent identity details about this other node to the receiver - nodeData->setLastBroadcastTime(otherNode->getLocalID(), usecTimestampNow()); - } - } - - QVector& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getLocalID()); - - const bool distanceAdjust = true; - const bool dropFaceTracking = false; - AvatarDataPacket::SendStatus sendStatus; - sendStatus.sendUUID = true; - - do { - auto startSerialize = chrono::high_resolution_clock::now(); - QByteArray bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, - sendStatus, dropFaceTracking, distanceAdjust, myPosition, - &lastSentJointsForOther, avatarSpaceAvailable); - auto endSerialize = chrono::high_resolution_clock::now(); - _stats.toByteArrayElapsedTime += - (quint64)chrono::duration_cast(endSerialize - startSerialize).count(); - - avatarPacket->write(bytes); - avatarSpaceAvailable -= bytes.size(); - numAvatarDataBytes += bytes.size(); - if (!sendStatus || avatarSpaceAvailable < (int)AvatarDataPacket::MIN_BULK_PACKET_SIZE) { - // Weren't able to fit everything. - nodeList->sendPacket(std::move(avatarPacket), *destinationNode); - ++numPacketsSent; - avatarPacket = NLPacket::create(PacketType::BulkAvatarData); - avatarSpaceAvailable = avatarPacketCapacity; - } - } while (!sendStatus); - - if (detail != AvatarData::NoData) { - _stats.numOthersIncluded++; - if (otherAvatar->getPriorityAvatar()) { - _stats.numHeroesIncluded++; + // NOTE: Here's where we determine if we are over budget and drop remaining avatars, + // or send minimal avatar data in uncommon case of PALIsOpen. + int minimRemainingAvatarBytes = minimumBytesPerAvatar * remainingAvatars; + auto frameByteEstimate = identityBytesSent + traitBytesSent + numAvatarDataBytes + minimRemainingAvatarBytes; + bool overBudget = frameByteEstimate > maxAvatarBytesPerFrame; + if (overBudget) { + if (PALIsOpen) { + _stats.overBudgetAvatars++; + detail = AvatarData::PALMinimum; + } else { + _stats.overBudgetAvatars += remainingAvatars; + break; + } } - // increment the number of avatars sent to this receiver - nodeData->incrementNumAvatarsSentLastFrame(); + bool overHeroBudget = currentVariant == kHero && numAvatarDataBytes > maxHeroBytesPerFrame; + if (overHeroBudget) { + break; // No more heroes (this frame). + } - // set the last sent sequence number for this sender on the receiver - nodeData->setLastBroadcastSequenceNumber(otherNode->getLocalID(), - otherNodeData->getLastReceivedSequenceNumber()); - nodeData->setLastOtherAvatarEncodeTime(otherNode->getLocalID(), usecTimestampNow()); + auto startAvatarDataPacking = chrono::high_resolution_clock::now(); + + const AvatarMixerClientData* otherNodeData = reinterpret_cast(otherNode->getLinkedData()); + const MixerAvatar* otherAvatar = otherNodeData->getConstAvatarData(); + + // Typically all out-of-view avatars but such avatars' priorities will rise with time: + bool isLowerPriority = sortedAvatar.getPriority() <= OUT_OF_VIEW_THRESHOLD; + + if (isLowerPriority) { + detail = PALIsOpen ? AvatarData::PALMinimum : AvatarData::MinimumData; + nodeData->incrementAvatarOutOfView(); + } else if (!overBudget) { + detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO ? AvatarData::SendAllData : AvatarData::CullSmallData; + nodeData->incrementAvatarInView(); + + // If the time that the mixer sent AVATAR DATA about Avatar B to Avatar A is BEFORE OR EQUAL TO + // the time that Avatar B flagged an IDENTITY DATA change, send IDENTITY DATA about Avatar B to Avatar A. + if (otherAvatar->hasProcessedFirstIdentity() + && nodeData->getLastBroadcastTime(otherNode->getLocalID()) <= otherNodeData->getIdentityChangeTimestamp()) { + identityBytesSent += sendIdentityPacket(*identityPacketList, otherNodeData, *destinationNode); + + // remember the last time we sent identity details about this other node to the receiver + nodeData->setLastBroadcastTime(otherNode->getLocalID(), usecTimestampNow()); + } + } + + QVector& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getLocalID()); + + const bool distanceAdjust = true; + const bool dropFaceTracking = false; + AvatarDataPacket::SendStatus sendStatus; + sendStatus.sendUUID = true; + + do { + auto startSerialize = chrono::high_resolution_clock::now(); + QByteArray bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, + sendStatus, dropFaceTracking, distanceAdjust, myPosition, + &lastSentJointsForOther, avatarSpaceAvailable); + auto endSerialize = chrono::high_resolution_clock::now(); + _stats.toByteArrayElapsedTime += + (quint64)chrono::duration_cast(endSerialize - startSerialize).count(); + + avatarPacket->write(bytes); + avatarSpaceAvailable -= bytes.size(); + numAvatarDataBytes += bytes.size(); + if (!sendStatus || avatarSpaceAvailable < (int)AvatarDataPacket::MIN_BULK_PACKET_SIZE) { + // Weren't able to fit everything. + nodeList->sendPacket(std::move(avatarPacket), *destinationNode); + ++numPacketsSent; + avatarPacket = NLPacket::create(PacketType::BulkAvatarData); + avatarSpaceAvailable = avatarPacketCapacity; + } + } while (!sendStatus); + + if (detail != AvatarData::NoData) { + _stats.numOthersIncluded++; + if (otherAvatar->getPriorityAvatar()) { + _stats.numHeroesIncluded++; + } + + // increment the number of avatars sent to this receiver + nodeData->incrementNumAvatarsSentLastFrame(); + + // set the last sent sequence number for this sender on the receiver + nodeData->setLastBroadcastSequenceNumber(otherNode->getLocalID(), + otherNodeData->getLastReceivedSequenceNumber()); + nodeData->setLastOtherAvatarEncodeTime(otherNode->getLocalID(), usecTimestampNow()); + } + + auto endAvatarDataPacking = chrono::high_resolution_clock::now(); + _stats.avatarDataPackingElapsedTime += + (quint64)chrono::duration_cast(endAvatarDataPacking - startAvatarDataPacking).count(); + + if (!overBudget) { + // use helper to add any changed traits to our packet list + traitBytesSent += addChangedTraitsToBulkPacket(nodeData, otherNodeData, *traitsPacketList); + } + numAvatarsSent++; + remainingAvatars--; } - auto endAvatarDataPacking = chrono::high_resolution_clock::now(); - _stats.avatarDataPackingElapsedTime += - (quint64) chrono::duration_cast(endAvatarDataPacking - startAvatarDataPacking).count(); - - if (!overBudget) { - // use helper to add any changed traits to our packet list - traitBytesSent += addChangedTraitsToBulkPacket(nodeData, otherNodeData, *traitsPacketList); + if (currentVariant == kHero) { // Dump any remaining heroes into the commoners. + for (auto avIter = sortedAvatarVector.begin() + numAvatarsSent; avIter < sortedAvatarVector.end(); ++avIter) { + avatarPriorityQueues[kNonhero].push(*avIter); + } } - - remainingAvatars--; } if (nodeData->getNumAvatarsSentLastFrame() > numToSendEst) { From 6df28da0a58a5c0c27736712a370418656f740d6 Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Wed, 27 Feb 2019 12:43:41 -0800 Subject: [PATCH 232/474] Fix Avatar Mixer divide by zero error in stats reporting Under load, the frame count may not be updated between stats reports, causing the diff between frame counts to be zero, resulting in divide by zero errors. --- assignment-client/src/avatars/AvatarMixer.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 500772c1b5..57009ab8a9 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -656,8 +656,11 @@ void AvatarMixer::handleRadiusIgnoreRequestPacket(QSharedPointer Date: Wed, 27 Feb 2019 12:53:30 -0800 Subject: [PATCH 233/474] Add test logging, stats, and force domain pings to timer thread Test code for load testing only. --- domain-server/src/DomainServer.cpp | 5 ++++ libraries/networking/src/DomainHandler.cpp | 2 +- libraries/networking/src/NodeList.cpp | 24 ++++++++++++++++++- libraries/networking/src/NodeList.h | 3 +++ .../networking/src/ThreadedAssignment.cpp | 14 ++++++++++- libraries/networking/src/ThreadedAssignment.h | 2 ++ 6 files changed, 47 insertions(+), 3 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 258038b8f1..052648032d 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1055,6 +1055,11 @@ void DomainServer::processListRequestPacket(QSharedPointer mess _gatekeeper.cleanupICEPeerForNode(sendingNode->getUUID()); } + if (sendingNode->getType() == NodeType::AvatarMixer) { + qWarning() << "Avatar Mixer Node Report in."; + } + + // guard against patched agents asking to hear about other agents auto safeInterestSet = nodeRequestData.interestList.toSet(); if (sendingNode->getType() == NodeType::Agent) { diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 2513510b05..af03a3e061 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -545,7 +545,7 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer MAX_SILENT_DOMAIN_SERVER_CHECK_INS) { // we haven't heard back from DS in MAX_SILENT_DOMAIN_SERVER_CHECK_INS // so emit our signal that says that diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 5e8909db2b..9f1f674501 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -246,6 +246,7 @@ void NodeList::processICEPingPacket(QSharedPointer message) { void NodeList::reset(bool skipDomainHandlerReset) { if (thread() != QThread::currentThread()) { + QMetaObject::invokeMethod(this, "reset", Q_ARG(bool, skipDomainHandlerReset)); return; } @@ -291,16 +292,30 @@ void NodeList::addSetOfNodeTypesToNodeInterestSet(const NodeSet& setOfNodeTypes) void NodeList::sendDomainServerCheckIn() { + static bool foo = false; + + qWarning() << "Send Domain Server Checkin"; + if (!_sendDomainServerCheckInEnabled) { qCDebug(networking) << "Refusing to send a domain-server check in while it is disabled."; return; } - if (thread() != QThread::currentThread()) { + _globalPostedEvents = getGlobalPostedEventCount(); + + if (false && thread() != QThread::currentThread()) { + qWarning() << "Transition threads on send domain server checkin"; QMetaObject::invokeMethod(this, "sendDomainServerCheckIn", Qt::QueuedConnection); + + if (foo) { + qWarning() << "swapping threads before previous call completed"; + } + + foo = true; return; } + foo = false; if (_isShuttingDown) { qCDebug(networking) << "Refusing to send a domain-server check in while shutting down."; return; @@ -433,10 +448,17 @@ void NodeList::sendDomainServerCheckIn() { checkinCount = std::min(checkinCount, MAX_CHECKINS_TOGETHER); for (int i = 1; i < checkinCount; ++i) { auto packetCopy = domainPacket->createCopy(*domainPacket); + qWarning() << "Domain List/Connect"; sendPacket(std::move(packetCopy), _domainHandler.getSockAddr()); } + qWarning() << "Domain List/Connect"; sendPacket(std::move(domainPacket), _domainHandler.getSockAddr()); + } else if (_domainHandler.getIP().isNull()) { + qWarning() << "Domain Handler IP Is Null"; + } + else { + qWarning() << "Checkin packet timed out."; } } diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index e135bc937d..71b17696d3 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -100,6 +100,8 @@ public: virtual Node::LocalID getDomainLocalID() const override { return _domainHandler.getLocalID(); } virtual HifiSockAddr getDomainSockAddr() const override { return _domainHandler.getSockAddr(); } + unsigned getGlobalPostedEventCount() { return _globalPostedEvents; } + public slots: void reset(bool skipDomainHandlerReset = false); void resetFromDomainHandler() { reset(true); } @@ -171,6 +173,7 @@ private: bool _isShuttingDown { false }; QTimer _keepAlivePingTimer; bool _requestsDomainListData { false }; + unsigned _globalPostedEvents { 0 }; bool _sendDomainServerCheckInEnabled { true }; diff --git a/libraries/networking/src/ThreadedAssignment.cpp b/libraries/networking/src/ThreadedAssignment.cpp index bdba47f0ed..c14bbf8d2c 100644 --- a/libraries/networking/src/ThreadedAssignment.cpp +++ b/libraries/networking/src/ThreadedAssignment.cpp @@ -37,6 +37,7 @@ ThreadedAssignment::ThreadedAssignment(ReceivedMessage& message) : // if the NL tells us we got a DS response, clear our member variable of queued check-ins auto nodeList = DependencyManager::get(); connect(nodeList.data(), &NodeList::receivedDomainServerList, this, &ThreadedAssignment::clearQueuedCheckIns); + timestamp = p_high_resolution_clock::now(); } void ThreadedAssignment::setFinished(bool isFinished) { @@ -102,6 +103,14 @@ void ThreadedAssignment::addPacketStatsAndSendStatsPacket(QJsonObject statsObjec statsObject["io_stats"] = ioStats; + QJsonObject assignmentStats; + assignmentStats["numQueuedCheckIns"] = _numQueuedCheckIns; + + assignmentStats["globalPostedEventCount"] = (long long)nodeList->getGlobalPostedEventCount(); + + assignmentStats["domainReportDuration"] = domainServerReportPerSec.count(); + statsObject["assignmentStats"] = assignmentStats; + nodeList->sendStatsToDomainServer(statsObject); } @@ -113,13 +122,16 @@ void ThreadedAssignment::sendStatsPacket() { void ThreadedAssignment::checkInWithDomainServerOrExit() { // verify that the number of queued check-ins is not >= our max // the number of queued check-ins is cleared anytime we get a response from the domain-server + + timestamp = p_high_resolution_clock::now(); + if (_numQueuedCheckIns >= MAX_SILENT_DOMAIN_SERVER_CHECK_INS) { qCDebug(networking) << "At least" << MAX_SILENT_DOMAIN_SERVER_CHECK_INS << "have been queued without a response from domain-server" << "Stopping the current assignment"; stop(); } else { auto nodeList = DependencyManager::get(); - QMetaObject::invokeMethod(nodeList.data(), "sendDomainServerCheckIn"); + QMetaObject::invokeMethod(nodeList.data(), "sendDomainServerCheckIn", Qt::DirectConnection); // increase the number of queued check ins _numQueuedCheckIns++; diff --git a/libraries/networking/src/ThreadedAssignment.h b/libraries/networking/src/ThreadedAssignment.h index e76533b2a1..c30ef4169f 100644 --- a/libraries/networking/src/ThreadedAssignment.h +++ b/libraries/networking/src/ThreadedAssignment.h @@ -48,6 +48,8 @@ protected: QTimer _domainServerTimer; QTimer _statsTimer; int _numQueuedCheckIns { 0 }; + p_high_resolution_clock::time_point timestamp; + std::chrono::milliseconds domainServerReportPerSec{ 0 }; protected slots: void domainSettingsRequestFailed(); From b5f590063364a41f24662929140c7e2d02e04ed0 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Tue, 12 Feb 2019 11:49:30 -0800 Subject: [PATCH 234/474] Replace animation scale with scale from avatar default pose This allows avatars to have scale on their joints without being clobbered by animations. Renamed variables for easier maintenance. Also small optimization when no ikNode is present. --- libraries/animation/src/AnimClip.cpp | 83 ++++++++++++++-------------- libraries/animation/src/Rig.cpp | 18 +++--- 2 files changed, 51 insertions(+), 50 deletions(-) diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index 1adc04ee1b..a35e0237d0 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -99,65 +99,64 @@ void AnimClip::copyFromNetworkAnim() { assert(_networkAnim && _networkAnim->isLoaded() && _skeleton); _anim.clear(); - // build a mapping from animation joint indices to skeleton joint indices. + auto avatarSkeleton = getSkeleton(); + + // build a mapping from animation joint indices to avatar joint indices. // by matching joints with the same name. - const HFMModel& hfmModel = _networkAnim->getHFMModel(); - AnimSkeleton animSkeleton(hfmModel); - const auto animJointCount = animSkeleton.getNumJoints(); - const auto skeletonJointCount = _skeleton->getNumJoints(); - std::vector jointMap; - jointMap.reserve(animJointCount); - for (int i = 0; i < animJointCount; i++) { - int skeletonJoint = _skeleton->nameToJointIndex(animSkeleton.getJointName(i)); - jointMap.push_back(skeletonJoint); + const HFMModel& animModel = _networkAnim->getHFMModel(); + AnimSkeleton animSkeleton(animModel); + const int animJointCount = animSkeleton.getNumJoints(); + const int avatarJointCount = avatarSkeleton->getNumJoints(); + std::vector animToAvatarJointIndexMap; + animToAvatarJointIndexMap.reserve(animJointCount); + for (int animJointIndex = 0; animJointIndex < animJointCount; animJointIndex++) { + QString animJointName = animSkeleton.getJointName(animJointIndex); + int avatarJointIndex = avatarSkeleton->nameToJointIndex(animJointName); + animToAvatarJointIndexMap.push_back(avatarJointIndex); } - const int frameCount = hfmModel.animationFrames.size(); - _anim.resize(frameCount); + const int animFrameCount = animModel.animationFrames.size(); + _anim.resize(animFrameCount); - for (int frame = 0; frame < frameCount; frame++) { + for (int frame = 0; frame < animFrameCount; frame++) { - const HFMAnimationFrame& hfmAnimFrame = hfmModel.animationFrames[frame]; + const HFMAnimationFrame& animFrame = animModel.animationFrames[frame]; // init all joints in animation to default pose - // this will give us a resonable result for bones in the model skeleton but not in the animation. - _anim[frame].reserve(skeletonJointCount); - for (int skeletonJoint = 0; skeletonJoint < skeletonJointCount; skeletonJoint++) { - _anim[frame].push_back(_skeleton->getRelativeDefaultPose(skeletonJoint)); + // this will give us a resonable result for bones in the avatar skeleton but not in the animation. + _anim[frame].reserve(avatarJointCount); + for (int avatarJointIndex = 0; avatarJointIndex < avatarJointCount; avatarJointIndex++) { + _anim[frame].push_back(avatarSkeleton->getRelativeDefaultPose(avatarJointIndex)); } - for (int animJoint = 0; animJoint < animJointCount; animJoint++) { - int skeletonJoint = jointMap[animJoint]; + for (int animJointIndex = 0; animJointIndex < animJointCount; animJointIndex++) { + int avatarJointIndex = animToAvatarJointIndexMap[animJointIndex]; - const glm::vec3& hfmAnimTrans = hfmAnimFrame.translations[animJoint]; - const glm::quat& hfmAnimRot = hfmAnimFrame.rotations[animJoint]; + // skip joints that are in the animation but not in the avatar. + if (avatarJointIndex >= 0 && avatarJointIndex < avatarJointCount) { - // skip joints that are in the animation but not in the skeleton. - if (skeletonJoint >= 0 && skeletonJoint < skeletonJointCount) { + const glm::vec3& animTrans = animFrame.translations[animJointIndex]; + const glm::quat& animRot = animFrame.rotations[animJointIndex]; - AnimPose preRot, postRot; - preRot = animSkeleton.getPreRotationPose(animJoint); - postRot = animSkeleton.getPostRotationPose(animJoint); + const AnimPose& animPreRotPose = animSkeleton.getPreRotationPose(animJointIndex); + AnimPose animPostRotPose = animSkeleton.getPostRotationPose(animJointIndex); + AnimPose animRotPose(glm::vec3(1.0f), animRot, glm::vec3()); - // cancel out scale - preRot.scale() = glm::vec3(1.0f); - postRot.scale() = glm::vec3(1.0f); + // adjust anim scale to equal the scale from the avatar joint. + // we do not support animated scale. + const AnimPose& avatarDefaultPose = avatarSkeleton->getRelativeDefaultPose(avatarJointIndex); + animPostRotPose.scale() = avatarDefaultPose.scale(); - AnimPose rot(glm::vec3(1.0f), hfmAnimRot, glm::vec3()); - - // adjust translation offsets, so large translation animatons on the reference skeleton - // will be adjusted when played on a skeleton with short limbs. - const glm::vec3& hfmZeroTrans = hfmModel.animationFrames[0].translations[animJoint]; - const AnimPose& relDefaultPose = _skeleton->getRelativeDefaultPose(skeletonJoint); + // retarget translation from animation to avatar + const glm::vec3& animZeroTrans = animModel.animationFrames[0].translations[animJointIndex]; float boneLengthScale = 1.0f; const float EPSILON = 0.0001f; - if (fabsf(glm::length(hfmZeroTrans)) > EPSILON) { - boneLengthScale = glm::length(relDefaultPose.trans()) / glm::length(hfmZeroTrans); + if (fabsf(glm::length(animZeroTrans)) > EPSILON) { + boneLengthScale = glm::length(avatarDefaultPose.trans()) / glm::length(animZeroTrans); } + AnimPose animTransPose = AnimPose(glm::vec3(1.0f), glm::quat(), avatarDefaultPose.trans() + boneLengthScale * (animTrans - animZeroTrans)); - AnimPose trans = AnimPose(glm::vec3(1.0f), glm::quat(), relDefaultPose.trans() + boneLengthScale * (hfmAnimTrans - hfmZeroTrans)); - - _anim[frame][skeletonJoint] = trans * preRot * rot * postRot; + _anim[frame][avatarJointIndex] = animTransPose * animPreRotPose * animRotPose * animPostRotPose; } } } @@ -165,7 +164,7 @@ void AnimClip::copyFromNetworkAnim() { // mirrorAnim will be re-built on demand, if needed. _mirrorAnim.clear(); - _poses.resize(skeletonJointCount); + _poses.resize(avatarJointCount); } void AnimClip::buildMirrorAnim() { diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 25f154e9fd..0413210776 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1878,13 +1878,15 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo }; std::shared_ptr ikNode = getAnimInverseKinematicsNode(); - for (int i = 0; i < (int)NumSecondaryControllerTypes; i++) { - int index = indexOfJoint(secondaryControllerJointNames[i]); - if ((index >= 0) && (ikNode)) { - if (params.secondaryControllerFlags[i] & (uint8_t)ControllerFlags::Enabled) { - ikNode->setSecondaryTargetInRigFrame(index, params.secondaryControllerPoses[i]); - } else { - ikNode->clearSecondaryTarget(index); + if (ikNode) { + for (int i = 0; i < (int)NumSecondaryControllerTypes; i++) { + int index = indexOfJoint(secondaryControllerJointNames[i]); + if (index >= 0) { + if (params.secondaryControllerFlags[i] & (uint8_t)ControllerFlags::Enabled) { + ikNode->setSecondaryTargetInRigFrame(index, params.secondaryControllerPoses[i]); + } else { + ikNode->clearSecondaryTarget(index); + } } } } @@ -2177,4 +2179,4 @@ void Rig::initFlow(bool isActive) { _internalFlow.cleanUp(); _networkFlow.cleanUp(); } -} \ No newline at end of file +} From 29ec5486f6f49d288692eb2c4e140d4ce1f49436 Mon Sep 17 00:00:00 2001 From: amer cerkic Date: Wed, 27 Feb 2019 15:31:23 -0800 Subject: [PATCH 235/474] manual pushing args to the app. Removed some debugging printouts --- .../questInterface/InterfaceActivity.java | 8 -------- .../io/highfidelity/oculus/OculusMobileActivity.java | 11 +++-------- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/android/apps/questInterface/src/main/java/io/highfidelity/questInterface/InterfaceActivity.java b/android/apps/questInterface/src/main/java/io/highfidelity/questInterface/InterfaceActivity.java index 119b009e6a..cdc48dfa37 100644 --- a/android/apps/questInterface/src/main/java/io/highfidelity/questInterface/InterfaceActivity.java +++ b/android/apps/questInterface/src/main/java/io/highfidelity/questInterface/InterfaceActivity.java @@ -14,14 +14,6 @@ public class InterfaceActivity extends OculusMobileActivity { @Override public void onCreate(Bundle savedInstanceState) { - if(this.getIntent().hasExtra("applicationArguments")){ - System.out.println("QQQ_ InterfaceActivity: args EXISTS"); - System.out.println("QQQ_ "+ this.getIntent().getStringExtra("applicationArguments")); - } - else{ - System.out.println("QQQ_ InterfaceActivity: NO argmument"); - } - HifiUtils.upackAssets(getAssets(), getCacheDir().getAbsolutePath()); super.onCreate(savedInstanceState); } diff --git a/android/libraries/oculus/src/main/java/io/highfidelity/oculus/OculusMobileActivity.java b/android/libraries/oculus/src/main/java/io/highfidelity/oculus/OculusMobileActivity.java index 095236ecc3..75faf1e0dd 100644 --- a/android/libraries/oculus/src/main/java/io/highfidelity/oculus/OculusMobileActivity.java +++ b/android/libraries/oculus/src/main/java/io/highfidelity/oculus/OculusMobileActivity.java @@ -39,19 +39,14 @@ public class OculusMobileActivity extends QtActivity implements SurfaceHolder.Ca private SurfaceHolder mSurfaceHolder; public void onCreate(Bundle savedInstanceState) { - if(this.getIntent().hasExtra("applicationArguments")){ - System.out.println("QQQ_ OculusMobileActivity has arguments"); - System.out.println("QQQ_ "+ this.getIntent().getStringExtra("applicationArguments")); + if(getIntent().hasExtra("applicationArguments")){ + super.APPLICATION_PARAMETERS=getIntent().getStringExtra("applicationArguments"); } - else{ - System.out.println("QQQ_ OculusMobileActivity has NO arguments"); - } - - super.onCreate(savedInstanceState); + Log.w(TAG, "QQQ onCreate"); // Create a native surface for VR rendering (Qt GL surfaces are not suitable // because of the lack of fine control over the surface callbacks) From a425becc8a624ef215a94142b4cdf132673524e5 Mon Sep 17 00:00:00 2001 From: amer cerkic Date: Wed, 27 Feb 2019 15:35:33 -0800 Subject: [PATCH 236/474] clean up of debugging --- android/apps/questInterface/src/main/AndroidManifest.xml | 2 +- .../highfidelity/questInterface/PermissionsChecker.java | 6 ++++-- .../java/io/highfidelity/oculus/OculusMobileActivity.java | 1 - interface/src/Application.cpp | 8 ++++---- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/android/apps/questInterface/src/main/AndroidManifest.xml b/android/apps/questInterface/src/main/AndroidManifest.xml index 0f3616612a..a5de47bdce 100644 --- a/android/apps/questInterface/src/main/AndroidManifest.xml +++ b/android/apps/questInterface/src/main/AndroidManifest.xml @@ -28,7 +28,7 @@ android:excludeFromRecents="true"> - + ()->handleLookupString(QUEST_DEV); + const QString QUEST_DEV = "hifi://quest-dev"; + DependencyManager::get()->handleLookupString(QUEST_DEV); #endif } @@ -3669,8 +3669,8 @@ void Application::handleSandboxStatus(QNetworkReply* reply) { // If this is a first run we short-circuit the address passed in if (_firstRun.get()) { #if !defined(Q_OS_ANDROID) - // DependencyManager::get()->goToEntry(); - // sentTo = SENT_TO_ENTRY; + DependencyManager::get()->goToEntry(); + sentTo = SENT_TO_ENTRY; #endif _firstRun.set(false); From 95628dfc111e771842e60cd43ddf08ed70019005 Mon Sep 17 00:00:00 2001 From: amer cerkic Date: Wed, 27 Feb 2019 15:36:49 -0800 Subject: [PATCH 237/474] removing unused imports --- .../io/highfidelity/questInterface/InterfaceActivity.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/android/apps/questInterface/src/main/java/io/highfidelity/questInterface/InterfaceActivity.java b/android/apps/questInterface/src/main/java/io/highfidelity/questInterface/InterfaceActivity.java index cdc48dfa37..df05576ea9 100644 --- a/android/apps/questInterface/src/main/java/io/highfidelity/questInterface/InterfaceActivity.java +++ b/android/apps/questInterface/src/main/java/io/highfidelity/questInterface/InterfaceActivity.java @@ -1,16 +1,10 @@ package io.highfidelity.questInterface; -import android.content.Intent; import android.os.Bundle; -import android.text.TextUtils; - import io.highfidelity.oculus.OculusMobileActivity; import io.highfidelity.utils.HifiUtils; public class InterfaceActivity extends OculusMobileActivity { - public static final String DOMAIN_URL = "url"; - private static final String TAG = "Interface"; - public static final String EXTRA_ARGS = "args"; @Override public void onCreate(Bundle savedInstanceState) { From fede8d052516a90ac183e4d448eeae7841ff9afe Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Wed, 27 Feb 2019 15:37:11 -0800 Subject: [PATCH 238/474] Minor clean-up --- .../src/avatars/AvatarMixerClientData.cpp | 6 ++-- .../src/avatars/AvatarMixerSlave.cpp | 35 ++++--------------- 2 files changed, 9 insertions(+), 32 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index a63a76829b..e24d48a9ed 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -145,9 +145,9 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message, const SlaveShared FindPriorityZone findPriorityZone { newPosition, false } ; entityTree.recurseTreeWithOperation(&FindPriorityZone::operation, &findPriorityZone); _avatar->setPriorityAvatar(findPriorityZone.isInPriorityZone); - if (findPriorityZone.isInPriorityZone) { - qCWarning(avatars) << "Avatar" << _avatar->getSessionDisplayName() << "in hero zone"; - } + //if (findPriorityZone.isInPriorityZone) { + // qCWarning(avatars) << "Avatar" << _avatar->getSessionDisplayName() << "in hero zone"; + //} #endif } diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 7627f6a2e5..a38553ddeb 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -307,25 +307,6 @@ namespace { } // Close anonymous namespace. -//// Specialize computePriority() for avatars: -//namespace PrioritySortUtil { -//template<> float PriorityQueue::computePriority(const SortableAvatar& thing) const { -// static constexpr float AVATAR_HERO_BONUS { 25.0f }; // Higher than any normal priority. -// -// float priority = std::numeric_limits::min(); -// -// for (const auto& view : _views) { -// priority = std::max(priority, computePriority(view, thing)); -// } -// -// if (thing.getAvatar()->getPriorityAvatar()) { -// priority += AVATAR_HERO_BONUS; -// } -// -// return priority; -//} -//} - void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) { const float AVATAR_HERO_FRACTION { 0.4f }; const Node* destinationNode = node.data(); @@ -392,18 +373,14 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) using AvatarPriorityQueue = PrioritySortUtil::PriorityQueue; // Keep two independent queues, one for heroes and one for the riff-raff. enum PriorityVariants { kHero, kNonhero }; - AvatarPriorityQueue avatarPriorityQueues[2] = { {cameraViews, - AvatarData::_avatarSortCoefficientSize, AvatarData::_avatarSortCoefficientCenter, AvatarData::_avatarSortCoefficientAge}, - {cameraViews, - AvatarData::_avatarSortCoefficientSize, AvatarData::_avatarSortCoefficientCenter, AvatarData::_avatarSortCoefficientAge} + AvatarPriorityQueue avatarPriorityQueues[2] = + { + {cameraViews, AvatarData::_avatarSortCoefficientSize, + AvatarData::_avatarSortCoefficientCenter, AvatarData::_avatarSortCoefficientAge}, + {cameraViews, AvatarData::_avatarSortCoefficientSize, + AvatarData::_avatarSortCoefficientCenter, AvatarData::_avatarSortCoefficientAge} }; - //PrioritySortUtil::PriorityQueue sortedAvatars(cameraViews, - // AvatarData::_avatarSortCoefficientSize, - // AvatarData::_avatarSortCoefficientCenter, - // AvatarData::_avatarSortCoefficientAge); - //sortedAvatars.reserve(_end - _begin); - avatarPriorityQueues[kNonhero].reserve(_end - _begin); for (auto listedNode = _begin; listedNode != _end; ++listedNode) { From 59b9831c1d32beede3bd467111d9f58e3cf8b582 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Wed, 27 Feb 2019 15:56:01 -0800 Subject: [PATCH 239/474] Revert max check-ins to 4 --- libraries/networking/src/DomainHandler.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 219af3338c..94b1c39a45 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -35,7 +35,7 @@ const unsigned short DEFAULT_DOMAIN_SERVER_DTLS_PORT = 40103; const quint16 DOMAIN_SERVER_HTTP_PORT = 40100; const quint16 DOMAIN_SERVER_HTTPS_PORT = 40101; -const int MAX_SILENT_DOMAIN_SERVER_CHECK_INS = 10; // XXX +const int MAX_SILENT_DOMAIN_SERVER_CHECK_INS = 4; class DomainHandler : public QObject { Q_OBJECT From 03326cd471fbbb5e45db9f515e7b6d0bf98693a6 Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Wed, 27 Feb 2019 15:58:49 -0800 Subject: [PATCH 240/474] Build fix --- libraries/networking/src/NodeList.h | 4 ++-- libraries/networking/src/ThreadedAssignment.cpp | 3 +-- libraries/networking/src/ThreadedAssignment.h | 1 - 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index 71b17696d3..fb14dae2d1 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -100,7 +100,7 @@ public: virtual Node::LocalID getDomainLocalID() const override { return _domainHandler.getLocalID(); } virtual HifiSockAddr getDomainSockAddr() const override { return _domainHandler.getSockAddr(); } - unsigned getGlobalPostedEventCount() { return _globalPostedEvents; } + int getGlobalPostedEventCount() { return _globalPostedEvents; } public slots: void reset(bool skipDomainHandlerReset = false); @@ -173,7 +173,7 @@ private: bool _isShuttingDown { false }; QTimer _keepAlivePingTimer; bool _requestsDomainListData { false }; - unsigned _globalPostedEvents { 0 }; + int _globalPostedEvents { 0 }; bool _sendDomainServerCheckInEnabled { true }; diff --git a/libraries/networking/src/ThreadedAssignment.cpp b/libraries/networking/src/ThreadedAssignment.cpp index c14bbf8d2c..441976a962 100644 --- a/libraries/networking/src/ThreadedAssignment.cpp +++ b/libraries/networking/src/ThreadedAssignment.cpp @@ -106,9 +106,8 @@ void ThreadedAssignment::addPacketStatsAndSendStatsPacket(QJsonObject statsObjec QJsonObject assignmentStats; assignmentStats["numQueuedCheckIns"] = _numQueuedCheckIns; - assignmentStats["globalPostedEventCount"] = (long long)nodeList->getGlobalPostedEventCount(); + assignmentStats["globalPostedEventCount"] = nodeList->getGlobalPostedEventCount(); - assignmentStats["domainReportDuration"] = domainServerReportPerSec.count(); statsObject["assignmentStats"] = assignmentStats; nodeList->sendStatsToDomainServer(statsObject); diff --git a/libraries/networking/src/ThreadedAssignment.h b/libraries/networking/src/ThreadedAssignment.h index c30ef4169f..8fade4dd89 100644 --- a/libraries/networking/src/ThreadedAssignment.h +++ b/libraries/networking/src/ThreadedAssignment.h @@ -49,7 +49,6 @@ protected: QTimer _statsTimer; int _numQueuedCheckIns { 0 }; p_high_resolution_clock::time_point timestamp; - std::chrono::milliseconds domainServerReportPerSec{ 0 }; protected slots: void domainSettingsRequestFailed(); From 17c0e40347377b29b9b2effd6a047c0e8ba4d442 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Wed, 27 Feb 2019 16:42:36 -0800 Subject: [PATCH 241/474] Add ability to filter on zone-type with JSON filter --- assignment-client/src/avatars/AvatarMixer.cpp | 2 +- libraries/entities/src/EntityItem.cpp | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 3064a5cd14..e0f7ae59ee 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -958,7 +958,7 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) { } void AvatarMixer::setupEntityQuery() { - static char queryJsonString[] = R"({"avatarPriority": true, "serverScripts": "+"})"; + static char queryJsonString[] = R"({"avatarPriority": true, "type": "Zone"})"; _entityViewer.init(); DependencyManager::registerInheritance(); diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 3ecbdf497a..7ce8ae2ab9 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -2656,6 +2656,7 @@ bool EntityItem::matchesJSONFilters(const QJsonObject& jsonFilters) const { // which means that we only handle a filtered query asking for entities where the serverScripts property is non-default static const QString SERVER_SCRIPTS_PROPERTY = "serverScripts"; + static const QString ENTITY_TYPE_PROPERTY = "type"; foreach(const auto& property, jsonFilters.keys()) { if (property == SERVER_SCRIPTS_PROPERTY && jsonFilters[property] == EntityQueryFilterSymbol::NonDefault) { @@ -2665,6 +2666,8 @@ bool EntityItem::matchesJSONFilters(const QJsonObject& jsonFilters) const { } else { return false; } + } else if (property == ENTITY_TYPE_PROPERTY) { + return (jsonFilters[property] == EntityTypes::getEntityTypeName(getType()) ); } } From db8bd661ce2acf284a1811ea66c12f02c470a009 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Wed, 27 Feb 2019 16:50:07 -0800 Subject: [PATCH 242/474] Max domain check-ins was 5, not 4, dammit --- libraries/networking/src/DomainHandler.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 94b1c39a45..620ffb9641 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -35,7 +35,7 @@ const unsigned short DEFAULT_DOMAIN_SERVER_DTLS_PORT = 40103; const quint16 DOMAIN_SERVER_HTTP_PORT = 40100; const quint16 DOMAIN_SERVER_HTTPS_PORT = 40101; -const int MAX_SILENT_DOMAIN_SERVER_CHECK_INS = 4; +const int MAX_SILENT_DOMAIN_SERVER_CHECK_INS = 5; class DomainHandler : public QObject { Q_OBJECT From 041a561dbcaa7280fd2c14ba2051b2add756ca6f Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Wed, 27 Feb 2019 17:27:25 -0800 Subject: [PATCH 243/474] Handcraft the JSON filter-object rather than parse a string --- assignment-client/src/avatars/AvatarMixer.cpp | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index e0f7ae59ee..67bc9b4cf7 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -958,21 +958,17 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) { } void AvatarMixer::setupEntityQuery() { - static char queryJsonString[] = R"({"avatarPriority": true, "type": "Zone"})"; - _entityViewer.init(); DependencyManager::registerInheritance(); DependencyManager::set(_entityViewer.getTree()); _slaveSharedData.entityTree = _entityViewer.getTree(); - QJsonParseError jsonParseError; - const QJsonDocument priorityZoneQuery(QJsonDocument::fromJson(queryJsonString, &jsonParseError)); - if (jsonParseError.error != QJsonParseError::NoError) { - qCDebug(avatars) << "Error parsing:" << queryJsonString << " - " << jsonParseError.errorString(); - return; - } - _entityViewer.getOctreeQuery().setJSONParameters(priorityZoneQuery.object()); + // ES query: {"avatarPriority": true, "type": "Zone"} + QJsonObject priorityZoneQuery; + priorityZoneQuery["avatarPriority"] = true; + priorityZoneQuery["type"] = "Zone"; + _entityViewer.getOctreeQuery().setJSONParameters(priorityZoneQuery); } void AvatarMixer::handleOctreePacket(QSharedPointer message, SharedNodePointer senderNode) { From 717c12fe1696a41b01b123b62e38822f1eb67a4d Mon Sep 17 00:00:00 2001 From: Clement Date: Wed, 27 Feb 2019 18:22:49 -0800 Subject: [PATCH 244/474] Add new connection rate limitting Limit rate of connection for new Agents --- assignment-client/src/avatars/AvatarMixer.cpp | 8 +++ .../resources/describe-settings.json | 8 +++ domain-server/src/DomainServer.cpp | 11 ++- libraries/networking/src/NodeList.cpp | 68 ++++++++++++++----- libraries/networking/src/NodeList.h | 24 +++++++ 5 files changed, 96 insertions(+), 23 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 500772c1b5..7c21daefc3 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -904,6 +904,14 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) { qCDebug(avatars) << "Avatar mixer will automatically determine number of threads to use. Using:" << _slavePool.numThreads() << "threads."; } + { + const QString CONNECTION_RATE = "connection_rate"; + auto nodeList = DependencyManager::get(); + auto defaultConnectionRate = nodeList->getMaxConnectionRate(); + int connectionRate = avatarMixerGroupObject[CONNECTION_RATE].toInt(defaultConnectionRate); + nodeList->setMaxConnectionRate(connectionRate); + } + const QString AVATARS_SETTINGS_KEY = "avatars"; static const QString MIN_HEIGHT_OPTION = "min_avatar_height"; diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 49023c9af8..140c7d6c17 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -1302,6 +1302,14 @@ "placeholder": "1", "default": "1", "advanced": true + }, + { + "name": "connection_rate", + "label": "Connection Rate", + "help": "Number of new agents that can connect to the mixer every second", + "placeholder": "50", + "default": "50", + "advanced": true } ] }, diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 258038b8f1..8ba0cbc115 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1243,12 +1243,11 @@ void DomainServer::broadcastNewNode(const SharedNodePointer& addedNode) { limitedNodeList->eachMatchingNode( [this, addedNode](const SharedNodePointer& node)->bool { - if (node->getLinkedData() && node->getActiveSocket() && node != addedNode) { - // is the added Node in this node's interest list? - return isInInterestSet(node, addedNode); - } else { - return false; - } + // is the added Node in this node's interest list? + return node->getLinkedData() + && node->getActiveSocket() + && node != addedNode + && isInInterestSet(node, addedNode); }, [this, &addNodePacket, connectionSecretIndex, addedNode, limitedNodeListWeak](const SharedNodePointer& node) { // send off this packet to the node diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 5e8909db2b..1de710e8ca 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -37,7 +37,9 @@ #include "SharedUtil.h" #include -const int KEEPALIVE_PING_INTERVAL_MS = 1000; +using namespace std::chrono_literals; +static const std::chrono::milliseconds CONNECTION_RATE_INTERVAL = 1s; +static const std::chrono::milliseconds KEEPALIVE_PING_INTERVAL = 1s; NodeList::NodeList(char newOwnerType, int socketListenPort, int dtlsListenPort) : LimitedNodeList(socketListenPort, dtlsListenPort), @@ -104,7 +106,7 @@ NodeList::NodeList(char newOwnerType, int socketListenPort, int dtlsListenPort) connect(this, &LimitedNodeList::nodeActivated, this, &NodeList::maybeSendIgnoreSetToNode); // setup our timer to send keepalive pings (it's started and stopped on domain connect/disconnect) - _keepAlivePingTimer.setInterval(KEEPALIVE_PING_INTERVAL_MS); // 1s, Qt::CoarseTimer acceptable + _keepAlivePingTimer.setInterval(KEEPALIVE_PING_INTERVAL); // 1s, Qt::CoarseTimer acceptable connect(&_keepAlivePingTimer, &QTimer::timeout, this, &NodeList::sendKeepAlivePings); connect(&_domainHandler, SIGNAL(connectedToDomain(QUrl)), &_keepAlivePingTimer, SLOT(start())); connect(&_domainHandler, &DomainHandler::disconnectedFromDomain, &_keepAlivePingTimer, &QTimer::stop); @@ -116,6 +118,11 @@ NodeList::NodeList(char newOwnerType, int socketListenPort, int dtlsListenPort) // we definitely want STUN to update our public socket, so call the LNL to kick that off startSTUNPublicSocketUpdate(); + // check for local socket updates every so often + QTimer* delayedAddsFlushTimer = new QTimer(this); + connect(delayedAddsFlushTimer, &QTimer::timeout, this, &NodeList::processDelayedAdds); + delayedAddsFlushTimer->start(CONNECTION_RATE_INTERVAL); + auto& packetReceiver = getPacketReceiver(); packetReceiver.registerListener(PacketType::DomainList, this, "processDomainServerList"); packetReceiver.registerListener(PacketType::Ping, this, "processPingPacket"); @@ -200,7 +207,6 @@ void NodeList::timePingReply(ReceivedMessage& message, const SharedNodePointer& } void NodeList::processPingPacket(QSharedPointer message, SharedNodePointer sendingNode) { - // send back a reply auto replyPacket = constructPingReplyPacket(*message); const HifiSockAddr& senderSockAddr = message->getSenderSockAddr(); @@ -711,27 +717,38 @@ void NodeList::processDomainServerRemovedNode(QSharedPointer me } void NodeList::parseNodeFromPacketStream(QDataStream& packetStream) { - // setup variables to read into from QDataStream - qint8 nodeType; - QUuid nodeUUID, connectionSecretUUID; - HifiSockAddr nodePublicSocket, nodeLocalSocket; - NodePermissions permissions; - bool isReplicated; - Node::LocalID sessionLocalID; + NewNodeInfo info; - packetStream >> nodeType >> nodeUUID >> nodePublicSocket >> nodeLocalSocket >> permissions - >> isReplicated >> sessionLocalID; + packetStream >> info.type + >> info.uuid + >> info.publicSocket + >> info.localSocket + >> info.permissions + >> info.isReplicated + >> info.sessionLocalID + >> info.connectionSecretUUID; // if the public socket address is 0 then it's reachable at the same IP // as the domain server - if (nodePublicSocket.getAddress().isNull()) { - nodePublicSocket.setAddress(_domainHandler.getIP()); + if (info.publicSocket.getAddress().isNull()) { + info.publicSocket.setAddress(_domainHandler.getIP()); } - packetStream >> connectionSecretUUID; + // Throttle connection of new agents. + if (info.type != NodeType::Agent + || _nodesAddedInCurrentTimeSlice < _maxConnectionRate) { - SharedNodePointer node = addOrUpdateNode(nodeUUID, nodeType, nodePublicSocket, nodeLocalSocket, - sessionLocalID, isReplicated, false, connectionSecretUUID, permissions); + addNewNode(info); + ++_nodesAddedInCurrentTimeSlice; + } else { + delayNodeAdd(info); + } +} + +void NodeList::addNewNode(NewNodeInfo info) { + SharedNodePointer node = addOrUpdateNode(info.uuid, info.type, info.publicSocket, info.localSocket, + info.sessionLocalID, info.isReplicated, false, + info.connectionSecretUUID, info.permissions); // nodes that are downstream or upstream of our own type are kept alive when we hear about them from the domain server // and always have their public socket as their active socket @@ -739,6 +756,23 @@ void NodeList::parseNodeFromPacketStream(QDataStream& packetStream) { node->setLastHeardMicrostamp(usecTimestampNow()); node->activatePublicSocket(); } +}; + +void NodeList::delayNodeAdd(NewNodeInfo info) { + _delayedNodeAdds.push_back(info); +}; + +void NodeList::processDelayedAdds() { + _nodesAddedInCurrentTimeSlice = 0; + + auto nodesToAdd = glm::min(_delayedNodeAdds.size(), _maxConnectionRate); + auto firstNodeToAdd = _delayedNodeAdds.begin(); + auto lastNodeToAdd = firstNodeToAdd + nodesToAdd; + + for (auto it = firstNodeToAdd; it != lastNodeToAdd; ++it) { + addNewNode(*it); + } + _delayedNodeAdds.erase(firstNodeToAdd, lastNodeToAdd); } void NodeList::sendAssignment(Assignment& assignment) { diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index e135bc937d..f913b7c3b9 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -38,6 +38,8 @@ const quint64 DOMAIN_SERVER_CHECK_IN_MSECS = 1 * 1000; +static const size_t DEFAULT_MAX_CONNECTION_RATE { 50 }; + using PacketOrPacketList = std::pair, std::unique_ptr>; using NodePacketOrPacketListPair = std::pair; @@ -93,6 +95,9 @@ public: bool getSendDomainServerCheckInEnabled() { return _sendDomainServerCheckInEnabled; } void setSendDomainServerCheckInEnabled(bool enabled) { _sendDomainServerCheckInEnabled = enabled; } + size_t getMaxConnectionRate() { return _maxConnectionRate; } + void setMaxConnectionRate(size_t rate) { _maxConnectionRate = rate; } + void removeFromIgnoreMuteSets(const QUuid& nodeID); virtual bool isDomainServer() const override { return false; } @@ -146,6 +151,17 @@ private slots: void maybeSendIgnoreSetToNode(SharedNodePointer node); private: + struct NewNodeInfo { + qint8 type; + QUuid uuid; + HifiSockAddr publicSocket; + HifiSockAddr localSocket; + NodePermissions permissions; + bool isReplicated; + Node::LocalID sessionLocalID; + QUuid connectionSecretUUID; + }; + NodeList() : LimitedNodeList(INVALID_PORT, INVALID_PORT) { assert(false); } // Not implemented, needed for DependencyManager templates compile NodeList(char ownerType, int socketListenPort = INVALID_PORT, int dtlsListenPort = INVALID_PORT); NodeList(NodeList const&) = delete; // Don't implement, needed to avoid copies of singleton @@ -164,6 +180,10 @@ private: bool sockAddrBelongsToDomainOrNode(const HifiSockAddr& sockAddr); + void addNewNode(NewNodeInfo info); + void delayNodeAdd(NewNodeInfo info); + void processDelayedAdds(); + std::atomic _ownerType; NodeSet _nodeTypesOfInterest; DomainHandler _domainHandler; @@ -181,6 +201,10 @@ private: mutable QReadWriteLock _avatarGainMapLock; tbb::concurrent_unordered_map _avatarGainMap; + size_t _maxConnectionRate { DEFAULT_MAX_CONNECTION_RATE }; + size_t _nodesAddedInCurrentTimeSlice { 0 }; + std::vector _delayedNodeAdds; + void sendIgnoreRadiusStateToNode(const SharedNodePointer& destinationNode); #if defined(Q_OS_ANDROID) Setting::Handle _ignoreRadiusEnabled { "IgnoreRadiusEnabled", false }; From 80320635125577c0370c7ebae39872f40525b518 Mon Sep 17 00:00:00 2001 From: Clement Date: Wed, 27 Feb 2019 19:14:36 -0800 Subject: [PATCH 245/474] Move to LimitedNodeList --- libraries/networking/src/LimitedNodeList.cpp | 57 +++++++++++++++++++- libraries/networking/src/LimitedNodeList.h | 27 ++++++++++ libraries/networking/src/NodeList.cpp | 56 ++++--------------- libraries/networking/src/NodeList.h | 24 --------- 4 files changed, 93 insertions(+), 71 deletions(-) diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index eaa02f059e..f00614141c 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -40,6 +40,9 @@ static Setting::Handle LIMITED_NODELIST_LOCAL_PORT("LimitedNodeList.LocalPort", 0); +using namespace std::chrono_literals; +static const std::chrono::milliseconds CONNECTION_RATE_INTERVAL = 1s; + const std::set SOLO_NODE_TYPES = { NodeType::AvatarMixer, NodeType::AudioMixer, @@ -88,6 +91,11 @@ LimitedNodeList::LimitedNodeList(int socketListenPort, int dtlsListenPort) : connect(statsSampleTimer, &QTimer::timeout, this, &LimitedNodeList::sampleConnectionStats); statsSampleTimer->start(CONNECTION_STATS_SAMPLE_INTERVAL_MSECS); + // Flush delayed adds every second + QTimer* delayedAddsFlushTimer = new QTimer(this); + connect(delayedAddsFlushTimer, &QTimer::timeout, this, &NodeList::processDelayedAdds); + delayedAddsFlushTimer->start(CONNECTION_RATE_INTERVAL); + // check the local socket right now updateLocalSocket(); @@ -367,7 +375,7 @@ bool LimitedNodeList::packetSourceAndHashMatchAndTrackBandwidth(const udt::Packe return true; - } else { + } else if (!isDelayedNode(sourceID)){ HIFI_FCDEBUG(networking(), "Packet of type" << headerType << "received from unknown node with Local ID" << sourceLocalID); } @@ -736,6 +744,53 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t return newNodePointer; } +void LimitedNodeList::addNewNode(NewNodeInfo info) { + // Throttle connection of new agents. + if (info.type == NodeType::Agent && _nodesAddedInCurrentTimeSlice >= _maxConnectionRate) { + delayNodeAdd(info); + return; + } + + SharedNodePointer node = addOrUpdateNode(info.uuid, info.type, info.publicSocket, info.localSocket, + info.sessionLocalID, info.isReplicated, false, + info.connectionSecretUUID, info.permissions); + + ++_nodesAddedInCurrentTimeSlice; +} + +void LimitedNodeList::delayNodeAdd(NewNodeInfo info) { + _delayedNodeAdds.push_back(info); +} + +void LimitedNodeList::removeDelayedAdd(QUuid nodeUUID) { + auto it = std::find_if(_delayedNodeAdds.begin(), _delayedNodeAdds.end(), [&](auto info) { + return info.uuid == nodeUUID; + }); + if (it != _delayedNodeAdds.end()) { + _delayedNodeAdds.erase(it); + } +} + +bool LimitedNodeList::isDelayedNode(QUuid nodeUUID) { + auto it = std::find_if(_delayedNodeAdds.begin(), _delayedNodeAdds.end(), [&](auto info) { + return info.uuid == nodeUUID; + }); + return it != _delayedNodeAdds.end(); +} + +void LimitedNodeList::processDelayedAdds() { + _nodesAddedInCurrentTimeSlice = 0; + + auto nodesToAdd = glm::min(_delayedNodeAdds.size(), _maxConnectionRate); + auto firstNodeToAdd = _delayedNodeAdds.begin(); + auto lastNodeToAdd = firstNodeToAdd + nodesToAdd; + + for (auto it = firstNodeToAdd; it != lastNodeToAdd; ++it) { + addNewNode(*it); + } + _delayedNodeAdds.erase(firstNodeToAdd, lastNodeToAdd); +} + std::unique_ptr LimitedNodeList::constructPingPacket(const QUuid& nodeId, PingType_t pingType) { int packetSize = sizeof(PingType_t) + sizeof(quint64) + sizeof(int64_t); diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index 450fad96a9..fbd37ae065 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -51,6 +51,8 @@ const int INVALID_PORT = -1; const quint64 NODE_SILENCE_THRESHOLD_MSECS = 5 * 1000; +static const size_t DEFAULT_MAX_CONNECTION_RATE { 50 }; + extern const std::set SOLO_NODE_TYPES; const char DEFAULT_ASSIGNMENT_SERVER_HOSTNAME[] = "localhost"; @@ -316,6 +318,9 @@ public: void sendFakedHandshakeRequestToNode(SharedNodePointer node); #endif + size_t getMaxConnectionRate() { return _maxConnectionRate; } + void setMaxConnectionRate(size_t rate) { _maxConnectionRate = rate; } + int getInboundPPS() const { return _inboundPPS; } int getOutboundPPS() const { return _outboundPPS; } float getInboundKbps() const { return _inboundKbps; } @@ -367,7 +372,20 @@ protected slots: void clientConnectionToSockAddrReset(const HifiSockAddr& sockAddr); + void processDelayedAdds(); + protected: + struct NewNodeInfo { + qint8 type; + QUuid uuid; + HifiSockAddr publicSocket; + HifiSockAddr localSocket; + NodePermissions permissions; + bool isReplicated; + Node::LocalID sessionLocalID; + QUuid connectionSecretUUID; + }; + LimitedNodeList(int socketListenPort = INVALID_PORT, int dtlsListenPort = INVALID_PORT); LimitedNodeList(LimitedNodeList const&) = delete; // Don't implement, needed to avoid copies of singleton void operator=(LimitedNodeList const&) = delete; // Don't implement, needed to avoid copies of singleton @@ -390,6 +408,11 @@ protected: bool sockAddrBelongsToNode(const HifiSockAddr& sockAddr); + void addNewNode(NewNodeInfo info); + void delayNodeAdd(NewNodeInfo info); + void removeDelayedAdd(QUuid nodeUUID); + bool isDelayedNode(QUuid nodeUUID); + NodeHash _nodeHash; mutable QReadWriteLock _nodeMutex { QReadWriteLock::Recursive }; udt::Socket _nodeSocket; @@ -440,6 +463,10 @@ private: Node::LocalID _sessionLocalID { 0 }; bool _flagTimeForConnectionStep { false }; // only keep track in interface + size_t _maxConnectionRate { DEFAULT_MAX_CONNECTION_RATE }; + size_t _nodesAddedInCurrentTimeSlice { 0 }; + std::vector _delayedNodeAdds; + int _inboundPPS { 0 }; int _outboundPPS { 0 }; float _inboundKbps { 0.0f }; diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 1de710e8ca..4097ddf1a3 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -38,7 +38,6 @@ #include using namespace std::chrono_literals; -static const std::chrono::milliseconds CONNECTION_RATE_INTERVAL = 1s; static const std::chrono::milliseconds KEEPALIVE_PING_INTERVAL = 1s; NodeList::NodeList(char newOwnerType, int socketListenPort, int dtlsListenPort) : @@ -118,11 +117,6 @@ NodeList::NodeList(char newOwnerType, int socketListenPort, int dtlsListenPort) // we definitely want STUN to update our public socket, so call the LNL to kick that off startSTUNPublicSocketUpdate(); - // check for local socket updates every so often - QTimer* delayedAddsFlushTimer = new QTimer(this); - connect(delayedAddsFlushTimer, &QTimer::timeout, this, &NodeList::processDelayedAdds); - delayedAddsFlushTimer->start(CONNECTION_RATE_INTERVAL); - auto& packetReceiver = getPacketReceiver(); packetReceiver.registerListener(PacketType::DomainList, this, "processDomainServerList"); packetReceiver.registerListener(PacketType::Ping, this, "processPingPacket"); @@ -714,6 +708,7 @@ void NodeList::processDomainServerRemovedNode(QSharedPointer me QUuid nodeUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); qCDebug(networking) << "Received packet from domain-server to remove node with UUID" << uuidStringWithoutCurlyBraces(nodeUUID); killNodeWithUUID(nodeUUID); + removeDelayedAdd(nodeUUID); } void NodeList::parseNodeFromPacketStream(QDataStream& packetStream) { @@ -734,45 +729,7 @@ void NodeList::parseNodeFromPacketStream(QDataStream& packetStream) { info.publicSocket.setAddress(_domainHandler.getIP()); } - // Throttle connection of new agents. - if (info.type != NodeType::Agent - || _nodesAddedInCurrentTimeSlice < _maxConnectionRate) { - - addNewNode(info); - ++_nodesAddedInCurrentTimeSlice; - } else { - delayNodeAdd(info); - } -} - -void NodeList::addNewNode(NewNodeInfo info) { - SharedNodePointer node = addOrUpdateNode(info.uuid, info.type, info.publicSocket, info.localSocket, - info.sessionLocalID, info.isReplicated, false, - info.connectionSecretUUID, info.permissions); - - // nodes that are downstream or upstream of our own type are kept alive when we hear about them from the domain server - // and always have their public socket as their active socket - if (node->getType() == NodeType::downstreamType(_ownerType) || node->getType() == NodeType::upstreamType(_ownerType)) { - node->setLastHeardMicrostamp(usecTimestampNow()); - node->activatePublicSocket(); - } -}; - -void NodeList::delayNodeAdd(NewNodeInfo info) { - _delayedNodeAdds.push_back(info); -}; - -void NodeList::processDelayedAdds() { - _nodesAddedInCurrentTimeSlice = 0; - - auto nodesToAdd = glm::min(_delayedNodeAdds.size(), _maxConnectionRate); - auto firstNodeToAdd = _delayedNodeAdds.begin(); - auto lastNodeToAdd = firstNodeToAdd + nodesToAdd; - - for (auto it = firstNodeToAdd; it != lastNodeToAdd; ++it) { - addNewNode(*it); - } - _delayedNodeAdds.erase(firstNodeToAdd, lastNodeToAdd); + addNewNode(info); } void NodeList::sendAssignment(Assignment& assignment) { @@ -819,7 +776,6 @@ void NodeList::pingPunchForInactiveNode(const SharedNodePointer& node) { } void NodeList::startNodeHolePunch(const SharedNodePointer& node) { - // we don't hole punch to downstream servers, since it is assumed that we have a direct line to them // we also don't hole punch to relayed upstream nodes, since we do not communicate directly with them @@ -833,6 +789,14 @@ void NodeList::startNodeHolePunch(const SharedNodePointer& node) { // ping this node immediately pingPunchForInactiveNode(node); } + + // nodes that are downstream or upstream of our own type are kept alive when we hear about them from the domain server + // and always have their public socket as their active socket + if (node->getType() == NodeType::downstreamType(_ownerType) || node->getType() == NodeType::upstreamType(_ownerType)) { + node->setLastHeardMicrostamp(usecTimestampNow()); + node->activatePublicSocket(); + } + } void NodeList::handleNodePingTimeout() { diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index f913b7c3b9..e135bc937d 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -38,8 +38,6 @@ const quint64 DOMAIN_SERVER_CHECK_IN_MSECS = 1 * 1000; -static const size_t DEFAULT_MAX_CONNECTION_RATE { 50 }; - using PacketOrPacketList = std::pair, std::unique_ptr>; using NodePacketOrPacketListPair = std::pair; @@ -95,9 +93,6 @@ public: bool getSendDomainServerCheckInEnabled() { return _sendDomainServerCheckInEnabled; } void setSendDomainServerCheckInEnabled(bool enabled) { _sendDomainServerCheckInEnabled = enabled; } - size_t getMaxConnectionRate() { return _maxConnectionRate; } - void setMaxConnectionRate(size_t rate) { _maxConnectionRate = rate; } - void removeFromIgnoreMuteSets(const QUuid& nodeID); virtual bool isDomainServer() const override { return false; } @@ -151,17 +146,6 @@ private slots: void maybeSendIgnoreSetToNode(SharedNodePointer node); private: - struct NewNodeInfo { - qint8 type; - QUuid uuid; - HifiSockAddr publicSocket; - HifiSockAddr localSocket; - NodePermissions permissions; - bool isReplicated; - Node::LocalID sessionLocalID; - QUuid connectionSecretUUID; - }; - NodeList() : LimitedNodeList(INVALID_PORT, INVALID_PORT) { assert(false); } // Not implemented, needed for DependencyManager templates compile NodeList(char ownerType, int socketListenPort = INVALID_PORT, int dtlsListenPort = INVALID_PORT); NodeList(NodeList const&) = delete; // Don't implement, needed to avoid copies of singleton @@ -180,10 +164,6 @@ private: bool sockAddrBelongsToDomainOrNode(const HifiSockAddr& sockAddr); - void addNewNode(NewNodeInfo info); - void delayNodeAdd(NewNodeInfo info); - void processDelayedAdds(); - std::atomic _ownerType; NodeSet _nodeTypesOfInterest; DomainHandler _domainHandler; @@ -201,10 +181,6 @@ private: mutable QReadWriteLock _avatarGainMapLock; tbb::concurrent_unordered_map _avatarGainMap; - size_t _maxConnectionRate { DEFAULT_MAX_CONNECTION_RATE }; - size_t _nodesAddedInCurrentTimeSlice { 0 }; - std::vector _delayedNodeAdds; - void sendIgnoreRadiusStateToNode(const SharedNodePointer& destinationNode); #if defined(Q_OS_ANDROID) Setting::Handle _ignoreRadiusEnabled { "IgnoreRadiusEnabled", false }; From de3c5e0ffeee801a75b4e91ed928730dcb0b0006 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Thu, 28 Feb 2019 17:51:58 +0100 Subject: [PATCH 246/474] relative URLs in FST material parsing --- libraries/model-baker/src/model-baker/Baker.cpp | 3 ++- .../src/model-baker/ParseMaterialMappingTask.cpp | 9 +++++---- .../src/model-baker/ParseMaterialMappingTask.h | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/libraries/model-baker/src/model-baker/Baker.cpp b/libraries/model-baker/src/model-baker/Baker.cpp index dfb18eef86..f47a9dcd62 100644 --- a/libraries/model-baker/src/model-baker/Baker.cpp +++ b/libraries/model-baker/src/model-baker/Baker.cpp @@ -155,7 +155,8 @@ namespace baker { const auto jointIndices = jointInfoOut.getN(2); // Parse material mapping - const auto materialMapping = model.addJob("ParseMaterialMapping", mapping); + const auto parseMaterialMappingInputs = ParseMaterialMappingTask::Input(url, mapping).asVarying(); + const auto materialMapping = model.addJob("ParseMaterialMapping", parseMaterialMappingInputs); // Combine the outputs into a new hfm::Model const auto buildBlendshapesInputs = BuildBlendshapesTask::Input(blendshapesPerMeshIn, normalsPerBlendshapePerMesh, tangentsPerBlendshapePerMesh).asVarying(); diff --git a/libraries/model-baker/src/model-baker/ParseMaterialMappingTask.cpp b/libraries/model-baker/src/model-baker/ParseMaterialMappingTask.cpp index 7a923a3702..f8634e4170 100644 --- a/libraries/model-baker/src/model-baker/ParseMaterialMappingTask.cpp +++ b/libraries/model-baker/src/model-baker/ParseMaterialMappingTask.cpp @@ -10,7 +10,9 @@ #include "ModelBakerLogging.h" -void ParseMaterialMappingTask::run(const baker::BakeContextPointer& context, const Input& mapping, Output& output) { +void ParseMaterialMappingTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) { + const auto& url = input.get0(); + const auto& mapping = input.get1(); MaterialMapping materialMapping; auto mappingIter = mapping.find("materialMap"); @@ -59,14 +61,13 @@ void ParseMaterialMappingTask::run(const baker::BakeContextPointer& context, con { NetworkMaterialResourcePointer materialResource = NetworkMaterialResourcePointer(new NetworkMaterialResource(), [](NetworkMaterialResource* ptr) { ptr->deleteLater(); }); materialResource->moveToThread(qApp->thread()); - // TODO: add baseURL to allow FSTs to reference relative files next to them - materialResource->parsedMaterials = NetworkMaterialResource::parseJSONMaterials(QJsonDocument(mappingValue), QUrl()); + materialResource->parsedMaterials = NetworkMaterialResource::parseJSONMaterials(QJsonDocument(mappingValue), url); materialMapping.push_back(std::pair(mapping.toStdString(), materialResource)); } } else if (mappingJSON.isString()) { auto mappingValue = mappingJSON.toString(); - materialMapping.push_back(std::pair(mapping.toStdString(), MaterialCache::instance().getMaterial(mappingValue))); + materialMapping.push_back(std::pair(mapping.toStdString(), MaterialCache::instance().getMaterial(url.resolved(mappingValue)))); } } } diff --git a/libraries/model-baker/src/model-baker/ParseMaterialMappingTask.h b/libraries/model-baker/src/model-baker/ParseMaterialMappingTask.h index 69e00b0324..8ad98edeb9 100644 --- a/libraries/model-baker/src/model-baker/ParseMaterialMappingTask.h +++ b/libraries/model-baker/src/model-baker/ParseMaterialMappingTask.h @@ -19,7 +19,7 @@ class ParseMaterialMappingTask { public: - using Input = QVariantHash; + using Input = baker::VaryingSet2 ; using Output = MaterialMapping; using JobModel = baker::Job::ModelIO; From faedc61c37909f8b38beff48cbe6f47a60aea174 Mon Sep 17 00:00:00 2001 From: amer cerkic Date: Thu, 28 Feb 2019 10:54:30 -0800 Subject: [PATCH 247/474] removing quest-demo specific changes --- android/apps/questInterface/build.gradle | 13 ++++--------- .../highfidelity/oculus/OculusMobileActivity.java | 6 +++--- interface/src/Application.cpp | 6 ------ libraries/animation/src/AnimInverseKinematics.cpp | 6 ------ .../src/OculusMobileDisplayPlugin.cpp | 14 +++++++------- 5 files changed, 14 insertions(+), 31 deletions(-) diff --git a/android/apps/questInterface/build.gradle b/android/apps/questInterface/build.gradle index bf600b5df1..43ce0c0c37 100644 --- a/android/apps/questInterface/build.gradle +++ b/android/apps/questInterface/build.gradle @@ -44,15 +44,10 @@ android { } signingConfigs { release { - // Hack for Quest demo - storeFile file("keystore.jks") - storePassword "password" - keyAlias "key0" - keyPassword "password" - // storeFile project.hasProperty("HIFI_ANDROID_KEYSTORE") ? file(HIFI_ANDROID_KEYSTORE) : null - // storePassword project.hasProperty("HIFI_ANDROID_KEYSTORE_PASSWORD") ? HIFI_ANDROID_KEYSTORE_PASSWORD : '' - // keyAlias project.hasProperty("HIFI_ANDROID_KEY_ALIAS") ? HIFI_ANDROID_KEY_ALIAS : '' - // keyPassword project.hasProperty("HIFI_ANDROID_KEY_PASSWORD") ? HIFI_ANDROID_KEY_PASSWORD : '' + storeFile project.hasProperty("HIFI_ANDROID_KEYSTORE") ? file(HIFI_ANDROID_KEYSTORE) : null + storePassword project.hasProperty("HIFI_ANDROID_KEYSTORE_PASSWORD") ? HIFI_ANDROID_KEYSTORE_PASSWORD : '' + keyAlias project.hasProperty("HIFI_ANDROID_KEY_ALIAS") ? HIFI_ANDROID_KEY_ALIAS : '' + keyPassword project.hasProperty("HIFI_ANDROID_KEY_PASSWORD") ? HIFI_ANDROID_KEY_PASSWORD : '' v2SigningEnabled false } } diff --git a/android/libraries/oculus/src/main/java/io/highfidelity/oculus/OculusMobileActivity.java b/android/libraries/oculus/src/main/java/io/highfidelity/oculus/OculusMobileActivity.java index 125ed46ec9..8ee22749c9 100644 --- a/android/libraries/oculus/src/main/java/io/highfidelity/oculus/OculusMobileActivity.java +++ b/android/libraries/oculus/src/main/java/io/highfidelity/oculus/OculusMobileActivity.java @@ -108,14 +108,14 @@ public class OculusMobileActivity extends QtActivity implements SurfaceHolder.Ca @Override protected void onRestart() { super.onRestart(); - Log.w(TAG, "QQQ_ onRestart called ****"); + Log.w(TAG, "QQQ_ onRestart called"); questOnAppAfterLoad(); questNativeAwayMode(); } @Override public void surfaceCreated(SurfaceHolder holder) { - Log.w(TAG, "QQQ_ surfaceCreated ************************************"); + Log.w(TAG, "QQQ_ surfaceCreated"); nativeOnSurfaceChanged(holder.getSurface()); mSurfaceHolder = holder; } @@ -129,7 +129,7 @@ public class OculusMobileActivity extends QtActivity implements SurfaceHolder.Ca @Override public void surfaceDestroyed(SurfaceHolder holder) { - Log.w(TAG, "QQQ_ surfaceDestroyed ***************************************************"); + Log.w(TAG, "QQQ_ surfaceDestroyed"); nativeOnSurfaceChanged(null); mSurfaceHolder = null; } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 08785ecc90..c889377d6f 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2413,15 +2413,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(&AndroidHelper::instance(), &AndroidHelper::enterBackground, this, &Application::enterBackground); connect(&AndroidHelper::instance(), &AndroidHelper::enterForeground, this, &Application::enterForeground); connect(&AndroidHelper::instance(), &AndroidHelper::toggleAwayMode, this, &Application::toggleAwayMode); - AndroidHelper::instance().notifyLoadComplete(); #endif pauseUntilLoginDetermined(); - -#if defined(Q_OS_ANDROID) - const QString QUEST_DEV = "hifi://quest-dev"; - DependencyManager::get()->handleLookupString(QUEST_DEV); -#endif } void Application::updateVerboseLogging() { diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 37859c939a..9a55b66a39 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -865,12 +865,6 @@ const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVar //virtual const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut, const AnimPoseVec& underPoses) { - -#ifdef Q_OS_ANDROID - // disable IK on android - return underPoses; -#endif - // allows solutionSource to be overridden by an animVar auto solutionSource = animVars.lookup(_solutionSourceVar, (int)_solutionSource); diff --git a/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp b/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp index a63b954f02..9809d02866 100644 --- a/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp +++ b/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp @@ -58,7 +58,7 @@ void OculusMobileDisplayPlugin::deinit() { bool OculusMobileDisplayPlugin::internalActivate() { _renderTargetSize = { 1024, 512 }; - _cullingProjection = ovr::toGlm(ovrMatrix4f_CreateProjectionFov(90.0f, 90.0f, 90.0f, 90.0f, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP)); + _cullingProjection = ovr::toGlm(ovrMatrix4f_CreateProjectionFov(90.0f, 90.0f, 0.0f, 0.0f, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP)); withOvrJava([&](const ovrJava* java){ @@ -220,12 +220,12 @@ bool OculusMobileDisplayPlugin::beginFrameRender(uint32_t frameIndex) { }); } - static uint32_t count = 0; - if ((++count % 1000) == 0) { - AbstractViewStateInterface::instance()->postLambdaEvent([] { - goToDevMobile(); - }); - } + // static uint32_t count = 0; + // if ((++count % 1000) == 0) { + // AbstractViewStateInterface::instance()->postLambdaEvent([] { + // goToDevMobile(); + // }); + // } return result && Parent::beginFrameRender(frameIndex); } From a8e6d0127913487fb56322a665f653b37a3ff248 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 28 Feb 2019 11:33:09 -0800 Subject: [PATCH 248/474] fix context overlay --- .../ui/overlays/ContextOverlayInterface.cpp | 20 +++++++++---------- .../src/ui/overlays/ContextOverlayInterface.h | 1 - 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index 24c0986d09..e5cec70f64 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -50,11 +50,14 @@ ContextOverlayInterface::ContextOverlayInterface() { _entityPropertyFlags += PROP_OWNING_AVATAR_ID; auto entityScriptingInterface = DependencyManager::get().data(); - connect(entityScriptingInterface, &EntityScriptingInterface::clickDownOnEntity, this, &ContextOverlayInterface::clickDownOnEntity); - connect(entityScriptingInterface, &EntityScriptingInterface::holdingClickOnEntity, this, &ContextOverlayInterface::holdingClickOnEntity); + connect(entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity, this, &ContextOverlayInterface::clickDownOnEntity); connect(entityScriptingInterface, &EntityScriptingInterface::mouseReleaseOnEntity, this, &ContextOverlayInterface::mouseReleaseOnEntity); connect(entityScriptingInterface, &EntityScriptingInterface::hoverEnterEntity, this, &ContextOverlayInterface::contextOverlays_hoverEnterEntity); connect(entityScriptingInterface, &EntityScriptingInterface::hoverLeaveEntity, this, &ContextOverlayInterface::contextOverlays_hoverLeaveEntity); + + connect(&qApp->getOverlays(), &Overlays::hoverEnterOverlay, this, &ContextOverlayInterface::contextOverlays_hoverEnterOverlay); + connect(&qApp->getOverlays(), &Overlays::hoverLeaveOverlay, this, &ContextOverlayInterface::contextOverlays_hoverLeaveOverlay); + connect(_tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"), &TabletProxy::tabletShownChanged, this, [&]() { if (_contextOverlayJustClicked && _hmdScriptingInterface->isMounted()) { QUuid tabletFrameID = _hmdScriptingInterface->getCurrentTabletFrameID(); @@ -70,8 +73,6 @@ ContextOverlayInterface::ContextOverlayInterface() { } }); connect(entityScriptingInterface, &EntityScriptingInterface::deletingEntity, this, &ContextOverlayInterface::deletingEntity); - connect(&qApp->getOverlays(), &Overlays::hoverEnterOverlay, this, &ContextOverlayInterface::contextOverlays_hoverEnterOverlay); - connect(&qApp->getOverlays(), &Overlays::hoverLeaveOverlay, this, &ContextOverlayInterface::contextOverlays_hoverLeaveOverlay); { _selectionScriptingInterface->enableListHighlight("contextOverlayHighlightList", QVariantMap()); @@ -106,7 +107,7 @@ void ContextOverlayInterface::clickDownOnEntity(const EntityItemID& id, const Po if (_enabled && event.getButton() == PointerEvent::SecondaryButton && contextOverlayFilterPassed(id)) { _mouseDownEntity = id; _mouseDownEntityTimestamp = usecTimestampNow(); - } else if (id == _contextOverlayID && event.getButton() == PointerEvent::PrimaryButton) { + } else if ((event.shouldFocus() || event.getButton() == PointerEvent::PrimaryButton) && id == _contextOverlayID) { qCDebug(context_overlay) << "Clicked Context Overlay. Entity ID:" << _currentEntityWithContextOverlay << "ID:" << id; emit contextOverlayClicked(_currentEntityWithContextOverlay); _contextOverlayJustClicked = true; @@ -119,13 +120,10 @@ void ContextOverlayInterface::clickDownOnEntity(const EntityItemID& id, const Po } static const float CONTEXT_OVERLAY_CLICK_HOLD_TIME_MSEC = 400.0f; -void ContextOverlayInterface::holdingClickOnEntity(const EntityItemID& entityItemID, const PointerEvent& event) { +void ContextOverlayInterface::mouseReleaseOnEntity(const EntityItemID& entityItemID, const PointerEvent& event) { if (!_mouseDownEntity.isNull() && ((usecTimestampNow() - _mouseDownEntityTimestamp) > (CONTEXT_OVERLAY_CLICK_HOLD_TIME_MSEC * USECS_PER_MSEC))) { _mouseDownEntity = EntityItemID(); } -} - -void ContextOverlayInterface::mouseReleaseOnEntity(const EntityItemID& entityItemID, const PointerEvent& event) { if (_enabled && event.getButton() == PointerEvent::SecondaryButton && contextOverlayFilterPassed(entityItemID) && _mouseDownEntity == entityItemID) { createOrDestroyContextOverlay(entityItemID, event); } @@ -253,7 +251,7 @@ bool ContextOverlayInterface::destroyContextOverlay(const EntityItemID& entityIt } void ContextOverlayInterface::contextOverlays_hoverEnterOverlay(const QUuid& id, const PointerEvent& event) { - if (_contextOverlayID != UNKNOWN_ENTITY_ID) { + if (_contextOverlayID == id) { qCDebug(context_overlay) << "Started hovering over Context Overlay. ID:" << id; EntityItemProperties properties; properties.setColor(CONTEXT_OVERLAY_COLOR); @@ -265,7 +263,7 @@ void ContextOverlayInterface::contextOverlays_hoverEnterOverlay(const QUuid& id, } void ContextOverlayInterface::contextOverlays_hoverLeaveOverlay(const QUuid& id, const PointerEvent& event) { - if (_contextOverlayID != UNKNOWN_ENTITY_ID) { + if (_contextOverlayID == id) { qCDebug(context_overlay) << "Stopped hovering over Context Overlay. ID:" << id; EntityItemProperties properties; properties.setColor(CONTEXT_OVERLAY_COLOR); diff --git a/interface/src/ui/overlays/ContextOverlayInterface.h b/interface/src/ui/overlays/ContextOverlayInterface.h index 57fc8ebe6e..b1b62aa0c4 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.h +++ b/interface/src/ui/overlays/ContextOverlayInterface.h @@ -59,7 +59,6 @@ signals: public slots: void clickDownOnEntity(const EntityItemID& entityItemID, const PointerEvent& event); - void holdingClickOnEntity(const EntityItemID& entityItemID, const PointerEvent& event); void mouseReleaseOnEntity(const EntityItemID& entityItemID, const PointerEvent& event); bool createOrDestroyContextOverlay(const EntityItemID& entityItemID, const PointerEvent& event); From c1516df58dda201958bb2d5405284968f7fe6992 Mon Sep 17 00:00:00 2001 From: raveenajain Date: Thu, 28 Feb 2019 11:35:29 -0800 Subject: [PATCH 249/474] transforms using joints --- libraries/fbx/src/GLTFSerializer.cpp | 43 +++++++++++++--------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/libraries/fbx/src/GLTFSerializer.cpp b/libraries/fbx/src/GLTFSerializer.cpp index 736e7831c1..6dadea29d8 100755 --- a/libraries/fbx/src/GLTFSerializer.cpp +++ b/libraries/fbx/src/GLTFSerializer.cpp @@ -763,17 +763,23 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const QUrl& url) { nodecount++; } - //Build default joints - hfmModel.joints.resize(1); - hfmModel.joints[0].parentIndex = -1; - hfmModel.joints[0].distanceToParent = 0; - hfmModel.joints[0].translation = glm::vec3(0, 0, 0); - hfmModel.joints[0].rotationMin = glm::vec3(0, 0, 0); - hfmModel.joints[0].rotationMax = glm::vec3(0, 0, 0); - hfmModel.joints[0].name = "OBJ"; - hfmModel.joints[0].isSkeletonJoint = true; - - hfmModel.jointIndices["x"] = 1; + HFMJoint joint; + joint.isSkeletonJoint = true; + joint.bindTransformFoundInCluster = false; + joint.distanceToParent = 0; + joint.parentIndex = -1; + hfmModel.joints.resize(_file.nodes.size()); + hfmModel.jointIndices["x"] = _file.nodes.size(); + int jointInd = 0; + for (auto& node : _file.nodes) { + joint.preTransform = glm::mat4(1); + for (int i = 0; i < node.transforms.size(); i++) { + joint.preTransform = node.transforms[i] * joint.preTransform; + } + joint.name = node.name; + hfmModel.joints[jointInd] = joint; + jointInd++; + } //Build materials QVector materialIDs; @@ -804,7 +810,7 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const QUrl& url) { hfmModel.meshes.append(HFMMesh()); HFMMesh& mesh = hfmModel.meshes[hfmModel.meshes.size() - 1]; HFMCluster cluster; - cluster.jointIndex = 0; + cluster.jointIndex = nodecount; cluster.inverseBindMatrix = glm::mat4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, @@ -907,7 +913,7 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const QUrl& url) { int stride = (accessor.type == GLTFAccessorType::VEC4) ? 4 : 3; for (int n = 0; n < tangents.size() - 3; n += stride) { float tanW = stride == 4 ? tangents[n + 3] : 1; - mesh.tangents.push_back(glm::vec3(tanW * tangents[n], tangents[n + 1], tangents[n + 2])); + mesh.tangents.push_back(glm::vec3(tanW * tangents[n], tangents[n + 1], tanW * tangents[n + 2])); } } else if (key == "TEXCOORD_0") { QVector texcoords; @@ -957,16 +963,7 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const QUrl& url) { mesh.meshExtents.addPoint(vertex); hfmModel.meshExtents.addPoint(vertex); } - - // since mesh.modelTransform seems to not have any effect I apply the transformation the model - for (int h = 0; h < mesh.vertices.size(); h++) { - glm::vec4 ver = glm::vec4(mesh.vertices[h], 1); - if (node.transforms.size() > 0) { - ver = node.transforms[0] * ver; // for model dependency should multiply also by parents transforms? - mesh.vertices[h] = glm::vec3(ver[0], ver[1], ver[2]); - } - } - + mesh.meshIndex = hfmModel.meshes.size(); } From 15989e3a89bb10fe93e9dfe139132144001b6e66 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Thu, 28 Feb 2019 11:43:16 -0800 Subject: [PATCH 250/474] WIP. --- tools/nitpick/src/ImageComparer.cpp | 20 +++++++++++++++-- tools/nitpick/src/ImageComparer.h | 11 +++++++++- tools/nitpick/src/MismatchWindow.cpp | 32 ++++++++++++++++++++++++++++ tools/nitpick/src/MismatchWindow.h | 2 ++ tools/nitpick/src/Nitpick.cpp | 2 +- tools/nitpick/src/Test.cpp | 18 ++++++++++++---- tools/nitpick/src/Test.h | 4 ++-- tools/nitpick/src/common.h | 8 +++++++ 8 files changed, 87 insertions(+), 10 deletions(-) diff --git a/tools/nitpick/src/ImageComparer.cpp b/tools/nitpick/src/ImageComparer.cpp index fa73f97887..b9d556e544 100644 --- a/tools/nitpick/src/ImageComparer.cpp +++ b/tools/nitpick/src/ImageComparer.cpp @@ -12,9 +12,17 @@ #include +ImageComparer::ImageComparer() { + _ssimResults = new SSIMResults(); +} + +ImageComparer::~ImageComparer() { + delete _ssimResults; +} + // Computes SSIM - see https://en.wikipedia.org/wiki/Structural_similarity // The value is computed for the luminance component and the average value is returned -double ImageComparer::compareImages(QImage resultImage, QImage expectedImage) const { +double ImageComparer::compareImages(const QImage& resultImage, const QImage& expectedImage) const { const int L = 255; // (2^number of bits per pixel) - 1 const double K1 { 0.01 }; @@ -96,6 +104,7 @@ double ImageComparer::compareImages(QImage resultImage, QImage expectedImage) co double numerator = (2.0 * mP * mQ + c1) * (2.0 * sigPQ + c2); double denominator = (mP * mP + mQ * mQ + c1) * (sigsqP + sigsqQ + c2); + _ssimResults->results.push_back(numerator / denominator); ssim += numerator / denominator; ++windowCounter; @@ -106,5 +115,12 @@ double ImageComparer::compareImages(QImage resultImage, QImage expectedImage) co y = 0; } + _ssimResults->width = (int)(expectedImage.width() / WIN_SIZE); + _ssimResults->height = (int)(expectedImage.height() / WIN_SIZE); + return ssim / windowCounter; -}; \ No newline at end of file +}; + +SSIMResults* ImageComparer::getSSIMResults() { + return _ssimResults; +} diff --git a/tools/nitpick/src/ImageComparer.h b/tools/nitpick/src/ImageComparer.h index 7b7b8b0b74..5bea8151ec 100644 --- a/tools/nitpick/src/ImageComparer.h +++ b/tools/nitpick/src/ImageComparer.h @@ -10,12 +10,21 @@ #ifndef hifi_ImageComparer_h #define hifi_ImageComparer_h +#include "common.h" + #include #include class ImageComparer { public: - double compareImages(QImage resultImage, QImage expectedImage) const; + ImageComparer(); + ~ImageComparer(); + + double compareImages(const QImage& resultImage, const QImage& expectedImage) const; + SSIMResults* getSSIMResults(); + +private: + SSIMResults* _ssimResults; }; #endif // hifi_ImageComparer_h diff --git a/tools/nitpick/src/MismatchWindow.cpp b/tools/nitpick/src/MismatchWindow.cpp index 58189b4795..7649929551 100644 --- a/tools/nitpick/src/MismatchWindow.cpp +++ b/tools/nitpick/src/MismatchWindow.cpp @@ -99,3 +99,35 @@ void MismatchWindow::on_abortTestsButton_clicked() { QPixmap MismatchWindow::getComparisonImage() { return _diffPixmap; } + +QPixmap MismatchWindow::getSSIMResultsImage(SSIMResults* ssimResults) { + // This is an optimization, as QImage.setPixel() is embarrassingly slow + const int ELEMENT_SIZE { 8 }; + unsigned char* buffer = new unsigned char[(ssimResults->height * ELEMENT_SIZE) * (ssimResults->width * ELEMENT_SIZE ) * 3]; + + + // loop over each SSIM result (a double in [-1.0 .. 1.0] + int i { 0 }; + for (int y = 0; y < ssimResults->height; ++y) { + for (int x = 0; x < ssimResults->width; ++x) { + ////QRgb pixelP = expectedImage.pixel(QPoint(x, y)); + + ////// Convert to luminance + ////double p = R_Y * qRed(pixelP) + G_Y * qGreen(pixelP) + B_Y * qBlue(pixelP); + + ////// The intensity value is modified to increase the brightness of the displayed image + ////double absoluteDifference = fabs(p - q) / 255.0; + ////double modifiedDifference = sqrt(absoluteDifference); + + ////int difference = (int)(modifiedDifference * 255.0); + + ////buffer[3 * (x + y * expectedImage.width()) + 0] = difference; + ////buffer[3 * (x + y * expectedImage.width()) + 1] = difference; + ////buffer[3 * (x + y * expectedImage.width()) + 2] = difference; + + ++i; + } + } + + return QPixmap(); +} diff --git a/tools/nitpick/src/MismatchWindow.h b/tools/nitpick/src/MismatchWindow.h index 040e0b8bf1..d80e371ba0 100644 --- a/tools/nitpick/src/MismatchWindow.h +++ b/tools/nitpick/src/MismatchWindow.h @@ -25,7 +25,9 @@ public: UserResponse getUserResponse() { return _userResponse; } QPixmap computeDiffPixmap(QImage expectedImage, QImage resultImage); + QPixmap getComparisonImage(); + QPixmap getSSIMResultsImage(SSIMResults* ssimResults); private slots: void on_passTestButton_clicked(); diff --git a/tools/nitpick/src/Nitpick.cpp b/tools/nitpick/src/Nitpick.cpp index 39800c6bc6..e17ea94634 100644 --- a/tools/nitpick/src/Nitpick.cpp +++ b/tools/nitpick/src/Nitpick.cpp @@ -40,7 +40,7 @@ Nitpick::Nitpick(QWidget* parent) : QMainWindow(parent) { _ui.plainTextEdit->setReadOnly(true); - setWindowTitle("Nitpick - v3.0.0"); + setWindowTitle("Nitpick - v3.0.1"); clientProfiles << "VR-High" << "Desktop-High" << "Desktop-Low" << "Mobile-Touch" << "VR-Standalone"; _ui.clientProfileComboBox->insertItems(0, clientProfiles); diff --git a/tools/nitpick/src/Test.cpp b/tools/nitpick/src/Test.cpp index e8e284bf32..a1f1bf92eb 100644 --- a/tools/nitpick/src/Test.cpp +++ b/tools/nitpick/src/Test.cpp @@ -100,12 +100,14 @@ int Test::compareImageLists() { }; _mismatchWindow.setTestResult(testResult); + + QPixmap ssimResultsPixMap = _mismatchWindow.getSSIMResultsImage(_imageComparer.getSSIMResults()); if (similarityIndex < THRESHOLD) { ++numberOfFailures; if (!isInteractiveMode) { - appendTestResultsToFile(testResult, _mismatchWindow.getComparisonImage(), true); + appendTestResultsToFile(testResult, _mismatchWindow.getComparisonImage(), ssimResultsPixMap, true); } else { _mismatchWindow.exec(); @@ -113,7 +115,7 @@ int Test::compareImageLists() { case USER_RESPONSE_PASS: break; case USE_RESPONSE_FAIL: - appendTestResultsToFile(testResult, _mismatchWindow.getComparisonImage(), true); + appendTestResultsToFile(testResult, _mismatchWindow.getComparisonImage(), ssimResultsPixMap, true); break; case USER_RESPONSE_ABORT: keepOn = false; @@ -124,7 +126,7 @@ int Test::compareImageLists() { } } } else { - appendTestResultsToFile(testResult, _mismatchWindow.getComparisonImage(), false); + appendTestResultsToFile(testResult, _mismatchWindow.getComparisonImage(), ssimResultsPixMap, false); } _progressBar->setValue(i); @@ -156,7 +158,7 @@ int Test::checkTextResults() { return testsFailed.length(); } -void Test::appendTestResultsToFile(TestResult testResult, QPixmap comparisonImage, bool hasFailed) { +void Test::appendTestResultsToFile(const TestResult& testResult, const QPixmap& comparisonImage, const QPixmap& ssimResultsImage, bool hasFailed) { // Critical error if Test Results folder does not exist if (!QDir().exists(_testResultsFolderPath)) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Folder " + _testResultsFolderPath + " not found"); @@ -216,6 +218,14 @@ void Test::appendTestResultsToFile(TestResult testResult, QPixmap comparisonImag exit(-1); } + // Create the SSIM results image + sourceFile = testResult._pathname + testResult._actualImageFilename; + destinationFile = resultFolderPath + "/" + "SSIM results.png"; + if (!QFile::copy(sourceFile, destinationFile)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to copy " + sourceFile + " to " + destinationFile); + exit(-1); + } + comparisonImage.save(resultFolderPath + "/" + "Difference Image.png"); } diff --git a/tools/nitpick/src/Test.h b/tools/nitpick/src/Test.h index 23011d0c31..752dcbc5af 100644 --- a/tools/nitpick/src/Test.h +++ b/tools/nitpick/src/Test.h @@ -87,7 +87,7 @@ public: void includeTest(QTextStream& textStream, const QString& testPathname); - void appendTestResultsToFile(TestResult testResult, QPixmap comparisonImage, bool hasFailed); + void appendTestResultsToFile(const TestResult& testResult, const QPixmap& comparisonImage, const QPixmap& ssimResultsImage, bool hasFailed); void appendTestResultsToFile(QString testResultFilename, bool hasFailed); bool createTestResultsFolderPath(const QString& directory); @@ -116,7 +116,7 @@ private: const QString TEST_RESULTS_FOLDER { "TestResults" }; const QString TEST_RESULTS_FILENAME { "TestResults.txt" }; - const double THRESHOLD{ 0.935 }; + const double THRESHOLD{ 0.98 }; QDir _imageDirectory; diff --git a/tools/nitpick/src/common.h b/tools/nitpick/src/common.h index 5df4e9c921..ac776995b7 100644 --- a/tools/nitpick/src/common.h +++ b/tools/nitpick/src/common.h @@ -10,6 +10,7 @@ #ifndef hifi_common_h #define hifi_common_h +#include #include class TestResult { @@ -39,4 +40,11 @@ const double R_Y = 0.212655f; const double G_Y = 0.715158f; const double B_Y = 0.072187f; +class SSIMResults { +public: + int width; + int height; + std::vector results; +}; + #endif // hifi_common_h \ No newline at end of file From a72b02cd25989f7d11af0cf2dd4a511de6b77bde Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Thu, 28 Feb 2019 13:08:05 -0800 Subject: [PATCH 251/474] Clean up code and add comments. --- .../controllers/controllerModules/farGrabEntity.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/scripts/system/controllers/controllerModules/farGrabEntity.js b/scripts/system/controllers/controllerModules/farGrabEntity.js index 5e621809b2..65a3671cae 100644 --- a/scripts/system/controllers/controllerModules/farGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farGrabEntity.js @@ -83,14 +83,11 @@ Script.include("/~/system/libraries/controllers.js"); 100, makeLaserParams(this.hand, false)); - //enableDispatcherModule("LeftFarGrabEntity", leftFarGrabEntity); - //enableDispatcherModule("RightFarGrabEntity", rightFarGrabEntity); - this.getOtherModule = function () { - // Used to fetch other module. return getEnabledModuleByName(this.hand === RIGHT_HAND ? ("LeftFarGrabEntity") : ("RightFarGrabEntity")); }; + // Get the rotation of the fargrabbed entity. this.getTargetRotation = function () { if (this.targetIsNull()) { return null; @@ -108,10 +105,12 @@ Script.include("/~/system/libraries/controllers.js"); return (_this.hand === RIGHT_HAND ? _this.leftTrigger : _this.rightTrigger); } + // Activation criteria for rotating a fargrabbed entity. If we're changing the mapping, this is where to do it. this.shouldManipulateTarget = function () { return (_this.getOffhandTrigger() > TRIGGER_ON_VALUE) ? true : false; }; + // Get the delta between the current rotation and where the controller was when manipulation started. this.calculateEntityRotationManipulation = function (controllerRotation) { return Quat.multiply(controllerRotation, Quat.inverse(this.initialControllerRotation)); }; @@ -303,11 +302,14 @@ Script.include("/~/system/libraries/controllers.js"); this.lastJointRotation = Quat.multiply(doubleRot, this.initialEntityRotation); this.setJointRotation(this.lastJointRotation); } else { + // If we were manipulating but the user isn't currently expressing this intent, we want to know so we preserve the rotation + // between manipulations without ending the fargrab. if (this.manipulating) { this.initialEntityRotation = this.lastJointRotation; this.wasManipulating = true; } this.manipulating = false; + // Reset the inital controller position. this.initialControllerRotation = Quat.IDENTITY; } this.setJointTranslation(newTargetPosLocal); From f715dbe243ce4ea094b455c327331888aa8eb073 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Thu, 28 Feb 2019 11:53:20 -0800 Subject: [PATCH 252/474] fix tablet scaling --- .../src/RenderableWebEntityItem.cpp | 3 +- libraries/entities/src/EntityItem.cpp | 42 +++++------ libraries/entities/src/EntityItem.h | 2 +- .../entities/src/EntityItemProperties.cpp | 46 ++++++------ libraries/entities/src/EntityItemProperties.h | 4 +- libraries/entities/src/EntityPropertyFlags.h | 4 +- libraries/entities/src/ModelEntityItem.cpp | 35 +++++++-- libraries/entities/src/ModelEntityItem.h | 8 +- libraries/networking/src/udt/PacketHeaders.h | 1 + .../nearParentGrabOverlay.js | 7 +- scripts/system/libraries/WebTablet.js | 2 +- scripts/system/libraries/utils.js | 74 +++++++++++++++++-- 12 files changed, 164 insertions(+), 64 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 0f2c708d17..3b615ba467 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -266,6 +266,7 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene _webSurface->resize(QSize(windowSize.x, windowSize.y)); updateModelTransformAndBound(); _renderTransform = getModelTransform(); + _renderTransform.setScale(1.0f); _renderTransform.postScale(entity->getScaledDimensions()); }); }); @@ -487,4 +488,4 @@ QObject* WebEntityRenderer::getEventHandler() { void WebEntityRenderer::emitScriptEvent(const QVariant& message) { emit scriptEventReceived(message); -} \ No newline at end of file +} diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 5dc218bc3b..5da1c05aa2 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -79,6 +79,8 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param // Core requestedProperties += PROP_SIMULATION_OWNER; + requestedProperties += PROP_PARENT_ID; + requestedProperties += PROP_PARENT_JOINT_INDEX; requestedProperties += PROP_VISIBLE; requestedProperties += PROP_NAME; requestedProperties += PROP_LOCKED; @@ -93,8 +95,6 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param requestedProperties += PROP_LAST_EDITED_BY; requestedProperties += PROP_ENTITY_HOST_TYPE; requestedProperties += PROP_OWNING_AVATAR_ID; - requestedProperties += PROP_PARENT_ID; - requestedProperties += PROP_PARENT_JOINT_INDEX; requestedProperties += PROP_QUERY_AA_CUBE; requestedProperties += PROP_CAN_CAST_SHADOW; requestedProperties += PROP_VISIBLE_IN_SECONDARY_CAMERA; @@ -260,6 +260,14 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet // PROP_CUSTOM_PROPERTIES_INCLUDED, APPEND_ENTITY_PROPERTY(PROP_SIMULATION_OWNER, _simulationOwner.toByteArray()); + // convert AVATAR_SELF_ID to actual sessionUUID. + QUuid actualParentID = getParentID(); + if (actualParentID == AVATAR_SELF_ID) { + auto nodeList = DependencyManager::get(); + actualParentID = nodeList->getSessionUUID(); + } + APPEND_ENTITY_PROPERTY(PROP_PARENT_ID, actualParentID); + APPEND_ENTITY_PROPERTY(PROP_PARENT_JOINT_INDEX, getParentJointIndex()); APPEND_ENTITY_PROPERTY(PROP_VISIBLE, getVisible()); APPEND_ENTITY_PROPERTY(PROP_NAME, getName()); APPEND_ENTITY_PROPERTY(PROP_LOCKED, getLocked()); @@ -274,14 +282,6 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet APPEND_ENTITY_PROPERTY(PROP_LAST_EDITED_BY, getLastEditedBy()); // APPEND_ENTITY_PROPERTY(PROP_ENTITY_HOST_TYPE, (uint32_t)getEntityHostType()); // not sent over the wire // APPEND_ENTITY_PROPERTY(PROP_OWNING_AVATAR_ID, getOwningAvatarID()); // not sent over the wire - // convert AVATAR_SELF_ID to actual sessionUUID. - QUuid actualParentID = getParentID(); - if (actualParentID == AVATAR_SELF_ID) { - auto nodeList = DependencyManager::get(); - actualParentID = nodeList->getSessionUUID(); - } - APPEND_ENTITY_PROPERTY(PROP_PARENT_ID, actualParentID); - APPEND_ENTITY_PROPERTY(PROP_PARENT_JOINT_INDEX, getParentJointIndex()); APPEND_ENTITY_PROPERTY(PROP_QUERY_AA_CUBE, getQueryAACube()); APPEND_ENTITY_PROPERTY(PROP_CAN_CAST_SHADOW, getCanCastShadow()); // APPEND_ENTITY_PROPERTY(PROP_VISIBLE_IN_SECONDARY_CAMERA, getIsVisibleInSecondaryCamera()); // not sent over the wire @@ -792,6 +792,13 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef // Core // PROP_SIMULATION_OWNER handled above + { // parentID and parentJointIndex are protected by simulation ownership + bool oldOverwrite = overwriteLocalData; + overwriteLocalData = overwriteLocalData && !weOwnSimulation; + READ_ENTITY_PROPERTY(PROP_PARENT_ID, QUuid, setParentID); + READ_ENTITY_PROPERTY(PROP_PARENT_JOINT_INDEX, quint16, setParentJointIndex); + overwriteLocalData = oldOverwrite; + } READ_ENTITY_PROPERTY(PROP_VISIBLE, bool, setVisible); READ_ENTITY_PROPERTY(PROP_NAME, QString, setName); READ_ENTITY_PROPERTY(PROP_LOCKED, bool, setLocked); @@ -835,13 +842,6 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef READ_ENTITY_PROPERTY(PROP_LAST_EDITED_BY, QUuid, setLastEditedBy); // READ_ENTITY_PROPERTY(PROP_ENTITY_HOST_TYPE, entity::HostType, setEntityHostType); // not sent over the wire // READ_ENTITY_PROPERTY(PROP_OWNING_AVATAR_ID, QUuuid, setOwningAvatarID); // not sent over the wire - { // parentID and parentJointIndex are protected by simulation ownership - bool oldOverwrite = overwriteLocalData; - overwriteLocalData = overwriteLocalData && !weOwnSimulation; - READ_ENTITY_PROPERTY(PROP_PARENT_ID, QUuid, setParentID); - READ_ENTITY_PROPERTY(PROP_PARENT_JOINT_INDEX, quint16, setParentJointIndex); - overwriteLocalData = oldOverwrite; - } { // See comment above auto customUpdateQueryAACubeFromNetwork = [this, shouldUpdate, lastEdited](AACube value) { if (shouldUpdate(_lastUpdatedQueryAACubeTimestamp, value != _lastUpdatedQueryAACubeValue)) { @@ -1309,6 +1309,8 @@ EntityItemProperties EntityItem::getProperties(const EntityPropertyFlags& desire // Core COPY_ENTITY_PROPERTY_TO_PROPERTIES(simulationOwner, getSimulationOwner); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(parentID, getParentID); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(parentJointIndex, getParentJointIndex); COPY_ENTITY_PROPERTY_TO_PROPERTIES(visible, getVisible); COPY_ENTITY_PROPERTY_TO_PROPERTIES(name, getName); COPY_ENTITY_PROPERTY_TO_PROPERTIES(locked, getLocked); @@ -1323,8 +1325,6 @@ EntityItemProperties EntityItem::getProperties(const EntityPropertyFlags& desire COPY_ENTITY_PROPERTY_TO_PROPERTIES(lastEditedBy, getLastEditedBy); COPY_ENTITY_PROPERTY_TO_PROPERTIES(entityHostType, getEntityHostType); COPY_ENTITY_PROPERTY_TO_PROPERTIES(owningAvatarID, getOwningAvatarID); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(parentID, getParentID); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(parentJointIndex, getParentJointIndex); COPY_ENTITY_PROPERTY_TO_PROPERTIES(queryAACube, getQueryAACube); COPY_ENTITY_PROPERTY_TO_PROPERTIES(canCastShadow, getCanCastShadow); COPY_ENTITY_PROPERTY_TO_PROPERTIES(isVisibleInSecondaryCamera, isVisibleInSecondaryCamera); @@ -1456,6 +1456,8 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { // Core SET_ENTITY_PROPERTY_FROM_PROPERTIES(simulationOwner, setSimulationOwner); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(parentID, setParentID); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(parentJointIndex, setParentJointIndex); SET_ENTITY_PROPERTY_FROM_PROPERTIES(visible, setVisible); SET_ENTITY_PROPERTY_FROM_PROPERTIES(name, setName); SET_ENTITY_PROPERTY_FROM_PROPERTIES(locked, setLocked); @@ -1470,8 +1472,6 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(lastEditedBy, setLastEditedBy); SET_ENTITY_PROPERTY_FROM_PROPERTIES(entityHostType, setEntityHostType); SET_ENTITY_PROPERTY_FROM_PROPERTIES(owningAvatarID, setOwningAvatarID); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(parentID, setParentID); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(parentJointIndex, setParentJointIndex); SET_ENTITY_PROPERTY_FROM_PROPERTIES(queryAACube, setQueryAACube); SET_ENTITY_PROPERTY_FROM_PROPERTIES(canCastShadow, setCanCastShadow); SET_ENTITY_PROPERTY_FROM_PROPERTIES(isVisibleInSecondaryCamera, setIsVisibleInSecondaryCamera); diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 0ca851e228..bd1977f053 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -197,7 +197,7 @@ public: void setDescription(const QString& value); /// Dimensions in meters (0.0 - TREE_SCALE) - glm::vec3 getScaledDimensions() const; + virtual glm::vec3 getScaledDimensions() const; virtual void setScaledDimensions(const glm::vec3& value); virtual glm::vec3 getRaycastDimensions() const { return getScaledDimensions(); } diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 7575763bf9..5ae9b30869 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -462,6 +462,8 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { // Core CHECK_PROPERTY_CHANGE(PROP_SIMULATION_OWNER, simulationOwner); + CHECK_PROPERTY_CHANGE(PROP_PARENT_ID, parentID); + CHECK_PROPERTY_CHANGE(PROP_PARENT_JOINT_INDEX, parentJointIndex); CHECK_PROPERTY_CHANGE(PROP_VISIBLE, visible); CHECK_PROPERTY_CHANGE(PROP_NAME, name); CHECK_PROPERTY_CHANGE(PROP_LOCKED, locked); @@ -476,8 +478,6 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_LAST_EDITED_BY, lastEditedBy); CHECK_PROPERTY_CHANGE(PROP_ENTITY_HOST_TYPE, entityHostType); CHECK_PROPERTY_CHANGE(PROP_OWNING_AVATAR_ID, owningAvatarID); - CHECK_PROPERTY_CHANGE(PROP_PARENT_ID, parentID); - CHECK_PROPERTY_CHANGE(PROP_PARENT_JOINT_INDEX, parentJointIndex); CHECK_PROPERTY_CHANGE(PROP_QUERY_AA_CUBE, queryAACube); CHECK_PROPERTY_CHANGE(PROP_CAN_CAST_SHADOW, canCastShadow); CHECK_PROPERTY_CHANGE(PROP_VISIBLE_IN_SECONDARY_CAMERA, isVisibleInSecondaryCamera); @@ -1551,6 +1551,8 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool // Core properties //COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SIMULATION_OWNER, simulationOwner); // not exposed yet + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_PARENT_ID, parentID); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_PARENT_JOINT_INDEX, parentJointIndex); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_VISIBLE, visible); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_NAME, name); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCKED, locked); @@ -1565,8 +1567,6 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LAST_EDITED_BY, lastEditedBy); COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_ENTITY_HOST_TYPE, entityHostType, getEntityHostTypeAsString()); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_OWNING_AVATAR_ID, owningAvatarID); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_PARENT_ID, parentID); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_PARENT_JOINT_INDEX, parentJointIndex); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_QUERY_AA_CUBE, queryAACube); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CAN_CAST_SHADOW, canCastShadow); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_VISIBLE_IN_SECONDARY_CAMERA, isVisibleInSecondaryCamera); @@ -1956,6 +1956,8 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool // not handled yet // COPY_PROPERTY_FROM_QSCRIPTVALUE(simulationOwner, SimulationOwner, setSimulationOwner); } + COPY_PROPERTY_FROM_QSCRIPTVALUE(parentID, QUuid, setParentID); + COPY_PROPERTY_FROM_QSCRIPTVALUE(parentJointIndex, quint16, setParentJointIndex); COPY_PROPERTY_FROM_QSCRIPTVALUE(visible, bool, setVisible); COPY_PROPERTY_FROM_QSCRIPTVALUE(name, QString, setName); COPY_PROPERTY_FROM_QSCRIPTVALUE(locked, bool, setLocked); @@ -1972,8 +1974,6 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(entityHostType, EntityHostType); COPY_PROPERTY_FROM_QSCRIPTVALUE(owningAvatarID, QUuid, setOwningAvatarID); } - COPY_PROPERTY_FROM_QSCRIPTVALUE(parentID, QUuid, setParentID); - COPY_PROPERTY_FROM_QSCRIPTVALUE(parentJointIndex, quint16, setParentJointIndex); COPY_PROPERTY_FROM_QSCRIPTVALUE(queryAACube, AACube, setQueryAACube); // TODO: should scripts be able to set this? COPY_PROPERTY_FROM_QSCRIPTVALUE(canCastShadow, bool, setCanCastShadow); COPY_PROPERTY_FROM_QSCRIPTVALUE(isVisibleInSecondaryCamera, bool, setIsVisibleInSecondaryCamera); @@ -2243,6 +2243,8 @@ void EntityItemProperties::copyFromJSONString(QScriptEngine& scriptEngine, const void EntityItemProperties::merge(const EntityItemProperties& other) { // Core COPY_PROPERTY_IF_CHANGED(simulationOwner); + COPY_PROPERTY_IF_CHANGED(parentID); + COPY_PROPERTY_IF_CHANGED(parentJointIndex); COPY_PROPERTY_IF_CHANGED(visible); COPY_PROPERTY_IF_CHANGED(name); COPY_PROPERTY_IF_CHANGED(locked); @@ -2257,8 +2259,6 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(lastEditedBy); COPY_PROPERTY_IF_CHANGED(entityHostType); COPY_PROPERTY_IF_CHANGED(owningAvatarID); - COPY_PROPERTY_IF_CHANGED(parentID); - COPY_PROPERTY_IF_CHANGED(parentJointIndex); COPY_PROPERTY_IF_CHANGED(queryAACube); COPY_PROPERTY_IF_CHANGED(canCastShadow); COPY_PROPERTY_IF_CHANGED(isVisibleInSecondaryCamera); @@ -2526,6 +2526,8 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr std::call_once(initMap, []() { // Core ADD_PROPERTY_TO_MAP(PROP_SIMULATION_OWNER, SimulationOwner, simulationOwner, SimulationOwner); + ADD_PROPERTY_TO_MAP(PROP_PARENT_ID, ParentID, parentID, QUuid); + ADD_PROPERTY_TO_MAP(PROP_PARENT_JOINT_INDEX, ParentJointIndex, parentJointIndex, uint16_t); ADD_PROPERTY_TO_MAP(PROP_VISIBLE, Visible, visible, bool); ADD_PROPERTY_TO_MAP(PROP_NAME, Name, name, QString); ADD_PROPERTY_TO_MAP(PROP_LOCKED, Locked, locked, bool); @@ -2541,8 +2543,6 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr ADD_PROPERTY_TO_MAP(PROP_LAST_EDITED_BY, LastEditedBy, lastEditedBy, QUuid); ADD_PROPERTY_TO_MAP(PROP_ENTITY_HOST_TYPE, EntityHostType, entityHostType, entity::HostType); ADD_PROPERTY_TO_MAP(PROP_OWNING_AVATAR_ID, OwningAvatarID, owningAvatarID, QUuid); - ADD_PROPERTY_TO_MAP(PROP_PARENT_ID, ParentID, parentID, QUuid); - ADD_PROPERTY_TO_MAP(PROP_PARENT_JOINT_INDEX, ParentJointIndex, parentJointIndex, uint16_t); ADD_PROPERTY_TO_MAP(PROP_QUERY_AA_CUBE, QueryAACube, queryAACube, AACube); ADD_PROPERTY_TO_MAP(PROP_CAN_CAST_SHADOW, CanCastShadow, canCastShadow, bool); ADD_PROPERTY_TO_MAP(PROP_VISIBLE_IN_SECONDARY_CAMERA, IsVisibleInSecondaryCamera, isVisibleInSecondaryCamera, bool); @@ -2998,6 +2998,8 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_SIMULATION_OWNER, properties._simulationOwner.toByteArray()); + APPEND_ENTITY_PROPERTY(PROP_PARENT_ID, properties.getParentID()); + APPEND_ENTITY_PROPERTY(PROP_PARENT_JOINT_INDEX, properties.getParentJointIndex()); APPEND_ENTITY_PROPERTY(PROP_VISIBLE, properties.getVisible()); APPEND_ENTITY_PROPERTY(PROP_NAME, properties.getName()); APPEND_ENTITY_PROPERTY(PROP_LOCKED, properties.getLocked()); @@ -3012,8 +3014,6 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_LAST_EDITED_BY, properties.getLastEditedBy()); // APPEND_ENTITY_PROPERTY(PROP_ENTITY_HOST_TYPE, (uint32_t)properties.getEntityHostType()); // not sent over the wire // APPEND_ENTITY_PROPERTY(PROP_OWNING_AVATAR_ID, properties.getOwningAvatarID()); // not sent over the wire - APPEND_ENTITY_PROPERTY(PROP_PARENT_ID, properties.getParentID()); - APPEND_ENTITY_PROPERTY(PROP_PARENT_JOINT_INDEX, properties.getParentJointIndex()); APPEND_ENTITY_PROPERTY(PROP_QUERY_AA_CUBE, properties.getQueryAACube()); APPEND_ENTITY_PROPERTY(PROP_CAN_CAST_SHADOW, properties.getCanCastShadow()); // APPEND_ENTITY_PROPERTY(PROP_VISIBLE_IN_SECONDARY_CAMERA, properties.getIsVisibleInSecondaryCamera()); // not sent over the wire @@ -3124,6 +3124,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_TEXTURES, properties.getTextures()); APPEND_ENTITY_PROPERTY(PROP_MODEL_URL, properties.getModelURL()); + APPEND_ENTITY_PROPERTY(PROP_MODEL_SCALE, properties.getModelScale()); APPEND_ENTITY_PROPERTY(PROP_JOINT_ROTATIONS_SET, properties.getJointRotationsSet()); APPEND_ENTITY_PROPERTY(PROP_JOINT_ROTATIONS, properties.getJointRotations()); APPEND_ENTITY_PROPERTY(PROP_JOINT_TRANSLATIONS_SET, properties.getJointTranslationsSet()); @@ -3477,6 +3478,8 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int processedBytes += propertyFlags.getEncodedLength(); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SIMULATION_OWNER, QByteArray, setSimulationOwner); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PARENT_ID, QUuid, setParentID); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PARENT_JOINT_INDEX, quint16, setParentJointIndex); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VISIBLE, bool, setVisible); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_NAME, QString, setName); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LOCKED, bool, setLocked); @@ -3491,8 +3494,6 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LAST_EDITED_BY, QUuid, setLastEditedBy); // READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ENTITY_HOST_TYPE, entity::HostType, setEntityHostType); // not sent over the wire // READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_OWNING_AVATAR_ID, QUuid, setOwningAvatarID); // not sent over the wire - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PARENT_ID, QUuid, setParentID); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PARENT_JOINT_INDEX, quint16, setParentJointIndex); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_QUERY_AA_CUBE, AACube, setQueryAACube); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CAN_CAST_SHADOW, bool, setCanCastShadow); // READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VISIBLE_IN_SECONDARY_CAMERA, bool, setIsVisibleInSecondaryCamera); // not sent over the wire @@ -3599,6 +3600,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TEXTURES, QString, setTextures); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MODEL_URL, QString, setModelURL); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MODEL_SCALE, vec3, setModelScale); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_JOINT_ROTATIONS_SET, QVector, setJointRotationsSet); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_JOINT_ROTATIONS, QVector, setJointRotations); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_JOINT_TRANSLATIONS_SET, QVector, setJointTranslationsSet); @@ -3884,6 +3886,8 @@ bool EntityItemProperties::decodeCloneEntityMessage(const QByteArray& buffer, in void EntityItemProperties::markAllChanged() { // Core _simulationOwnerChanged = true; + _parentIDChanged = true; + _parentJointIndexChanged = true; _visibleChanged = true; _nameChanged = true; _lockedChanged = true; @@ -3898,8 +3902,6 @@ void EntityItemProperties::markAllChanged() { _lastEditedByChanged = true; _entityHostTypeChanged = true; _owningAvatarIDChanged = true; - _parentIDChanged = true; - _parentJointIndexChanged = true; _queryAACubeChanged = true; _canCastShadowChanged = true; _isVisibleInSecondaryCameraChanged = true; @@ -4232,6 +4234,12 @@ QList EntityItemProperties::listChangedProperties() { if (simulationOwnerChanged()) { out += "simulationOwner"; } + if (parentIDChanged()) { + out += "parentID"; + } + if (parentJointIndexChanged()) { + out += "parentJointIndex"; + } if (visibleChanged()) { out += "visible"; } @@ -4274,12 +4282,6 @@ QList EntityItemProperties::listChangedProperties() { if (owningAvatarIDChanged()) { out += "owningAvatarID"; } - if (parentIDChanged()) { - out += "parentID"; - } - if (parentJointIndexChanged()) { - out += "parentJointIndex"; - } if (queryAACubeChanged()) { out += "queryAACube"; } diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index afc3537559..00a93dd129 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -161,6 +161,8 @@ public: // Core Properties DEFINE_PROPERTY_REF(PROP_SIMULATION_OWNER, SimulationOwner, simulationOwner, SimulationOwner, SimulationOwner()); + DEFINE_PROPERTY_REF(PROP_PARENT_ID, ParentID, parentID, QUuid, UNKNOWN_ENTITY_ID); + DEFINE_PROPERTY_REF(PROP_PARENT_JOINT_INDEX, ParentJointIndex, parentJointIndex, quint16, -1); DEFINE_PROPERTY(PROP_VISIBLE, Visible, visible, bool, ENTITY_ITEM_DEFAULT_VISIBLE); DEFINE_PROPERTY_REF(PROP_NAME, Name, name, QString, ENTITY_ITEM_DEFAULT_NAME); DEFINE_PROPERTY(PROP_LOCKED, Locked, locked, bool, ENTITY_ITEM_DEFAULT_LOCKED); @@ -175,8 +177,6 @@ public: DEFINE_PROPERTY_REF(PROP_LAST_EDITED_BY, LastEditedBy, lastEditedBy, QUuid, ENTITY_ITEM_DEFAULT_LAST_EDITED_BY); DEFINE_PROPERTY_REF_ENUM(PROP_ENTITY_HOST_TYPE, EntityHostType, entityHostType, entity::HostType, entity::HostType::DOMAIN); DEFINE_PROPERTY_REF_WITH_SETTER(PROP_OWNING_AVATAR_ID, OwningAvatarID, owningAvatarID, QUuid, UNKNOWN_ENTITY_ID); - DEFINE_PROPERTY_REF(PROP_PARENT_ID, ParentID, parentID, QUuid, UNKNOWN_ENTITY_ID); - DEFINE_PROPERTY_REF(PROP_PARENT_JOINT_INDEX, ParentJointIndex, parentJointIndex, quint16, -1); DEFINE_PROPERTY_REF(PROP_QUERY_AA_CUBE, QueryAACube, queryAACube, AACube, AACube()); DEFINE_PROPERTY(PROP_CAN_CAST_SHADOW, CanCastShadow, canCastShadow, bool, ENTITY_ITEM_DEFAULT_CAN_CAST_SHADOW); DEFINE_PROPERTY(PROP_VISIBLE_IN_SECONDARY_CAMERA, IsVisibleInSecondaryCamera, isVisibleInSecondaryCamera, bool, ENTITY_ITEM_DEFAULT_VISIBLE_IN_SECONDARY_CAMERA); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index cce30c9614..0326268f55 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -20,6 +20,8 @@ enum EntityPropertyList { // Core properties PROP_SIMULATION_OWNER, + PROP_PARENT_ID, + PROP_PARENT_JOINT_INDEX, PROP_VISIBLE, PROP_NAME, PROP_LOCKED, @@ -34,8 +36,6 @@ enum EntityPropertyList { PROP_LAST_EDITED_BY, PROP_ENTITY_HOST_TYPE, // not sent over the wire PROP_OWNING_AVATAR_ID, // not sent over the wire - PROP_PARENT_ID, - PROP_PARENT_JOINT_INDEX, PROP_QUERY_AA_CUBE, PROP_CAN_CAST_SHADOW, PROP_VISIBLE_IN_SECONDARY_CAMERA, // not sent over the wire diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index bb8f375302..505ee26c0f 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -301,6 +301,31 @@ void ModelEntityItem::setModelURL(const QString& url) { }); } +glm::vec3 ModelEntityItem::getScaledDimensions() const { + glm::vec3 parentScale = getTransform().getScale(); + return _unscaledDimensions * parentScale; +} + +void ModelEntityItem::setScaledDimensions(const glm::vec3& value) { + glm::vec3 parentScale = getTransform().getScale(); + setUnscaledDimensions(value / parentScale); +} + +const Transform ModelEntityItem::getTransform() const { + bool success; + return getTransform(success); +} + +const Transform ModelEntityItem::getTransform(bool& success, int depth) const { + const Transform parentTransform = getParentTransform(success, depth); + Transform localTransform = getLocalTransform(); + localTransform.postScale(getModelScale()); + + Transform worldTransform; + Transform::mult(worldTransform, parentTransform, localTransform); + + return worldTransform; +} void ModelEntityItem::setCompoundShapeURL(const QString& url) { withWriteLock([&] { if (_compoundShapeURL.get() != url) { @@ -715,13 +740,13 @@ bool ModelEntityItem::applyNewAnimationProperties(AnimationPropertyGroup newProp } glm::vec3 ModelEntityItem::getModelScale() const { - return _modelScaleLock.resultWithReadLock([&] { - return getSNScale(); + return resultWithReadLock([&] { + return _modelScale; }); } void ModelEntityItem::setModelScale(const glm::vec3& modelScale) { - _modelScaleLock.withWriteLock([&] { - setSNScale(modelScale); + withWriteLock([&] { + _modelScale = modelScale; }); -} \ No newline at end of file +} diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index 234cfa435e..0efbbbb16c 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -66,6 +66,12 @@ public: static const QString DEFAULT_MODEL_URL; QString getModelURL() const; + virtual glm::vec3 getScaledDimensions() const override; + virtual void setScaledDimensions(const glm::vec3& value) override; + + virtual const Transform getTransform(bool& success, int depth = 0) const override; + virtual const Transform getTransform() const override; + static const QString DEFAULT_COMPOUND_SHAPE_URL; QString getCompoundShapeURL() const; @@ -144,7 +150,6 @@ protected: // they aren't currently updated from data in the model/rig, and they don't have a direct effect // on what's rendered. ReadWriteLockable _jointDataLock; - ReadWriteLockable _modelScaleLock; bool _jointRotationsExplicitlySet { false }; // were the joints set as a property or just side effect of animations bool _jointTranslationsExplicitlySet{ false }; // were the joints set as a property or just side effect of animations @@ -159,6 +164,7 @@ protected: int _lastKnownCurrentFrame{-1}; glm::u8vec3 _color; + glm::vec3 _modelScale; QString _modelURL; bool _relayParentJoints; bool _groupCulled { false }; diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index b6fcd2f2ce..4229a4a152 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -263,6 +263,7 @@ enum class EntityVersion : PacketVersion { ShowKeyboardFocusHighlight, WebBillboardMode, ModelScale, + ReOrderParentIDProperties, // Add new versions above here NUM_PACKET_TYPE, diff --git a/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js b/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js index e59b2e35ad..5dcfee23cb 100644 --- a/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js +++ b/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js @@ -117,11 +117,12 @@ Script.include("/~/system/libraries/utils.js"); this.previousParentID[this.grabbedThingID] = grabbedProperties.parentID; this.previousParentJointIndex[this.grabbedThingID] = grabbedProperties.parentJointIndex; } - Overlays.editOverlay(this.grabbedThingID, reparentProps); // resizeTablet to counter adjust offsets to account for change of scale from sensorToWorldMatrix if (HMD.tabletID && this.grabbedThingID === HMD.tabletID) { - resizeTablet(getTabletWidthFromSettings(), reparentProps.parentJointIndex); + reparentAndScaleTablet(getTabletWidthFromSettings(), reparentProps); + } else { + Entities.editEntity(this.grabbedThingID, reparentProps); } Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ @@ -140,7 +141,7 @@ Script.include("/~/system/libraries/utils.js"); }); } else if (!this.robbed){ // before we grabbed it, overlay was a child of something; put it back. - Overlays.editOverlay(this.grabbedThingID, { + Entities.editEntity(this.grabbedThingID, { parentID: this.previousParentID[this.grabbedThingID], parentJointIndex: this.previousParentJointIndex[this.grabbedThingID] }); diff --git a/scripts/system/libraries/WebTablet.js b/scripts/system/libraries/WebTablet.js index c0e3178521..c5ae9730f2 100644 --- a/scripts/system/libraries/WebTablet.js +++ b/scripts/system/libraries/WebTablet.js @@ -159,7 +159,7 @@ WebTablet = function (url, width, dpi, hand, location, visible) { url: url, localPosition: { x: 0, y: WEB_ENTITY_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET }, localRotation: Quat.angleAxis(180, Y_AXIS), - dimensions: {x: screenWidth, y: screenHeight, z: 0.1}, + dimensions: {x: screenWidth, y: screenHeight, z: 1.0}, dpi: tabletDpi, color: { red: 255, green: 255, blue: 255 }, alpha: 1.0, diff --git a/scripts/system/libraries/utils.js b/scripts/system/libraries/utils.js index 931c346299..6f74b43a8e 100644 --- a/scripts/system/libraries/utils.js +++ b/scripts/system/libraries/utils.js @@ -395,7 +395,7 @@ resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride) var tabletDpi = DEFAULT_DPI * (DEFAULT_WIDTH / tabletWidth); // update tablet model dimensions - Overlays.editOverlay(HMD.tabletID, { + Entities.editEntity(HMD.tabletID, { dimensions: { x: tabletWidth, y: tabletHeight, z: tabletDepth } }); @@ -405,9 +405,9 @@ resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride) var screenWidth = 0.9367 * tabletWidth; var screenHeight = 0.9000 * tabletHeight; var landscape = Tablet.getTablet("com.highfidelity.interface.tablet.system").landscape; - Overlays.editOverlay(HMD.tabletScreenID, { + Entities.editEntity(HMD.tabletScreenID, { localPosition: { x: 0, y: WEB_ENTITY_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET}, - dimensions: {x: landscape ? screenHeight : screenWidth, y: landscape ? screenWidth : screenHeight, z: 0.1}, + dimensions: {x: landscape ? screenHeight : screenWidth, y: landscape ? screenWidth : screenHeight, z: 1.0}, dpi: tabletDpi }); @@ -416,19 +416,83 @@ resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride) var HOME_BUTTON_X_OFFSET = 0.00079 * sensorScaleOffsetOverride * sensorScaleFactor; var HOME_BUTTON_Y_OFFSET = -1 * ((tabletHeight / 2) - (4.0 * tabletScaleFactor / 2)) * sensorScaleOffsetOverride; var HOME_BUTTON_Z_OFFSET = (tabletDepth / 1.9) * sensorScaleOffsetOverride; - Overlays.editOverlay(HMD.homeButtonID, { + Entities.editEntity(HMD.homeButtonID, { localPosition: { x: HOME_BUTTON_X_OFFSET, y: HOME_BUTTON_Y_OFFSET, z: -HOME_BUTTON_Z_OFFSET }, localRotation: { x: 0, y: 1, z: 0, w: 0 }, dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim } }); - Overlays.editOverlay(HMD.homeButtonHighlightID, { + Entities.editEntity(HMD.homeButtonHighlightID, { localPosition: { x: -HOME_BUTTON_X_OFFSET, y: HOME_BUTTON_Y_OFFSET, z: -HOME_BUTTON_Z_OFFSET }, localRotation: { x: 0, y: 1, z: 0, w: 0 }, dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim } }); }; + +reparentAndScaleTablet = function(width, reparentProps) { + + if (!HMD.tabletID || !HMD.tabletScreenID || !HMD.homeButtonID || !HMD.homeButtonHighlightID) { + return; + } + var sensorScaleFactor = MyAvatar.sensorToWorldScale; + var sensorScaleOffsetOverride = 1; + var SENSOR_TO_ROOM_MATRIX = 65534; + var parentJointIndex = reparentProps.parentJointIndex; + if (parentJointIndex === SENSOR_TO_ROOM_MATRIX) { + sensorScaleOffsetOverride = 1 / sensorScaleFactor; + } + + + // will need to be recaclulated if dimensions of fbx model change. + var TABLET_NATURAL_DIMENSIONS = {x: 32.083, y: 48.553, z: 2.269}; + var DEFAULT_DPI = 31; + var DEFAULT_WIDTH = 0.4375; + + // scale factor of natural tablet dimensions. + var tabletWidth = (width || DEFAULT_WIDTH) * sensorScaleFactor; + var tabletScaleFactor = tabletWidth / TABLET_NATURAL_DIMENSIONS.x; + var tabletHeight = TABLET_NATURAL_DIMENSIONS.y * tabletScaleFactor; + var tabletDepth = TABLET_NATURAL_DIMENSIONS.z * tabletScaleFactor; + var tabletDpi = DEFAULT_DPI * (DEFAULT_WIDTH / tabletWidth); + + // update tablet model dimensions + + Entities.editEntity(HMD.tabletID, { + parentID: reparentProps.parentID, + parentJointIndex: reparentProps.parentJointIndex, + dimensions: { x: tabletWidth, y: tabletHeight, z: tabletDepth} + }); + // update webOverlay + var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2.5) * sensorScaleOffsetOverride; + var WEB_ENTITY_Y_OFFSET = 1.25 * tabletScaleFactor * sensorScaleOffsetOverride; + var screenWidth = 0.9367 * tabletWidth; + var screenHeight = 0.9000 * tabletHeight; + var landscape = Tablet.getTablet("com.highfidelity.interface.tablet.system").landscape; + Entities.editEntity(HMD.tabletScreenID, { + localPosition: { x: 0, y: WEB_ENTITY_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET}, + dimensions: {x: landscape ? screenHeight : screenWidth, y: landscape ? screenWidth : screenHeight, z: 1.0}, + dpi: tabletDpi + }); + + // update homeButton + var homeButtonDim = 4.0 * tabletScaleFactor / 1.5; + var HOME_BUTTON_X_OFFSET = 0.00079 * sensorScaleOffsetOverride * sensorScaleFactor; + var HOME_BUTTON_Y_OFFSET = -1 * ((tabletHeight / 2) - (4.0 * tabletScaleFactor / 2)) * sensorScaleOffsetOverride; + var HOME_BUTTON_Z_OFFSET = (tabletDepth / 1.9) * sensorScaleOffsetOverride; + Entities.editEntity(HMD.homeButtonID, { + localPosition: { x: HOME_BUTTON_X_OFFSET, y: HOME_BUTTON_Y_OFFSET, z: -HOME_BUTTON_Z_OFFSET }, + localRotation: { x: 0, y: 1, z: 0, w: 0 }, + dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim } + }); + + Entities.editEntity(HMD.homeButtonHighlightID, { + localPosition: { x: -HOME_BUTTON_X_OFFSET, y: HOME_BUTTON_Y_OFFSET, z: -HOME_BUTTON_Z_OFFSET }, + localRotation: { x: 0, y: 1, z: 0, w: 0 }, + dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim } + }); +} + getMainTabletIDs = function () { var tabletIDs = []; if (HMD.tabletID) { From c7b04865ced2818b6b24c5ccf72b4c6fd9c8c7ec Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Thu, 28 Feb 2019 13:58:33 -0800 Subject: [PATCH 253/474] Remove debugging code, some thread safety changes --- domain-server/src/DomainServer.cpp | 5 -- libraries/networking/src/DomainHandler.cpp | 1 - libraries/networking/src/NodeList.cpp | 62 ++++++------------- libraries/networking/src/NodeList.h | 3 - .../networking/src/ThreadedAssignment.cpp | 11 ++-- 5 files changed, 23 insertions(+), 59 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 052648032d..258038b8f1 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1055,11 +1055,6 @@ void DomainServer::processListRequestPacket(QSharedPointer mess _gatekeeper.cleanupICEPeerForNode(sendingNode->getUUID()); } - if (sendingNode->getType() == NodeType::AvatarMixer) { - qWarning() << "Avatar Mixer Node Report in."; - } - - // guard against patched agents asking to hear about other agents auto safeInterestSet = nodeRequestData.interestList.toSet(); if (sendingNode->getType() == NodeType::Agent) { diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index af03a3e061..cfaf1b4c27 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -545,7 +545,6 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer MAX_SILENT_DOMAIN_SERVER_CHECK_INS) { // we haven't heard back from DS in MAX_SILENT_DOMAIN_SERVER_CHECK_INS // so emit our signal that says that diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 9f1f674501..c9d1757f0d 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -246,7 +246,6 @@ void NodeList::processICEPingPacket(QSharedPointer message) { void NodeList::reset(bool skipDomainHandlerReset) { if (thread() != QThread::currentThread()) { - QMetaObject::invokeMethod(this, "reset", Q_ARG(bool, skipDomainHandlerReset)); return; } @@ -292,30 +291,17 @@ void NodeList::addSetOfNodeTypesToNodeInterestSet(const NodeSet& setOfNodeTypes) void NodeList::sendDomainServerCheckIn() { - static bool foo = false; - - qWarning() << "Send Domain Server Checkin"; + // This function is called by the server check-in timer thread + // not the NodeList thread. Calling it on the NodeList thread + // resulted in starvation of the server check-in function. + // be VERY CAREFUL modifying this code as members of NodeList + // may be called by multiple threads. if (!_sendDomainServerCheckInEnabled) { qCDebug(networking) << "Refusing to send a domain-server check in while it is disabled."; return; } - _globalPostedEvents = getGlobalPostedEventCount(); - - if (false && thread() != QThread::currentThread()) { - qWarning() << "Transition threads on send domain server checkin"; - QMetaObject::invokeMethod(this, "sendDomainServerCheckIn", Qt::QueuedConnection); - - if (foo) { - qWarning() << "swapping threads before previous call completed"; - } - - foo = true; - return; - } - - foo = false; if (_isShuttingDown) { qCDebug(networking) << "Refusing to send a domain-server check in while shutting down."; return; @@ -329,17 +315,18 @@ void NodeList::sendDomainServerCheckIn() { handleICEConnectionToDomainServer(); // let the domain handler know we are due to send a checkin packet } else if (!_domainHandler.getIP().isNull() && !_domainHandler.checkInPacketTimeout()) { - - PacketType domainPacketType = !_domainHandler.isConnected() + bool domainIsConnected = _domainHandler.isConnected(); + HifiSockAddr domainSockAddr = _domainHandler.getSockAddr(); + PacketType domainPacketType = !domainIsConnected ? PacketType::DomainConnectRequest : PacketType::DomainListRequest; - if (!_domainHandler.isConnected()) { + if (!domainIsConnected) { qCDebug(networking) << "Sending connect request to domain-server at" << _domainHandler.getHostname(); // is this our localhost domain-server? // if so we need to make sure we have an up-to-date local port in case it restarted - if (_domainHandler.getSockAddr().getAddress() == QHostAddress::LocalHost + if (domainSockAddr.getAddress() == QHostAddress::LocalHost || _domainHandler.getHostname() == "localhost") { quint16 domainPort = DEFAULT_DOMAIN_SERVER_PORT; @@ -353,7 +340,7 @@ void NodeList::sendDomainServerCheckIn() { auto accountManager = DependencyManager::get(); const QUuid& connectionToken = _domainHandler.getConnectionToken(); - bool requiresUsernameSignature = !_domainHandler.isConnected() && !connectionToken.isNull(); + bool requiresUsernameSignature = !domainIsConnected && !connectionToken.isNull(); if (requiresUsernameSignature && !accountManager->getAccountInfo().hasPrivateKey()) { qWarning() << "A keypair is required to present a username signature to the domain-server" @@ -368,6 +355,7 @@ void NodeList::sendDomainServerCheckIn() { QDataStream packetStream(domainPacket.get()); + HifiSockAddr localSockAddr = _localSockAddr; if (domainPacketType == PacketType::DomainConnectRequest) { #if (PR_BUILD || DEV_BUILD) @@ -376,13 +364,9 @@ void NodeList::sendDomainServerCheckIn() { } #endif - QUuid connectUUID; + QUuid connectUUID = _domainHandler.getAssignmentUUID(); - if (!_domainHandler.getAssignmentUUID().isNull()) { - // this is a connect request and we're an assigned node - // so set our packetUUID as the assignment UUID - connectUUID = _domainHandler.getAssignmentUUID(); - } else if (_domainHandler.requiresICE()) { + if (connectUUID.isNull() && _domainHandler.requiresICE()) { // this is a connect request and we're an interface client // that used ice to discover the DS // so send our ICE client UUID with the connect request @@ -398,10 +382,9 @@ void NodeList::sendDomainServerCheckIn() { // if possible, include the MAC address for the current interface in our connect request QString hardwareAddress; - for (auto networkInterface : QNetworkInterface::allInterfaces()) { for (auto interfaceAddress : networkInterface.addressEntries()) { - if (interfaceAddress.ip() == _localSockAddr.getAddress()) { + if (interfaceAddress.ip() == localSockAddr.getAddress()) { // this is the interface whose local IP matches what we've detected the current IP to be hardwareAddress = networkInterface.hardwareAddress(); @@ -425,10 +408,10 @@ void NodeList::sendDomainServerCheckIn() { // pack our data to send to the domain-server including // the hostname information (so the domain-server can see which place name we came in on) - packetStream << _ownerType.load() << _publicSockAddr << _localSockAddr << _nodeTypesOfInterest.toList(); + packetStream << _ownerType.load() << _publicSockAddr << localSockAddr << _nodeTypesOfInterest.toList(); packetStream << DependencyManager::get()->getPlaceName(); - if (!_domainHandler.isConnected()) { + if (!domainIsConnected) { DataServerAccountInfo& accountInfo = accountManager->getAccountInfo(); packetStream << accountInfo.getUsername(); @@ -448,17 +431,10 @@ void NodeList::sendDomainServerCheckIn() { checkinCount = std::min(checkinCount, MAX_CHECKINS_TOGETHER); for (int i = 1; i < checkinCount; ++i) { auto packetCopy = domainPacket->createCopy(*domainPacket); - qWarning() << "Domain List/Connect"; - sendPacket(std::move(packetCopy), _domainHandler.getSockAddr()); + sendPacket(std::move(packetCopy), domainSockAddr); } - qWarning() << "Domain List/Connect"; - sendPacket(std::move(domainPacket), _domainHandler.getSockAddr()); + sendPacket(std::move(domainPacket), domainSockAddr); - } else if (_domainHandler.getIP().isNull()) { - qWarning() << "Domain Handler IP Is Null"; - } - else { - qWarning() << "Checkin packet timed out."; } } diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index fb14dae2d1..e135bc937d 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -100,8 +100,6 @@ public: virtual Node::LocalID getDomainLocalID() const override { return _domainHandler.getLocalID(); } virtual HifiSockAddr getDomainSockAddr() const override { return _domainHandler.getSockAddr(); } - int getGlobalPostedEventCount() { return _globalPostedEvents; } - public slots: void reset(bool skipDomainHandlerReset = false); void resetFromDomainHandler() { reset(true); } @@ -173,7 +171,6 @@ private: bool _isShuttingDown { false }; QTimer _keepAlivePingTimer; bool _requestsDomainListData { false }; - int _globalPostedEvents { 0 }; bool _sendDomainServerCheckInEnabled { true }; diff --git a/libraries/networking/src/ThreadedAssignment.cpp b/libraries/networking/src/ThreadedAssignment.cpp index a5141b3d28..9b9a53b469 100644 --- a/libraries/networking/src/ThreadedAssignment.cpp +++ b/libraries/networking/src/ThreadedAssignment.cpp @@ -37,7 +37,6 @@ ThreadedAssignment::ThreadedAssignment(ReceivedMessage& message) : // if the NL tells us we got a DS response, clear our member variable of queued check-ins auto nodeList = DependencyManager::get(); connect(nodeList.data(), &NodeList::receivedDomainServerList, this, &ThreadedAssignment::clearQueuedCheckIns); - timestamp = p_high_resolution_clock::now(); } void ThreadedAssignment::setFinished(bool isFinished) { @@ -106,8 +105,6 @@ void ThreadedAssignment::addPacketStatsAndSendStatsPacket(QJsonObject statsObjec QJsonObject assignmentStats; assignmentStats["numQueuedCheckIns"] = _numQueuedCheckIns; - assignmentStats["globalPostedEventCount"] = nodeList->getGlobalPostedEventCount(); - statsObject["assignmentStats"] = assignmentStats; nodeList->sendStatsToDomainServer(statsObject); @@ -121,16 +118,16 @@ void ThreadedAssignment::sendStatsPacket() { void ThreadedAssignment::checkInWithDomainServerOrExit() { // verify that the number of queued check-ins is not >= our max // the number of queued check-ins is cleared anytime we get a response from the domain-server - - timestamp = p_high_resolution_clock::now(); - if (_numQueuedCheckIns >= MAX_SILENT_DOMAIN_SERVER_CHECK_INS) { qCDebug(networking) << "At least" << MAX_SILENT_DOMAIN_SERVER_CHECK_INS << "have been queued without a response from domain-server" << "Stopping the current assignment"; stop(); } else { auto nodeList = DependencyManager::get(); - QMetaObject::invokeMethod(nodeList.data(), "sendDomainServerCheckIn", Qt::DirectConnection); + // Call sendDomainServerCheckIn directly instead of putting it on + // the event queue. Under high load, the event queue can back up + // longer than the total timeout period and cause a restart + nodeList->sendDomainServerCheckIn(); // increase the number of queued check ins _numQueuedCheckIns++; From 15064f0097d49126271e9187800360120b75dce2 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 28 Feb 2019 14:04:22 -0800 Subject: [PATCH 254/474] fix lasers and keyboard on reload --- interface/src/Application.cpp | 5 +++++ interface/src/ui/Keyboard.cpp | 8 ++++++++ interface/src/ui/Keyboard.h | 2 ++ 3 files changed, 15 insertions(+) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ebc1176ee1..bfae3e7303 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5770,6 +5770,11 @@ void Application::reloadResourceCaches() { DependencyManager::get()->reset(); // Force redownload of .fst models + DependencyManager::get()->reloadAllScripts(); + getOffscreenUI()->clearCache(); + + DependencyManager::get()->createKeyboard(); + getMyAvatar()->resetFullAvatarURL(); } diff --git a/interface/src/ui/Keyboard.cpp b/interface/src/ui/Keyboard.cpp index 1ff1c0248b..d344e27d54 100644 --- a/interface/src/ui/Keyboard.cpp +++ b/interface/src/ui/Keyboard.cpp @@ -259,6 +259,12 @@ void Keyboard::setUse3DKeyboard(bool use) { void Keyboard::createKeyboard() { auto pointerManager = DependencyManager::get(); + if (_created) { + pointerManager->removePointer(_leftHandStylus); + pointerManager->removePointer(_rightHandStylus); + clearKeyboardKeys(); + } + QVariantMap modelProperties { { "url", MALLET_MODEL_URL } }; @@ -289,6 +295,8 @@ void Keyboard::createKeyboard() { loadKeyboardFile(keyboardSvg); _keySound = DependencyManager::get()->getSound(SOUND_FILE); + + _created = true; } bool Keyboard::isRaised() const { diff --git a/interface/src/ui/Keyboard.h b/interface/src/ui/Keyboard.h index 627eb68dfd..b3358e486d 100644 --- a/interface/src/ui/Keyboard.h +++ b/interface/src/ui/Keyboard.h @@ -193,6 +193,8 @@ private: QSet _itemsToIgnore; std::vector> _keyboardLayers; + + bool _created { false }; }; #endif From 7d9efe4c091521e8c3a0362a8cd673410a072263 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 28 Feb 2019 14:20:11 -0800 Subject: [PATCH 255/474] avoid mem-leak for serverless domains --- interface/src/Application.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 83b287b7ae..9913e0d128 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3773,9 +3773,12 @@ std::map Application::prepareServerlessDomainContents(QUrl dom tmpTree->reaverageOctreeElements(); tmpTree->sendEntities(&_entityEditSender, getEntities()->getTree(), 0, 0, 0); } + std::map namedPaths = tmpTree->getNamedPaths(); - return tmpTree->getNamedPaths(); + // we must manually eraseAllOctreeElements(false) else the tmpTree will mem-leak + tmpTree->eraseAllOctreeElements(false); + return namedPaths; } void Application::loadServerlessDomain(QUrl domainURL) { From ed9ebf84c971ed040612d6cc7075ae6d9cd521bd Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 28 Feb 2019 14:21:14 -0800 Subject: [PATCH 256/474] remove unuseful log entry --- libraries/entities-renderer/src/EntityTreeRenderer.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index efd1399f30..e54258fc3e 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -232,8 +232,6 @@ void EntityTreeRenderer::clearNonLocalEntities() { } } scene->enqueueTransaction(transaction); - } else { - qCWarning(entitiesrenderer) << "EntitityTreeRenderer::clear(), Unexpected null scene, possibly during application shutdown"; } _renderablesToUpdate = savedEntities; From 33d6e2ce3b2e562bdac33368d7a7750113e3bd3b Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 28 Feb 2019 14:21:35 -0800 Subject: [PATCH 257/474] avoid mem-leaked entities when clearing tree --- libraries/entities/src/EntityTree.cpp | 40 +++++++++++++++++++-------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 53b4fb4fe4..8855bc28a7 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -52,7 +52,15 @@ EntityTree::EntityTree(bool shouldReaverage) : } EntityTree::~EntityTree() { - eraseAllOctreeElements(false); + // NOTE: to eraseAllOctreeElements() in this context is useless because + // any OctreeElements in the tree still have shared backpointers to this Tree + // which means the dtor never would have been called in the first place! + // + // I'm keeping this useless commented-out line to remind us: + // we don't need shared pointer overhead for EntityTrees. + // TODO: EntityTreeElement::_tree should be raw back pointer. + // AND: EntityItem::_element should be a raw back pointer. + //eraseAllOctreeElements(false); // KEEP THIS } void EntityTree::setEntityScriptSourceWhitelist(const QString& entityScriptSourceWhitelist) { @@ -74,16 +82,15 @@ void EntityTree::eraseNonLocalEntities() { emit clearingEntities(); if (_simulation) { - // This will clear all entities host types including local entities, because local entities - // are not in the physics simulation + // local entities are not in the simulation, so we clear ALL _simulation->clearEntities(); } - _staleProxies.clear(); - QHash localMap; - localMap.swap(_entityMap); - QHash savedEntities; this->withWriteLock([&] { - foreach(EntityItemPointer entity, localMap) { + QHash savedEntities; + // NOTE: lock the Tree first, then lock the _entityMap. + // It should never be done the other way around. + QReadLocker locker(&_entityMapLock); + foreach(EntityItemPointer entity, _entityMap) { EntityTreeElementPointer element = entity->getElement(); if (element) { element->cleanupNonLocalEntities(); @@ -91,11 +98,16 @@ void EntityTree::eraseNonLocalEntities() { if (entity->isLocalEntity()) { savedEntities[entity->getEntityItemID()] = entity; + } else { + int32_t spaceIndex = entity->getSpaceIndex(); + if (spaceIndex != -1) { + // stale spaceIndices will be freed later + _staleProxies.push_back(spaceIndex); + } } } + _entityMap.swap(savedEntities); }); - localMap.clear(); - _entityMap = savedEntities; resetClientEditStats(); clearDeletedEntities(); @@ -113,13 +125,13 @@ void EntityTree::eraseNonLocalEntities() { _needsParentFixup = localEntitiesNeedsParentFixup; } } + void EntityTree::eraseAllOctreeElements(bool createNewRoot) { emit clearingEntities(); if (_simulation) { _simulation->clearEntities(); } - _staleProxies.clear(); QHash localMap; localMap.swap(_entityMap); this->withWriteLock([&] { @@ -128,6 +140,11 @@ void EntityTree::eraseAllOctreeElements(bool createNewRoot) { if (element) { element->cleanupEntities(); } + int32_t spaceIndex = entity->getSpaceIndex(); + if (spaceIndex != -1) { + // assume stale spaceIndices will be freed later + _staleProxies.push_back(spaceIndex); + } } }); localMap.clear(); @@ -767,6 +784,7 @@ void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator) // keep a record of valid stale spaceIndices so they can be removed from the Space int32_t spaceIndex = theEntity->getSpaceIndex(); if (spaceIndex != -1) { + // stale spaceIndices will be freed later _staleProxies.push_back(spaceIndex); } } From 6aede024f40a23eada9ea5e2a07a613f84b3f7e4 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Thu, 28 Feb 2019 23:22:55 +0100 Subject: [PATCH 258/474] use fst mapping path as reference instead of model path --- .../model-baker/src/model-baker/Baker.cpp | 7 ++-- libraries/model-baker/src/model-baker/Baker.h | 3 +- .../model-baker/src/model-baker/BakerTypes.h | 2 ++ .../model-baker/ParseMaterialMappingTask.cpp | 4 +-- .../model-baker/ParseMaterialMappingTask.h | 3 +- .../src/model-baker/PrepareJointsTask.cpp | 4 +-- .../src/model-baker/PrepareJointsTask.h | 3 +- .../src/model-networking/ModelCache.cpp | 34 ++++++++++--------- .../src/model-networking/ModelCache.h | 9 +++-- 9 files changed, 40 insertions(+), 29 deletions(-) diff --git a/libraries/model-baker/src/model-baker/Baker.cpp b/libraries/model-baker/src/model-baker/Baker.cpp index f47a9dcd62..c0c473315d 100644 --- a/libraries/model-baker/src/model-baker/Baker.cpp +++ b/libraries/model-baker/src/model-baker/Baker.cpp @@ -117,7 +117,7 @@ namespace baker { class BakerEngineBuilder { public: - using Input = VaryingSet2; + using Input = VaryingSet2; using Output = VaryingSet2; using JobModel = Task::ModelIO; void build(JobModel& model, const Varying& input, Varying& output) { @@ -155,8 +155,7 @@ namespace baker { const auto jointIndices = jointInfoOut.getN(2); // Parse material mapping - const auto parseMaterialMappingInputs = ParseMaterialMappingTask::Input(url, mapping).asVarying(); - const auto materialMapping = model.addJob("ParseMaterialMapping", parseMaterialMappingInputs); + const auto materialMapping = model.addJob("ParseMaterialMapping", mapping); // Combine the outputs into a new hfm::Model const auto buildBlendshapesInputs = BuildBlendshapesTask::Input(blendshapesPerMeshIn, normalsPerBlendshapePerMesh, tangentsPerBlendshapePerMesh).asVarying(); @@ -170,7 +169,7 @@ namespace baker { } }; - Baker::Baker(const hfm::Model::Pointer& hfmModel, const QVariantHash& mapping) : + Baker::Baker(const hfm::Model::Pointer& hfmModel, const GeometryMappingPair& mapping) : _engine(std::make_shared(BakerEngineBuilder::JobModel::create("Baker"), std::make_shared())) { _engine->feedInput(0, hfmModel); _engine->feedInput(1, mapping); diff --git a/libraries/model-baker/src/model-baker/Baker.h b/libraries/model-baker/src/model-baker/Baker.h index 542be0b559..856b5f0142 100644 --- a/libraries/model-baker/src/model-baker/Baker.h +++ b/libraries/model-baker/src/model-baker/Baker.h @@ -17,13 +17,14 @@ #include #include "Engine.h" +#include "BakerTypes.h" #include "ParseMaterialMappingTask.h" namespace baker { class Baker { public: - Baker(const hfm::Model::Pointer& hfmModel, const QVariantHash& mapping); + Baker(const hfm::Model::Pointer& hfmModel, const GeometryMappingPair& mapping); void run(); diff --git a/libraries/model-baker/src/model-baker/BakerTypes.h b/libraries/model-baker/src/model-baker/BakerTypes.h index 5d14ee5420..8b80b0bde4 100644 --- a/libraries/model-baker/src/model-baker/BakerTypes.h +++ b/libraries/model-baker/src/model-baker/BakerTypes.h @@ -12,6 +12,7 @@ #ifndef hifi_BakerTypes_h #define hifi_BakerTypes_h +#include #include namespace baker { @@ -35,6 +36,7 @@ namespace baker { using TangentsPerBlendshape = std::vector>; using MeshIndicesToModelNames = QHash; + using GeometryMappingPair = std::pair; }; #endif // hifi_BakerTypes_h diff --git a/libraries/model-baker/src/model-baker/ParseMaterialMappingTask.cpp b/libraries/model-baker/src/model-baker/ParseMaterialMappingTask.cpp index f8634e4170..0a1964d8cd 100644 --- a/libraries/model-baker/src/model-baker/ParseMaterialMappingTask.cpp +++ b/libraries/model-baker/src/model-baker/ParseMaterialMappingTask.cpp @@ -11,8 +11,8 @@ #include "ModelBakerLogging.h" void ParseMaterialMappingTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) { - const auto& url = input.get0(); - const auto& mapping = input.get1(); + const auto& url = input.first; + const auto& mapping = input.second; MaterialMapping materialMapping; auto mappingIter = mapping.find("materialMap"); diff --git a/libraries/model-baker/src/model-baker/ParseMaterialMappingTask.h b/libraries/model-baker/src/model-baker/ParseMaterialMappingTask.h index 8ad98edeb9..5f5eff327d 100644 --- a/libraries/model-baker/src/model-baker/ParseMaterialMappingTask.h +++ b/libraries/model-baker/src/model-baker/ParseMaterialMappingTask.h @@ -14,12 +14,13 @@ #include #include "Engine.h" +#include "BakerTypes.h" #include class ParseMaterialMappingTask { public: - using Input = baker::VaryingSet2 ; + using Input = baker::GeometryMappingPair; using Output = MaterialMapping; using JobModel = baker::Job::ModelIO; diff --git a/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp b/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp index 3b1a57cb43..a896766058 100644 --- a/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp +++ b/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp @@ -58,7 +58,7 @@ void PrepareJointsTask::run(const baker::BakeContextPointer& context, const Inpu auto& jointIndices = output.edit2(); // Get joint renames - auto jointNameMapping = getJointNameMapping(mapping); + auto jointNameMapping = getJointNameMapping(mapping.second); // Apply joint metadata from FST file mappings for (const auto& jointIn : jointsIn) { jointsOut.push_back(jointIn); @@ -73,7 +73,7 @@ void PrepareJointsTask::run(const baker::BakeContextPointer& context, const Inpu } // Get joint rotation offsets from FST file mappings - auto offsets = getJointRotationOffsets(mapping); + auto offsets = getJointRotationOffsets(mapping.second); for (auto itr = offsets.begin(); itr != offsets.end(); itr++) { QString jointName = itr.key(); int jointIndex = jointIndices.value(jointName) - 1; diff --git a/libraries/model-baker/src/model-baker/PrepareJointsTask.h b/libraries/model-baker/src/model-baker/PrepareJointsTask.h index e12d8ffd2c..b18acdfceb 100644 --- a/libraries/model-baker/src/model-baker/PrepareJointsTask.h +++ b/libraries/model-baker/src/model-baker/PrepareJointsTask.h @@ -17,10 +17,11 @@ #include #include "Engine.h" +#include "BakerTypes.h" class PrepareJointsTask { public: - using Input = baker::VaryingSet2, QVariantHash /*mapping*/>; + using Input = baker::VaryingSet2, baker::GeometryMappingPair /*mapping*/>; using Output = baker::VaryingSet3, QMap /*jointRotationOffsets*/, QHash /*jointIndices*/>; using JobModel = baker::Job::ModelIO; diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index 581196b2cc..8852e2a262 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -35,11 +35,13 @@ class GeometryReader; class GeometryExtra { public: - const QVariantHash& mapping; + const GeometryMappingPair& mapping; const QUrl& textureBaseUrl; bool combineParts; }; +int geometryMappingPairTypeId = qRegisterMetaType("GeometryMappingPair"); + // From: https://stackoverflow.com/questions/41145012/how-to-hash-qvariant class QVariantHasher { public: @@ -78,7 +80,7 @@ namespace std { struct hash { size_t operator()(const GeometryExtra& geometryExtra) const { size_t result = 0; - hash_combine(result, geometryExtra.mapping, geometryExtra.textureBaseUrl, geometryExtra.combineParts); + hash_combine(result, geometryExtra.mapping.second, geometryExtra.textureBaseUrl, geometryExtra.combineParts); return result; } }; @@ -151,7 +153,7 @@ void GeometryMappingResource::downloadFinished(const QByteArray& data) { } auto modelCache = DependencyManager::get(); - GeometryExtra extra { _mapping, _textureBaseUrl, false }; + GeometryExtra extra { GeometryMappingPair(_url, _mapping), _textureBaseUrl, false }; // Get the raw GeometryResource _geometryResource = modelCache->getResource(url, QUrl(), &extra, std::hash()(extra)).staticCast(); @@ -191,7 +193,7 @@ void GeometryMappingResource::onGeometryMappingLoaded(bool success) { class GeometryReader : public QRunnable { public: - GeometryReader(const ModelLoader& modelLoader, QWeakPointer& resource, const QUrl& url, const QVariantHash& mapping, + GeometryReader(const ModelLoader& modelLoader, QWeakPointer& resource, const QUrl& url, const GeometryMappingPair& mapping, const QByteArray& data, bool combineParts, const QString& webMediaType) : _modelLoader(modelLoader), _resource(resource), _url(url), _mapping(mapping), _data(data), _combineParts(combineParts), _webMediaType(webMediaType) { @@ -204,7 +206,7 @@ private: ModelLoader _modelLoader; QWeakPointer _resource; QUrl _url; - QVariantHash _mapping; + GeometryMappingPair _mapping; QByteArray _data; bool _combineParts; QString _webMediaType; @@ -244,7 +246,7 @@ void GeometryReader::run() { } HFMModel::Pointer hfmModel; - QVariantHash serializerMapping = _mapping; + QVariantHash serializerMapping = _mapping.second; serializerMapping["combineParts"] = _combineParts; if (_url.path().toLower().endsWith(".gz")) { @@ -270,15 +272,14 @@ void GeometryReader::run() { } // Add scripts to hfmModel - if (!_mapping.value(SCRIPT_FIELD).isNull()) { - QVariantList scripts = _mapping.values(SCRIPT_FIELD); + if (!serializerMapping.value(SCRIPT_FIELD).isNull()) { + QVariantList scripts = serializerMapping.values(SCRIPT_FIELD); for (auto &script : scripts) { hfmModel->scripts.push_back(script.toString()); } } - QMetaObject::invokeMethod(resource.data(), "setGeometryDefinition", - Q_ARG(HFMModel::Pointer, hfmModel), Q_ARG(QVariantHash, _mapping)); + Q_ARG(HFMModel::Pointer, hfmModel), Q_ARG(GeometryMappingPair, _mapping)); } catch (const std::exception&) { auto resource = _resource.toStrongRef(); if (resource) { @@ -312,17 +313,17 @@ public: void setExtra(void* extra) override; protected: - Q_INVOKABLE void setGeometryDefinition(HFMModel::Pointer hfmModel, QVariantHash mapping); + Q_INVOKABLE void setGeometryDefinition(HFMModel::Pointer hfmModel, const GeometryMappingPair& mapping); private: ModelLoader _modelLoader; - QVariantHash _mapping; + GeometryMappingPair _mapping; bool _combineParts; }; void GeometryDefinitionResource::setExtra(void* extra) { const GeometryExtra* geometryExtra = static_cast(extra); - _mapping = geometryExtra ? geometryExtra->mapping : QVariantHash(); + _mapping = geometryExtra ? geometryExtra->mapping : GeometryMappingPair(QUrl(), QVariantHash()); _textureBaseUrl = geometryExtra ? resolveTextureBaseUrl(_url, geometryExtra->textureBaseUrl) : QUrl(); _combineParts = geometryExtra ? geometryExtra->combineParts : true; } @@ -335,7 +336,7 @@ void GeometryDefinitionResource::downloadFinished(const QByteArray& data) { QThreadPool::globalInstance()->start(new GeometryReader(_modelLoader, _self, _effectiveBaseURL, _mapping, data, _combineParts, _request->getWebMediaType())); } -void GeometryDefinitionResource::setGeometryDefinition(HFMModel::Pointer hfmModel, QVariantHash mapping) { +void GeometryDefinitionResource::setGeometryDefinition(HFMModel::Pointer hfmModel, const GeometryMappingPair& mapping) { // Do processing on the model baker::Baker modelBaker(hfmModel, mapping); modelBaker.run(); @@ -398,7 +399,7 @@ QSharedPointer ModelCache::createResourceCopy(const QSharedPointer()(geometryExtra)).staticCast(); @@ -411,7 +412,8 @@ GeometryResource::Pointer ModelCache::getGeometryResource(const QUrl& url, } GeometryResource::Pointer ModelCache::getCollisionGeometryResource(const QUrl& url, - const QVariantHash& mapping, const QUrl& textureBaseUrl) { + const GeometryMappingPair& mapping, + const QUrl& textureBaseUrl) { bool combineParts = false; GeometryExtra geometryExtra = { mapping, textureBaseUrl, combineParts }; GeometryResource::Pointer resource = getResource(url, QUrl(), &geometryExtra, std::hash()(geometryExtra)).staticCast(); diff --git a/libraries/model-networking/src/model-networking/ModelCache.h b/libraries/model-networking/src/model-networking/ModelCache.h index 4cd7048dca..ca1ceaff16 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.h +++ b/libraries/model-networking/src/model-networking/ModelCache.h @@ -26,6 +26,9 @@ class MeshPart; class GeometryMappingResource; +using GeometryMappingPair = std::pair; +Q_DECLARE_METATYPE(GeometryMappingPair) + class Geometry { public: using Pointer = std::shared_ptr; @@ -145,11 +148,13 @@ class ModelCache : public ResourceCache, public Dependency { public: GeometryResource::Pointer getGeometryResource(const QUrl& url, - const QVariantHash& mapping = QVariantHash(), + const GeometryMappingPair& mapping = + GeometryMappingPair(QUrl(), QVariantHash()), const QUrl& textureBaseUrl = QUrl()); GeometryResource::Pointer getCollisionGeometryResource(const QUrl& url, - const QVariantHash& mapping = QVariantHash(), + const GeometryMappingPair& mapping = + GeometryMappingPair(QUrl(), QVariantHash()), const QUrl& textureBaseUrl = QUrl()); protected: From c721d68ec3e03374f3b63a96aa7bbff5686ac527 Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Thu, 28 Feb 2019 14:35:29 -0800 Subject: [PATCH 259/474] Remove unneeded cruft --- libraries/networking/src/DomainHandler.cpp | 1 + libraries/networking/src/ThreadedAssignment.h | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index cfaf1b4c27..2513510b05 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -545,6 +545,7 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer MAX_SILENT_DOMAIN_SERVER_CHECK_INS) { // we haven't heard back from DS in MAX_SILENT_DOMAIN_SERVER_CHECK_INS // so emit our signal that says that diff --git a/libraries/networking/src/ThreadedAssignment.h b/libraries/networking/src/ThreadedAssignment.h index 8fade4dd89..e76533b2a1 100644 --- a/libraries/networking/src/ThreadedAssignment.h +++ b/libraries/networking/src/ThreadedAssignment.h @@ -48,7 +48,6 @@ protected: QTimer _domainServerTimer; QTimer _statsTimer; int _numQueuedCheckIns { 0 }; - p_high_resolution_clock::time_point timestamp; protected slots: void domainSettingsRequestFailed(); From 8a7853a701a2b4790f77b05d49e94b7e7fbd48d6 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 28 Feb 2019 14:38:13 -0800 Subject: [PATCH 260/474] remove crufty comment --- libraries/entities/src/EntityTree.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 8855bc28a7..6e404ce690 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -781,7 +781,6 @@ void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator) _simulation->prepareEntityForDelete(theEntity); } - // keep a record of valid stale spaceIndices so they can be removed from the Space int32_t spaceIndex = theEntity->getSpaceIndex(); if (spaceIndex != -1) { // stale spaceIndices will be freed later From ff8cb27256bea4a9c82bdc43e1088592130b557f Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Thu, 28 Feb 2019 23:45:08 +0100 Subject: [PATCH 261/474] include fst URL in the GeometryExtra hash --- libraries/model-networking/src/model-networking/ModelCache.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index 8852e2a262..9e0df4a3c7 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -80,7 +80,8 @@ namespace std { struct hash { size_t operator()(const GeometryExtra& geometryExtra) const { size_t result = 0; - hash_combine(result, geometryExtra.mapping.second, geometryExtra.textureBaseUrl, geometryExtra.combineParts); + hash_combine(result, geometryExtra.mapping.first, geometryExtra.mapping.second, geometryExtra.textureBaseUrl, + geometryExtra.combineParts); return result; } }; From 9fe7a3350c619d14a4514b3808d41580e8f9478a Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 28 Feb 2019 13:10:10 -0800 Subject: [PATCH 262/474] Fix windows compile error --- assignment-client/src/avatars/AvatarMixer.cpp | 2 +- libraries/networking/src/LimitedNodeList.cpp | 18 +++++++++--------- libraries/networking/src/NodeList.cpp | 5 ++--- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 7c21daefc3..dc3570c7b3 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -908,7 +908,7 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) { const QString CONNECTION_RATE = "connection_rate"; auto nodeList = DependencyManager::get(); auto defaultConnectionRate = nodeList->getMaxConnectionRate(); - int connectionRate = avatarMixerGroupObject[CONNECTION_RATE].toInt(defaultConnectionRate); + int connectionRate = avatarMixerGroupObject[CONNECTION_RATE].toInt((int)defaultConnectionRate); nodeList->setMaxConnectionRate(connectionRate); } diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index f00614141c..01c7a16166 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -41,7 +41,7 @@ static Setting::Handle LIMITED_NODELIST_LOCAL_PORT("LimitedNodeList.LocalPort", 0); using namespace std::chrono_literals; -static const std::chrono::milliseconds CONNECTION_RATE_INTERVAL = 1s; +static const std::chrono::milliseconds CONNECTION_RATE_INTERVAL_MS = 1s; const std::set SOLO_NODE_TYPES = { NodeType::AvatarMixer, @@ -94,7 +94,7 @@ LimitedNodeList::LimitedNodeList(int socketListenPort, int dtlsListenPort) : // Flush delayed adds every second QTimer* delayedAddsFlushTimer = new QTimer(this); connect(delayedAddsFlushTimer, &QTimer::timeout, this, &NodeList::processDelayedAdds); - delayedAddsFlushTimer->start(CONNECTION_RATE_INTERVAL); + delayedAddsFlushTimer->start(CONNECTION_RATE_INTERVAL_MS.count()); // check the local socket right now updateLocalSocket(); @@ -848,13 +848,13 @@ unsigned int LimitedNodeList::broadcastToNodes(std::unique_ptr packet, eachNode([&](const SharedNodePointer& node){ if (node && destinationNodeTypes.contains(node->getType())) { - if (packet->isReliable()) { - auto packetCopy = NLPacket::createCopy(*packet); - sendPacket(std::move(packetCopy), *node); - } else { - sendUnreliablePacket(*packet, *node); - } - ++n; + if (packet->isReliable()) { + auto packetCopy = NLPacket::createCopy(*packet); + sendPacket(std::move(packetCopy), *node); + } else { + sendUnreliablePacket(*packet, *node); + } + ++n; } }); diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 4097ddf1a3..d45e466291 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -37,8 +37,7 @@ #include "SharedUtil.h" #include -using namespace std::chrono_literals; -static const std::chrono::milliseconds KEEPALIVE_PING_INTERVAL = 1s; +const int KEEPALIVE_PING_INTERVAL_MS = 1000; NodeList::NodeList(char newOwnerType, int socketListenPort, int dtlsListenPort) : LimitedNodeList(socketListenPort, dtlsListenPort), @@ -105,7 +104,7 @@ NodeList::NodeList(char newOwnerType, int socketListenPort, int dtlsListenPort) connect(this, &LimitedNodeList::nodeActivated, this, &NodeList::maybeSendIgnoreSetToNode); // setup our timer to send keepalive pings (it's started and stopped on domain connect/disconnect) - _keepAlivePingTimer.setInterval(KEEPALIVE_PING_INTERVAL); // 1s, Qt::CoarseTimer acceptable + _keepAlivePingTimer.setInterval(KEEPALIVE_PING_INTERVAL_MS); // 1s, Qt::CoarseTimer acceptable connect(&_keepAlivePingTimer, &QTimer::timeout, this, &NodeList::sendKeepAlivePings); connect(&_domainHandler, SIGNAL(connectedToDomain(QUrl)), &_keepAlivePingTimer, SLOT(start())); connect(&_domainHandler, &DomainHandler::disconnectedFromDomain, &_keepAlivePingTimer, &QTimer::stop); From 1476251dcb359f335084e4413f6f4c1634085a7f Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Thu, 28 Feb 2019 15:21:24 -0800 Subject: [PATCH 263/474] Enable bubble on AFK, respect user setting upon return from AFK. --- scripts/system/away.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/scripts/system/away.js b/scripts/system/away.js index 45b6f43b73..3deb2249be 100644 --- a/scripts/system/away.js +++ b/scripts/system/away.js @@ -65,7 +65,7 @@ var eventMappingName = "io.highfidelity.away"; // goActive on hand controller bu var eventMapping = Controller.newMapping(eventMappingName); var avatarPosition = MyAvatar.position; var wasHmdMounted = HMD.mounted; - +var previousBubbleState = Users.getIgnoreRadiusEnabled(); // some intervals we may create/delete var avatarMovedInterval; @@ -166,7 +166,12 @@ function goAway(fromStartup) { avatarMovedInterval = Script.setInterval(ifAvatarMovedGoActive, BASIC_TIMER_INTERVAL); }, WAIT_FOR_MOVE_ON_STARTUP); } - + + previousBubbleState = Users.getIgnoreRadiusEnabled(); + if (!previousBubbleState) { + Users.toggleIgnoreRadius(); + } + UserActivityLogger.bubbleToggled(Users.getIgnoreRadiusEnabled()); UserActivityLogger.toggledAway(true); MyAvatar.isAway = true; } @@ -179,6 +184,11 @@ function goActive() { UserActivityLogger.toggledAway(false); MyAvatar.isAway = false; + if (Users.getIgnoreRadiusEnabled() !== previousBubbleState) { + Users.toggleIgnoreRadius(); + UserActivityLogger.bubbleToggled(Users.getIgnoreRadiusEnabled()); + } + if (!Window.hasFocus()) { Window.setFocus(); } From 9ea41f7122ab0aa4ddb86cc11d278195e843e36e Mon Sep 17 00:00:00 2001 From: Clement Date: Thu, 28 Feb 2019 15:22:07 -0800 Subject: [PATCH 264/474] CR --- libraries/networking/src/LimitedNodeList.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index fbd37ae065..18b9a318ef 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -318,7 +318,7 @@ public: void sendFakedHandshakeRequestToNode(SharedNodePointer node); #endif - size_t getMaxConnectionRate() { return _maxConnectionRate; } + size_t getMaxConnectionRate() const { return _maxConnectionRate; } void setMaxConnectionRate(size_t rate) { _maxConnectionRate = rate; } int getInboundPPS() const { return _inboundPPS; } From d33400f6ad114e85086d2b738e0f8f85fd94cc4b Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 28 Feb 2019 15:25:15 -0800 Subject: [PATCH 265/474] fix GCC compiler warning --- libraries/physics/src/ObjectActionTractor.cpp | 2 +- libraries/shared/src/NestableTransformNode.h | 2 +- libraries/shared/src/SpatiallyNestable.cpp | 22 +++++++++---------- libraries/shared/src/SpatiallyNestable.h | 6 ++--- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/libraries/physics/src/ObjectActionTractor.cpp b/libraries/physics/src/ObjectActionTractor.cpp index a46aac3f29..c7681e217c 100644 --- a/libraries/physics/src/ObjectActionTractor.cpp +++ b/libraries/physics/src/ObjectActionTractor.cpp @@ -60,7 +60,7 @@ bool ObjectActionTractor::getTarget(float deltaTimeStep, glm::quat& rotation, gl } if (other && otherIsReady) { bool success; - glm::vec3 otherWorldPosition = other->getWorldPosition(_otherJointIndex, success); + glm::vec3 otherWorldPosition = other->getJointWorldPosition(_otherJointIndex, success); if (!success) { linearTimeScale = FLT_MAX; angularTimeScale = FLT_MAX; diff --git a/libraries/shared/src/NestableTransformNode.h b/libraries/shared/src/NestableTransformNode.h index be017a696d..a584bcd308 100644 --- a/libraries/shared/src/NestableTransformNode.h +++ b/libraries/shared/src/NestableTransformNode.h @@ -32,7 +32,7 @@ public: } bool success; - Transform jointWorldTransform = nestable->getTransform(_jointIndex, success); + Transform jointWorldTransform = nestable->getJointTransform(_jointIndex, success); if (!success) { return Transform(); diff --git a/libraries/shared/src/SpatiallyNestable.cpp b/libraries/shared/src/SpatiallyNestable.cpp index e7bdc87bf3..d202afc59f 100644 --- a/libraries/shared/src/SpatiallyNestable.cpp +++ b/libraries/shared/src/SpatiallyNestable.cpp @@ -92,7 +92,7 @@ Transform SpatiallyNestable::getParentTransform(bool& success, int depth) const return result; } if (parent) { - result = parent->getTransform(_parentJointIndex, success, depth + 1); + result = parent->getJointTransform(_parentJointIndex, success, depth + 1); if (getScalesWithParent()) { result.setScale(parent->scaleForChildren()); } @@ -203,7 +203,7 @@ glm::vec3 SpatiallyNestable::worldToLocal(const glm::vec3& position, } if (parent) { - parentTransform = parent->getTransform(parentJointIndex, success); + parentTransform = parent->getJointTransform(parentJointIndex, success); if (!success) { return glm::vec3(0.0f); } @@ -240,7 +240,7 @@ glm::quat SpatiallyNestable::worldToLocal(const glm::quat& orientation, } if (parent) { - parentTransform = parent->getTransform(parentJointIndex, success); + parentTransform = parent->getJointTransform(parentJointIndex, success); if (!success) { return glm::quat(); } @@ -350,7 +350,7 @@ glm::vec3 SpatiallyNestable::localToWorld(const glm::vec3& position, } if (parent) { - parentTransform = parent->getTransform(parentJointIndex, success); + parentTransform = parent->getJointTransform(parentJointIndex, success); if (!success) { return glm::vec3(0.0f); } @@ -390,7 +390,7 @@ glm::quat SpatiallyNestable::localToWorld(const glm::quat& orientation, } if (parent) { - parentTransform = parent->getTransform(parentJointIndex, success); + parentTransform = parent->getJointTransform(parentJointIndex, success); if (!success) { return glm::quat(); } @@ -525,8 +525,8 @@ glm::vec3 SpatiallyNestable::getWorldPosition() const { return result; } -glm::vec3 SpatiallyNestable::getWorldPosition(int jointIndex, bool& success) const { - return getTransform(jointIndex, success).getTranslation(); +glm::vec3 SpatiallyNestable::getJointWorldPosition(int jointIndex, bool& success) const { + return getJointTransform(jointIndex, success).getTranslation(); } void SpatiallyNestable::setWorldPosition(const glm::vec3& position, bool& success, bool tellPhysics) { @@ -579,7 +579,7 @@ glm::quat SpatiallyNestable::getWorldOrientation() const { } glm::quat SpatiallyNestable::getWorldOrientation(int jointIndex, bool& success) const { - return getTransform(jointIndex, success).getRotation(); + return getJointTransform(jointIndex, success).getRotation(); } void SpatiallyNestable::setWorldOrientation(const glm::quat& orientation, bool& success, bool tellPhysics) { @@ -765,7 +765,7 @@ void SpatiallyNestable::breakParentingLoop() const { } } -const Transform SpatiallyNestable::getTransform(int jointIndex, bool& success, int depth) const { +const Transform SpatiallyNestable::getJointTransform(int jointIndex, bool& success, int depth) const { // this returns the world-space transform for this object. It finds its parent's transform (which may // cause this object's parent to query its parent, etc) and multiplies this object's local transform onto it. Transform jointInWorldFrame; @@ -832,8 +832,8 @@ glm::vec3 SpatiallyNestable::getSNScale(bool& success) const { return getTransform(success).getScale(); } -glm::vec3 SpatiallyNestable::getSNScale(int jointIndex, bool& success) const { - return getTransform(jointIndex, success).getScale(); +glm::vec3 SpatiallyNestable::getJointSNScale(int jointIndex, bool& success) const { + return getJointTransform(jointIndex, success).getScale(); } void SpatiallyNestable::setSNScale(const glm::vec3& scale) { diff --git a/libraries/shared/src/SpatiallyNestable.h b/libraries/shared/src/SpatiallyNestable.h index a802a25e89..6b709f0352 100644 --- a/libraries/shared/src/SpatiallyNestable.h +++ b/libraries/shared/src/SpatiallyNestable.h @@ -129,9 +129,9 @@ public: virtual void setSNScale(const glm::vec3& scale, bool& success); // get world-frame values for a specific joint - virtual const Transform getTransform(int jointIndex, bool& success, int depth = 0) const; - virtual glm::vec3 getWorldPosition(int jointIndex, bool& success) const; - virtual glm::vec3 getSNScale(int jointIndex, bool& success) const; + virtual const Transform getJointTransform(int jointIndex, bool& success, int depth = 0) const; + virtual glm::vec3 getJointWorldPosition(int jointIndex, bool& success) const; + virtual glm::vec3 getJointSNScale(int jointIndex, bool& success) const; // object's parent's frame virtual Transform getLocalTransform() const; From 8d5a3debfa98acce922cb41f53c0752006552471 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 28 Feb 2019 15:24:32 -0800 Subject: [PATCH 266/474] fix chat.js textSize --- scripts/system/chat.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/scripts/system/chat.js b/scripts/system/chat.js index b0a2e114a3..749665f3d8 100644 --- a/scripts/system/chat.js +++ b/scripts/system/chat.js @@ -45,6 +45,17 @@ var speechBubbleLineHeight = 0.05; // The height of a line of text in the speech bubble. var SPEECH_BUBBLE_MAX_WIDTH = 1; // meters + var textSizeOverlay = Overlays.addOverlay("text3d", { + position: MyAvatar.position, + lineHeight: speechBubbleLineHeight, + leftMargin: 0, + topMargin: 0, + rightMargin: 0, + bottomMargin: 0, + ignoreRayIntersection: true, + visible: false + }); + // Load the persistent variables from the Settings, with defaults. function loadSettings() { chatName = Settings.getValue('Chat_chatName', MyAvatar.displayName); @@ -63,6 +74,9 @@ speechBubbleOffset = Settings.getValue('Chat_speechBubbleOffset', {x: 0.0, y: 0.3, z:0.0}); speechBubbleJointName = Settings.getValue('Chat_speechBubbleJointName', 'Head'); speechBubbleLineHeight = Settings.getValue('Chat_speechBubbleLineHeight', 0.05); + Overlays.editOverlay(textSizeOverlay, { + lineHeight: speechBubbleLineHeight + }); saveSettings(); } @@ -637,7 +651,7 @@ // Only overlay text3d has a way to measure the text, not entities. // So we make a temporary one just for measuring text, then delete it. var speechBubbleTextOverlayID = Overlays.addOverlay("text3d", speechBubbleParams); - var textSize = Overlays.textSize(speechBubbleTextOverlayID, speechBubbleMessage); + var textSize = Overlays.textSize(textSizeOverlay, speechBubbleMessage); try { Overlays.deleteOverlay(speechBubbleTextOverlayID); } catch (e) {} @@ -971,6 +985,8 @@ unidentifyAvatars(); disconnectWebHandler(); + Overlays.deleteOverlay(textSizeOverlay); + if (onChatPage) { tablet.gotoHomeScreen(); onChatPage = false; From a804d3532e6b04fb86e5e520147bd34c331b3f36 Mon Sep 17 00:00:00 2001 From: Clement Date: Thu, 28 Feb 2019 15:30:57 -0800 Subject: [PATCH 267/474] Set default limit for other nodes to size_t max --- libraries/networking/src/LimitedNodeList.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index 18b9a318ef..eb1a3e2dde 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -51,7 +51,7 @@ const int INVALID_PORT = -1; const quint64 NODE_SILENCE_THRESHOLD_MSECS = 5 * 1000; -static const size_t DEFAULT_MAX_CONNECTION_RATE { 50 }; +static const size_t DEFAULT_MAX_CONNECTION_RATE { std::numeric_limits::max() }; extern const std::set SOLO_NODE_TYPES; From b26b02fed6d40c5acd75ccc3a4c50fea03738198 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 28 Feb 2019 16:59:31 -0800 Subject: [PATCH 268/474] potential 2d overlay threading fixes --- interface/src/ui/overlays/Overlay.cpp | 19 +--- interface/src/ui/overlays/Overlay.h | 9 +- interface/src/ui/overlays/Overlay2D.cpp | 22 +--- interface/src/ui/overlays/Overlay2D.h | 5 - interface/src/ui/overlays/Overlays.cpp | 133 +++++++++++------------ interface/src/ui/overlays/QmlOverlay.cpp | 28 ++--- interface/src/ui/overlays/QmlOverlay.h | 4 +- 7 files changed, 77 insertions(+), 143 deletions(-) diff --git a/interface/src/ui/overlays/Overlay.cpp b/interface/src/ui/overlays/Overlay.cpp index 714db97bc2..bf79a46dcb 100644 --- a/interface/src/ui/overlays/Overlay.cpp +++ b/interface/src/ui/overlays/Overlay.cpp @@ -16,8 +16,7 @@ #include "Application.h" Overlay::Overlay() : - _renderItemID(render::Item::INVALID_ITEM_ID), - _visible(true) + _renderItemID(render::Item::INVALID_ITEM_ID) { } @@ -34,20 +33,6 @@ void Overlay::setProperties(const QVariantMap& properties) { } } -QVariant Overlay::getProperty(const QString& property) { - if (property == "type") { - return QVariant(getType()); - } - if (property == "id") { - return getID(); - } - if (property == "visible") { - return _visible; - } - - return QVariant(); -} - bool Overlay::addToScene(Overlay::Pointer overlay, const render::ScenePointer& scene, render::Transaction& transaction) { _renderItemID = scene->allocateID(); transaction.resetItem(_renderItemID, std::make_shared(overlay)); @@ -65,7 +50,7 @@ render::ItemKey Overlay::getKey() { builder.withViewSpace(); builder.withLayer(render::hifi::LAYER_2D); - if (!getVisible()) { + if (!_visible) { builder.withInvisible(); } diff --git a/interface/src/ui/overlays/Overlay.h b/interface/src/ui/overlays/Overlay.h index ee6e281193..72373d2d20 100644 --- a/interface/src/ui/overlays/Overlay.h +++ b/interface/src/ui/overlays/Overlay.h @@ -31,7 +31,6 @@ public: virtual render::ItemKey getKey(); virtual AABox getBounds() const = 0; - virtual bool supportsGetProperty() const { return true; } virtual bool addToScene(Overlay::Pointer overlay, const render::ScenePointer& scene, render::Transaction& transaction); virtual void removeFromScene(Overlay::Pointer overlay, const render::ScenePointer& scene, render::Transaction& transaction); @@ -42,17 +41,15 @@ public: // getters virtual QString getType() const = 0; - bool isLoaded() { return true; } bool getVisible() const { return _visible; } // setters - virtual void setVisible(bool visible) { _visible = visible; } + void setVisible(bool visible) { _visible = visible; } unsigned int getStackOrder() const { return _stackOrder; } void setStackOrder(unsigned int stackOrder) { _stackOrder = stackOrder; } - Q_INVOKABLE virtual void setProperties(const QVariantMap& properties); + Q_INVOKABLE virtual void setProperties(const QVariantMap& properties) = 0; Q_INVOKABLE virtual Overlay* createClone() const = 0; - Q_INVOKABLE virtual QVariant getProperty(const QString& property); render::ItemID getRenderItemID() const { return _renderItemID; } void setRenderItemID(render::ItemID renderItemID) { _renderItemID = renderItemID; } @@ -60,7 +57,7 @@ public: protected: render::ItemID _renderItemID { render::Item::INVALID_ITEM_ID }; - bool _visible; + bool _visible { true }; unsigned int _stackOrder { 0 }; private: diff --git a/interface/src/ui/overlays/Overlay2D.cpp b/interface/src/ui/overlays/Overlay2D.cpp index 71b74e9452..91c7198e49 100644 --- a/interface/src/ui/overlays/Overlay2D.cpp +++ b/interface/src/ui/overlays/Overlay2D.cpp @@ -65,24 +65,4 @@ void Overlay2D::setProperties(const QVariantMap& properties) { } setBounds(newBounds); } -} - -QVariant Overlay2D::getProperty(const QString& property) { - if (property == "bounds") { - return qRectToVariant(_bounds); - } - if (property == "x") { - return _bounds.x(); - } - if (property == "y") { - return _bounds.y(); - } - if (property == "width") { - return _bounds.width(); - } - if (property == "height") { - return _bounds.height(); - } - - return Overlay::getProperty(property); -} +} \ No newline at end of file diff --git a/interface/src/ui/overlays/Overlay2D.h b/interface/src/ui/overlays/Overlay2D.h index 54ab52b469..cfcb114398 100644 --- a/interface/src/ui/overlays/Overlay2D.h +++ b/interface/src/ui/overlays/Overlay2D.h @@ -26,10 +26,6 @@ public: virtual uint32_t fetchMetaSubItems(render::ItemIDs& subItems) const override { subItems.push_back(getRenderItemID()); return 1; } // getters - int getX() const { return _bounds.x(); } - int getY() const { return _bounds.y(); } - int getWidth() const { return _bounds.width(); } - int getHeight() const { return _bounds.height(); } const QRect& getBoundingRect() const { return _bounds; } // setters @@ -40,7 +36,6 @@ public: void setBounds(const QRect& bounds) { _bounds = bounds; } void setProperties(const QVariantMap& properties) override; - QVariant getProperty(const QString& property) override; protected: QRect _bounds; // where on the screen to draw diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 9b2f741531..8fd5d236a0 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -772,29 +772,29 @@ QUuid Overlays::addOverlay(const QString& type, const QVariant& properties) { return UNKNOWN_ENTITY_ID; } - if (QThread::currentThread() != thread()) { - QUuid result; - PROFILE_RANGE(script, __FUNCTION__); - BLOCKING_INVOKE_METHOD(this, "addOverlay", Q_RETURN_ARG(QUuid, result), Q_ARG(const QString&, type), Q_ARG(const QVariant&, properties)); - return result; - } - - Overlay::Pointer overlay; - if (type == ImageOverlay::TYPE) { + if (type == ImageOverlay::TYPE || type == TextOverlay::TYPE || type == RectangleOverlay::TYPE) { #if !defined(DISABLE_QML) - overlay = Overlay::Pointer(new ImageOverlay(), [](Overlay* ptr) { ptr->deleteLater(); }); -#endif - } else if (type == TextOverlay::TYPE) { -#if !defined(DISABLE_QML) - overlay = Overlay::Pointer(new TextOverlay(), [](Overlay* ptr) { ptr->deleteLater(); }); -#endif - } else if (type == RectangleOverlay::TYPE) { - overlay = Overlay::Pointer(new RectangleOverlay(), [](Overlay* ptr) { ptr->deleteLater(); }); - } + if (QThread::currentThread() != thread()) { + QUuid result; + PROFILE_RANGE(script, __FUNCTION__); + BLOCKING_INVOKE_METHOD(this, "addOverlay", Q_RETURN_ARG(QUuid, result), Q_ARG(const QString&, type), Q_ARG(const QVariant&, properties)); + return result; + } - if (overlay) { - overlay->setProperties(properties.toMap()); - return add2DOverlay(overlay); + Overlay::Pointer overlay; + if (type == ImageOverlay::TYPE) { + overlay = Overlay::Pointer(new ImageOverlay(), [](Overlay* ptr) { ptr->deleteLater(); }); + } else if (type == TextOverlay::TYPE) { + overlay = Overlay::Pointer(new TextOverlay(), [](Overlay* ptr) { ptr->deleteLater(); }); + } else if (type == RectangleOverlay::TYPE) { + overlay = Overlay::Pointer(new RectangleOverlay(), [](Overlay* ptr) { ptr->deleteLater(); }); + } + if (overlay) { + overlay->setProperties(properties.toMap()); + return add2DOverlay(overlay); + } +#endif + return QUuid(); } QString entityType = overlayToEntityType(type); @@ -835,15 +835,14 @@ QUuid Overlays::cloneOverlay(const QUuid& id) { return UNKNOWN_ENTITY_ID; } - if (QThread::currentThread() != thread()) { - QUuid result; - PROFILE_RANGE(script, __FUNCTION__); - BLOCKING_INVOKE_METHOD(this, "cloneOverlay", Q_RETURN_ARG(QUuid, result), Q_ARG(const QUuid&, id)); - return result; - } - Overlay::Pointer overlay = get2DOverlay(id); if (overlay) { + if (QThread::currentThread() != thread()) { + QUuid result; + PROFILE_RANGE(script, __FUNCTION__); + BLOCKING_INVOKE_METHOD(this, "cloneOverlay", Q_RETURN_ARG(QUuid, result), Q_ARG(const QUuid&, id)); + return result; + } return add2DOverlay(Overlay::Pointer(overlay->createClone(), [](Overlay* ptr) { ptr->deleteLater(); })); } @@ -919,6 +918,11 @@ void Overlays::deleteOverlay(const QUuid& id) { Overlay::Pointer overlay = take2DOverlay(id); if (overlay) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "deleteOverlay", Q_ARG(const QUuid&, id)); + return; + } + _overlaysToDelete.push_back(overlay); emit overlayDeleted(id); return; @@ -933,15 +937,14 @@ QString Overlays::getOverlayType(const QUuid& id) { return ""; } - if (QThread::currentThread() != thread()) { - QString result; - PROFILE_RANGE(script, __FUNCTION__); - BLOCKING_INVOKE_METHOD(this, "getOverlayType", Q_RETURN_ARG(QString, result), Q_ARG(const QUuid&, id)); - return result; - } - Overlay::Pointer overlay = get2DOverlay(id); if (overlay) { + if (QThread::currentThread() != thread()) { + QString result; + PROFILE_RANGE(script, __FUNCTION__); + BLOCKING_INVOKE_METHOD(this, "getOverlayType", Q_RETURN_ARG(QString, result), Q_ARG(const QUuid&, id)); + return result; + } return overlay->getType(); } @@ -949,15 +952,14 @@ QString Overlays::getOverlayType(const QUuid& id) { } QObject* Overlays::getOverlayObject(const QUuid& id) { - if (QThread::currentThread() != thread()) { - QObject* result; - PROFILE_RANGE(script, __FUNCTION__); - BLOCKING_INVOKE_METHOD(this, "getOverlayObject", Q_RETURN_ARG(QObject*, result), Q_ARG(const QUuid&, id)); - return result; - } - Overlay::Pointer overlay = get2DOverlay(id); if (overlay) { + if (QThread::currentThread() != thread()) { + QObject* result; + PROFILE_RANGE(script, __FUNCTION__); + BLOCKING_INVOKE_METHOD(this, "getOverlayObject", Q_RETURN_ARG(QObject*, result), Q_ARG(const QUuid&, id)); + return result; + } return qobject_cast(&(*overlay)); } @@ -969,6 +971,12 @@ QUuid Overlays::getOverlayAtPoint(const glm::vec2& point) { return UNKNOWN_ENTITY_ID; } + if (QThread::currentThread() != thread()) { + QUuid result; + BLOCKING_INVOKE_METHOD(this, "getOverlayAtPoint", Q_RETURN_ARG(QUuid, result), Q_ARG(const glm::vec2&, point)); + return result; + } + QMutexLocker locker(&_mutex); QMapIterator i(_overlays); unsigned int bestStackOrder = 0; @@ -976,8 +984,7 @@ QUuid Overlays::getOverlayAtPoint(const glm::vec2& point) { while (i.hasNext()) { i.next(); auto thisOverlay = std::dynamic_pointer_cast(i.value()); - if (thisOverlay && thisOverlay->getVisible() && thisOverlay->isLoaded() && - thisOverlay->getBoundingRect().contains(point.x, point.y, false)) { + if (thisOverlay && thisOverlay->getVisible() && thisOverlay->getBoundingRect().contains(point.x, point.y, false)) { if (thisOverlay->getStackOrder() > bestStackOrder) { bestID = i.key(); bestStackOrder = thisOverlay->getStackOrder(); @@ -991,9 +998,7 @@ QUuid Overlays::getOverlayAtPoint(const glm::vec2& point) { QVariant Overlays::getProperty(const QUuid& id, const QString& property) { Overlay::Pointer overlay = get2DOverlay(id); if (overlay) { - if (overlay->supportsGetProperty()) { - return overlay->getProperty(property); - } + // We don't support getting properties from QML Overlays right now return QVariant(); } @@ -1009,12 +1014,8 @@ QVariantMap Overlays::getProperties(const QUuid& id, const QStringList& properti Overlay::Pointer overlay = get2DOverlay(id); QVariantMap result; if (overlay) { - if (overlay->supportsGetProperty()) { - for (const auto& property : properties) { - result.insert(property, overlay->getProperty(property)); - } - } - return result; + // We don't support getting properties from QML Overlays right now + return QVariantMap(); } QVariantMap overlayProperties = convertEntityToOverlayProperties(DependencyManager::get()->getEntityProperties(id)); @@ -1141,38 +1142,30 @@ void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& object, R } bool Overlays::isLoaded(const QUuid& id) { - if (QThread::currentThread() != thread()) { - bool result; - PROFILE_RANGE(script, __FUNCTION__); - BLOCKING_INVOKE_METHOD(this, "isLoaded", Q_RETURN_ARG(bool, result), Q_ARG(const QUuid&, id)); - return result; - } - Overlay::Pointer overlay = get2DOverlay(id); if (overlay) { - return overlay->isLoaded(); + return true; } return DependencyManager::get()->isLoaded(id); } QSizeF Overlays::textSize(const QUuid& id, const QString& text) { - if (QThread::currentThread() != thread()) { - QSizeF result; - PROFILE_RANGE(script, __FUNCTION__); - BLOCKING_INVOKE_METHOD(this, "textSize", Q_RETURN_ARG(QSizeF, result), Q_ARG(const QUuid&, id), Q_ARG(QString, text)); - return result; - } - Overlay::Pointer overlay = get2DOverlay(id); if (overlay) { + if (QThread::currentThread() != thread()) { + QSizeF result; + PROFILE_RANGE(script, __FUNCTION__); + BLOCKING_INVOKE_METHOD(this, "textSize", Q_RETURN_ARG(QSizeF, result), Q_ARG(const QUuid&, id), Q_ARG(QString, text)); + return result; + } if (auto textOverlay = std::dynamic_pointer_cast(overlay)) { return textOverlay->textSize(text); } return QSizeF(0.0f, 0.0f); - } else { - return DependencyManager::get()->textSize(id, text); } + + return DependencyManager::get()->textSize(id, text); } bool Overlays::isAddedOverlay(const QUuid& id) { diff --git a/interface/src/ui/overlays/QmlOverlay.cpp b/interface/src/ui/overlays/QmlOverlay.cpp index 537c421ca7..f301a23d49 100644 --- a/interface/src/ui/overlays/QmlOverlay.cpp +++ b/interface/src/ui/overlays/QmlOverlay.cpp @@ -57,29 +57,15 @@ QmlOverlay::~QmlOverlay() { // QmlOverlay replaces Overlay's properties with those defined in the QML file used but keeps Overlay2D's properties. void QmlOverlay::setProperties(const QVariantMap& properties) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "setProperties", Q_ARG(QVariantMap, properties)); - return; - } - Overlay2D::setProperties(properties); - auto bounds = _bounds; + // check to see if qmlElement still exists if (_qmlElement) { - _qmlElement->setX(bounds.left()); - _qmlElement->setY(bounds.top()); - _qmlElement->setWidth(bounds.width()); - _qmlElement->setHeight(bounds.height()); + _qmlElement->setX(_bounds.left()); + _qmlElement->setY(_bounds.top()); + _qmlElement->setWidth(_bounds.width()); + _qmlElement->setHeight(_bounds.height()); + _qmlElement->setVisible(_visible); QMetaObject::invokeMethod(_qmlElement, "updatePropertiesFromScript", Qt::DirectConnection, Q_ARG(QVariant, properties)); } -} - -void QmlOverlay::render(RenderArgs* args) { - if (!_qmlElement) { - return; - } - - if (_visible != _qmlElement->isVisible()) { - _qmlElement->setVisible(_visible); - } -} +} \ No newline at end of file diff --git a/interface/src/ui/overlays/QmlOverlay.h b/interface/src/ui/overlays/QmlOverlay.h index 0951a04772..32badde28b 100644 --- a/interface/src/ui/overlays/QmlOverlay.h +++ b/interface/src/ui/overlays/QmlOverlay.h @@ -25,10 +25,8 @@ public: QmlOverlay(const QUrl& url, const QmlOverlay* overlay); ~QmlOverlay(); - bool supportsGetProperty() const override { return false; } - void setProperties(const QVariantMap& properties) override; - void render(RenderArgs* args) override; + void render(RenderArgs* args) override {} private: Q_INVOKABLE void qmlElementDestroyed(); From 9fe0309ae65d0ae2d42a48e43812935ebab6a752 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Thu, 28 Feb 2019 17:11:48 -0800 Subject: [PATCH 269/474] another login fix --- interface/src/Application.cpp | 8 +++++--- interface/src/Application.h | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ebc1176ee1..274d6919af 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4980,10 +4980,11 @@ void Application::idle() { } { - if (_keyboardFocusWaitingOnRenderable && getEntities()->renderableForEntityId(_keyboardFocusedEntity.get())) { - _keyboardFocusWaitingOnRenderable = false; - QUuid entityId = _keyboardFocusedEntity.get(); + if (_keyboardFocusWaitingOnRenderable && getEntities()->renderableForEntityId(_entityIdWaitingOnRenderable.get())) { + QUuid entityId = _entityIdWaitingOnRenderable.get(); + _entityIdWaitingOnRenderable.set(UNKNOWN_ENTITY_ID); setKeyboardFocusEntity(UNKNOWN_ENTITY_ID); + _keyboardFocusWaitingOnRenderable = false; setKeyboardFocusEntity(entityId); } } @@ -5836,6 +5837,7 @@ void Application::setKeyboardFocusEntity(const QUuid& id) { auto entityItemRenderable = entities->renderableForEntityId(entityId); if (!entityItemRenderable) { _keyboardFocusWaitingOnRenderable = true; + _entityIdWaitingOnRenderable.set(id); } else if (entityItemRenderable->wantsKeyboardFocus()) { entities->setProxyWindow(entityId, _window->windowHandle()); if (_keyboardMouseDevice->isActive()) { diff --git a/interface/src/Application.h b/interface/src/Application.h index c16f260192..5e3090973a 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -733,6 +733,7 @@ private: bool _reticleClickPressed { false }; bool _keyboardFocusWaitingOnRenderable { false }; + ThreadSafeValueCache _entityIdWaitingOnRenderable; int _avatarAttachmentRequest = 0; From 5a8ecdffabbe80e12480a4eaf707e19432db0fea Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Thu, 28 Feb 2019 17:37:56 -0800 Subject: [PATCH 270/474] EntityStats can have embedded EntityData --- assignment-client/src/avatars/AvatarMixer.cpp | 13 +++++++++++++ .../src/avatars/AvatarMixerClientData.cpp | 6 +++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 67bc9b4cf7..6384ad2b40 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -976,7 +976,20 @@ void AvatarMixer::handleOctreePacket(QSharedPointer message, Sh switch (packetType) { case PacketType::OctreeStats: + { // Ignore stats, but may have a different Entity packet appended. + OctreeHeadlessViewer::parseOctreeStats(message, senderNode); + const auto piggyBackedSizeWithHeader = message->getBytesLeftToRead(); + if (piggyBackedSizeWithHeader > 0) { + // pull out the piggybacked packet and create a new QSharedPointer for it + auto buffer = std::unique_ptr(new char[piggyBackedSizeWithHeader]); + memcpy(buffer.get(), message->getRawMessage() + message->getPosition(), piggyBackedSizeWithHeader); + + auto newPacket = NLPacket::fromReceivedPacket(std::move(buffer), piggyBackedSizeWithHeader, message->getSenderSockAddr()); + auto newMessage = QSharedPointer::create(*newPacket); + handleOctreePacket(newMessage, senderNode); + } break; + } case PacketType::EntityData: _entityViewer.processDatagram(*message, senderNode); diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index e24d48a9ed..a63a76829b 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -145,9 +145,9 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message, const SlaveShared FindPriorityZone findPriorityZone { newPosition, false } ; entityTree.recurseTreeWithOperation(&FindPriorityZone::operation, &findPriorityZone); _avatar->setPriorityAvatar(findPriorityZone.isInPriorityZone); - //if (findPriorityZone.isInPriorityZone) { - // qCWarning(avatars) << "Avatar" << _avatar->getSessionDisplayName() << "in hero zone"; - //} + if (findPriorityZone.isInPriorityZone) { + qCWarning(avatars) << "Avatar" << _avatar->getSessionDisplayName() << "in hero zone"; + } #endif } From 991bcfe2b210364bb44d051b459b73af1ed30134 Mon Sep 17 00:00:00 2001 From: Clement Date: Thu, 28 Feb 2019 18:00:21 -0800 Subject: [PATCH 271/474] Remove dead code --- .../src/audio/AudioMixerSlavePool.cpp | 23 ------------------- .../src/avatars/AvatarMixerSlavePool.cpp | 23 ------------------- 2 files changed, 46 deletions(-) diff --git a/assignment-client/src/audio/AudioMixerSlavePool.cpp b/assignment-client/src/audio/AudioMixerSlavePool.cpp index 7cc7ac9f93..78efb98b37 100644 --- a/assignment-client/src/audio/AudioMixerSlavePool.cpp +++ b/assignment-client/src/audio/AudioMixerSlavePool.cpp @@ -64,10 +64,6 @@ bool AudioMixerSlaveThread::try_pop(SharedNodePointer& node) { return _pool._queue.try_pop(node); } -#ifdef AUDIO_SINGLE_THREADED -static AudioMixerSlave slave; -#endif - void AudioMixerSlavePool::processPackets(ConstIter begin, ConstIter end) { _function = &AudioMixerSlave::processPackets; _configure = [](AudioMixerSlave& slave) {}; @@ -87,19 +83,9 @@ void AudioMixerSlavePool::run(ConstIter begin, ConstIter end) { _begin = begin; _end = end; -#ifdef AUDIO_SINGLE_THREADED - _configure(slave); - std::for_each(begin, end, [&](const SharedNodePointer& node) { - _function(slave, node); - }); -#else // fill the queue std::for_each(_begin, _end, [&](const SharedNodePointer& node) { -#if defined(__clang__) && defined(Q_OS_LINUX) _queue.push(node); -#else - _queue.emplace(node); -#endif }); { @@ -119,17 +105,12 @@ void AudioMixerSlavePool::run(ConstIter begin, ConstIter end) { } assert(_queue.empty()); -#endif } void AudioMixerSlavePool::each(std::function functor) { -#ifdef AUDIO_SINGLE_THREADED - functor(slave); -#else for (auto& slave : _slaves) { functor(*slave.get()); } -#endif } void AudioMixerSlavePool::setNumThreads(int numThreads) { @@ -155,9 +136,6 @@ void AudioMixerSlavePool::setNumThreads(int numThreads) { void AudioMixerSlavePool::resize(int numThreads) { assert(_numThreads == (int)_slaves.size()); -#ifdef AUDIO_SINGLE_THREADED - qDebug("%s: running single threaded", __FUNCTION__, numThreads); -#else qDebug("%s: set %d threads (was %d)", __FUNCTION__, numThreads, _numThreads); Lock lock(_mutex); @@ -205,5 +183,4 @@ void AudioMixerSlavePool::resize(int numThreads) { _numThreads = _numStarted = _numFinished = numThreads; assert(_numThreads == (int)_slaves.size()); -#endif } diff --git a/assignment-client/src/avatars/AvatarMixerSlavePool.cpp b/assignment-client/src/avatars/AvatarMixerSlavePool.cpp index cf842ac792..013d914cbe 100644 --- a/assignment-client/src/avatars/AvatarMixerSlavePool.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlavePool.cpp @@ -63,10 +63,6 @@ bool AvatarMixerSlaveThread::try_pop(SharedNodePointer& node) { return _pool._queue.try_pop(node); } -#ifdef AVATAR_SINGLE_THREADED -static AvatarMixerSlave slave; -#endif - void AvatarMixerSlavePool::processIncomingPackets(ConstIter begin, ConstIter end) { _function = &AvatarMixerSlave::processIncomingPackets; _configure = [=](AvatarMixerSlave& slave) { @@ -89,19 +85,9 @@ void AvatarMixerSlavePool::run(ConstIter begin, ConstIter end) { _begin = begin; _end = end; -#ifdef AUDIO_SINGLE_THREADED - _configure(slave); - std::for_each(begin, end, [&](const SharedNodePointer& node) { - _function(slave, node); -}); -#else // fill the queue std::for_each(_begin, _end, [&](const SharedNodePointer& node) { -#if defined(__clang__) && defined(Q_OS_LINUX) _queue.push(node); -#else - _queue.emplace(node); -#endif }); { @@ -121,18 +107,13 @@ void AvatarMixerSlavePool::run(ConstIter begin, ConstIter end) { } assert(_queue.empty()); -#endif } void AvatarMixerSlavePool::each(std::function functor) { -#ifdef AVATAR_SINGLE_THREADED - functor(slave); -#else for (auto& slave : _slaves) { functor(*slave.get()); } -#endif } void AvatarMixerSlavePool::setNumThreads(int numThreads) { @@ -158,9 +139,6 @@ void AvatarMixerSlavePool::setNumThreads(int numThreads) { void AvatarMixerSlavePool::resize(int numThreads) { assert(_numThreads == (int)_slaves.size()); -#ifdef AVATAR_SINGLE_THREADED - qDebug("%s: running single threaded", __FUNCTION__, numThreads); -#else qDebug("%s: set %d threads (was %d)", __FUNCTION__, numThreads, _numThreads); Lock lock(_mutex); @@ -208,5 +186,4 @@ void AvatarMixerSlavePool::resize(int numThreads) { _numThreads = _numStarted = _numFinished = numThreads; assert(_numThreads == (int)_slaves.size()); -#endif } From 9a2bd87278e964f8623d7c5f2f7fdfde1e441834 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Thu, 28 Feb 2019 18:02:07 -0800 Subject: [PATCH 272/474] Fix for case when animated joints are missing from the target avatar skeleton By copying the animation rotations over to the target avatar in absolute frame, rather then relative, we can properly "combine" animated rotations that aren't in the target avatar skeleton. --- libraries/animation/src/AnimClip.cpp | 99 ++++++++++++++++-------- libraries/animation/src/AnimSkeleton.cpp | 11 +++ libraries/animation/src/AnimSkeleton.h | 1 + 3 files changed, 78 insertions(+), 33 deletions(-) diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index a35e0237d0..18e97cf0a1 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -95,57 +95,85 @@ void AnimClip::setCurrentFrameInternal(float frame) { _frame = ::accumulateTime(_startFrame, _endFrame, _timeScale, frame + _startFrame, dt, _loopFlag, _id, triggers); } +static std::vector buildJointIndexMap(const AnimSkeleton& dstSkeleton, const AnimSkeleton& srcSkeleton) { + std::vector jointIndexMap; + int srcJointCount = srcSkeleton.getNumJoints(); + jointIndexMap.reserve(srcJointCount); + for (int srcJointIndex = 0; srcJointIndex < srcJointCount; srcJointIndex++) { + QString srcJointName = srcSkeleton.getJointName(srcJointIndex); + int dstJointIndex = dstSkeleton.nameToJointIndex(srcJointName); + jointIndexMap.push_back(dstJointIndex); + } + return jointIndexMap; +} + void AnimClip::copyFromNetworkAnim() { assert(_networkAnim && _networkAnim->isLoaded() && _skeleton); _anim.clear(); auto avatarSkeleton = getSkeleton(); - - // build a mapping from animation joint indices to avatar joint indices. - // by matching joints with the same name. const HFMModel& animModel = _networkAnim->getHFMModel(); AnimSkeleton animSkeleton(animModel); const int animJointCount = animSkeleton.getNumJoints(); const int avatarJointCount = avatarSkeleton->getNumJoints(); - std::vector animToAvatarJointIndexMap; - animToAvatarJointIndexMap.reserve(animJointCount); - for (int animJointIndex = 0; animJointIndex < animJointCount; animJointIndex++) { - QString animJointName = animSkeleton.getJointName(animJointIndex); - int avatarJointIndex = avatarSkeleton->nameToJointIndex(animJointName); - animToAvatarJointIndexMap.push_back(avatarJointIndex); - } + + // build a mapping from animation joint indices to avatar joint indices by matching joints with the same name. + std::vector avatarToAnimJointIndexMap = buildJointIndexMap(animSkeleton, *avatarSkeleton); const int animFrameCount = animModel.animationFrames.size(); _anim.resize(animFrameCount); for (int frame = 0; frame < animFrameCount; frame++) { - const HFMAnimationFrame& animFrame = animModel.animationFrames[frame]; - // init all joints in animation to default pose - // this will give us a resonable result for bones in the avatar skeleton but not in the animation. - _anim[frame].reserve(avatarJointCount); - for (int avatarJointIndex = 0; avatarJointIndex < avatarJointCount; avatarJointIndex++) { - _anim[frame].push_back(avatarSkeleton->getRelativeDefaultPose(avatarJointIndex)); + // extract the full rotations from the animFrame (including pre and post rotations from the animModel). + std::vector animRotations; + animRotations.reserve(animJointCount); + for (int i = 0; i < animJointCount; i++) { + animRotations.push_back(animModel.joints[i].preRotation * animFrame.rotations[i] * animModel.joints[i].postRotation); } - for (int animJointIndex = 0; animJointIndex < animJointCount; animJointIndex++) { - int avatarJointIndex = animToAvatarJointIndexMap[animJointIndex]; + // convert rotations into absolute frame + animSkeleton.convertRelativeRotationsToAbsolute(animRotations); - // skip joints that are in the animation but not in the avatar. - if (avatarJointIndex >= 0 && avatarJointIndex < avatarJointCount) { + // build absolute rotations for the avatar + std::vector avatarRotations; + avatarRotations.reserve(avatarJointCount); + for (int avatarJointIndex = 0; avatarJointIndex < avatarJointCount; avatarJointIndex++) { + int animJointIndex = avatarToAnimJointIndexMap[avatarJointIndex]; + if (animJointIndex >= 0) { + // This joint is in both animation and avatar. + // Set the absolute rotation directly + avatarRotations.push_back(animRotations[animJointIndex]); + } else { + // This joint is NOT in the animation at all. + // Set it so that the default relative rotation remains unchanged. + glm::quat avatarRelativeDefaultRot = avatarSkeleton->getRelativeDefaultPose(avatarJointIndex).rot(); + glm::quat avatarParentAbsoluteRot; + int avatarParentJointIndex = avatarSkeleton->getParentIndex(avatarJointIndex); + if (avatarParentJointIndex >= 0) { + avatarParentAbsoluteRot = avatarRotations[avatarParentJointIndex]; + } + avatarRotations.push_back(avatarParentAbsoluteRot * avatarRelativeDefaultRot); + } + } + // convert avatar rotations into relative frame + avatarSkeleton->convertAbsoluteRotationsToRelative(avatarRotations); + + _anim[frame].reserve(avatarJointCount); + + for (int avatarJointIndex = 0; avatarJointIndex < avatarJointCount; avatarJointIndex++) { + const AnimPose& avatarDefaultPose = avatarSkeleton->getRelativeDefaultPose(avatarJointIndex); + + // copy scale over from avatar default pose + glm::vec3 relativeScale = avatarDefaultPose.scale(); + + glm::vec3 relativeTranslation; + int animJointIndex = avatarToAnimJointIndexMap[avatarJointIndex]; + if (animJointIndex >= 0) { + // This joint is in both animation and avatar. const glm::vec3& animTrans = animFrame.translations[animJointIndex]; - const glm::quat& animRot = animFrame.rotations[animJointIndex]; - - const AnimPose& animPreRotPose = animSkeleton.getPreRotationPose(animJointIndex); - AnimPose animPostRotPose = animSkeleton.getPostRotationPose(animJointIndex); - AnimPose animRotPose(glm::vec3(1.0f), animRot, glm::vec3()); - - // adjust anim scale to equal the scale from the avatar joint. - // we do not support animated scale. - const AnimPose& avatarDefaultPose = avatarSkeleton->getRelativeDefaultPose(avatarJointIndex); - animPostRotPose.scale() = avatarDefaultPose.scale(); // retarget translation from animation to avatar const glm::vec3& animZeroTrans = animModel.animationFrames[0].translations[animJointIndex]; @@ -154,10 +182,15 @@ void AnimClip::copyFromNetworkAnim() { if (fabsf(glm::length(animZeroTrans)) > EPSILON) { boneLengthScale = glm::length(avatarDefaultPose.trans()) / glm::length(animZeroTrans); } - AnimPose animTransPose = AnimPose(glm::vec3(1.0f), glm::quat(), avatarDefaultPose.trans() + boneLengthScale * (animTrans - animZeroTrans)); - - _anim[frame][avatarJointIndex] = animTransPose * animPreRotPose * animRotPose * animPostRotPose; + relativeTranslation = avatarDefaultPose.trans() + boneLengthScale * (animTrans - animZeroTrans); + } else { + // This joint is NOT in the animation at all. + // preserve the default translation. + relativeTranslation = avatarDefaultPose.trans(); } + + // build the final pose + _anim[frame].push_back(AnimPose(relativeScale, avatarRotations[avatarJointIndex], relativeTranslation)); } } diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index 03e3ac6ebd..993a2c6632 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -149,6 +149,17 @@ void AnimSkeleton::convertAbsolutePosesToRelative(AnimPoseVec& poses) const { } } +void AnimSkeleton::convertRelativeRotationsToAbsolute(std::vector& rotations) const { + // poses start off relative and leave in absolute frame + int lastIndex = std::min((int)rotations.size(), _jointsSize); + for (int i = 0; i < lastIndex; ++i) { + int parentIndex = _parentIndices[i]; + if (parentIndex != -1) { + rotations[i] = rotations[parentIndex] * rotations[i]; + } + } +} + void AnimSkeleton::convertAbsoluteRotationsToRelative(std::vector& rotations) const { // poses start off absolute and leave in relative frame int lastIndex = std::min((int)rotations.size(), _jointsSize); diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index 0eefbf973e..5309d26354 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -54,6 +54,7 @@ public: void convertRelativePosesToAbsolute(AnimPoseVec& poses) const; void convertAbsolutePosesToRelative(AnimPoseVec& poses) const; + void convertRelativeRotationsToAbsolute(std::vector& rotations) const; void convertAbsoluteRotationsToRelative(std::vector& rotations) const; void saveNonMirroredPoses(const AnimPoseVec& poses) const; From 7bcf727a835cc446b1f917a130f80af45c1ddfd4 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Thu, 28 Feb 2019 18:11:51 -0800 Subject: [PATCH 273/474] review changes --- interface/src/Application.cpp | 6 ++---- interface/src/Application.h | 1 - 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 274d6919af..248ada4376 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4980,9 +4980,8 @@ void Application::idle() { } { - if (_keyboardFocusWaitingOnRenderable && getEntities()->renderableForEntityId(_entityIdWaitingOnRenderable.get())) { - QUuid entityId = _entityIdWaitingOnRenderable.get(); - _entityIdWaitingOnRenderable.set(UNKNOWN_ENTITY_ID); + if (_keyboardFocusWaitingOnRenderable && getEntities()->renderableForEntityId(_keyboardFocusedEntity.get())) { + QUuid entityId = _keyboardFocusedEntity.get(); setKeyboardFocusEntity(UNKNOWN_ENTITY_ID); _keyboardFocusWaitingOnRenderable = false; setKeyboardFocusEntity(entityId); @@ -5837,7 +5836,6 @@ void Application::setKeyboardFocusEntity(const QUuid& id) { auto entityItemRenderable = entities->renderableForEntityId(entityId); if (!entityItemRenderable) { _keyboardFocusWaitingOnRenderable = true; - _entityIdWaitingOnRenderable.set(id); } else if (entityItemRenderable->wantsKeyboardFocus()) { entities->setProxyWindow(entityId, _window->windowHandle()); if (_keyboardMouseDevice->isActive()) { diff --git a/interface/src/Application.h b/interface/src/Application.h index 5e3090973a..c16f260192 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -733,7 +733,6 @@ private: bool _reticleClickPressed { false }; bool _keyboardFocusWaitingOnRenderable { false }; - ThreadSafeValueCache _entityIdWaitingOnRenderable; int _avatarAttachmentRequest = 0; From 64a2025bcdf2c427484edf6d27788092011f0418 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Thu, 28 Feb 2019 18:40:53 -0800 Subject: [PATCH 274/474] Renaming in main loop to make source/destination clear Also comment clean-up, etc --- .../src/avatars/AvatarMixerSlave.cpp | 123 +++++++++--------- 1 file changed, 62 insertions(+), 61 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index a38553ddeb..ac18268860 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -320,21 +320,21 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) _stats.nodesBroadcastedTo++; - AvatarMixerClientData* nodeData = reinterpret_cast(destinationNode->getLinkedData()); + AvatarMixerClientData* destinationNodeData = reinterpret_cast(destinationNode->getLinkedData()); - nodeData->resetInViewStats(); + destinationNodeData->resetInViewStats(); - const AvatarData& avatar = nodeData->getAvatar(); + const AvatarData& avatar = destinationNodeData->getAvatar(); glm::vec3 myPosition = avatar.getClientGlobalPosition(); // reset the internal state for correct random number distribution distribution.reset(); // Estimate number to sort on number sent last frame (with min. of 20). - const int numToSendEst = std::max(int(nodeData->getNumAvatarsSentLastFrame() * 2.5f), 20); + const int numToSendEst = std::max(int(destinationNodeData->getNumAvatarsSentLastFrame() * 2.5f), 20); // reset the number of sent avatars - nodeData->resetNumAvatarsSentLastFrame(); + destinationNodeData->resetNumAvatarsSentLastFrame(); // keep track of outbound data rate specifically for avatar data int numAvatarDataBytes = 0; @@ -353,8 +353,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // When this is true, the AvatarMixer will send Avatar data to a client // about avatars they've ignored or that are out of view - bool PALIsOpen = nodeData->getRequestsDomainListData(); - bool PALWasOpen = nodeData->getPrevRequestsDomainListData(); + bool PALIsOpen = destinationNodeData->getRequestsDomainListData(); + bool PALWasOpen = destinationNodeData->getPrevRequestsDomainListData(); // When this is true, the AvatarMixer will send Avatar data to a client about avatars that have ignored them bool getsAnyIgnored = PALIsOpen && destinationNode->getCanKick(); @@ -368,7 +368,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) AABox nodeBox = computeBubbleBox(avatar, MY_AVATAR_BUBBLE_EXPANSION_FACTOR); // prepare to sort - const auto& cameraViews = nodeData->getViewFrustums(); + const auto& cameraViews = destinationNodeData->getViewFrustums(); using AvatarPriorityQueue = PrioritySortUtil::PriorityQueue; // Keep two independent queues, one for heroes and one for the riff-raff. @@ -391,47 +391,47 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) continue; } - auto avatarNode = otherNodeRaw; + auto sourceAvatarNode = otherNodeRaw; - bool shouldIgnore = false; + bool sendAvatar = true; // We will consider this source avatar for sending. // We ignore other nodes for a couple of reasons: // 1) ignore bubbles and ignore specific node // 2) the node hasn't really updated it's frame data recently, this can // happen if for example the avatar is connected on a desktop and sending // updates at ~30hz. So every 3 frames we skip a frame. - assert(avatarNode); // we can't have gotten here without the avatarData being a valid key in the map + assert(sourceAvatarNode); // we can't have gotten here without the avatarData being a valid key in the map - const AvatarMixerClientData* avatarClientNodeData = reinterpret_cast(avatarNode->getLinkedData()); - assert(avatarClientNodeData); // we can't have gotten here without avatarNode having valid data + const AvatarMixerClientData* sourceAvatarNodeData = reinterpret_cast(sourceAvatarNode->getLinkedData()); + assert(sourceAvatarNodeData); // we can't have gotten here without sourceAvatarNode having valid data quint64 startIgnoreCalculation = usecTimestampNow(); // make sure we have data for this avatar, that it isn't the same node, // and isn't an avatar that the viewing node has ignored // or that has ignored the viewing node - if ((destinationNode->isIgnoringNodeWithID(avatarNode->getUUID()) && !PALIsOpen) - || (avatarNode->isIgnoringNodeWithID(destinationNode->getUUID()) && !getsAnyIgnored)) { - shouldIgnore = true; + if ((destinationNode->isIgnoringNodeWithID(sourceAvatarNode->getUUID()) && !PALIsOpen) + || (sourceAvatarNode->isIgnoringNodeWithID(destinationNode->getUUID()) && !getsAnyIgnored)) { + sendAvatar = false; } else { // Check to see if the space bubble is enabled // Don't bother with these checks if the other avatar has their bubble enabled and we're gettingAnyIgnored - if (nodeData->isIgnoreRadiusEnabled() || (avatarClientNodeData->isIgnoreRadiusEnabled() && !getsAnyIgnored)) { + if (destinationNodeData->isIgnoreRadiusEnabled() || (sourceAvatarNodeData->isIgnoreRadiusEnabled() && !getsAnyIgnored)) { // Perform the collision check between the two bounding boxes - AABox otherNodeBox = avatarClientNodeData->getAvatar().getDefaultBubbleBox(); + AABox otherNodeBox = sourceAvatarNodeData->getAvatar().getDefaultBubbleBox(); if (nodeBox.touches(otherNodeBox)) { - nodeData->ignoreOther(destinationNode, avatarNode); - shouldIgnore = !getsAnyIgnored; + destinationNodeData->ignoreOther(destinationNode, sourceAvatarNode); + sendAvatar = getsAnyIgnored; } } // Not close enough to ignore - if (!shouldIgnore) { - nodeData->removeFromRadiusIgnoringSet(avatarNode->getUUID()); + if (sendAvatar) { + destinationNodeData->removeFromRadiusIgnoringSet(sourceAvatarNode->getUUID()); } } - if (!shouldIgnore) { - AvatarDataSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(avatarNode->getLocalID()); - AvatarDataSequenceNumber lastSeqFromSender = avatarClientNodeData->getLastReceivedSequenceNumber(); + if (sendAvatar) { + AvatarDataSequenceNumber lastSeqToReceiver = destinationNodeData->getLastBroadcastSequenceNumber(sourceAvatarNode->getLocalID()); + AvatarDataSequenceNumber lastSeqFromSender = sourceAvatarNodeData->getLastReceivedSequenceNumber(); // FIXME - This code does appear to be working. But it seems brittle. // It supports determining if the frame of data for this "other" @@ -445,12 +445,12 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // or that somehow we haven't sent if (lastSeqToReceiver == lastSeqFromSender && lastSeqToReceiver != 0) { ++numAvatarsHeldBack; - shouldIgnore = true; + sendAvatar = false; } else if (lastSeqFromSender == 0) { - // We have have not yet recieved any data about this avatar. Ignore it for now + // We have have not yet received any data about this avatar. Ignore it for now // This is important for Agent scripts that are not avatar // so that they don't appear to be an avatar at the origin - shouldIgnore = true; + sendAvatar = false; } else if (lastSeqFromSender - lastSeqToReceiver > 1) { // this is a skip - we still send the packet but capture the presence of the skip so we see it happening ++numAvatarsWithSkippedFrames; @@ -459,13 +459,13 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) quint64 endIgnoreCalculation = usecTimestampNow(); _stats.ignoreCalculationElapsedTime += (endIgnoreCalculation - startIgnoreCalculation); - if (!shouldIgnore) { + if (sendAvatar) { // sort this one for later - const MixerAvatar* avatarNodeData = avatarClientNodeData->getConstAvatarData(); - auto lastEncodeTime = nodeData->getLastOtherAvatarEncodeTime(avatarNode->getLocalID()); + const MixerAvatar* avatarNodeData = sourceAvatarNodeData->getConstAvatarData(); + auto lastEncodeTime = destinationNodeData->getLastOtherAvatarEncodeTime(sourceAvatarNode->getLocalID()); avatarPriorityQueues[avatarNodeData->getPriorityAvatar() ? kHero : kNonhero].push( - SortableAvatar(avatarNodeData, avatarNode, lastEncodeTime)); + SortableAvatar(avatarNodeData, sourceAvatarNode, lastEncodeTime)); } // If Avatar A's PAL WAS open but is no longer open, AND @@ -475,18 +475,18 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // will be sent when it doesn't need to be (but where it _should_ be OK to send). // However, it's less heavy-handed than using `shouldIgnore`. if (PALWasOpen && !PALIsOpen && - (destinationNode->isIgnoringNodeWithID(avatarNode->getUUID()) || - avatarNode->isIgnoringNodeWithID(destinationNode->getUUID()))) { + (destinationNode->isIgnoringNodeWithID(sourceAvatarNode->getUUID()) || + sourceAvatarNode->isIgnoringNodeWithID(destinationNode->getUUID()))) { // ...send a Kill Packet to Node A, instructing Node A to kill Avatar B, // then have Node A cleanup the killed Node B. auto packet = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason), true); - packet->write(avatarNode->getUUID().toRfc4122()); + packet->write(sourceAvatarNode->getUUID().toRfc4122()); packet->writePrimitive(KillAvatarReason::AvatarIgnored); nodeList->sendPacket(std::move(packet), *destinationNode); - nodeData->cleanupKilledNode(avatarNode->getUUID(), avatarNode->getLocalID()); + destinationNodeData->cleanupKilledNode(sourceAvatarNode->getUUID(), sourceAvatarNode->getLocalID()); } - nodeData->setPrevRequestsDomainListData(PALIsOpen); + destinationNodeData->setPrevRequestsDomainListData(PALIsOpen); } // loop through our sorted avatars and allocate our bandwidth to them accordingly @@ -501,13 +501,14 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) int numAvatarsSent = 0; auto identityPacketList = NLPacketList::create(PacketType::AvatarIdentity, QByteArray(), true, true); + // Loop over two priorities - hero avatars then everyone else: for (PriorityVariants currentVariant = kHero; currentVariant <= kNonhero; ++((int&)currentVariant)) { const auto& sortedAvatarVector = avatarPriorityQueues[currentVariant].getSortedVector(numToSendEst); for (const auto& sortedAvatar : sortedAvatarVector) { - const Node* otherNode = sortedAvatar.getNode(); + const Node* sourceNode = sortedAvatar.getNode(); auto lastEncodeForOther = sortedAvatar.getTimestamp(); - assert(otherNode); // we can't have gotten here without the avatarData being a valid key in the map + assert(sourceNode); // we can't have gotten here without the avatarData being a valid key in the map AvatarData::AvatarDataDetail detail = AvatarData::NoData; @@ -533,31 +534,31 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) auto startAvatarDataPacking = chrono::high_resolution_clock::now(); - const AvatarMixerClientData* otherNodeData = reinterpret_cast(otherNode->getLinkedData()); - const MixerAvatar* otherAvatar = otherNodeData->getConstAvatarData(); + const AvatarMixerClientData* sourceNodeData = reinterpret_cast(sourceNode->getLinkedData()); + const MixerAvatar* sourceAvatar = sourceNodeData->getConstAvatarData(); // Typically all out-of-view avatars but such avatars' priorities will rise with time: - bool isLowerPriority = sortedAvatar.getPriority() <= OUT_OF_VIEW_THRESHOLD; + bool isLowerPriority = sortedAvatar.getPriority() <= OUT_OF_VIEW_THRESHOLD; // XXX: hero handling? if (isLowerPriority) { detail = PALIsOpen ? AvatarData::PALMinimum : AvatarData::MinimumData; - nodeData->incrementAvatarOutOfView(); + destinationNodeData->incrementAvatarOutOfView(); } else if (!overBudget) { detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO ? AvatarData::SendAllData : AvatarData::CullSmallData; - nodeData->incrementAvatarInView(); + destinationNodeData->incrementAvatarInView(); // If the time that the mixer sent AVATAR DATA about Avatar B to Avatar A is BEFORE OR EQUAL TO // the time that Avatar B flagged an IDENTITY DATA change, send IDENTITY DATA about Avatar B to Avatar A. - if (otherAvatar->hasProcessedFirstIdentity() - && nodeData->getLastBroadcastTime(otherNode->getLocalID()) <= otherNodeData->getIdentityChangeTimestamp()) { - identityBytesSent += sendIdentityPacket(*identityPacketList, otherNodeData, *destinationNode); + if (sourceAvatar->hasProcessedFirstIdentity() + && destinationNodeData->getLastBroadcastTime(sourceNode->getLocalID()) <= sourceNodeData->getIdentityChangeTimestamp()) { + identityBytesSent += sendIdentityPacket(*identityPacketList, sourceNodeData, *destinationNode); // remember the last time we sent identity details about this other node to the receiver - nodeData->setLastBroadcastTime(otherNode->getLocalID(), usecTimestampNow()); + destinationNodeData->setLastBroadcastTime(sourceNode->getLocalID(), usecTimestampNow()); } } - QVector& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getLocalID()); + QVector& lastSentJointsForOther = destinationNodeData->getLastOtherAvatarSentJoints(sourceNode->getLocalID()); const bool distanceAdjust = true; const bool dropFaceTracking = false; @@ -566,7 +567,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) do { auto startSerialize = chrono::high_resolution_clock::now(); - QByteArray bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, + QByteArray bytes = sourceAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, sendStatus, dropFaceTracking, distanceAdjust, myPosition, &lastSentJointsForOther, avatarSpaceAvailable); auto endSerialize = chrono::high_resolution_clock::now(); @@ -587,17 +588,17 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) if (detail != AvatarData::NoData) { _stats.numOthersIncluded++; - if (otherAvatar->getPriorityAvatar()) { + if (sourceAvatar->getPriorityAvatar()) { _stats.numHeroesIncluded++; } // increment the number of avatars sent to this receiver - nodeData->incrementNumAvatarsSentLastFrame(); + destinationNodeData->incrementNumAvatarsSentLastFrame(); // set the last sent sequence number for this sender on the receiver - nodeData->setLastBroadcastSequenceNumber(otherNode->getLocalID(), - otherNodeData->getLastReceivedSequenceNumber()); - nodeData->setLastOtherAvatarEncodeTime(otherNode->getLocalID(), usecTimestampNow()); + destinationNodeData->setLastBroadcastSequenceNumber(sourceNode->getLocalID(), + sourceNodeData->getLastReceivedSequenceNumber()); + destinationNodeData->setLastOtherAvatarEncodeTime(sourceNode->getLocalID(), usecTimestampNow()); } auto endAvatarDataPacking = chrono::high_resolution_clock::now(); @@ -606,7 +607,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) if (!overBudget) { // use helper to add any changed traits to our packet list - traitBytesSent += addChangedTraitsToBulkPacket(nodeData, otherNodeData, *traitsPacketList); + traitBytesSent += addChangedTraitsToBulkPacket(destinationNodeData, sourceNodeData, *traitsPacketList); } numAvatarsSent++; remainingAvatars--; @@ -619,8 +620,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) } } - if (nodeData->getNumAvatarsSentLastFrame() > numToSendEst) { - qCWarning(avatars) << "More avatars sent than upper estimate" << nodeData->getNumAvatarsSentLastFrame() + if (destinationNodeData->getNumAvatarsSentLastFrame() > numToSendEst) { + qCWarning(avatars) << "More avatars sent than upper estimate" << destinationNodeData->getNumAvatarsSentLastFrame() << " / " << numToSendEst; } @@ -651,12 +652,12 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) } // record the bytes sent for other avatar data in the AvatarMixerClientData - nodeData->recordSentAvatarData(numAvatarDataBytes, traitBytesSent); + destinationNodeData->recordSentAvatarData(numAvatarDataBytes, traitBytesSent); // record the number of avatars held back this frame - nodeData->recordNumOtherAvatarStarves(numAvatarsHeldBack); - nodeData->recordNumOtherAvatarSkips(numAvatarsWithSkippedFrames); + destinationNodeData->recordNumOtherAvatarStarves(numAvatarsHeldBack); + destinationNodeData->recordNumOtherAvatarSkips(numAvatarsWithSkippedFrames); quint64 endPacketSending = usecTimestampNow(); _stats.packetSendingElapsedTime += (endPacketSending - startPacketSending); From d4fa1e25fb0d4be161de1735c7368e9063fd18c1 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Thu, 28 Feb 2019 20:33:21 -0800 Subject: [PATCH 275/474] Working. --- tools/nitpick/src/AWSInterface.cpp | 33 ++++++++++++------ tools/nitpick/src/AWSInterface.h | 6 ++++ tools/nitpick/src/ImageComparer.cpp | 39 ++++++++++++--------- tools/nitpick/src/ImageComparer.h | 9 +++-- tools/nitpick/src/MismatchWindow.cpp | 51 ++++++++++++++-------------- tools/nitpick/src/MismatchWindow.h | 6 ++-- tools/nitpick/src/Nitpick.cpp | 10 +++--- tools/nitpick/src/Nitpick.h | 3 +- tools/nitpick/src/Test.cpp | 41 ++++++++++++---------- tools/nitpick/src/Test.h | 6 +++- tools/nitpick/src/common.h | 27 ++++++++++----- tools/nitpick/ui/Nitpick.ui | 42 ++++++++++++++++++++--- 12 files changed, 175 insertions(+), 98 deletions(-) diff --git a/tools/nitpick/src/AWSInterface.cpp b/tools/nitpick/src/AWSInterface.cpp index 4e83460b9e..6dcc255286 100644 --- a/tools/nitpick/src/AWSInterface.cpp +++ b/tools/nitpick/src/AWSInterface.cpp @@ -27,7 +27,10 @@ AWSInterface::AWSInterface(QObject* parent) : QObject(parent) { void AWSInterface::createWebPageFromResults(const QString& testResults, const QString& workingDirectory, QCheckBox* updateAWSCheckBox, - QLineEdit* urlLineEdit) { + QRadioButton* diffImageRadioButton, + QRadioButton* ssimImageRadionButton, + QLineEdit* urlLineEdit +) { _workingDirectory = workingDirectory; // Verify filename is in correct format @@ -52,6 +55,13 @@ void AWSInterface::createWebPageFromResults(const QString& testResults, QString zipFilenameWithoutExtension = zipFilename.split('.')[0]; extractTestFailuresFromZippedFolder(_workingDirectory + "/" + zipFilenameWithoutExtension); + + if (diffImageRadioButton->isChecked()) { + _comparisonImageFilename = "Difference Image.png"; + } else { + _comparisonImageFilename = "SSIM Image.png"; + } + createHTMLFile(); if (updateAWSCheckBox->isChecked()) { @@ -353,7 +363,7 @@ void AWSInterface::openTable(QTextStream& stream, const QString& testResult, con stream << "\t\t\t\t

      Test

      \n"; stream << "\t\t\t\t

      Actual Image

      \n"; stream << "\t\t\t\t

      Expected Image

      \n"; - stream << "\t\t\t\t

      Difference Image

      \n"; + stream << "\t\t\t\t

      Comparison Image

      \n"; stream << "\t\t\t\n"; } } @@ -378,12 +388,13 @@ void AWSInterface::createEntry(const int index, const QString& testResult, QText QString folder; bool differenceFileFound; + if (isFailure) { folder = FAILURES_FOLDER; - differenceFileFound = QFile::exists(_htmlFailuresFolder + "/" + resultName + "/Difference Image.png"); + differenceFileFound = QFile::exists(_htmlFailuresFolder + "/" + resultName + "/" + _comparisonImageFilename); } else { folder = SUCCESSES_FOLDER; - differenceFileFound = QFile::exists(_htmlSuccessesFolder + "/" + resultName + "/Difference Image.png"); + differenceFileFound = QFile::exists(_htmlSuccessesFolder + "/" + resultName + "/" + _comparisonImageFilename); } if (textResultsFileFound) { @@ -450,7 +461,7 @@ void AWSInterface::createEntry(const int index, const QString& testResult, QText stream << "\t\t\t\t\n"; if (differenceFileFound) { - stream << "\t\t\t\t\n"; + stream << "\t\t\t\t\n"; } else { stream << "\t\t\t\t

      No Image Found

      \n"; } @@ -512,12 +523,12 @@ void AWSInterface::updateAWS() { stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << filename << "/" << "Expected Image.png" << "', Body=data)\n\n"; - if (QFile::exists(_htmlFailuresFolder + "/" + parts[parts.length() - 1] + "/Difference Image.png")) { + if (QFile::exists(_htmlFailuresFolder + "/" + parts[parts.length() - 1] + "/" + _comparisonImageFilename)) { stream << "data = open('" << _workingDirectory << "/" << filename << "/" - << "Difference Image.png" + << _comparisonImageFilename << "', 'rb')\n"; - stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << filename << "/" << "Difference Image.png" << "', Body=data)\n\n"; + stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << filename << "/" << _comparisonImageFilename << "', Body=data)\n\n"; } } } @@ -555,12 +566,12 @@ void AWSInterface::updateAWS() { stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << filename << "/" << "Expected Image.png" << "', Body=data)\n\n"; - if (QFile::exists(_htmlSuccessesFolder + "/" + parts[parts.length() - 1] + "/Difference Image.png")) { + if (QFile::exists(_htmlSuccessesFolder + "/" + parts[parts.length() - 1] + "/" + _comparisonImageFilename)) { stream << "data = open('" << _workingDirectory << "/" << filename << "/" - << "Difference Image.png" + << _comparisonImageFilename << "', 'rb')\n"; - stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << filename << "/" << "Difference Image.png" << "', Body=data)\n\n"; + stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << filename << "/" << _comparisonImageFilename << "', Body=data)\n\n"; } } } diff --git a/tools/nitpick/src/AWSInterface.h b/tools/nitpick/src/AWSInterface.h index d95b8ecf2f..77d500fa7c 100644 --- a/tools/nitpick/src/AWSInterface.h +++ b/tools/nitpick/src/AWSInterface.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include "BusyWindow.h" @@ -28,6 +29,8 @@ public: void createWebPageFromResults(const QString& testResults, const QString& workingDirectory, QCheckBox* updateAWSCheckBox, + QRadioButton* diffImageRadioButton, + QRadioButton* ssimImageRadionButton, QLineEdit* urlLineEdit); void extractTestFailuresFromZippedFolder(const QString& folderName); @@ -67,6 +70,9 @@ private: QString AWS_BUCKET{ "hifi-qa" }; QLineEdit* _urlLineEdit; + + + QString _comparisonImageFilename; }; #endif // hifi_AWSInterface_h \ No newline at end of file diff --git a/tools/nitpick/src/ImageComparer.cpp b/tools/nitpick/src/ImageComparer.cpp index b9d556e544..7e3e6eaf63 100644 --- a/tools/nitpick/src/ImageComparer.cpp +++ b/tools/nitpick/src/ImageComparer.cpp @@ -12,17 +12,9 @@ #include -ImageComparer::ImageComparer() { - _ssimResults = new SSIMResults(); -} - -ImageComparer::~ImageComparer() { - delete _ssimResults; -} - // Computes SSIM - see https://en.wikipedia.org/wiki/Structural_similarity // The value is computed for the luminance component and the average value is returned -double ImageComparer::compareImages(const QImage& resultImage, const QImage& expectedImage) const { +void ImageComparer::compareImages(const QImage& resultImage, const QImage& expectedImage) { const int L = 255; // (2^number of bits per pixel) - 1 const double K1 { 0.01 }; @@ -47,8 +39,13 @@ double ImageComparer::compareImages(const QImage& resultImage, const QImage& exp double p[WIN_SIZE * WIN_SIZE]; double q[WIN_SIZE * WIN_SIZE]; + _ssimResults.results.clear(); + int windowCounter{ 0 }; double ssim{ 0.0 }; + double min { 1.0 }; + double max { -1.0 }; + while (x < expectedImage.width()) { int lastX = x + WIN_SIZE - 1; if (lastX > expectedImage.width() - 1) { @@ -104,8 +101,13 @@ double ImageComparer::compareImages(const QImage& resultImage, const QImage& exp double numerator = (2.0 * mP * mQ + c1) * (2.0 * sigPQ + c2); double denominator = (mP * mP + mQ * mQ + c1) * (sigsqP + sigsqQ + c2); - _ssimResults->results.push_back(numerator / denominator); - ssim += numerator / denominator; + double value { numerator / denominator }; + _ssimResults.results.push_back(value); + ssim += value; + + if (value < min) min = value; + if (value > max) max = value; + ++windowCounter; y += WIN_SIZE; @@ -115,12 +117,17 @@ double ImageComparer::compareImages(const QImage& resultImage, const QImage& exp y = 0; } - _ssimResults->width = (int)(expectedImage.width() / WIN_SIZE); - _ssimResults->height = (int)(expectedImage.height() / WIN_SIZE); - - return ssim / windowCounter; + _ssimResults.width = (int)(expectedImage.width() / WIN_SIZE); + _ssimResults.height = (int)(expectedImage.height() / WIN_SIZE); + _ssimResults.min = min; + _ssimResults.max = max; + _ssimResults.ssim = ssim / windowCounter; }; -SSIMResults* ImageComparer::getSSIMResults() { +double ImageComparer::getSSIMValue() { + return _ssimResults.ssim; +} + +SSIMResults ImageComparer::getSSIMResults() { return _ssimResults; } diff --git a/tools/nitpick/src/ImageComparer.h b/tools/nitpick/src/ImageComparer.h index 5bea8151ec..fc14dab94d 100644 --- a/tools/nitpick/src/ImageComparer.h +++ b/tools/nitpick/src/ImageComparer.h @@ -17,14 +17,13 @@ class ImageComparer { public: - ImageComparer(); - ~ImageComparer(); + void compareImages(const QImage& resultImage, const QImage& expectedImage); + double getSSIMValue(); - double compareImages(const QImage& resultImage, const QImage& expectedImage) const; - SSIMResults* getSSIMResults(); + SSIMResults getSSIMResults(); private: - SSIMResults* _ssimResults; + SSIMResults _ssimResults; }; #endif // hifi_ImageComparer_h diff --git a/tools/nitpick/src/MismatchWindow.cpp b/tools/nitpick/src/MismatchWindow.cpp index 7649929551..fd5df0dd4e 100644 --- a/tools/nitpick/src/MismatchWindow.cpp +++ b/tools/nitpick/src/MismatchWindow.cpp @@ -21,7 +21,7 @@ MismatchWindow::MismatchWindow(QWidget *parent) : QDialog(parent) { diffImage->setScaledContents(true); } -QPixmap MismatchWindow::computeDiffPixmap(QImage expectedImage, QImage resultImage) { +QPixmap MismatchWindow::computeDiffPixmap(const QImage& expectedImage, const QImage& resultImage) { // Create an empty difference image if the images differ in size if (expectedImage.height() != resultImage.height() || expectedImage.width() != resultImage.width()) { return QPixmap(); @@ -60,7 +60,7 @@ QPixmap MismatchWindow::computeDiffPixmap(QImage expectedImage, QImage resultIma return resultPixmap; } -void MismatchWindow::setTestResult(TestResult testResult) { +void MismatchWindow::setTestResult(const TestResult& testResult) { errorLabel->setText("Similarity: " + QString::number(testResult._error)); imagePath->setText("Path to test: " + testResult._pathname); @@ -100,34 +100,35 @@ QPixmap MismatchWindow::getComparisonImage() { return _diffPixmap; } -QPixmap MismatchWindow::getSSIMResultsImage(SSIMResults* ssimResults) { +QPixmap MismatchWindow::getSSIMResultsImage(const SSIMResults& ssimResults) { // This is an optimization, as QImage.setPixel() is embarrassingly slow const int ELEMENT_SIZE { 8 }; - unsigned char* buffer = new unsigned char[(ssimResults->height * ELEMENT_SIZE) * (ssimResults->width * ELEMENT_SIZE ) * 3]; + const int WIDTH{ ssimResults.width * ELEMENT_SIZE }; + const int HEIGHT{ ssimResults.height * ELEMENT_SIZE }; + + unsigned char* buffer = new unsigned char[WIDTH * HEIGHT * 3]; - // loop over each SSIM result (a double in [-1.0 .. 1.0] - int i { 0 }; - for (int y = 0; y < ssimResults->height; ++y) { - for (int x = 0; x < ssimResults->width; ++x) { - ////QRgb pixelP = expectedImage.pixel(QPoint(x, y)); - - ////// Convert to luminance - ////double p = R_Y * qRed(pixelP) + G_Y * qGreen(pixelP) + B_Y * qBlue(pixelP); - - ////// The intensity value is modified to increase the brightness of the displayed image - ////double absoluteDifference = fabs(p - q) / 255.0; - ////double modifiedDifference = sqrt(absoluteDifference); - - ////int difference = (int)(modifiedDifference * 255.0); - - ////buffer[3 * (x + y * expectedImage.width()) + 0] = difference; - ////buffer[3 * (x + y * expectedImage.width()) + 1] = difference; - ////buffer[3 * (x + y * expectedImage.width()) + 2] = difference; - - ++i; + // loop over each SSIM result + for (int y = 0; y < ssimResults.height; ++y) { + for (int x = 0; x < ssimResults.width; ++x) { + double scaledResult = (ssimResults.results[x * ssimResults.height + y] + 1.0) / (2.0); + //double scaledResult = (ssimResults.results[x * ssimResults.height + y] - ssimResults.min) / (ssimResults.max - ssimResults.min); + // Create a square + for (int yy = 0; yy < ELEMENT_SIZE; ++yy) { + for (int xx = 0; xx < ELEMENT_SIZE; ++xx) { + buffer[(xx + yy * WIDTH + x * ELEMENT_SIZE + y * WIDTH * ELEMENT_SIZE) * 3 + 0] = 255 * (1.0 - scaledResult); // R + buffer[(xx + yy * WIDTH + x * ELEMENT_SIZE + y * WIDTH * ELEMENT_SIZE) * 3 + 1] = 255 * scaledResult; // G + buffer[(xx + yy * WIDTH + x * ELEMENT_SIZE + y * WIDTH * ELEMENT_SIZE) * 3 + 2] = 0; // B + } + } } } - return QPixmap(); + QImage image(buffer, WIDTH, HEIGHT, QImage::Format_RGB888); + QPixmap pixmap = QPixmap::fromImage(image); + + delete[] buffer; + + return pixmap; } diff --git a/tools/nitpick/src/MismatchWindow.h b/tools/nitpick/src/MismatchWindow.h index d80e371ba0..116d35dfc5 100644 --- a/tools/nitpick/src/MismatchWindow.h +++ b/tools/nitpick/src/MismatchWindow.h @@ -20,14 +20,14 @@ class MismatchWindow : public QDialog, public Ui::MismatchWindow { public: MismatchWindow(QWidget *parent = Q_NULLPTR); - void setTestResult(TestResult testResult); + void setTestResult(const TestResult& testResult); UserResponse getUserResponse() { return _userResponse; } - QPixmap computeDiffPixmap(QImage expectedImage, QImage resultImage); + QPixmap computeDiffPixmap(const QImage& expectedImage, const QImage& resultImage); QPixmap getComparisonImage(); - QPixmap getSSIMResultsImage(SSIMResults* ssimResults); + QPixmap getSSIMResultsImage(const SSIMResults& ssimResults); private slots: void on_passTestButton_clicked(); diff --git a/tools/nitpick/src/Nitpick.cpp b/tools/nitpick/src/Nitpick.cpp index e17ea94634..c07a76fc58 100644 --- a/tools/nitpick/src/Nitpick.cpp +++ b/tools/nitpick/src/Nitpick.cpp @@ -148,10 +148,6 @@ void Nitpick::on_tabWidget_currentChanged(int index) { } } -void Nitpick::on_evaluateTestsPushbutton_clicked() { - _test->startTestsEvaluation(false, false); -} - void Nitpick::on_createRecursiveScriptPushbutton_clicked() { _test->createRecursiveScript(); } @@ -242,6 +238,10 @@ void Nitpick::on_showTaskbarPushbutton_clicked() { #endif } +void Nitpick::on_evaluateTestsPushbutton_clicked() { + _test->startTestsEvaluation(false, false); +} + void Nitpick::on_closePushbutton_clicked() { exit(0); } @@ -255,7 +255,7 @@ void Nitpick::on_createXMLScriptRadioButton_clicked() { } void Nitpick::on_createWebPagePushbutton_clicked() { - _test->createWebPage(_ui.updateAWSCheckBox, _ui.awsURLLineEdit); + _test->createWebPage(_ui.updateAWSCheckBox, _ui.diffImageRadioButton, _ui.ssimImageRadioButton, _ui.awsURLLineEdit); } void Nitpick::downloadFile(const QUrl& url) { diff --git a/tools/nitpick/src/Nitpick.h b/tools/nitpick/src/Nitpick.h index 80fef934d6..3095a14c05 100644 --- a/tools/nitpick/src/Nitpick.h +++ b/tools/nitpick/src/Nitpick.h @@ -56,7 +56,6 @@ private slots: void on_tabWidget_currentChanged(int index); - void on_evaluateTestsPushbutton_clicked(); void on_createRecursiveScriptPushbutton_clicked(); void on_createAllRecursiveScriptsPushbutton_clicked(); void on_createTestsPushbutton_clicked(); @@ -82,6 +81,8 @@ private slots: void on_hideTaskbarPushbutton_clicked(); void on_showTaskbarPushbutton_clicked(); + void on_evaluateTestsPushbutton_clicked(); + void on_createPythonScriptRadioButton_clicked(); void on_createXMLScriptRadioButton_clicked(); diff --git a/tools/nitpick/src/Test.cpp b/tools/nitpick/src/Test.cpp index a1f1bf92eb..7269fb3f02 100644 --- a/tools/nitpick/src/Test.cpp +++ b/tools/nitpick/src/Test.cpp @@ -89,25 +89,25 @@ int Test::compareImageLists() { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Images are not the same size"); similarityIndex = -100.0; } else { - similarityIndex = _imageComparer.compareImages(resultImage, expectedImage); + _imageComparer.compareImages(resultImage, expectedImage); + similarityIndex = _imageComparer.getSSIMValue(); } TestResult testResult = TestResult{ (float)similarityIndex, _expectedImagesFullFilenames[i].left(_expectedImagesFullFilenames[i].lastIndexOf("/") + 1), // path to the test (including trailing /) QFileInfo(_expectedImagesFullFilenames[i].toStdString().c_str()).fileName(), // filename of expected image - QFileInfo(_resultImagesFullFilenames[i].toStdString().c_str()).fileName() // filename of result image + QFileInfo(_resultImagesFullFilenames[i].toStdString().c_str()).fileName(), // filename of result image + _imageComparer.getSSIMResults() // results of SSIM algoritm }; _mismatchWindow.setTestResult(testResult); - QPixmap ssimResultsPixMap = _mismatchWindow.getSSIMResultsImage(_imageComparer.getSSIMResults()); - if (similarityIndex < THRESHOLD) { ++numberOfFailures; if (!isInteractiveMode) { - appendTestResultsToFile(testResult, _mismatchWindow.getComparisonImage(), ssimResultsPixMap, true); + appendTestResultsToFile(testResult, _mismatchWindow.getComparisonImage(), _mismatchWindow.getSSIMResultsImage(testResult._ssimResults), true); } else { _mismatchWindow.exec(); @@ -115,7 +115,7 @@ int Test::compareImageLists() { case USER_RESPONSE_PASS: break; case USE_RESPONSE_FAIL: - appendTestResultsToFile(testResult, _mismatchWindow.getComparisonImage(), ssimResultsPixMap, true); + appendTestResultsToFile(testResult, _mismatchWindow.getComparisonImage(), _mismatchWindow.getSSIMResultsImage(testResult._ssimResults), true); break; case USER_RESPONSE_ABORT: keepOn = false; @@ -126,7 +126,7 @@ int Test::compareImageLists() { } } } else { - appendTestResultsToFile(testResult, _mismatchWindow.getComparisonImage(), ssimResultsPixMap, false); + appendTestResultsToFile(testResult, _mismatchWindow.getComparisonImage(), _mismatchWindow.getSSIMResultsImage(testResult._ssimResults), false); } _progressBar->setValue(i); @@ -218,15 +218,10 @@ void Test::appendTestResultsToFile(const TestResult& testResult, const QPixmap& exit(-1); } - // Create the SSIM results image - sourceFile = testResult._pathname + testResult._actualImageFilename; - destinationFile = resultFolderPath + "/" + "SSIM results.png"; - if (!QFile::copy(sourceFile, destinationFile)) { - QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to copy " + sourceFile + " to " + destinationFile); - exit(-1); - } - comparisonImage.save(resultFolderPath + "/" + "Difference Image.png"); + + // Save the SSIM results image + ssimResultsImage.save(resultFolderPath + "/" + "SSIM Image.png"); } void::Test::appendTestResultsToFile(QString testResultFilename, bool hasFailed) { @@ -1105,7 +1100,12 @@ void Test::setTestRailCreateMode(TestRailCreateMode testRailCreateMode) { _testRailCreateMode = testRailCreateMode; } -void Test::createWebPage(QCheckBox* updateAWSCheckBox, QLineEdit* urlLineEdit) { +void Test::createWebPage( + QCheckBox* updateAWSCheckBox, + QRadioButton* diffImageRadioButton, + QRadioButton* ssimImageRadionButton, + QLineEdit* urlLineEdit +) { QString testResults = QFileDialog::getOpenFileName(nullptr, "Please select the zipped test results to update from", nullptr, "Zipped Test Results (TestResults--*.zip)"); if (testResults.isNull()) { @@ -1122,5 +1122,12 @@ void Test::createWebPage(QCheckBox* updateAWSCheckBox, QLineEdit* urlLineEdit) { _awsInterface = new AWSInterface; } - _awsInterface->createWebPageFromResults(testResults, workingDirectory, updateAWSCheckBox, urlLineEdit); + _awsInterface->createWebPageFromResults( + testResults, + workingDirectory, + updateAWSCheckBox, + diffImageRadioButton, + ssimImageRadionButton, + urlLineEdit + ); } \ No newline at end of file diff --git a/tools/nitpick/src/Test.h b/tools/nitpick/src/Test.h index 752dcbc5af..8753b9fcda 100644 --- a/tools/nitpick/src/Test.h +++ b/tools/nitpick/src/Test.h @@ -102,7 +102,11 @@ public: void setTestRailCreateMode(TestRailCreateMode testRailCreateMode); - void createWebPage(QCheckBox* updateAWSCheckBox, QLineEdit* urlLineEdit); + void createWebPage( + QCheckBox* updateAWSCheckBox, + QRadioButton* diffImageRadioButton, + QRadioButton* ssimImageRadionButton, + QLineEdit* urlLineEdit); private: QProgressBar* _progressBar; diff --git a/tools/nitpick/src/common.h b/tools/nitpick/src/common.h index ac776995b7..eb228ff2b3 100644 --- a/tools/nitpick/src/common.h +++ b/tools/nitpick/src/common.h @@ -13,19 +13,35 @@ #include #include +class SSIMResults { +public: + int width; + int height; + std::vector results; + double ssim; + + // Used for scaling + double min; + double max; +}; + class TestResult { public: - TestResult(float error, QString pathname, QString expectedImageFilename, QString actualImageFilename) : + TestResult(float error, const QString& pathname, const QString& expectedImageFilename, const QString& actualImageFilename, const SSIMResults& ssimResults) : _error(error), _pathname(pathname), _expectedImageFilename(expectedImageFilename), - _actualImageFilename(actualImageFilename) + _actualImageFilename(actualImageFilename), + _ssimResults(ssimResults) {} double _error; + QString _pathname; QString _expectedImageFilename; QString _actualImageFilename; + + SSIMResults _ssimResults; }; enum UserResponse { @@ -40,11 +56,4 @@ const double R_Y = 0.212655f; const double G_Y = 0.715158f; const double B_Y = 0.072187f; -class SSIMResults { -public: - int width; - int height; - std::vector results; -}; - #endif // hifi_common_h \ No newline at end of file diff --git a/tools/nitpick/ui/Nitpick.ui b/tools/nitpick/ui/Nitpick.ui index 47471522db..1857a2118f 100644 --- a/tools/nitpick/ui/Nitpick.ui +++ b/tools/nitpick/ui/Nitpick.ui @@ -43,7 +43,7 @@ - 0 + 5 @@ -760,7 +760,7 @@ 190 - 180 + 200 131 20 @@ -776,7 +776,7 @@ 330 - 170 + 190 181 51 @@ -889,8 +889,8 @@ - 270 - 30 + 370 + 20 160 51 @@ -922,6 +922,38 @@ + + + + 260 + 50 + 95 + 20 + + + + Diff Image + + + false + + + + + + 260 + 30 + 95 + 20 + + + + SSIM Image + + + true + + groupBox updateTestRailRunResultsPushbutton From d391d4af389ca10f3211dbbe0772fc6d517dbe67 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Fri, 1 Mar 2019 10:08:22 -0800 Subject: [PATCH 276/474] Debugging + tweaks --- assignment-client/src/avatars/AvatarMixerSlave.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index ac18268860..d57d3a5011 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -522,6 +522,9 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) _stats.overBudgetAvatars++; detail = AvatarData::PALMinimum; } else { + if (currentVariant == kHero) { + qCWarning(avatars) << "Overbudget break with hero avatars!" << destinationNode->getUUID().toString(); + } _stats.overBudgetAvatars += remainingAvatars; break; } @@ -538,7 +541,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) const MixerAvatar* sourceAvatar = sourceNodeData->getConstAvatarData(); // Typically all out-of-view avatars but such avatars' priorities will rise with time: - bool isLowerPriority = sortedAvatar.getPriority() <= OUT_OF_VIEW_THRESHOLD; // XXX: hero handling? + bool isLowerPriority = currentVariant != kHero && sortedAvatar.getPriority() <= OUT_OF_VIEW_THRESHOLD; // XXX: hero handling? if (isLowerPriority) { detail = PALIsOpen ? AvatarData::PALMinimum : AvatarData::MinimumData; From cfb84967d55748d2c24989149dfbb427f809a0f6 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Fri, 1 Mar 2019 10:54:44 -0800 Subject: [PATCH 277/474] these seemed dangerous --- .../ui/src/ui/TabletScriptingInterface.cpp | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp index a78b9a17fc..7a1c37af33 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -458,6 +458,11 @@ void TabletProxy::emitWebEvent(const QVariant& msg) { } void TabletProxy::onTabletShown() { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "onTabletShown"); + return; + } + if (_tabletShown) { Setting::Handle notificationSounds{ QStringLiteral("play_notification_sounds"), true}; Setting::Handle notificationSoundTablet{ QStringLiteral("play_notification_sounds_tablet"), true}; @@ -485,7 +490,11 @@ bool TabletProxy::isPathLoaded(const QVariant& path) { } void TabletProxy::setQmlTabletRoot(OffscreenQmlSurface* qmlOffscreenSurface) { - Q_ASSERT(QThread::currentThread() == qApp->thread()); + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setQmlTabletRoot", Q_ARG(OffscreenQmlSurface*, qmlOffscreenSurface)); + return; + } + _qmlOffscreenSurface = qmlOffscreenSurface; _qmlTabletRoot = qmlOffscreenSurface ? qmlOffscreenSurface->getRootItem() : nullptr; if (_qmlTabletRoot && _qmlOffscreenSurface) { @@ -654,6 +663,11 @@ void TabletProxy::loadQMLSource(const QVariant& path, bool resizable) { } void TabletProxy::stopQMLSource() { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "stopQMLSource"); + return; + } + // For desktop toolbar mode dialogs. if (!_toolbarMode || !_desktopWindow) { qCDebug(uiLogging) << "tablet cannot clear QML because not desktop toolbar mode"; @@ -879,6 +893,12 @@ void TabletProxy::sendToQml(const QVariant& msg) { OffscreenQmlSurface* TabletProxy::getTabletSurface() { + if (QThread::currentThread() != thread()) { + OffscreenQmlSurface* result = nullptr; + BLOCKING_INVOKE_METHOD(this, "getTabletSurface", Q_RETURN_ARG(OffscreenQmlSurface*, result)); + return result; + } + return _qmlOffscreenSurface; } @@ -888,6 +908,11 @@ void TabletProxy::desktopWindowClosed() { } void TabletProxy::unfocus() { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "unfocus"); + return; + } + if (_qmlOffscreenSurface) { _qmlOffscreenSurface->lowerKeyboard(); } From ad69611b7cd1d9c1e1ae29204cdeee74b91d772e Mon Sep 17 00:00:00 2001 From: amer cerkic Date: Fri, 1 Mar 2019 11:08:00 -0800 Subject: [PATCH 278/474] removing redef of the two functions in avatar.h --- libraries/avatars-renderer/src/avatars-renderer/Avatar.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 9f713637f5..6c31f9fc93 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -287,8 +287,6 @@ public: * @returns {Quat} */ Q_INVOKABLE glm::quat jointToWorldRotation(const glm::quat& rotation, const int jointIndex = -1) const; - virtual glm::vec3 getSpine2SplineOffset() const { return _spine2SplineOffset; } - virtual float getSpine2SplineRatio() const { return _spine2SplineRatio; } virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override; virtual void setAttachmentData(const QVector& attachmentData) override; From 82b2050229bb0ad42d9ab3a16106fe0654ea4292 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Fri, 1 Mar 2019 11:13:56 -0800 Subject: [PATCH 279/474] code review feedback --- libraries/animation/src/AnimSkeleton.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index 993a2c6632..0f1c18c9dc 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -150,7 +150,7 @@ void AnimSkeleton::convertAbsolutePosesToRelative(AnimPoseVec& poses) const { } void AnimSkeleton::convertRelativeRotationsToAbsolute(std::vector& rotations) const { - // poses start off relative and leave in absolute frame + // rotations start off relative and leave in absolute frame int lastIndex = std::min((int)rotations.size(), _jointsSize); for (int i = 0; i < lastIndex; ++i) { int parentIndex = _parentIndices[i]; @@ -161,7 +161,7 @@ void AnimSkeleton::convertRelativeRotationsToAbsolute(std::vector& ro } void AnimSkeleton::convertAbsoluteRotationsToRelative(std::vector& rotations) const { - // poses start off absolute and leave in relative frame + // rotations start off absolute and leave in relative frame int lastIndex = std::min((int)rotations.size(), _jointsSize); for (int i = lastIndex - 1; i >= 0; --i) { int parentIndex = _parentIndices[i]; From dfb17c922bc783182dc3242097534f840ed5bb5f Mon Sep 17 00:00:00 2001 From: raveenajain Date: Fri, 1 Mar 2019 11:21:29 -0800 Subject: [PATCH 280/474] fix model not loading in bounding box --- libraries/fbx/src/GLTFSerializer.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libraries/fbx/src/GLTFSerializer.cpp b/libraries/fbx/src/GLTFSerializer.cpp index 6dadea29d8..940ba69bdd 100755 --- a/libraries/fbx/src/GLTFSerializer.cpp +++ b/libraries/fbx/src/GLTFSerializer.cpp @@ -739,8 +739,10 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const QUrl& url) { //Build dependencies QVector> nodeDependencies(_file.nodes.size()); int nodecount = 0; + bool hasChildren = false; foreach(auto &node, _file.nodes) { //nodes_transforms.push_back(getModelTransform(node)); + hasChildren |= !node.children.isEmpty(); foreach(int child, node.children) nodeDependencies[child].push_back(nodecount); nodecount++; } @@ -772,8 +774,10 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const QUrl& url) { hfmModel.jointIndices["x"] = _file.nodes.size(); int jointInd = 0; for (auto& node : _file.nodes) { + int size = node.transforms.size(); + if (hasChildren) { size--; } joint.preTransform = glm::mat4(1); - for (int i = 0; i < node.transforms.size(); i++) { + for (int i = 0; i < size; i++) { joint.preTransform = node.transforms[i] * joint.preTransform; } joint.name = node.name; From 135c7b667eb126bcc4ebab948fa7450327209ae8 Mon Sep 17 00:00:00 2001 From: Clement Date: Fri, 1 Mar 2019 11:23:46 -0800 Subject: [PATCH 281/474] Reduce size of _nodeMutex critical sections --- assignment-client/src/avatars/AvatarMixer.cpp | 8 +++ libraries/networking/src/LimitedNodeList.cpp | 59 ++++++++----------- libraries/networking/src/LimitedNodeList.h | 19 +++--- 3 files changed, 43 insertions(+), 43 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 500772c1b5..2c5e6a90c9 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -239,6 +239,10 @@ void AvatarMixer::start() { }, &lockWait, &nodeTransform, &functor); auto end = usecTimestampNow(); _processQueuedAvatarDataPacketsElapsedTime += (end - start); + + _broadcastAvatarDataLockWait += lockWait; + _broadcastAvatarDataNodeTransform += nodeTransform; + _broadcastAvatarDataNodeFunctor += functor; } // process pending display names... this doesn't currently run on multiple threads, because it @@ -256,6 +260,10 @@ void AvatarMixer::start() { }, &lockWait, &nodeTransform, &functor); auto end = usecTimestampNow(); _displayNameManagementElapsedTime += (end - start); + + _broadcastAvatarDataLockWait += lockWait; + _broadcastAvatarDataNodeTransform += nodeTransform; + _broadcastAvatarDataNodeFunctor += functor; } // this is where we need to put the real work... diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index eaa02f059e..74ad8fc398 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -558,25 +558,23 @@ SharedNodePointer LimitedNodeList::nodeWithLocalID(Node::LocalID localID) const } void LimitedNodeList::eraseAllNodes() { - QSet killedNodes; + std::vector killedNodes; { // iterate the current nodes - grab them so we can emit that they are dying // and then remove them from the hash QWriteLocker writeLocker(&_nodeMutex); - _localIDMap.clear(); - if (_nodeHash.size() > 0) { qCDebug(networking) << "LimitedNodeList::eraseAllNodes() removing all nodes from NodeList."; - auto it = _nodeHash.begin(); - - while (it != _nodeHash.end()) { - killedNodes.insert(it->second); - it = _nodeHash.unsafe_erase(it); + killedNodes.reserve(_nodeHash.size()); + for (auto& pair : _nodeHash) { + killedNodes.push_back(pair.second); } } + _localIDMap.clear(); + _nodeHash.clear(); } foreach(const SharedNodePointer& killedNode, killedNodes) { @@ -593,18 +591,13 @@ void LimitedNodeList::reset() { } bool LimitedNodeList::killNodeWithUUID(const QUuid& nodeUUID, ConnectionID newConnectionID) { - QReadLocker readLocker(&_nodeMutex); - - NodeHash::iterator it = _nodeHash.find(nodeUUID); - if (it != _nodeHash.end()) { - SharedNodePointer matchingNode = it->second; - - readLocker.unlock(); + auto matchingNode = nodeWithUUID(nodeUUID); + if (matchingNode) { { QWriteLocker writeLocker(&_nodeMutex); _localIDMap.unsafe_erase(matchingNode->getLocalID()); - _nodeHash.unsafe_erase(it); + _nodeHash.unsafe_erase(matchingNode->getUUID()); } handleNodeKill(matchingNode, newConnectionID); @@ -645,30 +638,26 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, Node::LocalID localID, bool isReplicated, bool isUpstream, const QUuid& connectionSecret, const NodePermissions& permissions) { - { - QReadLocker readLocker(&_nodeMutex); - NodeHash::const_iterator it = _nodeHash.find(uuid); + auto matchingNode = nodeWithUUID(uuid); + if (matchingNode) { + matchingNode->setPublicSocket(publicSocket); + matchingNode->setLocalSocket(localSocket); + matchingNode->setPermissions(permissions); + matchingNode->setConnectionSecret(connectionSecret); + matchingNode->setIsReplicated(isReplicated); + matchingNode->setIsUpstream(isUpstream || NodeType::isUpstream(nodeType)); + matchingNode->setLocalID(localID); - if (it != _nodeHash.end()) { - SharedNodePointer& matchingNode = it->second; - - matchingNode->setPublicSocket(publicSocket); - matchingNode->setLocalSocket(localSocket); - matchingNode->setPermissions(permissions); - matchingNode->setConnectionSecret(connectionSecret); - matchingNode->setIsReplicated(isReplicated); - matchingNode->setIsUpstream(isUpstream || NodeType::isUpstream(nodeType)); - matchingNode->setLocalID(localID); - - return matchingNode; - } + return matchingNode; } auto removeOldNode = [&](auto node) { if (node) { - QWriteLocker writeLocker(&_nodeMutex); - _localIDMap.unsafe_erase(node->getLocalID()); - _nodeHash.unsafe_erase(node->getUUID()); + { + QWriteLocker writeLocker(&_nodeMutex); + _localIDMap.unsafe_erase(node->getLocalID()); + _nodeHash.unsafe_erase(node->getUUID()); + } handleNodeKill(node); } }; diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index 450fad96a9..57e03ce3b7 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -205,7 +205,10 @@ public: int* lockWaitOut = nullptr, int* nodeTransformOut = nullptr, int* functorOut = nullptr) { - auto start = usecTimestampNow(); + quint64 start, endTransform, endFunctor; + + start = usecTimestampNow(); + std::vector nodes; { QReadLocker readLock(&_nodeMutex); auto endLock = usecTimestampNow(); @@ -216,21 +219,21 @@ public: // Size of _nodeHash could change at any time, // so reserve enough memory for the current size // and then back insert all the nodes found - std::vector nodes; nodes.reserve(_nodeHash.size()); std::transform(_nodeHash.cbegin(), _nodeHash.cend(), std::back_inserter(nodes), [&](const NodeHash::value_type& it) { return it.second; }); - auto endTransform = usecTimestampNow(); + + endTransform = usecTimestampNow(); if (nodeTransformOut) { *nodeTransformOut = (endTransform - endLock); } + } - functor(nodes.cbegin(), nodes.cend()); - auto endFunctor = usecTimestampNow(); - if (functorOut) { - *functorOut = (endFunctor - endTransform); - } + functor(nodes.cbegin(), nodes.cend()); + endFunctor = usecTimestampNow(); + if (functorOut) { + *functorOut = (endFunctor - endTransform); } } From 16eb3444c11c6513ff0d8a1d73a50c9856bf432b Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Fri, 1 Mar 2019 11:31:01 -0800 Subject: [PATCH 282/474] Code Review Fixes --- libraries/networking/src/NodeList.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index c9d1757f0d..209fb87b11 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -307,27 +307,31 @@ void NodeList::sendDomainServerCheckIn() { return; } - if (_publicSockAddr.isNull()) { + auto publicSockAddr = _publicSockAddr; + auto domainHandlerIp = _domainHandler.getIP(); + + if (publicSockAddr.isNull()) { // we don't know our public socket and we need to send it to the domain server qCDebug(networking) << "Waiting for inital public socket from STUN. Will not send domain-server check in."; - } else if (_domainHandler.getIP().isNull() && _domainHandler.requiresICE()) { + } else if (domainHandlerIp.isNull() && _domainHandler.requiresICE()) { qCDebug(networking) << "Waiting for ICE discovered domain-server socket. Will not send domain-server check in."; handleICEConnectionToDomainServer(); // let the domain handler know we are due to send a checkin packet - } else if (!_domainHandler.getIP().isNull() && !_domainHandler.checkInPacketTimeout()) { + } else if (!domainHandlerIp.isNull() && !_domainHandler.checkInPacketTimeout()) { bool domainIsConnected = _domainHandler.isConnected(); HifiSockAddr domainSockAddr = _domainHandler.getSockAddr(); PacketType domainPacketType = !domainIsConnected ? PacketType::DomainConnectRequest : PacketType::DomainListRequest; if (!domainIsConnected) { - qCDebug(networking) << "Sending connect request to domain-server at" << _domainHandler.getHostname(); + auto hostname = _domainHandler.getHostname(); + qCDebug(networking) << "Sending connect request to domain-server at" << hostname; // is this our localhost domain-server? // if so we need to make sure we have an up-to-date local port in case it restarted if (domainSockAddr.getAddress() == QHostAddress::LocalHost - || _domainHandler.getHostname() == "localhost") { + || hostname == "localhost") { quint16 domainPort = DEFAULT_DOMAIN_SERVER_PORT; getLocalServerPortFromSharedMemory(DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY, domainPort); @@ -408,7 +412,7 @@ void NodeList::sendDomainServerCheckIn() { // pack our data to send to the domain-server including // the hostname information (so the domain-server can see which place name we came in on) - packetStream << _ownerType.load() << _publicSockAddr << localSockAddr << _nodeTypesOfInterest.toList(); + packetStream << _ownerType.load() << publicSockAddr << localSockAddr << _nodeTypesOfInterest.toList(); packetStream << DependencyManager::get()->getPlaceName(); if (!domainIsConnected) { From 8dadeb197bc4ef84858aef1cea6eb4b605e13b75 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Fri, 1 Mar 2019 11:35:08 -0800 Subject: [PATCH 283/474] Corrected error message. --- tools/nitpick/src/AdbInterface.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tools/nitpick/src/AdbInterface.cpp b/tools/nitpick/src/AdbInterface.cpp index 82ef1446e3..41eb947efa 100644 --- a/tools/nitpick/src/AdbInterface.cpp +++ b/tools/nitpick/src/AdbInterface.cpp @@ -16,12 +16,13 @@ QString AdbInterface::getAdbCommand() { #ifdef Q_OS_WIN if (_adbCommand.isNull()) { - QString adbPath = PathUtils::getPathToExecutable("adb.exe"); + QString adbExe{ "adb.exe" }; + QString adbPath = PathUtils::getPathToExecutable(adbExe); if (!adbPath.isNull()) { - _adbCommand = adbPath + _adbExe; + _adbCommand = adbExe; } else { - QMessageBox::critical(0, "python.exe not found", - "Please verify that pyton.exe is in the PATH"); + QMessageBox::critical(0, "adb.exe not found", + "Please verify that adb.exe is in the PATH"); exit(-1); } } From 217145f4c562c78785715d8068cd9179cd162b93 Mon Sep 17 00:00:00 2001 From: amantley Date: Fri, 22 Feb 2019 17:16:50 -0800 Subject: [PATCH 284/474] This pr addresses two issues related to avatars that have parents joints above their hip joints. First on the IK side this prevents parent joints from being included in the accumulators in AnimInverseKinematics. Second in AnimClip the boneLengthScale now takes into account translation and scale on these extra parent joints. --- libraries/animation/src/AnimClip.cpp | 41 ++++++++++++++++++- .../animation/src/AnimInverseKinematics.cpp | 6 +-- libraries/animation/src/AnimSkeleton.cpp | 3 ++ libraries/animation/src/AnimSkeleton.h | 2 + 4 files changed, 46 insertions(+), 6 deletions(-) diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index a35e0237d0..109be27b58 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -151,12 +151,49 @@ void AnimClip::copyFromNetworkAnim() { const glm::vec3& animZeroTrans = animModel.animationFrames[0].translations[animJointIndex]; float boneLengthScale = 1.0f; const float EPSILON = 0.0001f; - if (fabsf(glm::length(animZeroTrans)) > EPSILON) { - boneLengthScale = glm::length(avatarDefaultPose.trans()) / glm::length(animZeroTrans); + + const int avatarHipsParentIndex = avatarSkeleton->getParentIndex(avatarSkeleton->nameToJointIndex("Hips")); + if (avatarJointIndex == avatarSkeleton->nameToJointIndex("Hips") && (avatarHipsParentIndex >= 0)) { + + const AnimPose& animHipsAbsoluteDefaultPose = animSkeleton.getAbsoluteDefaultPose(animSkeleton.nameToJointIndex("Hips")); + const AnimPose& avatarHipsAbsoluteDefaultPose = avatarSkeleton->getAbsoluteDefaultPose(avatarSkeleton->nameToJointIndex("Hips")); + const AnimPose& avatarHipsParentAbsoluteDefaultPose = avatarSkeleton->getAbsoluteDefaultPose(avatarHipsParentIndex); + + // the get the units and the heights for the animation and the avatar + const float animationUnitScale = extractScale(animModel.offset).y; + const float avatarUnitScale = extractScale(avatarSkeleton->getGeometryOffset()).y; + const float avatarHeightInMeters = avatarUnitScale * avatarHipsAbsoluteDefaultPose.trans().y; + const float animHeightInMeters = animationUnitScale * animHipsAbsoluteDefaultPose.trans().y; + + // get the parent scales for the avatar and the animation + const float avatarHipsParentScale = avatarHipsParentAbsoluteDefaultPose.scale().y; + float animHipsParentScale = 1.0f; + const int animHipsParentIndex = animSkeleton.getParentIndex(animSkeleton.nameToJointIndex("Hips")); + // also, check to see if the animation hips have a scaled parent. + if (animHipsParentIndex >= 0) { + const AnimPose& animationHipsParentAbsoluteDefaultPose = animSkeleton.getAbsoluteDefaultPose(animHipsParentIndex); + animHipsParentScale = animationHipsParentAbsoluteDefaultPose.scale().y; + } + + // compute the ratios for the units, the heights in meters, and the parent scales + if ((fabsf(animHeightInMeters) > EPSILON) && (animationUnitScale > EPSILON) && (animHipsParentScale > EPSILON)) { + const float avatarToAnimationHeightRatio = avatarHeightInMeters / animHeightInMeters; + const float unitsRatio = 1.0f / (avatarUnitScale / animationUnitScale); + const float parentScaleRatio = 1.0f / (avatarHipsParentScale / animHipsParentScale); + + boneLengthScale = avatarToAnimationHeightRatio * unitsRatio * parentScaleRatio; + } + } else { + + if (fabsf(glm::length(animZeroTrans)) > EPSILON) { + boneLengthScale = glm::length(avatarDefaultPose.trans()) / glm::length(animZeroTrans); + } } + AnimPose animTransPose = AnimPose(glm::vec3(1.0f), glm::quat(), avatarDefaultPose.trans() + boneLengthScale * (animTrans - animZeroTrans)); _anim[frame][avatarJointIndex] = animTransPose * animPreRotPose * animRotPose * animPostRotPose; + } } } diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index d710e9d8ff..8da2ddde3e 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -298,10 +298,8 @@ void AnimInverseKinematics::solve(const AnimContext& context, const std::vector< } // harvest accumulated rotations and apply the average - for (int i = 0; i < (int)_relativePoses.size(); ++i) { - if (i == _hipsIndex) { - continue; // don't apply accumulators to hips - } + // don't apply accumulators to hips, or parents of hips + for (int i = (_hipsIndex+1); i < (int)_relativePoses.size(); ++i) { if (_rotationAccumulators[i].size() > 0) { _relativePoses[i].rot() = _rotationAccumulators[i].getAverage(); _rotationAccumulators[i].clear(); diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index 03e3ac6ebd..f7b5fa8c83 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -17,6 +17,9 @@ #include "AnimationLogging.h" AnimSkeleton::AnimSkeleton(const HFMModel& hfmModel) { + + _geometryOffset = hfmModel.offset; + // convert to std::vector of joints std::vector joints; joints.reserve(hfmModel.joints.size()); diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index 0eefbf973e..dcb35ac9cb 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -36,6 +36,7 @@ public: const AnimPoseVec& getRelativeDefaultPoses() const { return _relativeDefaultPoses; } const AnimPose& getAbsoluteDefaultPose(int jointIndex) const; const AnimPoseVec& getAbsoluteDefaultPoses() const { return _absoluteDefaultPoses; } + const glm::mat4& getGeometryOffset() const { return _geometryOffset; } // get pre transform which should include FBX pre potations const AnimPose& getPreRotationPose(int jointIndex) const; @@ -83,6 +84,7 @@ protected: std::vector _mirrorMap; QHash _jointIndicesByName; std::vector> _clusterBindMatrixOriginalValues; + glm::mat4 _geometryOffset; // no copies AnimSkeleton(const AnimSkeleton&) = delete; From 6697d9d49f097aa7b582f2bd22a7e437252b106c Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Fri, 1 Mar 2019 12:32:55 -0800 Subject: [PATCH 285/474] Use python downloader. --- tools/nitpick/README.md | 14 +- tools/nitpick/src/AWSInterface.cpp | 2 +- tools/nitpick/src/AdbInterface.h | 6 - tools/nitpick/src/Downloader.cpp | 71 +- tools/nitpick/src/Downloader.h | 29 +- tools/nitpick/src/Nitpick.cpp | 139 +-- tools/nitpick/src/Nitpick.h | 31 +- tools/nitpick/src/Test.cpp | 1116 ----------------------- tools/nitpick/src/Test.h | 168 ---- tools/nitpick/src/TestRailInterface.cpp | 4 +- tools/nitpick/src/TestRunner.cpp | 22 +- tools/nitpick/src/TestRunner.h | 20 +- tools/nitpick/src/TestRunnerDesktop.cpp | 51 +- tools/nitpick/src/TestRunnerDesktop.h | 5 +- tools/nitpick/src/TestRunnerMobile.cpp | 62 +- tools/nitpick/src/TestRunnerMobile.h | 4 +- tools/nitpick/ui/Nitpick.ui | 185 +++- 17 files changed, 424 insertions(+), 1505 deletions(-) delete mode 100644 tools/nitpick/src/Test.cpp delete mode 100644 tools/nitpick/src/Test.h diff --git a/tools/nitpick/README.md b/tools/nitpick/README.md index c7b9050070..62e614c214 100644 --- a/tools/nitpick/README.md +++ b/tools/nitpick/README.md @@ -47,14 +47,20 @@ These steps assume the hifi repository has been cloned to `~/hifi`. ### Windows 1. (First time) download and install Python 3 from https://hifi-qa.s3.amazonaws.com/nitpick/Windows/python-3.7.0-amd64.exe (also located at https://www.python.org/downloads/) 1. Click the "add python to path" checkbox on the python installer - 1. After installation - add the path to python.exe to the Windows PATH environment variable. + 1. After installation: + 1. Open a new terminal + 1. Enter `python` and hit enter + 1. Verify that python is available (the prompt will change to `>>>`) + 1. Type `exit()` and hit enter to close python + 1. Install requests (a python library to download files from URLs) + `pip3 install requests` 1. (First time) download and install AWS CLI from https://hifi-qa.s3.amazonaws.com/nitpick/Windows/AWSCLI64PY3.msi (also available at https://aws.amazon.com/cli/ 1. Open a new command prompt and run `aws configure` 1. Enter the AWS account number 1. Enter the secret key 1. Leave region name and ouput format as default [None] - 1. Install the latest release of Boto3 via pip: + 1. Install the latest release of Boto3 via pip (from a terminal): `pip install boto3` 1. (First time) Download adb (Android Debug Bridge) from *https://dl.google.com/android/repository/platform-tools-latest-windows.zip* @@ -76,11 +82,13 @@ These steps assume the hifi repository has been cloned to `~/hifi`. `open "/Applications/Python 3.7/Install Certificates.command"`. This is needed because the Mac Python supplied no longer links with the deprecated Apple-supplied system OpenSSL libraries but rather supplies a private copy of OpenSSL 1.0.2 which does not automatically access the system default root certificates. 1. Verify that `/usr/local/bin/python3` exists. -1. (First time - AWS interface) Install pip with the script provided by the Python Packaging Authority: In a terminal: `curl -O https://bootstrap.pypa.io/get-pip.py` In a terminal: `python3 get-pip.py --user` + 1. Install requests (a python library to download files from URLs) + `pip3 install requests` +1. (First time - AWS interface) Install pip with the script provided by the Python Packaging Authority: 1. Use pip to install the AWS CLI. `pip3 install awscli --upgrade --user` This will install aws in your user. For user XXX, aws will be located in ~/Library/Python/3.7/bin diff --git a/tools/nitpick/src/AWSInterface.cpp b/tools/nitpick/src/AWSInterface.cpp index 4e83460b9e..7e11bd80fc 100644 --- a/tools/nitpick/src/AWSInterface.cpp +++ b/tools/nitpick/src/AWSInterface.cpp @@ -469,7 +469,7 @@ void AWSInterface::updateAWS() { if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), - "Could not create 'addTestCases.py'"); + "Could not create 'updateAWS.py'"); exit(-1); } diff --git a/tools/nitpick/src/AdbInterface.h b/tools/nitpick/src/AdbInterface.h index c1ce84c019..a2aa2be8ea 100644 --- a/tools/nitpick/src/AdbInterface.h +++ b/tools/nitpick/src/AdbInterface.h @@ -17,12 +17,6 @@ public: QString getAdbCommand(); private: -#ifdef Q_OS_WIN - const QString _adbExe{ "adb.exe" }; -#else - // Both Mac and Linux use "python" - const QString _adbExe{ "adb" }; -#endif QString _adbCommand; }; diff --git a/tools/nitpick/src/Downloader.cpp b/tools/nitpick/src/Downloader.cpp index 3256e79601..26e2140dbd 100644 --- a/tools/nitpick/src/Downloader.cpp +++ b/tools/nitpick/src/Downloader.cpp @@ -8,32 +8,65 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "Downloader.h" +#include "PythonInterface.h" -#include +#include +#include +#include +#include +#include -Downloader::Downloader(QUrl fileURL, QObject *parent) : QObject(parent) { - _networkAccessManager.get(QNetworkRequest(fileURL)); - - connect( - &_networkAccessManager, SIGNAL (finished(QNetworkReply*)), - this, SLOT (fileDownloaded(QNetworkReply*)) - ); +Downloader::Downloader() { + PythonInterface* pythonInterface = new PythonInterface(); + _pythonCommand = pythonInterface->getPythonCommand(); } -void Downloader::fileDownloaded(QNetworkReply* reply) { - QNetworkReply::NetworkError error = reply->error(); - if (error != QNetworkReply::NetworkError::NoError) { - QMessageBox::information(0, "Test Aborted", "Failed to download file: " + reply->errorString()); +void Downloader::downloadFiles(const QStringList& URLs, const QString& directoryName, const QStringList& filenames, void *caller) { + if (URLs.size() <= 0) { return; } - _downloadedData = reply->readAll(); + QString filename = directoryName + "/downloadFiles.py"; + if (QFile::exists(filename)) { + QFile::remove(filename); + } + QFile file(filename); - //emit a signal - reply->deleteLater(); - emit downloaded(); -} + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Could not create 'downloadFiles.py'"); + exit(-1); + } -QByteArray Downloader::downloadedData() const { - return _downloadedData; + QTextStream stream(&file); + + stream << "import requests\n"; + + for (int i = 0; i < URLs.size(); ++i) { + stream << "\nurl = '" + URLs[i] + "'\n"; + stream << "r = requests.get(url)\n"; + stream << "open('" + directoryName + '/' + filenames [i] + "', 'wb').write(r.content)\n"; + } + + file.close(); + +#ifdef Q_OS_WIN + QProcess* process = new QProcess(); + connect(process, &QProcess::started, this, [=]() { _busyWindow.exec(); }); + connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater())); + connect(process, static_cast(&QProcess::finished), this, + [=](int exitCode, QProcess::ExitStatus exitStatus) { _busyWindow.hide(); }); + + QStringList parameters = QStringList() << filename; + process->start(_pythonCommand, parameters); +#elif defined Q_OS_MAC + QProcess* process = new QProcess(); + QStringList parameters = QStringList() << "-c" << _pythonCommand + " " + filename; + process->start("sh", parameters); + + // Wait for the last file to download + while (!QFile::exists(directoryName + '/' + filenames[filenames.length() - 1])) { + QThread::msleep(200); + } +#endif } diff --git a/tools/nitpick/src/Downloader.h b/tools/nitpick/src/Downloader.h index 742a88b890..e48c195999 100644 --- a/tools/nitpick/src/Downloader.h +++ b/tools/nitpick/src/Downloader.h @@ -11,38 +11,19 @@ #ifndef hifi_downloader_h #define hifi_downloader_h -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "BusyWindow.h" #include -#include -#include -#include -#include - class Downloader : public QObject { Q_OBJECT public: - explicit Downloader(QUrl fileURL, QObject *parent = 0); + Downloader(); - QByteArray downloadedData() const; - -signals: - void downloaded(); - -private slots: - void fileDownloaded(QNetworkReply* pReply); + void downloadFiles(const QStringList& URLs, const QString& directoryName, const QStringList& filenames, void *caller); private: - QNetworkAccessManager _networkAccessManager; - QByteArray _downloadedData; + QString _pythonCommand; + BusyWindow _busyWindow; }; #endif // hifi_downloader_h \ No newline at end of file diff --git a/tools/nitpick/src/Nitpick.cpp b/tools/nitpick/src/Nitpick.cpp index 39800c6bc6..03acd4a893 100644 --- a/tools/nitpick/src/Nitpick.cpp +++ b/tools/nitpick/src/Nitpick.cpp @@ -24,8 +24,6 @@ Nitpick::Nitpick(QWidget* parent) : QMainWindow(parent) { _ui.progressBar->setVisible(false); _ui.tabWidget->setCurrentIndex(0); - _signalMapper = new QSignalMapper(); - connect(_ui.actionClose, &QAction::triggered, this, &Nitpick::on_closePushbutton_clicked); connect(_ui.actionAbout, &QAction::triggered, this, &Nitpick::about); connect(_ui.actionContent, &QAction::triggered, this, &Nitpick::content); @@ -40,7 +38,7 @@ Nitpick::Nitpick(QWidget* parent) : QMainWindow(parent) { _ui.plainTextEdit->setReadOnly(true); - setWindowTitle("Nitpick - v3.0.0"); + setWindowTitle("Nitpick - v3.1.0"); clientProfiles << "VR-High" << "Desktop-High" << "Desktop-Low" << "Mobile-Touch" << "VR-Standalone"; _ui.clientProfileComboBox->insertItems(0, clientProfiles); @@ -48,10 +46,8 @@ Nitpick::Nitpick(QWidget* parent) : QMainWindow(parent) { } Nitpick::~Nitpick() { - delete _signalMapper; - - if (_test) { - delete _test; + if (_testCreator) { + delete _testCreator; } if (_testRunnerDesktop) { @@ -64,10 +60,10 @@ Nitpick::~Nitpick() { } void Nitpick::setup() { - if (_test) { - delete _test; + if (_testCreator) { + delete _testCreator; } - _test = new Test(_ui.progressBar, _ui.checkBoxInteractiveMode); + _testCreator = new TestCreator(_ui.progressBar, _ui.checkBoxInteractiveMode); std::vector dayCheckboxes; dayCheckboxes.emplace_back(_ui.mondayCheckBox); @@ -99,9 +95,12 @@ void Nitpick::setup() { timeEditCheckboxes, timeEdits, _ui.workingFolderRunOnDesktopLabel, - _ui.checkBoxServerless, + _ui.checkBoxServerless, + _ui.usePreviousInstallationOnDesktopCheckBox, _ui.runLatestOnDesktopCheckBox, _ui.urlOnDesktopLineEdit, + _ui.runFullSuiteOnDesktopCheckBox, + _ui.scriptURLOnDesktopLineEdit, _ui.runNowPushbutton, _ui.statusLabelOnDesktop ); @@ -118,8 +117,11 @@ void Nitpick::setup() { _ui.downloadAPKPushbutton, _ui.installAPKPushbutton, _ui.runInterfacePushbutton, + _ui.usePreviousInstallationOnMobileCheckBox, _ui.runLatestOnMobileCheckBox, _ui.urlOnMobileLineEdit, + _ui.runFullSuiteOnMobileCheckBox, + _ui.scriptURLOnMobileLineEdit, _ui.statusLabelOnMobile ); } @@ -130,7 +132,7 @@ void Nitpick::startTestsEvaluation(const bool isRunningFromCommandLine, const QString& branch, const QString& user ) { - _test->startTestsEvaluation(isRunningFromCommandLine, isRunningInAutomaticTestRun, snapshotDirectory, branch, user); + _testCreator->startTestsEvaluation(isRunningFromCommandLine, isRunningInAutomaticTestRun, snapshotDirectory, branch, user); } void Nitpick::on_tabWidget_currentChanged(int index) { @@ -149,47 +151,47 @@ void Nitpick::on_tabWidget_currentChanged(int index) { } void Nitpick::on_evaluateTestsPushbutton_clicked() { - _test->startTestsEvaluation(false, false); + _testCreator->startTestsEvaluation(false, false); } void Nitpick::on_createRecursiveScriptPushbutton_clicked() { - _test->createRecursiveScript(); + _testCreator->createRecursiveScript(); } void Nitpick::on_createAllRecursiveScriptsPushbutton_clicked() { - _test->createAllRecursiveScripts(); + _testCreator->createAllRecursiveScripts(); } void Nitpick::on_createTestsPushbutton_clicked() { - _test->createTests(_ui.clientProfileComboBox->currentText()); + _testCreator->createTests(_ui.clientProfileComboBox->currentText()); } void Nitpick::on_createMDFilePushbutton_clicked() { - _test->createMDFile(); + _testCreator->createMDFile(); } void Nitpick::on_createAllMDFilesPushbutton_clicked() { - _test->createAllMDFiles(); + _testCreator->createAllMDFiles(); } void Nitpick::on_createTestAutoScriptPushbutton_clicked() { - _test->createTestAutoScript(); + _testCreator->createTestAutoScript(); } void Nitpick::on_createAllTestAutoScriptsPushbutton_clicked() { - _test->createAllTestAutoScripts(); + _testCreator->createAllTestAutoScripts(); } void Nitpick::on_createTestsOutlinePushbutton_clicked() { - _test->createTestsOutline(); + _testCreator->createTestsOutline(); } void Nitpick::on_createTestRailTestCasesPushbutton_clicked() { - _test->createTestRailTestCases(); + _testCreator->createTestRailTestCases(); } void Nitpick::on_createTestRailRunButton_clicked() { - _test->createTestRailRun(); + _testCreator->createTestRailRun(); } void Nitpick::on_setWorkingFolderRunOnDesktopPushbutton_clicked() { @@ -206,16 +208,25 @@ void Nitpick::on_runNowPushbutton_clicked() { _testRunnerDesktop->run(); } +void Nitpick::on_usePreviousInstallationOnDesktopCheckBox_clicked() { + _ui.runLatestOnDesktopCheckBox->setEnabled(!_ui.usePreviousInstallationOnDesktopCheckBox->isChecked()); + _ui.urlOnDesktopLineEdit->setEnabled(!_ui.usePreviousInstallationOnDesktopCheckBox->isChecked() && !_ui.runLatestOnDesktopCheckBox->isChecked()); +} + void Nitpick::on_runLatestOnDesktopCheckBox_clicked() { _ui.urlOnDesktopLineEdit->setEnabled(!_ui.runLatestOnDesktopCheckBox->isChecked()); } +void Nitpick::on_runFullSuiteOnDesktopCheckBox_clicked() { + _ui.scriptURLOnDesktopLineEdit->setEnabled(!_ui.runFullSuiteOnDesktopCheckBox->isChecked()); +} + void Nitpick::automaticTestRunEvaluationComplete(QString zippedFolderName, int numberOfFailures) { _testRunnerDesktop->automaticTestRunEvaluationComplete(zippedFolderName, numberOfFailures); } void Nitpick::on_updateTestRailRunResultsPushbutton_clicked() { - _test->updateTestRailRunResult(); + _testCreator->updateTestRailRunResult(); } // To toggle between show and hide @@ -247,80 +258,15 @@ void Nitpick::on_closePushbutton_clicked() { } void Nitpick::on_createPythonScriptRadioButton_clicked() { - _test->setTestRailCreateMode(PYTHON); + _testCreator->setTestRailCreateMode(PYTHON); } void Nitpick::on_createXMLScriptRadioButton_clicked() { - _test->setTestRailCreateMode(XML); + _testCreator->setTestRailCreateMode(XML); } void Nitpick::on_createWebPagePushbutton_clicked() { - _test->createWebPage(_ui.updateAWSCheckBox, _ui.awsURLLineEdit); -} - -void Nitpick::downloadFile(const QUrl& url) { - _downloaders.emplace_back(new Downloader(url, this)); - connect(_downloaders[_index], SIGNAL(downloaded()), _signalMapper, SLOT(map())); - - _signalMapper->setMapping(_downloaders[_index], _index); - - ++_index; -} - -void Nitpick::downloadFiles(const QStringList& URLs, const QString& directoryName, const QStringList& filenames, void *caller) { - connect(_signalMapper, SIGNAL(mapped(int)), this, SLOT(saveFile(int))); - - _directoryName = directoryName; - _filenames = filenames; - _caller = caller; - - _numberOfFilesToDownload = URLs.size(); - _numberOfFilesDownloaded = 0; - _index = 0; - - _ui.progressBar->setMinimum(0); - _ui.progressBar->setMaximum(_numberOfFilesToDownload - 1); - _ui.progressBar->setValue(0); - _ui.progressBar->setVisible(true); - - foreach (auto downloader, _downloaders) { - delete downloader; - } - - _downloaders.clear(); - for (int i = 0; i < _numberOfFilesToDownload; ++i) { - downloadFile(URLs[i]); - } -} - -void Nitpick::saveFile(int index) { - try { - QFile file(_directoryName + "/" + _filenames[index]); - file.open(QIODevice::WriteOnly); - file.write(_downloaders[index]->downloadedData()); - file.close(); - } catch (...) { - QMessageBox::information(0, "Test Aborted", "Failed to save file: " + _filenames[index]); - _ui.progressBar->setVisible(false); - return; - } - - ++_numberOfFilesDownloaded; - - if (_numberOfFilesDownloaded == _numberOfFilesToDownload) { - disconnect(_signalMapper, SIGNAL(mapped(int)), this, SLOT(saveFile(int))); - if (_caller == _test) { - _test->finishTestsEvaluation(); - } else if (_caller == _testRunnerDesktop) { - _testRunnerDesktop->downloadComplete(); - } else if (_caller == _testRunnerMobile) { - _testRunnerMobile->downloadComplete(); - } - - _ui.progressBar->setVisible(false); - } else { - _ui.progressBar->setValue(_numberOfFilesDownloaded); - } + _testCreator->createWebPage(_ui.updateAWSCheckBox, _ui.awsURLLineEdit); } void Nitpick::about() { @@ -360,10 +306,19 @@ void Nitpick::on_connectDevicePushbutton_clicked() { _testRunnerMobile->connectDevice(); } +void Nitpick::on_usePreviousInstallationOnMobileCheckBox_clicked() { + _ui.runLatestOnMobileCheckBox->setEnabled(!_ui.usePreviousInstallationOnMobileCheckBox->isChecked()); + _ui.urlOnMobileLineEdit->setEnabled(!_ui.usePreviousInstallationOnMobileCheckBox->isChecked() && !_ui.runLatestOnMobileCheckBox->isChecked()); +} + void Nitpick::on_runLatestOnMobileCheckBox_clicked() { _ui.urlOnMobileLineEdit->setEnabled(!_ui.runLatestOnMobileCheckBox->isChecked()); } +void Nitpick::on_runFullSuiteOnMobileCheckBox_clicked() { + _ui.scriptURLOnMobileLineEdit->setEnabled(!_ui.runFullSuiteOnMobileCheckBox->isChecked()); +} + void Nitpick::on_downloadAPKPushbutton_clicked() { _testRunnerMobile->downloadAPK(); } diff --git a/tools/nitpick/src/Nitpick.h b/tools/nitpick/src/Nitpick.h index 80fef934d6..1e9d7bdee5 100644 --- a/tools/nitpick/src/Nitpick.h +++ b/tools/nitpick/src/Nitpick.h @@ -11,12 +11,10 @@ #define hifi_Nitpick_h #include -#include #include #include "ui_Nitpick.h" -#include "Downloader.h" -#include "Test.h" +#include "TestCreator.h" #include "TestRunnerDesktop.h" #include "TestRunnerMobile.h" @@ -38,9 +36,6 @@ public: void automaticTestRunEvaluationComplete(QString zippedFolderName, int numberOfFailures); - void downloadFile(const QUrl& url); - void downloadFiles(const QStringList& URLs, const QString& directoryName, const QStringList& filenames, void* caller); - void setUserText(const QString& user); QString getSelectedUser(); @@ -75,7 +70,9 @@ private slots: void on_setWorkingFolderRunOnDesktopPushbutton_clicked(); void on_runNowPushbutton_clicked(); + void on_usePreviousInstallationOnDesktopCheckBox_clicked(); void on_runLatestOnDesktopCheckBox_clicked(); + void on_runFullSuiteOnDesktopCheckBox_clicked(); void on_updateTestRailRunResultsPushbutton_clicked(); @@ -87,15 +84,16 @@ private slots: void on_createWebPagePushbutton_clicked(); - void saveFile(int index); - void about(); void content(); // Run on Mobile controls void on_setWorkingFolderRunOnMobilePushbutton_clicked(); void on_connectDevicePushbutton_clicked(); + + void on_usePreviousInstallationOnMobileCheckBox_clicked(); void on_runLatestOnMobileCheckBox_clicked(); + void on_runFullSuiteOnMobileCheckBox_clicked(); void on_downloadAPKPushbutton_clicked(); void on_installAPKPushbutton_clicked(); @@ -105,28 +103,13 @@ private slots: private: Ui::NitpickClass _ui; - Test* _test{ nullptr }; + TestCreator* _testCreator{ nullptr }; TestRunnerDesktop* _testRunnerDesktop{ nullptr }; TestRunnerMobile* _testRunnerMobile{ nullptr }; - std::vector _downloaders; - - // local storage for parameters - folder to store downloaded files in, and a list of their names - QString _directoryName; - QStringList _filenames; - - // Used to enable passing a parameter to slots - QSignalMapper* _signalMapper; - - int _numberOfFilesToDownload{ 0 }; - int _numberOfFilesDownloaded{ 0 }; - int _index{ 0 }; - bool _isRunningFromCommandline{ false }; - void* _caller; - QStringList clientProfiles; }; diff --git a/tools/nitpick/src/Test.cpp b/tools/nitpick/src/Test.cpp deleted file mode 100644 index e8e284bf32..0000000000 --- a/tools/nitpick/src/Test.cpp +++ /dev/null @@ -1,1116 +0,0 @@ -// -// Test.cpp -// -// Created by Nissim Hadar on 2 Nov 2017. -// Copyright 2013 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 "Test.h" - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "Nitpick.h" -extern Nitpick* nitpick; - -#include - -Test::Test(QProgressBar* progressBar, QCheckBox* checkBoxInteractiveMode) : _awsInterface(NULL) { - _progressBar = progressBar; - _checkBoxInteractiveMode = checkBoxInteractiveMode; - - _mismatchWindow.setModal(true); - - if (nitpick) { - nitpick->setUserText(GIT_HUB_DEFAULT_USER); - nitpick->setBranchText(GIT_HUB_DEFAULT_BRANCH); - } -} - -bool Test::createTestResultsFolderPath(const QString& directory) { - QDateTime now = QDateTime::currentDateTime(); - _testResultsFolderPath = directory + "/" + TEST_RESULTS_FOLDER + "--" + now.toString(DATETIME_FORMAT) + "(local)[" + QHostInfo::localHostName() + "]"; - QDir testResultsFolder(_testResultsFolderPath); - - // Create a new test results folder - return QDir().mkdir(_testResultsFolderPath); -} - -QString Test::zipAndDeleteTestResultsFolder() { - QString zippedResultsFileName { _testResultsFolderPath + ".zip" }; - QFileInfo fileInfo(zippedResultsFileName); - if (fileInfo.exists()) { - QFile::remove(zippedResultsFileName); - } - - QDir testResultsFolder(_testResultsFolderPath); - JlCompress::compressDir(_testResultsFolderPath + ".zip", _testResultsFolderPath); - - testResultsFolder.removeRecursively(); - - //In all cases, for the next evaluation - _testResultsFolderPath = ""; - _failureIndex = 1; - _successIndex = 1; - - return zippedResultsFileName; -} - -int Test::compareImageLists() { - _progressBar->setMinimum(0); - _progressBar->setMaximum(_expectedImagesFullFilenames.length() - 1); - _progressBar->setValue(0); - _progressBar->setVisible(true); - - // Loop over both lists and compare each pair of images - // Quit loop if user has aborted due to a failed test. - bool keepOn{ true }; - int numberOfFailures{ 0 }; - for (int i = 0; keepOn && i < _expectedImagesFullFilenames.length(); ++i) { - // First check that images are the same size - QImage resultImage(_resultImagesFullFilenames[i]); - QImage expectedImage(_expectedImagesFullFilenames[i]); - - double similarityIndex; // in [-1.0 .. 1.0], where 1.0 means images are identical - - bool isInteractiveMode = (!_isRunningFromCommandLine && _checkBoxInteractiveMode->isChecked() && !_isRunningInAutomaticTestRun); - - // similarityIndex is set to -100.0 to indicate images are not the same size - if (isInteractiveMode && (resultImage.width() != expectedImage.width() || resultImage.height() != expectedImage.height())) { - QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Images are not the same size"); - similarityIndex = -100.0; - } else { - similarityIndex = _imageComparer.compareImages(resultImage, expectedImage); - } - - TestResult testResult = TestResult{ - (float)similarityIndex, - _expectedImagesFullFilenames[i].left(_expectedImagesFullFilenames[i].lastIndexOf("/") + 1), // path to the test (including trailing /) - QFileInfo(_expectedImagesFullFilenames[i].toStdString().c_str()).fileName(), // filename of expected image - QFileInfo(_resultImagesFullFilenames[i].toStdString().c_str()).fileName() // filename of result image - }; - - _mismatchWindow.setTestResult(testResult); - - if (similarityIndex < THRESHOLD) { - ++numberOfFailures; - - if (!isInteractiveMode) { - appendTestResultsToFile(testResult, _mismatchWindow.getComparisonImage(), true); - } else { - _mismatchWindow.exec(); - - switch (_mismatchWindow.getUserResponse()) { - case USER_RESPONSE_PASS: - break; - case USE_RESPONSE_FAIL: - appendTestResultsToFile(testResult, _mismatchWindow.getComparisonImage(), true); - break; - case USER_RESPONSE_ABORT: - keepOn = false; - break; - default: - assert(false); - break; - } - } - } else { - appendTestResultsToFile(testResult, _mismatchWindow.getComparisonImage(), false); - } - - _progressBar->setValue(i); - } - - _progressBar->setVisible(false); - return numberOfFailures; -} - -int Test::checkTextResults() { - // Create lists of failed and passed tests - QStringList nameFilterFailed; - nameFilterFailed << "*.failed.txt"; - QStringList testsFailed = QDir(_snapshotDirectory).entryList(nameFilterFailed, QDir::Files, QDir::Name); - - QStringList nameFilterPassed; - nameFilterPassed << "*.passed.txt"; - QStringList testsPassed = QDir(_snapshotDirectory).entryList(nameFilterPassed, QDir::Files, QDir::Name); - - // Add results to Test Results folder - foreach(QString currentFilename, testsFailed) { - appendTestResultsToFile(currentFilename, true); - } - - foreach(QString currentFilename, testsPassed) { - appendTestResultsToFile(currentFilename, false); - } - - return testsFailed.length(); -} - -void Test::appendTestResultsToFile(TestResult testResult, QPixmap comparisonImage, bool hasFailed) { - // Critical error if Test Results folder does not exist - if (!QDir().exists(_testResultsFolderPath)) { - QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Folder " + _testResultsFolderPath + " not found"); - exit(-1); - } - - // There are separate subfolders for failures and passes - QString resultFolderPath; - if (hasFailed) { - resultFolderPath = _testResultsFolderPath + "/Failure_" + QString::number(_failureIndex) + "--" + - testResult._actualImageFilename.left(testResult._actualImageFilename.length() - 4); - - ++_failureIndex; - } else { - resultFolderPath = _testResultsFolderPath + "/Success_" + QString::number(_successIndex) + "--" + - testResult._actualImageFilename.left(testResult._actualImageFilename.length() - 4); - - ++_successIndex; - } - - if (!QDir().mkdir(resultFolderPath)) { - QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), - "Failed to create folder " + resultFolderPath); - exit(-1); - } - - QFile descriptionFile(resultFolderPath + "/" + TEST_RESULTS_FILENAME); - if (!descriptionFile.open(QIODevice::ReadWrite)) { - QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create file " + TEST_RESULTS_FILENAME); - exit(-1); - } - - // Create text file describing the failure - QTextStream stream(&descriptionFile); - stream << "Test in folder " << testResult._pathname.left(testResult._pathname.length() - 1) << endl; // remove trailing '/' - stream << "Expected image was " << testResult._expectedImageFilename << endl; - stream << "Actual image was " << testResult._actualImageFilename << endl; - stream << "Similarity index was " << testResult._error << endl; - - descriptionFile.close(); - - // Copy expected and actual images, and save the difference image - QString sourceFile; - QString destinationFile; - - sourceFile = testResult._pathname + testResult._expectedImageFilename; - destinationFile = resultFolderPath + "/" + "Expected Image.png"; - if (!QFile::copy(sourceFile, destinationFile)) { - QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to copy " + sourceFile + " to " + destinationFile); - exit(-1); - } - - sourceFile = testResult._pathname + testResult._actualImageFilename; - destinationFile = resultFolderPath + "/" + "Actual Image.png"; - if (!QFile::copy(sourceFile, destinationFile)) { - QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to copy " + sourceFile + " to " + destinationFile); - exit(-1); - } - - comparisonImage.save(resultFolderPath + "/" + "Difference Image.png"); -} - -void::Test::appendTestResultsToFile(QString testResultFilename, bool hasFailed) { - // The test name includes everything until the penultimate period - QString testNameTemp = testResultFilename.left(testResultFilename.lastIndexOf('.')); - QString testName = testResultFilename.left(testNameTemp.lastIndexOf('.')); - QString resultFolderPath; - if (hasFailed) { - resultFolderPath = _testResultsFolderPath + "/Failure_" + QString::number(_failureIndex) + "--" + testName; - ++_failureIndex; - } else { - resultFolderPath = _testResultsFolderPath + "/Success_" + QString::number(_successIndex) + "--" + testName; - ++_successIndex; - } - - if (!QDir().mkdir(resultFolderPath)) { - QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), - "Failed to create folder " + resultFolderPath); - exit(-1); - } - - QString source = _snapshotDirectory + "/" + testResultFilename; - QString destination = resultFolderPath + "/Result.txt"; - if (!QFile::copy(source, destination)) { - QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to copy " + testResultFilename + " to " + resultFolderPath); - exit(-1); - } -} - -void Test::startTestsEvaluation(const bool isRunningFromCommandLine, - const bool isRunningInAutomaticTestRun, - const QString& snapshotDirectory, - const QString& branchFromCommandLine, - const QString& userFromCommandLine -) { - _isRunningFromCommandLine = isRunningFromCommandLine; - _isRunningInAutomaticTestRun = isRunningInAutomaticTestRun; - - if (snapshotDirectory.isNull()) { - // Get list of PNG images in folder, sorted by name - QString previousSelection = _snapshotDirectory; - QString parent = previousSelection.left(previousSelection.lastIndexOf('/')); - if (!parent.isNull() && parent.right(1) != "/") { - parent += "/"; - } - _snapshotDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the snapshots", parent, - QFileDialog::ShowDirsOnly); - - // If user canceled then restore previous selection and return - if (_snapshotDirectory == "") { - _snapshotDirectory = previousSelection; - return; - } - } else { - _snapshotDirectory = snapshotDirectory; - _exitWhenComplete = (isRunningFromCommandLine && !isRunningInAutomaticTestRun); - } - - // Quit if test results folder could not be created - if (!createTestResultsFolderPath(_snapshotDirectory)) { - return; - } - - // Create two lists. The first is the test results, the second is the expected images - // The expected images are represented as a URL to enable download from GitHub - // Images that are in the wrong format are ignored. - - QStringList sortedTestResultsFilenames = createListOfAll_imagesInDirectory("png", _snapshotDirectory); - QStringList expectedImagesURLs; - - _resultImagesFullFilenames.clear(); - _expectedImagesFilenames.clear(); - _expectedImagesFullFilenames.clear(); - - QString branch = (branchFromCommandLine.isNull()) ? nitpick->getSelectedBranch() : branchFromCommandLine; - QString user = (userFromCommandLine.isNull()) ? nitpick->getSelectedUser() : userFromCommandLine; - - foreach(QString currentFilename, sortedTestResultsFilenames) { - QString fullCurrentFilename = _snapshotDirectory + "/" + currentFilename; - if (isInSnapshotFilenameFormat("png", currentFilename)) { - _resultImagesFullFilenames << fullCurrentFilename; - - QString expectedImagePartialSourceDirectory = getExpectedImagePartialSourceDirectory(currentFilename); - - // Images are stored on GitHub as ExpectedImage_ddddd.png - // Extract the digits at the end of the filename (excluding the file extension) - QString expectedImageFilenameTail = currentFilename.left(currentFilename.length() - 4).right(NUM_DIGITS); - QString expectedImageStoredFilename = EXPECTED_IMAGE_PREFIX + expectedImageFilenameTail + ".png"; - - QString imageURLString("https://raw.githubusercontent.com/" + user + "/" + GIT_HUB_REPOSITORY + "/" + branch + "/" + - expectedImagePartialSourceDirectory + "/" + expectedImageStoredFilename); - - expectedImagesURLs << imageURLString; - - // The image retrieved from GitHub needs a unique name - QString expectedImageFilename = currentFilename.replace("/", "_").replace(".png", "_EI.png"); - - _expectedImagesFilenames << expectedImageFilename; - _expectedImagesFullFilenames << _snapshotDirectory + "/" + expectedImageFilename; - } - } - - nitpick->downloadFiles(expectedImagesURLs, _snapshotDirectory, _expectedImagesFilenames, (void *)this); -} - -void Test::finishTestsEvaluation() { - // First - compare the pairs of images - int numberOfFailures = compareImageLists(); - - // Next - check text results - numberOfFailures += checkTextResults(); - - if (!_isRunningFromCommandLine && !_isRunningInAutomaticTestRun) { - if (numberOfFailures == 0) { - QMessageBox::information(0, "Success", "All images are as expected"); - } else { - QMessageBox::information(0, "Failure", "One or more images are not as expected"); - } - } - - QString zippedFolderName = zipAndDeleteTestResultsFolder(); - - if (_exitWhenComplete) { - exit(0); - } - - if (_isRunningInAutomaticTestRun) { - nitpick->automaticTestRunEvaluationComplete(zippedFolderName, numberOfFailures); - } -} - -bool Test::isAValidDirectory(const QString& pathname) { - // Only process directories - QDir dir(pathname); - if (!dir.exists()) { - return false; - } - - // Ignore '.', '..' directories - if (pathname[pathname.length() - 1] == '.') { - return false; - } - - return true; -} - -QString Test::extractPathFromTestsDown(const QString& fullPath) { - // `fullPath` includes the full path to the test. We need the portion below (and including) `tests` - QStringList pathParts = fullPath.split('/'); - int i{ 0 }; - while (i < pathParts.length() && pathParts[i] != "tests") { - ++i; - } - - if (i == pathParts.length()) { - QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Bad testPathname"); - exit(-1); - } - - QString partialPath; - for (int j = i; j < pathParts.length(); ++j) { - partialPath += "/" + pathParts[j]; - } - - return partialPath; -} - -void Test::includeTest(QTextStream& textStream, const QString& testPathname) { - QString partialPath = extractPathFromTestsDown(testPathname); - QString partialPathWithoutTests = partialPath.right(partialPath.length() - 7); - - textStream << "Script.include(testsRootPath + \"" << partialPathWithoutTests + "\");" << endl; -} - -void Test::createTests(const QString& clientProfile) { - // Rename files sequentially, as ExpectedResult_00000.png, ExpectedResult_00001.png and so on - // Any existing expected result images will be deleted - QString previousSelection = _snapshotDirectory; - QString parent = previousSelection.left(previousSelection.lastIndexOf('/')); - if (!parent.isNull() && parent.right(1) != "/") { - parent += "/"; - } - - _snapshotDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the snapshots", parent, - QFileDialog::ShowDirsOnly); - - // If user canceled then restore previous selection and return - if (_snapshotDirectory == "") { - _snapshotDirectory = previousSelection; - return; - } - - previousSelection = _testsRootDirectory; - parent = previousSelection.left(previousSelection.lastIndexOf('/')); - if (!parent.isNull() && parent.right(1) != "/") { - parent += "/"; - } - - _testsRootDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select test root folder", parent, - QFileDialog::ShowDirsOnly); - - // If user canceled then restore previous selection and return - if (_testsRootDirectory == "") { - _testsRootDirectory = previousSelection; - return; - } - - QStringList sortedImageFilenames = createListOfAll_imagesInDirectory("png", _snapshotDirectory); - - int i = 1; - const int maxImages = pow(10, NUM_DIGITS); - foreach (QString currentFilename, sortedImageFilenames) { - QString fullCurrentFilename = _snapshotDirectory + "/" + currentFilename; - if (isInSnapshotFilenameFormat("png", currentFilename)) { - if (i >= maxImages) { - QMessageBox::critical(0, "Error", "More than " + QString::number(maxImages) + " images not supported"); - exit(-1); - } - - // Path to test is extracted from the file name - // Example: - // filename is tests.engine.interaction.pointer.laser.distanceScaleEnd.00000.jpg - // path is <_testDirectory>/engine/interaction/pointer/laser/distanceScaleEnd - // - // Note: we don't use the first part and the last 2 parts of the filename at this stage - // - QStringList pathParts = currentFilename.split("."); - QString fullNewFileName = _testsRootDirectory; - for (int j = 1; j < pathParts.size() - 2; ++j) { - fullNewFileName += "/" + pathParts[j]; - } - - // The image _index is the penultimate component of the path parts (the last being the file extension) - QString newFilename = "ExpectedImage_" + pathParts[pathParts.size() - 2].rightJustified(5, '0') + ".png"; - fullNewFileName += "/" + newFilename; - - try { - if (QFile::exists(fullNewFileName)) { - QFile::remove(fullNewFileName); - } - QFile::copy(fullCurrentFilename, fullNewFileName); - } catch (...) { - QMessageBox::critical(0, "Error", "Could not copy file: " + fullCurrentFilename + " to " + fullNewFileName + "\n"); - exit(-1); - } - ++i; - } - } - - QMessageBox::information(0, "Success", "Test images have been created"); -} - -ExtractedText Test::getTestScriptLines(QString testFileName) { - ExtractedText relevantTextFromTest; - - QFile inputFile(testFileName); - inputFile.open(QIODevice::ReadOnly); - if (!inputFile.isOpen()) { - QMessageBox::critical(0, - "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), - "Failed to open \"" + testFileName - ); - } - - QTextStream stream(&inputFile); - QString line = stream.readLine(); - - // Name of test is the string in the following line: - // nitpick.perform("Apply Material Entities to Avatars", Script.resolvePath("."), function(testType) {... - const QString ws("\\h*"); //white-space character - const QString functionPerformName(ws + "nitpick" + ws + "\\." + ws + "perform"); - const QString quotedString("\\\".+\\\""); - QString regexTestTitle(ws + functionPerformName + "\\(" + quotedString); - QRegularExpression lineContainingTitle = QRegularExpression(regexTestTitle); - - - // Each step is either of the following forms: - // nitpick.addStepSnapshot("Take snapshot"... - // nitpick.addStep("Clean up after test"... - const QString functionAddStepSnapshotName(ws + "nitpick" + ws + "\\." + ws + "addStepSnapshot"); - const QString regexStepSnapshot(ws + functionAddStepSnapshotName + ws + "\\(" + ws + quotedString + ".*"); - const QRegularExpression lineStepSnapshot = QRegularExpression(regexStepSnapshot); - - const QString functionAddStepName(ws + "nitpick" + ws + "\\." + ws + "addStep"); - const QString regexStep(ws + functionAddStepName + ws + "\\(" + ws + quotedString + ".*"); - const QRegularExpression lineStep = QRegularExpression(regexStep); - - while (!line.isNull()) { - line = stream.readLine(); - if (lineContainingTitle.match(line).hasMatch()) { - QStringList tokens = line.split('"'); - relevantTextFromTest.title = tokens[1]; - } else if (lineStepSnapshot.match(line).hasMatch()) { - QStringList tokens = line.split('"'); - QString nameOfStep = tokens[1]; - - Step *step = new Step(); - step->text = nameOfStep; - step->takeSnapshot = true; - relevantTextFromTest.stepList.emplace_back(step); - - } else if (lineStep.match(line).hasMatch()) { - QStringList tokens = line.split('"'); - QString nameOfStep = tokens[1]; - - Step *step = new Step(); - step->text = nameOfStep; - step->takeSnapshot = false; - relevantTextFromTest.stepList.emplace_back(step); - } - } - - inputFile.close(); - - return relevantTextFromTest; -} - -bool Test::createFileSetup() { - // Folder selection - QString previousSelection = _testDirectory; - QString parent = previousSelection.left(previousSelection.lastIndexOf('/')); - if (!parent.isNull() && parent.right(1) != "/") { - parent += "/"; - } - - _testDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test", parent, - QFileDialog::ShowDirsOnly); - - // If user canceled then restore previous selection and return - if (_testDirectory == "") { - _testDirectory = previousSelection; - return false; - } - - return true; -} - -bool Test::createAllFilesSetup() { - // Select folder to start recursing from - QString previousSelection = _testsRootDirectory; - QString parent = previousSelection.left(previousSelection.lastIndexOf('/')); - if (!parent.isNull() && parent.right(1) != "/") { - parent += "/"; - } - - _testsRootDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the tests root folder", parent, - QFileDialog::ShowDirsOnly); - - // If user cancelled then restore previous selection and return - if (_testsRootDirectory == "") { - _testsRootDirectory = previousSelection; - return false; - } - - return true; -} - -// Create an MD file for a user-selected test. -// The folder selected must contain a script named "test.js", the file produced is named "test.md" -void Test::createMDFile() { - if (!createFileSetup()) { - return; - } - - if (createMDFile(_testDirectory)) { - QMessageBox::information(0, "Success", "MD file has been created"); - } -} - -void Test::createAllMDFiles() { - if (!createAllFilesSetup()) { - return; - } - - // First test if top-level folder has a test.js file - const QString testPathname{ _testsRootDirectory + "/" + TEST_FILENAME }; - QFileInfo fileInfo(testPathname); - if (fileInfo.exists()) { - createMDFile(_testsRootDirectory); - } - - QDirIterator it(_testsRootDirectory, QDirIterator::Subdirectories); - while (it.hasNext()) { - QString directory = it.next(); - - // Only process directories - QDir dir; - if (!isAValidDirectory(directory)) { - continue; - } - - const QString testPathname{ directory + "/" + TEST_FILENAME }; - QFileInfo fileInfo(testPathname); - if (fileInfo.exists()) { - createMDFile(directory); - } - } - - QMessageBox::information(0, "Success", "MD files have been created"); -} - -bool Test::createMDFile(const QString& directory) { - // Verify folder contains test.js file - QString testFileName(directory + "/" + TEST_FILENAME); - QFileInfo testFileInfo(testFileName); - if (!testFileInfo.exists()) { - QMessageBox::critical(0, "Error", "Could not find file: " + TEST_FILENAME); - return false; - } - - ExtractedText testScriptLines = getTestScriptLines(testFileName); - - QString mdFilename(directory + "/" + "test.md"); - QFile mdFile(mdFilename); - if (!mdFile.open(QIODevice::WriteOnly)) { - QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create file " + mdFilename); - exit(-1); - } - - QTextStream stream(&mdFile); - - //Test title - QString testName = testScriptLines.title; - stream << "# " << testName << "\n"; - - stream << "## Run this script URL: [Manual](./test.js?raw=true) [Auto](./testAuto.js?raw=true)(from menu/Edit/Open and Run scripts from URL...)." << "\n\n"; - - stream << "## Preconditions" << "\n"; - stream << "- In an empty region of a domain with editing rights." << "\n\n"; - - stream << "## Steps\n"; - stream << "Press '" + ADVANCE_KEY + "' key to advance step by step\n\n"; // note apostrophes surrounding 'ADVANCE_KEY' - - int snapShotIndex { 0 }; - for (size_t i = 0; i < testScriptLines.stepList.size(); ++i) { - stream << "### Step " << QString::number(i + 1) << "\n"; - stream << "- " << testScriptLines.stepList[i]->text << "\n"; - if ((i + 1 < testScriptLines.stepList.size()) && testScriptLines.stepList[i]->takeSnapshot) { - stream << "- ![](./ExpectedImage_" << QString::number(snapShotIndex).rightJustified(5, '0') << ".png)\n"; - ++snapShotIndex; - } - } - - mdFile.close(); - - foreach (auto test, testScriptLines.stepList) { - delete test; - } - testScriptLines.stepList.clear(); - - return true; -} - -void Test::createTestAutoScript() { - if (!createFileSetup()) { - return; - } - - if (createTestAutoScript(_testDirectory)) { - QMessageBox::information(0, "Success", "'testAuto.js` script has been created"); - } -} - -void Test::createAllTestAutoScripts() { - if (!createAllFilesSetup()) { - return; - } - - // First test if top-level folder has a test.js file - const QString testPathname{ _testsRootDirectory + "/" + TEST_FILENAME }; - QFileInfo fileInfo(testPathname); - if (fileInfo.exists()) { - createTestAutoScript(_testsRootDirectory); - } - - QDirIterator it(_testsRootDirectory, QDirIterator::Subdirectories); - while (it.hasNext()) { - QString directory = it.next(); - - // Only process directories - QDir dir; - if (!isAValidDirectory(directory)) { - continue; - } - - const QString testPathname{ directory + "/" + TEST_FILENAME }; - QFileInfo fileInfo(testPathname); - if (fileInfo.exists()) { - createTestAutoScript(directory); - } - } - - QMessageBox::information(0, "Success", "All 'testAuto.js' scripts have been created"); -} - -bool Test::createTestAutoScript(const QString& directory) { - // Verify folder contains test.js file - QString testFileName(directory + "/" + TEST_FILENAME); - QFileInfo testFileInfo(testFileName); - if (!testFileInfo.exists()) { - QMessageBox::critical(0, "Error", "Could not find file: " + TEST_FILENAME); - return false; - } - - QString testAutoScriptFilename(directory + "/" + "testAuto.js"); - QFile testAutoScriptFile(testAutoScriptFilename); - if (!testAutoScriptFile.open(QIODevice::WriteOnly)) { - QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), - "Failed to create file " + testAutoScriptFilename); - exit(-1); - } - - QTextStream stream(&testAutoScriptFile); - - stream << "if (typeof PATH_TO_THE_REPO_PATH_UTILS_FILE === 'undefined') PATH_TO_THE_REPO_PATH_UTILS_FILE = 'https://raw.githubusercontent.com/highfidelity/hifi_tests/master/tests/utils/branchUtils.js';\n"; - stream << "Script.include(PATH_TO_THE_REPO_PATH_UTILS_FILE);\n"; - stream << "var nitpick = createNitpick(Script.resolvePath('.'));\n\n"; - stream << "nitpick.enableAuto();\n\n"; - stream << "Script.include('./test.js?raw=true');\n"; - - testAutoScriptFile.close(); - return true; -} - -// Creates a single script in a user-selected folder. -// This script will run all text.js scripts in every applicable sub-folder -void Test::createRecursiveScript() { - if (!createFileSetup()) { - return; - } - - createRecursiveScript(_testDirectory, true); - QMessageBox::information(0, "Success", "'testRecursive.js` script has been created"); -} - -// This method creates a `testRecursive.js` script in every sub-folder. -void Test::createAllRecursiveScripts() { - if (!createAllFilesSetup()) { - return; - } - - createAllRecursiveScripts(_testsRootDirectory); - createRecursiveScript(_testsRootDirectory, false); - QMessageBox::information(0, "Success", "Scripts have been created"); -} - -void Test::createAllRecursiveScripts(const QString& directory) { - QDirIterator it(directory, QDirIterator::Subdirectories); - - while (it.hasNext()) { - QString nextDirectory = it.next(); - if (isAValidDirectory(nextDirectory)) { - createAllRecursiveScripts(nextDirectory); - createRecursiveScript(nextDirectory, false); - } - } -} - -void Test::createRecursiveScript(const QString& directory, bool interactiveMode) { - // If folder contains a test, then we are at a leaf - const QString testPathname{ directory + "/" + TEST_FILENAME }; - if (QFileInfo(testPathname).exists()) { - return; - } - - // Directories are included in reverse order. The nitpick scripts use a stack mechanism, - // so this ensures that the tests run in alphabetical order (a convenience when debugging) - QStringList directories; - QDirIterator it(directory); - while (it.hasNext()) { - QString subDirectory = it.next(); - - // Only process directories - if (!isAValidDirectory(subDirectory)) { - continue; - } - - const QString testPathname{ subDirectory + "/" + TEST_FILENAME }; - if (QFileInfo(testPathname).exists()) { - // Current folder contains a test script - directories.push_front(testPathname); - } - - const QString testRecursivePathname{ subDirectory + "/" + TEST_RECURSIVE_FILENAME }; - if (QFileInfo(testRecursivePathname).exists()) { - // Current folder contains a recursive script - directories.push_front(testRecursivePathname); - } - } - - // If 'directories' is empty, this means that this recursive script has no tests to call, so it is redundant - if (directories.length() == 0) { - return; - } - - // Open the recursive script file - const QString recursiveTestsFilename(directory + "/" + TEST_RECURSIVE_FILENAME); - QFile recursiveTestsFile(recursiveTestsFilename); - if (!recursiveTestsFile.open(QIODevice::WriteOnly | QIODevice::Text)) { - QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), - "Failed to create \"" + TEST_RECURSIVE_FILENAME + "\" in directory \"" + directory + "\""); - - exit(-1); - } - - QTextStream textStream(&recursiveTestsFile); - - textStream << "// This is an automatically generated file, created by nitpick" << endl; - - // Include 'nitpick.js' - QString branch = nitpick->getSelectedBranch(); - QString user = nitpick->getSelectedUser(); - - textStream << "PATH_TO_THE_REPO_PATH_UTILS_FILE = \"https://raw.githubusercontent.com/" + user + "/hifi_tests/" + branch + - "/tests/utils/branchUtils.js\";" - << endl; - textStream << "Script.include(PATH_TO_THE_REPO_PATH_UTILS_FILE);" << endl << endl; - - // The 'depth' variable is used to signal when to start running the recursive scripts - textStream << "if (typeof depth === 'undefined') {" << endl; - textStream << " depth = 0;" << endl; - textStream << " nitpick = createNitpick(Script.resolvePath(\".\"));" << endl; - textStream << " testsRootPath = nitpick.getTestsRootPath();" << endl << endl; - textStream << " nitpick.enableRecursive();" << endl; - textStream << " nitpick.enableAuto();" << endl; - textStream << "} else {" << endl; - textStream << " depth++" << endl; - textStream << "}" << endl << endl; - - // Now include the test scripts - for (int i = 0; i < directories.length(); ++i) { - includeTest(textStream, directories.at(i)); - } - - textStream << endl; - textStream << "if (depth > 0) {" << endl; - textStream << " depth--;" << endl; - textStream << "} else {" << endl; - textStream << " nitpick.runRecursive();" << endl; - textStream << "}" << endl << endl; - - recursiveTestsFile.close(); -} - -void Test::createTestsOutline() { - QString previousSelection = _testDirectory; - QString parent = previousSelection.left(previousSelection.lastIndexOf('/')); - if (!parent.isNull() && parent.right(1) != "/") { - parent += "/"; - } - - _testDirectory = - QFileDialog::getExistingDirectory(nullptr, "Please select the tests root folder", parent, QFileDialog::ShowDirsOnly); - - // If user canceled then restore previous selection and return - if (_testDirectory == "") { - _testDirectory = previousSelection; - return; - } - - const QString testsOutlineFilename { "testsOutline.md" }; - QString mdFilename(_testDirectory + "/" + testsOutlineFilename); - QFile mdFile(mdFilename); - if (!mdFile.open(QIODevice::WriteOnly)) { - QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create file " + mdFilename); - exit(-1); - } - - QTextStream stream(&mdFile); - - //Test title - stream << "# Outline of all tests\n"; - stream << "Directories with an appended (*) have an automatic test\n\n"; - - // We need to know our current depth, as this isn't given by QDirIterator - int rootDepth { _testDirectory.count('/') }; - - // Each test is shown as the folder name linking to the matching GitHub URL, and the path to the associated test.md file - QDirIterator it(_testDirectory, QDirIterator::Subdirectories); - while (it.hasNext()) { - QString directory = it.next(); - - // Only process directories - if (!isAValidDirectory(directory)) { - continue; - } - - // Ignore the utils directory - if (directory.right(5) == "utils") { - continue; - } - - // The prefix is the MarkDown prefix needed for correct indentation - // It consists of 2 spaces for each level of indentation, folled by a dash sign - int currentDepth = directory.count('/') - rootDepth; - QString prefix = QString(" ").repeated(2 * currentDepth - 1) + " - "; - - // The directory name appears after the last slash (we are assured there is at least 1). - QString directoryName = directory.right(directory.length() - directory.lastIndexOf("/") - 1); - - // nitpick is run on a clone of the repository. We use relative paths, so we can use both local disk and GitHub - // For a test in "D:/GitHub/hifi_tests/tests/content/entity/zone/ambientLightInheritance" the - // GitHub URL is "./content/entity/zone/ambientLightInheritance?raw=true" - QString partialPath = directory.right(directory.length() - (directory.lastIndexOf("/tests/") + QString("/tests").length() + 1)); - QString url = "./" + partialPath; - - stream << prefix << "[" << directoryName << "](" << url << "?raw=true" << ")"; - - // note that md files may be named 'test.md' or 'testStory.md' - QFileInfo fileInfo1(directory + "/test.md"); - if (fileInfo1.exists()) { - stream << " [(test description)](" << url << "/test.md)"; - } - - QFileInfo fileInfo2(directory + "/testStory.md"); - if (fileInfo2.exists()) { - stream << " [(test description)](" << url << "/testStory.md)"; - } - - QFileInfo fileInfo3(directory + "/" + TEST_FILENAME); - if (fileInfo3.exists()) { - stream << " (*)"; - } - stream << "\n"; - } - - mdFile.close(); - - QMessageBox::information(0, "Success", "Test outline file " + testsOutlineFilename + " has been created"); -} - -void Test::createTestRailTestCases() { - QString previousSelection = _testDirectory; - QString parent = previousSelection.left(previousSelection.lastIndexOf('/')); - if (!parent.isNull() && parent.right(1) != "/") { - parent += "/"; - } - - _testDirectory = - QFileDialog::getExistingDirectory(nullptr, "Please select the tests root folder", parent, QFileDialog::ShowDirsOnly); - - // If user cancelled then restore previous selection and return - if (_testDirectory.isNull()) { - _testDirectory = previousSelection; - return; - } - - QString outputDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select a folder to store generated files in", - nullptr, QFileDialog::ShowDirsOnly); - - // If user cancelled then return - if (outputDirectory.isNull()) { - return; - } - - if (!_testRailInterface) { - _testRailInterface = new TestRailInterface; - } - - if (_testRailCreateMode == PYTHON) { - _testRailInterface->createTestSuitePython(_testDirectory, outputDirectory, nitpick->getSelectedUser(), - nitpick->getSelectedBranch()); - } else { - _testRailInterface->createTestSuiteXML(_testDirectory, outputDirectory, nitpick->getSelectedUser(), - nitpick->getSelectedBranch()); - } -} - -void Test::createTestRailRun() { - QString outputDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select a folder to store generated files in", - nullptr, QFileDialog::ShowDirsOnly); - - if (outputDirectory.isNull()) { - return; - } - - - if (!_testRailInterface) { - _testRailInterface = new TestRailInterface; - } - - _testRailInterface->createTestRailRun(outputDirectory); -} - -void Test::updateTestRailRunResult() { - QString testResults = QFileDialog::getOpenFileName(nullptr, "Please select the zipped test results to update from", nullptr, - "Zipped Test Results (*.zip)"); - if (testResults.isNull()) { - return; - } - - QString tempDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select a folder to store temporary files in", - nullptr, QFileDialog::ShowDirsOnly); - if (tempDirectory.isNull()) { - return; - } - - - if (!_testRailInterface) { - _testRailInterface = new TestRailInterface; - } - - _testRailInterface->updateTestRailRunResults(testResults, tempDirectory); -} - -QStringList Test::createListOfAll_imagesInDirectory(const QString& imageFormat, const QString& pathToImageDirectory) { - _imageDirectory = QDir(pathToImageDirectory); - QStringList nameFilters; - nameFilters << "*." + imageFormat; - - return _imageDirectory.entryList(nameFilters, QDir::Files, QDir::Name); -} - -// Snapshots are files in the following format: -// Filename (i.e. without extension) contains tests (this is based on all test scripts being within the tests folder) -// Last 5 characters in filename are digits (after removing the extension) -// Extension is 'imageFormat' -bool Test::isInSnapshotFilenameFormat(const QString& imageFormat, const QString& filename) { - bool contains_tests = filename.contains("tests" + PATH_SEPARATOR); - - QString filenameWithoutExtension = filename.left(filename.lastIndexOf('.')); - bool last5CharactersAreDigits; - filenameWithoutExtension.right(5).toInt(&last5CharactersAreDigits, 10); - - bool extensionIsIMAGE_FORMAT = (filename.right(imageFormat.length()) == imageFormat); - - return (contains_tests && last5CharactersAreDigits && extensionIsIMAGE_FORMAT); -} - -// For a file named "D_GitHub_hifi-tests_tests_content_entity_zone_create_0.jpg", the test directory is -// D:/GitHub/hifi-tests/tests/content/entity/zone/create -// This method assumes the filename is in the correct format -QString Test::getExpectedImageDestinationDirectory(const QString& filename) { - QString filenameWithoutExtension = filename.left(filename.length() - 4); - QStringList filenameParts = filenameWithoutExtension.split(PATH_SEPARATOR); - - QString result = filenameParts[0] + ":"; - - for (int i = 1; i < filenameParts.length() - 1; ++i) { - result += "/" + filenameParts[i]; - } - - return result; -} - -// For a file named "D_GitHub_hifi-tests_tests_content_entity_zone_create_0.jpg", the source directory on GitHub -// is ...tests/content/entity/zone/create -// This is used to create the full URL -// This method assumes the filename is in the correct format -QString Test::getExpectedImagePartialSourceDirectory(const QString& filename) { - QString filenameWithoutExtension = filename.left(filename.length() - 4); - QStringList filenameParts = filenameWithoutExtension.split(PATH_SEPARATOR); - - // Note that the bottom-most "tests" folder is assumed to be the root - // This is required because the tests folder is named hifi_tests - int i { filenameParts.length() - 1 }; - while (i >= 0 && filenameParts[i] != "tests") { - --i; - } - - if (i < 0) { - QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Bad filename"); - exit(-1); - } - - QString result = filenameParts[i]; - - for (int j = i + 1; j < filenameParts.length() - 1; ++j) { - result += "/" + filenameParts[j]; - } - - return result; -} - -void Test::setTestRailCreateMode(TestRailCreateMode testRailCreateMode) { - _testRailCreateMode = testRailCreateMode; -} - -void Test::createWebPage(QCheckBox* updateAWSCheckBox, QLineEdit* urlLineEdit) { - QString testResults = QFileDialog::getOpenFileName(nullptr, "Please select the zipped test results to update from", nullptr, - "Zipped Test Results (TestResults--*.zip)"); - if (testResults.isNull()) { - return; - } - - QString workingDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select a folder to store temporary files in", - nullptr, QFileDialog::ShowDirsOnly); - if (workingDirectory.isNull()) { - return; - } - - if (!_awsInterface) { - _awsInterface = new AWSInterface; - } - - _awsInterface->createWebPageFromResults(testResults, workingDirectory, updateAWSCheckBox, urlLineEdit); -} \ No newline at end of file diff --git a/tools/nitpick/src/Test.h b/tools/nitpick/src/Test.h deleted file mode 100644 index 23011d0c31..0000000000 --- a/tools/nitpick/src/Test.h +++ /dev/null @@ -1,168 +0,0 @@ -// -// Test.h -// -// Created by Nissim Hadar on 2 Nov 2017. -// Copyright 2013 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_test_h -#define hifi_test_h - -#include -#include -#include -#include - -#include "AWSInterface.h" -#include "ImageComparer.h" -#include "MismatchWindow.h" -#include "TestRailInterface.h" - -class Step { -public: - QString text; - bool takeSnapshot; -}; - -using StepList = std::vector; - -class ExtractedText { -public: - QString title; - StepList stepList; -}; - -enum TestRailCreateMode { - PYTHON, - XML -}; - -class Test { -public: - Test(QProgressBar* progressBar, QCheckBox* checkBoxInteractiveMode); - - void startTestsEvaluation(const bool isRunningFromCommandLine, - const bool isRunningInAutomaticTestRun, - const QString& snapshotDirectory = QString(), - const QString& branchFromCommandLine = QString(), - const QString& userFromCommandLine = QString()); - - void finishTestsEvaluation(); - - void createTests(const QString& clientProfile); - - void createTestsOutline(); - - bool createFileSetup(); - bool createAllFilesSetup(); - - void createMDFile(); - void createAllMDFiles(); - bool createMDFile(const QString& directory); - - void createTestAutoScript(); - void createAllTestAutoScripts(); - bool createTestAutoScript(const QString& directory); - - void createTestRailTestCases(); - void createTestRailRun(); - - void updateTestRailRunResult(); - - void createAllRecursiveScripts(); - void createAllRecursiveScripts(const QString& directory); - - void createRecursiveScript(); - void createRecursiveScript(const QString& directory, bool interactiveMode); - - int compareImageLists(); - int checkTextResults(); - - QStringList createListOfAll_imagesInDirectory(const QString& imageFormat, const QString& pathToImageDirectory); - - bool isInSnapshotFilenameFormat(const QString& imageFormat, const QString& filename); - - void includeTest(QTextStream& textStream, const QString& testPathname); - - void appendTestResultsToFile(TestResult testResult, QPixmap comparisonImage, bool hasFailed); - void appendTestResultsToFile(QString testResultFilename, bool hasFailed); - - bool createTestResultsFolderPath(const QString& directory); - QString zipAndDeleteTestResultsFolder(); - - static bool isAValidDirectory(const QString& pathname); - QString extractPathFromTestsDown(const QString& fullPath); - QString getExpectedImageDestinationDirectory(const QString& filename); - QString getExpectedImagePartialSourceDirectory(const QString& filename); - - ExtractedText getTestScriptLines(QString testFileName); - - void setTestRailCreateMode(TestRailCreateMode testRailCreateMode); - - void createWebPage(QCheckBox* updateAWSCheckBox, QLineEdit* urlLineEdit); - -private: - QProgressBar* _progressBar; - QCheckBox* _checkBoxInteractiveMode; - - bool _isRunningFromCommandLine{ false }; - bool _isRunningInAutomaticTestRun{ false }; - - const QString TEST_FILENAME{ "test.js" }; - const QString TEST_RECURSIVE_FILENAME{ "testRecursive.js" }; - const QString TEST_RESULTS_FOLDER { "TestResults" }; - const QString TEST_RESULTS_FILENAME { "TestResults.txt" }; - - const double THRESHOLD{ 0.935 }; - - QDir _imageDirectory; - - MismatchWindow _mismatchWindow; - - ImageComparer _imageComparer; - - QString _testResultsFolderPath; - int _failureIndex{ 1 }; - int _successIndex{ 1 }; - - // Expected images are in the format ExpectedImage_dddd.jpg (d == decimal digit) - const int NUM_DIGITS { 5 }; - const QString EXPECTED_IMAGE_PREFIX { "ExpectedImage_" }; - - // We have two directories to work with. - // The first is the directory containing the test we are working with - // The second is the root directory of all tests - // The third contains the snapshots taken for test runs that need to be evaluated - QString _testDirectory; - QString _testsRootDirectory; - QString _snapshotDirectory; - - QStringList _expectedImagesFilenames; - QStringList _expectedImagesFullFilenames; - QStringList _resultImagesFullFilenames; - - // Used for accessing GitHub - const QString GIT_HUB_DEFAULT_USER{ "highfidelity" }; - const QString GIT_HUB_DEFAULT_BRANCH{ "master" }; - const QString GIT_HUB_REPOSITORY{ "hifi_tests" }; - - const QString DATETIME_FORMAT{ "yyyy-MM-dd_hh-mm-ss" }; - - // NOTE: these need to match the appropriate var's in nitpick.js - // var advanceKey = "n"; - // var pathSeparator = "."; - const QString ADVANCE_KEY{ "n" }; - const QString PATH_SEPARATOR{ "." }; - - bool _exitWhenComplete{ false }; - - TestRailInterface* _testRailInterface; - TestRailCreateMode _testRailCreateMode { PYTHON }; - - AWSInterface* _awsInterface; -}; - -#endif // hifi_test_h \ No newline at end of file diff --git a/tools/nitpick/src/TestRailInterface.cpp b/tools/nitpick/src/TestRailInterface.cpp index 6ed13a72b6..8b8803153f 100644 --- a/tools/nitpick/src/TestRailInterface.cpp +++ b/tools/nitpick/src/TestRailInterface.cpp @@ -9,7 +9,7 @@ // #include "TestRailInterface.h" -#include "Test.h" +#include "TestCreator.h" #include #include @@ -258,7 +258,7 @@ bool TestRailInterface::requestTestRailResultsDataFromUser() { } bool TestRailInterface::isAValidTestDirectory(const QString& directory) { - if (Test::isAValidDirectory(directory)) { + if (TestCreator::isAValidDirectory(directory)) { // Ignore the utils and preformance directories if (directory.right(QString("utils").length()) == "utils" || directory.right(QString("performance").length()) == "performance") { diff --git a/tools/nitpick/src/TestRunner.cpp b/tools/nitpick/src/TestRunner.cpp index 54246de80b..c4e991e5ee 100644 --- a/tools/nitpick/src/TestRunner.cpp +++ b/tools/nitpick/src/TestRunner.cpp @@ -14,6 +14,26 @@ #include "Nitpick.h" extern Nitpick* nitpick; +TestRunner::TestRunner( + QLabel* workingFolderLabel, + QLabel* statusLabel, + QCheckBox* usePreviousInstallationCheckBox, + QCheckBox* runLatest, + QLineEdit* url, + QCheckBox* runFullSuite, + QLineEdit* scriptURL +) { + _workingFolderLabel = workingFolderLabel; + _statusLabel = statusLabel; + _usePreviousInstallationCheckBox = usePreviousInstallationCheckBox; + _runLatest = runLatest; + _url = url; + _runFullSuite = runFullSuite; + _scriptURL = scriptURL; + + _downloader = new Downloader(); +} + void TestRunner::setWorkingFolder(QLabel* workingFolderLabel) { // Everything will be written to this folder QString previousSelection = _workingFolder; @@ -49,7 +69,7 @@ void TestRunner::downloadBuildXml(void* caller) { urls << DEV_BUILD_XML_URL; filenames << DEV_BUILD_XML_FILENAME; - nitpick->downloadFiles(urls, _workingFolder, filenames, caller); + _downloader->downloadFiles(urls, _workingFolder, filenames, caller); } void TestRunner::parseBuildInformation() { diff --git a/tools/nitpick/src/TestRunner.h b/tools/nitpick/src/TestRunner.h index d2468ec2fa..6d36f246f7 100644 --- a/tools/nitpick/src/TestRunner.h +++ b/tools/nitpick/src/TestRunner.h @@ -11,6 +11,8 @@ #ifndef hifi_testRunner_h #define hifi_testRunner_h +#include "Downloader.h" + #include #include #include @@ -28,7 +30,18 @@ public: class TestRunner { public: + TestRunner( + QLabel* workingFolderLabel, + QLabel* statusLabel, + QCheckBox* usePreviousInstallationOnMobileCheckBox, + QCheckBox* runLatest, + QLineEdit* url, + QCheckBox* runFullSuite, + QLineEdit* scriptURL + ); + void setWorkingFolder(QLabel* workingFolderLabel); + void downloadBuildXml(void* caller); void parseBuildInformation(); QString getInstallerNameFromURL(const QString& url); @@ -36,10 +49,15 @@ public: void appendLog(const QString& message); protected: + Downloader* _downloader; + QLabel* _workingFolderLabel; QLabel* _statusLabel; - QLineEdit* _url; + QCheckBox* _usePreviousInstallationCheckBox; QCheckBox* _runLatest; + QLineEdit* _url; + QCheckBox* _runFullSuite; + QLineEdit* _scriptURL; QString _workingFolder; diff --git a/tools/nitpick/src/TestRunnerDesktop.cpp b/tools/nitpick/src/TestRunnerDesktop.cpp index e45d895886..b9caaa0ecb 100644 --- a/tools/nitpick/src/TestRunnerDesktop.cpp +++ b/tools/nitpick/src/TestRunnerDesktop.cpp @@ -27,23 +27,22 @@ TestRunnerDesktop::TestRunnerDesktop( std::vector timeEdits, QLabel* workingFolderLabel, QCheckBox* runServerless, + QCheckBox* usePreviousInstallationOnMobileCheckBox, QCheckBox* runLatest, QLineEdit* url, + QCheckBox* runFullSuite, + QLineEdit* scriptURL, QPushButton* runNow, QLabel* statusLabel, QObject* parent -) : QObject(parent) +) : QObject(parent), TestRunner(workingFolderLabel, statusLabel, usePreviousInstallationOnMobileCheckBox, runLatest, url, runFullSuite, scriptURL) { _dayCheckboxes = dayCheckboxes; _timeEditCheckboxes = timeEditCheckboxes; _timeEdits = timeEdits; - _workingFolderLabel = workingFolderLabel; _runServerless = runServerless; - _runLatest = runLatest; - _url = url; _runNow = runNow; - _statusLabel = statusLabel; _installerThread = new QThread(); _installerWorker = new InstallerWorker(); @@ -179,10 +178,14 @@ void TestRunnerDesktop::run() { // This will be restored at the end of the tests saveExistingHighFidelityAppDataFolder(); - _statusLabel->setText("Downloading Build XML"); - downloadBuildXml((void*)this); + if (_usePreviousInstallationCheckBox->isChecked()) { + installationComplete(); + } else { + _statusLabel->setText("Downloading Build XML"); + downloadBuildXml((void*)this); - // `downloadComplete` will run after download has completed + downloadComplete(); + } } void TestRunnerDesktop::downloadComplete() { @@ -209,9 +212,9 @@ void TestRunnerDesktop::downloadComplete() { _statusLabel->setText("Downloading installer"); - nitpick->downloadFiles(urls, _workingFolder, filenames, (void*)this); + _downloader->downloadFiles(urls, _workingFolder, filenames, (void*)this); - // `downloadComplete` will run again after download has completed + downloadComplete(); } else { // Download of Installer has completed @@ -292,15 +295,19 @@ void TestRunnerDesktop::installationComplete() { void TestRunnerDesktop::verifyInstallationSucceeded() { // Exit if the executables are missing. - // On Windows, the reason is probably that UAC has blocked the installation. This is treated as a critical error #ifdef Q_OS_WIN QFileInfo interfaceExe(QDir::toNativeSeparators(_installationFolder) + "\\interface.exe"); QFileInfo assignmentClientExe(QDir::toNativeSeparators(_installationFolder) + "\\assignment-client.exe"); QFileInfo domainServerExe(QDir::toNativeSeparators(_installationFolder) + "\\domain-server.exe"); if (!interfaceExe.exists() || !assignmentClientExe.exists() || !domainServerExe.exists()) { - QMessageBox::critical(0, "Installation of High Fidelity has failed", "Please verify that UAC has been disabled"); - exit(-1); + if (_runLatest->isChecked()) { + // On Windows, the reason is probably that UAC has blocked the installation. This is treated as a critical error + QMessageBox::critical(0, "Installation of High Fidelity has failed", "Please verify that UAC has been disabled"); + exit(-1); + } else { + QMessageBox::critical(0, "Installation of High Fidelity not found", "Please verify that working folder contains a proper installation"); + } } #endif } @@ -457,8 +464,9 @@ void TestRunnerDesktop::runInterfaceWithTestScript() { QString deleteScript = QString("https://raw.githubusercontent.com/") + _user + "/hifi_tests/" + _branch + "/tests/utils/deleteNearbyEntities.js"; - QString testScript = - QString("https://raw.githubusercontent.com/") + _user + "/hifi_tests/" + _branch + "/tests/testRecursive.js"; + QString testScript = (_runFullSuite->isChecked()) + ? QString("https://raw.githubusercontent.com/") + _user + "/hifi_tests/" + _branch + "/tests/testRecursive.js" + : _scriptURL->text(); QString commandLine; #ifdef Q_OS_WIN @@ -537,15 +545,16 @@ void TestRunnerDesktop::runInterfaceWithTestScript() { } void TestRunnerDesktop::interfaceExecutionComplete() { + QThread::msleep(500); QFileInfo testCompleted(QDir::toNativeSeparators(_snapshotFolder) +"/tests_completed.txt"); if (!testCompleted.exists()) { QMessageBox::critical(0, "Tests not completed", "Interface seems to have crashed before completion of the test scripts\nExisting images will be evaluated"); } + killProcesses(); + evaluateResults(); - killProcesses(); - // The High Fidelity AppData folder will be restored after evaluation has completed } @@ -591,7 +600,6 @@ void TestRunnerDesktop::addBuildNumberToResults(const QString& zippedFolderName) if (!QFile::rename(zippedFolderName, augmentedFilename)) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Could not rename '" + zippedFolderName + "' to '" + augmentedFilename); exit(-1); - } } @@ -667,6 +675,13 @@ void TestRunnerDesktop::checkTime() { QString TestRunnerDesktop::getPRNumberFromURL(const QString& url) { try { QStringList urlParts = url.split("/"); + if (urlParts.size() <= 2) { +#ifdef Q_OS_WIN + throw "URL not in expected format, should look like `https://deployment.highfidelity.com/jobs/pr-build/label%3Dwindows/13023/HighFidelity-Beta-Interface-PR14006-be76c43.exe`"; +#elif defined Q_OS_MAC + throw "URL not in expected format, should look like `https://deployment.highfidelity.com/jobs/pr-build/label%3Dwindows/13023/HighFidelity-Beta-Interface-PR14006-be76c43.dmg`"; +#endif + } QStringList filenameParts = urlParts[urlParts.size() - 1].split("-"); if (filenameParts.size() <= 3) { #ifdef Q_OS_WIN diff --git a/tools/nitpick/src/TestRunnerDesktop.h b/tools/nitpick/src/TestRunnerDesktop.h index 140a81f465..dce2dce2ba 100644 --- a/tools/nitpick/src/TestRunnerDesktop.h +++ b/tools/nitpick/src/TestRunnerDesktop.h @@ -12,7 +12,6 @@ #define hifi_testRunnerDesktop_h #include -#include #include #include #include @@ -32,8 +31,11 @@ public: std::vector timeEdits, QLabel* workingFolderLabel, QCheckBox* runServerless, + QCheckBox* usePreviousInstallationOnMobileCheckBox, QCheckBox* runLatest, QLineEdit* url, + QCheckBox* runFullSuite, + QLineEdit* scriptURL, QPushButton* runNow, QLabel* statusLabel, @@ -99,7 +101,6 @@ private: std::vector _dayCheckboxes; std::vector _timeEditCheckboxes; std::vector _timeEdits; - QLabel* _workingFolderLabel; QCheckBox* _runServerless; QPushButton* _runNow; QTimer* _timer; diff --git a/tools/nitpick/src/TestRunnerMobile.cpp b/tools/nitpick/src/TestRunnerMobile.cpp index ab276f3337..4d0d18ef3d 100644 --- a/tools/nitpick/src/TestRunnerMobile.cpp +++ b/tools/nitpick/src/TestRunnerMobile.cpp @@ -25,14 +25,16 @@ TestRunnerMobile::TestRunnerMobile( QPushButton* downloadAPKPushbutton, QPushButton* installAPKPushbutton, QPushButton* runInterfacePushbutton, + QCheckBox* usePreviousInstallationOnMobileCheckBox, QCheckBox* runLatest, QLineEdit* url, + QCheckBox* runFullSuite, + QLineEdit* scriptURL, QLabel* statusLabel, QObject* parent -) : QObject(parent), _adbInterface(NULL) +) : QObject(parent), TestRunner(workingFolderLabel, statusLabel, usePreviousInstallationOnMobileCheckBox, runLatest, url, runFullSuite, scriptURL) { - _workingFolderLabel = workingFolderLabel; _connectDeviceButton = connectDeviceButton; _pullFolderButton = pullFolderButton; _detectedDeviceLabel = detectedDeviceLabel; @@ -40,13 +42,15 @@ TestRunnerMobile::TestRunnerMobile( _downloadAPKPushbutton = downloadAPKPushbutton; _installAPKPushbutton = installAPKPushbutton; _runInterfacePushbutton = runInterfacePushbutton; - _runLatest = runLatest; - _url = url; - _statusLabel = statusLabel; folderLineEdit->setText("/sdcard/DCIM/TEST"); modelNames["SM_G955U1"] = "Samsung S8+ unlocked"; + modelNames["SM_N960U1"] = "Samsung Note 9 unlocked"; + modelNames["SM_T380"] = "Samsung Tab A"; + modelNames["Quest"] = "Quest"; + + _adbInterface = NULL; } TestRunnerMobile::~TestRunnerMobile() { @@ -66,6 +70,7 @@ void TestRunnerMobile::connectDevice() { QString devicesFullFilename{ _workingFolder + "/devices.txt" }; QString command = _adbInterface->getAdbCommand() + " devices -l > " + devicesFullFilename; + appendLog(command); system(command.toStdString().c_str()); if (!QFile::exists(devicesFullFilename)) { @@ -93,7 +98,7 @@ void TestRunnerMobile::connectDevice() { QString deviceID = tokens[0]; QString modelID = tokens[3].split(':')[1]; - QString modelName = "UKNOWN"; + QString modelName = "UNKNOWN"; if (modelNames.count(modelID) == 1) { modelName = modelNames[modelID]; } @@ -102,6 +107,8 @@ void TestRunnerMobile::connectDevice() { _pullFolderButton->setEnabled(true); _folderLineEdit->setEnabled(true); _downloadAPKPushbutton->setEnabled(true); + _installAPKPushbutton->setEnabled(true); + _runInterfacePushbutton->setEnabled(true); } } #endif @@ -109,6 +116,8 @@ void TestRunnerMobile::connectDevice() { void TestRunnerMobile::downloadAPK() { downloadBuildXml((void*)this); + + downloadComplete(); } @@ -141,11 +150,12 @@ void TestRunnerMobile::downloadComplete() { _statusLabel->setText("Downloading installer"); - nitpick->downloadFiles(urls, _workingFolder, filenames, (void*)this); + _downloader->downloadFiles(urls, _workingFolder, filenames, (void*)this); } else { _statusLabel->setText("Installer download complete"); - _installAPKPushbutton->setEnabled(true); } + + _installAPKPushbutton->setEnabled(true); } void TestRunnerMobile::installAPK() { @@ -154,11 +164,25 @@ void TestRunnerMobile::installAPK() { _adbInterface = new AdbInterface(); } + if (_installerFilename.isNull()) { + QString installerPathname = QFileDialog::getOpenFileName(nullptr, "Please select the APK", _workingFolder, + "Available APKs (*.apk)" + ); + + if (installerPathname.isNull()) { + return; + } + + // Remove the path + QStringList parts = installerPathname.split('/'); + _installerFilename = parts[parts.length() - 1]; + } + _statusLabel->setText("Installing"); QString command = _adbInterface->getAdbCommand() + " install -r -d " + _workingFolder + "/" + _installerFilename + " >" + _workingFolder + "/installOutput.txt"; + appendLog(command); system(command.toStdString().c_str()); _statusLabel->setText("Installation complete"); - _runInterfacePushbutton->setEnabled(true); #endif } @@ -169,7 +193,22 @@ void TestRunnerMobile::runInterface() { } _statusLabel->setText("Starting Interface"); - QString command = _adbInterface->getAdbCommand() + " shell monkey -p io.highfidelity.hifiinterface -v 1"; + + QString testScript = (_runFullSuite->isChecked()) + ? QString("https://raw.githubusercontent.com/") + nitpick->getSelectedUser() + "/hifi_tests/" + nitpick->getSelectedBranch() + "/tests/testRecursive.js" + : _scriptURL->text(); + + QString command = _adbInterface->getAdbCommand() + + " shell am start -n io.highfidelity.hifiinterface/.PermissionChecker" + + " --es args \\\"" + + " --url file:///~/serverless/tutorial.json" + + " --no-updater" + + " --no-login-suggestion" + + " --testScript " + testScript + " quitWhenFinished" + + " --testResultsLocation /sdcard/snapshots" + + "\\\""; + + appendLog(command); system(command.toStdString().c_str()); _statusLabel->setText("Interface started"); #endif @@ -182,7 +221,8 @@ void TestRunnerMobile::pullFolder() { } _statusLabel->setText("Pulling folder"); - QString command = _adbInterface->getAdbCommand() + " pull " + _folderLineEdit->text() + " " + _workingFolder + _installerFilename; + QString command = _adbInterface->getAdbCommand() + " pull " + _folderLineEdit->text() + " " + _workingFolder; + appendLog(command); system(command.toStdString().c_str()); _statusLabel->setText("Pull complete"); #endif diff --git a/tools/nitpick/src/TestRunnerMobile.h b/tools/nitpick/src/TestRunnerMobile.h index 52c2ba096d..f7b16da6f8 100644 --- a/tools/nitpick/src/TestRunnerMobile.h +++ b/tools/nitpick/src/TestRunnerMobile.h @@ -12,7 +12,6 @@ #define hifi_testRunnerMobile_h #include -#include #include #include @@ -31,8 +30,11 @@ public: QPushButton* downloadAPKPushbutton, QPushButton* installAPKPushbutton, QPushButton* runInterfacePushbutton, + QCheckBox* usePreviousInstallationOnMobileCheckBox, QCheckBox* runLatest, QLineEdit* url, + QCheckBox* runFullSuite, + QLineEdit* scriptURL, QLabel* statusLabel, QObject* parent = 0 diff --git a/tools/nitpick/ui/Nitpick.ui b/tools/nitpick/ui/Nitpick.ui index 47471522db..4a5a18f8d4 100644 --- a/tools/nitpick/ui/Nitpick.ui +++ b/tools/nitpick/ui/Nitpick.ui @@ -34,6 +34,9 @@ + + true + 45 @@ -495,7 +498,7 @@ - 20 + 240 70 120 20 @@ -549,13 +552,80 @@ - 170 + 175 100 - 451 + 445 21 + + + + 128 + 125 + 40 + 31 + + + + Script + + + + + false + + + + 175 + 130 + 445 + 21 + + + + + + + 20 + 130 + 120 + 20 + + + + <html><head/><body><p>If unchecked, will not show results during evaluation</p></body></html> + + + Run Full Suite + + + true + + + + + true + + + + 20 + 70 + 171 + 20 + + + + <html><head/><body><p>If unchecked, will not show results during evaluation</p></body></html> + + + usePreviousInstallation + + + false + + @@ -568,7 +638,7 @@ 10 - 90 + 150 160 30 @@ -581,7 +651,7 @@ 190 - 96 + 156 320 30 @@ -623,7 +693,7 @@ 460 - 410 + 440 160 30 @@ -639,7 +709,7 @@ 10 - 410 + 440 440 30 @@ -651,9 +721,9 @@ - 170 - 170 - 451 + 175 + 245 + 445 21 @@ -662,7 +732,7 @@ 20 - 170 + 245 120 20 @@ -684,7 +754,7 @@ 10 - 210 + 100 160 30 @@ -696,7 +766,7 @@ - 300 + 20 60 41 31 @@ -709,7 +779,7 @@ - 350 + 70 60 271 31 @@ -726,7 +796,7 @@ 10 - 250 + 325 160 30 @@ -742,7 +812,7 @@ 10 - 300 + 375 160 30 @@ -751,6 +821,86 @@ Run Interface + + + + 140 + 240 + 31 + 31 + + + + URL + + + + + false + + + + 175 + 275 + 445 + 21 + + + + + + + 140 + 270 + 40 + 31 + + + + Script + + + + + + 20 + 275 + 120 + 20 + + + + <html><head/><body><p>If unchecked, will not show results during evaluation</p></body></html> + + + Run Full Suite + + + true + + + + + true + + + + 20 + 210 + 171 + 20 + + + + <html><head/><body><p>If unchecked, will not show results during evaluation</p></body></html> + + + usePreviousInstallation + + + false + + @@ -921,6 +1071,9 @@ 21 + + true + groupBox From 54f14b2772b9abc8fbdf54dbe77801a83330d97b Mon Sep 17 00:00:00 2001 From: amantley Date: Fri, 1 Mar 2019 12:41:05 -0800 Subject: [PATCH 286/474] added the case where my avatar has no parent of hips, but the animation does --- libraries/animation/src/AnimClip.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index 109be27b58..da5e9b6508 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -153,11 +153,11 @@ void AnimClip::copyFromNetworkAnim() { const float EPSILON = 0.0001f; const int avatarHipsParentIndex = avatarSkeleton->getParentIndex(avatarSkeleton->nameToJointIndex("Hips")); - if (avatarJointIndex == avatarSkeleton->nameToJointIndex("Hips") && (avatarHipsParentIndex >= 0)) { + const int animHipsParentIndex = animSkeleton.getParentIndex(animSkeleton.nameToJointIndex("Hips")); + if (avatarJointIndex == avatarSkeleton->nameToJointIndex("Hips") && (avatarHipsParentIndex >= 0) || (animHipsParentIndex >= 0)) { const AnimPose& animHipsAbsoluteDefaultPose = animSkeleton.getAbsoluteDefaultPose(animSkeleton.nameToJointIndex("Hips")); const AnimPose& avatarHipsAbsoluteDefaultPose = avatarSkeleton->getAbsoluteDefaultPose(avatarSkeleton->nameToJointIndex("Hips")); - const AnimPose& avatarHipsParentAbsoluteDefaultPose = avatarSkeleton->getAbsoluteDefaultPose(avatarHipsParentIndex); // the get the units and the heights for the animation and the avatar const float animationUnitScale = extractScale(animModel.offset).y; @@ -166,10 +166,12 @@ void AnimClip::copyFromNetworkAnim() { const float animHeightInMeters = animationUnitScale * animHipsAbsoluteDefaultPose.trans().y; // get the parent scales for the avatar and the animation - const float avatarHipsParentScale = avatarHipsParentAbsoluteDefaultPose.scale().y; + float avatarHipsParentScale = 1.0f; float animHipsParentScale = 1.0f; - const int animHipsParentIndex = animSkeleton.getParentIndex(animSkeleton.nameToJointIndex("Hips")); - // also, check to see if the animation hips have a scaled parent. + if (avatarHipsParentIndex >= 0) { + const AnimPose& avatarHipsParentAbsoluteDefaultPose = avatarSkeleton->getAbsoluteDefaultPose(avatarHipsParentIndex); + avatarHipsParentScale = avatarHipsParentAbsoluteDefaultPose.scale().y; + } if (animHipsParentIndex >= 0) { const AnimPose& animationHipsParentAbsoluteDefaultPose = animSkeleton.getAbsoluteDefaultPose(animHipsParentIndex); animHipsParentScale = animationHipsParentAbsoluteDefaultPose.scale().y; From 3cc932db96d6f2f37d3f8d3774a23b68351489a9 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Fri, 1 Mar 2019 12:43:39 -0800 Subject: [PATCH 287/474] More debugging for hero issues --- .../src/avatars/AvatarMixerSlave.cpp | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index d57d3a5011..54eefadda1 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -325,7 +325,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) destinationNodeData->resetInViewStats(); const AvatarData& avatar = destinationNodeData->getAvatar(); - glm::vec3 myPosition = avatar.getClientGlobalPosition(); + glm::vec3 destinationPosition = avatar.getClientGlobalPosition(); // reset the internal state for correct random number distribution distribution.reset(); @@ -355,6 +355,9 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // about avatars they've ignored or that are out of view bool PALIsOpen = destinationNodeData->getRequestsDomainListData(); bool PALWasOpen = destinationNodeData->getPrevRequestsDomainListData(); + if (PALIsOpen) { + qCWarning(avatars) << "PAL is open:" << avatar.getSessionDisplayName(); + } // When this is true, the AvatarMixer will send Avatar data to a client about avatars that have ignored them bool getsAnyIgnored = PALIsOpen && destinationNode->getCanKick(); @@ -365,7 +368,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // compute node bounding box const float MY_AVATAR_BUBBLE_EXPANSION_FACTOR = 4.0f; // magic number determined emperically - AABox nodeBox = computeBubbleBox(avatar, MY_AVATAR_BUBBLE_EXPANSION_FACTOR); + AABox destinationNodeBox = computeBubbleBox(avatar, MY_AVATAR_BUBBLE_EXPANSION_FACTOR); // prepare to sort const auto& cameraViews = destinationNodeData->getViewFrustums(); @@ -417,8 +420,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // Don't bother with these checks if the other avatar has their bubble enabled and we're gettingAnyIgnored if (destinationNodeData->isIgnoreRadiusEnabled() || (sourceAvatarNodeData->isIgnoreRadiusEnabled() && !getsAnyIgnored)) { // Perform the collision check between the two bounding boxes - AABox otherNodeBox = sourceAvatarNodeData->getAvatar().getDefaultBubbleBox(); - if (nodeBox.touches(otherNodeBox)) { + AABox sourceNodeBox = sourceAvatarNodeData->getAvatar().getDefaultBubbleBox(); + if (destinationNodeBox.touches(sourceNodeBox)) { destinationNodeData->ignoreOther(destinationNode, sourceAvatarNode); sendAvatar = getsAnyIgnored; } @@ -456,6 +459,13 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) ++numAvatarsWithSkippedFrames; } } + { + if (sourceAvatarNodeData->getConstAvatarData()->getPriorityAvatar() && !sendAvatar) { + qCWarning(avatars) << "Hero avatar dropped:" << sourceAvatarNodeData->getConstAvatarData()->getSessionDisplayName() + << "lastSeqToReceiver =" << destinationNodeData->getLastBroadcastSequenceNumber(sourceAvatarNode->getLocalID()) + << "lastSeqFromSender = " << sourceAvatarNodeData->getLastReceivedSequenceNumber(); + } + } quint64 endIgnoreCalculation = usecTimestampNow(); _stats.ignoreCalculationElapsedTime += (endIgnoreCalculation - startIgnoreCalculation); @@ -571,7 +581,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) do { auto startSerialize = chrono::high_resolution_clock::now(); QByteArray bytes = sourceAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, - sendStatus, dropFaceTracking, distanceAdjust, myPosition, + sendStatus, dropFaceTracking, distanceAdjust, destinationPosition, &lastSentJointsForOther, avatarSpaceAvailable); auto endSerialize = chrono::high_resolution_clock::now(); _stats.toByteArrayElapsedTime += From 8802eeadf654e4ef2411961e19c8997067c36088 Mon Sep 17 00:00:00 2001 From: amerhifi <43353902+amerhifi@users.noreply.github.com> Date: Fri, 1 Mar 2019 12:46:50 -0800 Subject: [PATCH 288/474] Make CI android build signed, don't fail on quest build fails (#18) merging build changes --- android/apps/interface/build.gradle | 13 +++++-------- android/apps/{questInterface => }/keystore.jks | Bin android/apps/questInterface/build.gradle | 8 ++++---- android/build_android.sh | 17 ++++++++++------- android/docker/Dockerfile | 2 +- 5 files changed, 20 insertions(+), 20 deletions(-) rename android/apps/{questInterface => }/keystore.jks (100%) diff --git a/android/apps/interface/build.gradle b/android/apps/interface/build.gradle index 4163df03b7..f954fbc1f4 100644 --- a/android/apps/interface/build.gradle +++ b/android/apps/interface/build.gradle @@ -66,10 +66,10 @@ android { } signingConfigs { release { - storeFile project.hasProperty("HIFI_ANDROID_KEYSTORE") ? file(HIFI_ANDROID_KEYSTORE) : null - storePassword project.hasProperty("HIFI_ANDROID_KEYSTORE_PASSWORD") ? HIFI_ANDROID_KEYSTORE_PASSWORD : '' - keyAlias project.hasProperty("HIFI_ANDROID_KEY_ALIAS") ? HIFI_ANDROID_KEY_ALIAS : '' - keyPassword project.hasProperty("HIFI_ANDROID_KEY_PASSWORD") ? HIFI_ANDROID_KEY_PASSWORD : '' + storeFile project.hasProperty("HIFI_ANDROID_KEYSTORE") ? file(HIFI_ANDROID_KEYSTORE) : file('../keystore.jks') + storePassword project.hasProperty("HIFI_ANDROID_KEYSTORE_PASSWORD") ? HIFI_ANDROID_KEYSTORE_PASSWORD : 'password' + keyAlias project.hasProperty("HIFI_ANDROID_KEY_ALIAS") ? HIFI_ANDROID_KEY_ALIAS : 'key0' + keyPassword project.hasProperty("HIFI_ANDROID_KEY_PASSWORD") ? HIFI_ANDROID_KEY_PASSWORD : 'password' } } } @@ -90,10 +90,7 @@ android { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - signingConfig project.hasProperty("HIFI_ANDROID_KEYSTORE") && - project.hasProperty("HIFI_ANDROID_KEYSTORE_PASSWORD") && - project.hasProperty("HIFI_ANDROID_KEY_ALIAS") && - project.hasProperty("HIFI_ANDROID_KEY_PASSWORD")? signingConfigs.release : null + signingConfig signingConfigs.release buildConfigField "String", "BACKTRACE_URL", "\"" + (System.getenv("CMAKE_BACKTRACE_URL") ? System.getenv("CMAKE_BACKTRACE_URL") : '') + "\"" buildConfigField "String", "BACKTRACE_TOKEN", "\"" + (System.getenv("CMAKE_BACKTRACE_TOKEN") ? System.getenv("CMAKE_BACKTRACE_TOKEN") : '') + "\"" buildConfigField "String", "OAUTH_CLIENT_ID", "\"" + (System.getenv("OAUTH_CLIENT_ID") ? System.getenv("OAUTH_CLIENT_ID") : '') + "\"" diff --git a/android/apps/questInterface/keystore.jks b/android/apps/keystore.jks similarity index 100% rename from android/apps/questInterface/keystore.jks rename to android/apps/keystore.jks diff --git a/android/apps/questInterface/build.gradle b/android/apps/questInterface/build.gradle index 43ce0c0c37..6f4f6b7441 100644 --- a/android/apps/questInterface/build.gradle +++ b/android/apps/questInterface/build.gradle @@ -44,10 +44,10 @@ android { } signingConfigs { release { - storeFile project.hasProperty("HIFI_ANDROID_KEYSTORE") ? file(HIFI_ANDROID_KEYSTORE) : null - storePassword project.hasProperty("HIFI_ANDROID_KEYSTORE_PASSWORD") ? HIFI_ANDROID_KEYSTORE_PASSWORD : '' - keyAlias project.hasProperty("HIFI_ANDROID_KEY_ALIAS") ? HIFI_ANDROID_KEY_ALIAS : '' - keyPassword project.hasProperty("HIFI_ANDROID_KEY_PASSWORD") ? HIFI_ANDROID_KEY_PASSWORD : '' + storeFile project.hasProperty("HIFI_ANDROID_KEYSTORE") ? file(HIFI_ANDROID_KEYSTORE) : file('../keystore.jks') + storePassword project.hasProperty("HIFI_ANDROID_KEYSTORE_PASSWORD") ? HIFI_ANDROID_KEYSTORE_PASSWORD : 'password' + keyAlias project.hasProperty("HIFI_ANDROID_KEY_ALIAS") ? HIFI_ANDROID_KEY_ALIAS : 'key0' + keyPassword project.hasProperty("HIFI_ANDROID_KEY_PASSWORD") ? HIFI_ANDROID_KEY_PASSWORD : 'password' v2SigningEnabled false } } diff --git a/android/build_android.sh b/android/build_android.sh index 106a295750..e9c69b09de 100755 --- a/android/build_android.sh +++ b/android/build_android.sh @@ -4,16 +4,19 @@ set -xeuo pipefail ANDROID_BUILD_TYPE=release ANDROID_BUILD_TARGET=assembleRelease -case "$RELEASE_TYPE" in - PR) ANDROID_APK_SUFFIX=PR${RELEASE_NUMBER}-${SHA7}.apk ;; - *) ANDROID_APK_SUFFIX=${RELEASE_NUMBER}-${SHA7}.apk ;; -esac +if [[ "$RELEASE_TYPE" == "PR" ]]; then +ANDROID_APK_SUFFIX=PR${RELEASE_NUMBER}-${SHA7}.apk ; +elif [[ "${STABLE_BUILD}" == "1" ]]; then +ANDROID_APK_SUFFIX=${RELEASE_NUMBER}.apk ; +else +ANDROID_APK_SUFFIX=${RELEASE_NUMBER}-${SHA7}.apk ; +fi # Interface build ANDROID_APP=interface ANDROID_OUTPUT_DIR=./apps/${ANDROID_APP}/build/outputs/apk/${ANDROID_BUILD_TYPE} -ANDROID_OUTPUT_FILE=${ANDROID_APP}-${ANDROID_BUILD_TYPE}-unsigned.apk +ANDROID_OUTPUT_FILE=${ANDROID_APP}-${ANDROID_BUILD_TYPE}.apk ANDROID_APK_NAME=HighFidelity-Beta-${ANDROID_APK_SUFFIX} ./gradlew -PHIFI_ANDROID_PRECOMPILED=${HIFI_ANDROID_PRECOMPILED} -PVERSION_CODE=${VERSION_CODE} -PRELEASE_NUMBER=${RELEASE_NUMBER} -PRELEASE_TYPE=${RELEASE_TYPE} ${ANDROID_APP}:${ANDROID_BUILD_TARGET} cp ${ANDROID_OUTPUT_DIR}/${ANDROID_OUTPUT_FILE} ./${ANDROID_APK_NAME} @@ -23,8 +26,8 @@ ANDROID_APP=questInterface ANDROID_OUTPUT_DIR=./apps/${ANDROID_APP}/build/outputs/apk/${ANDROID_BUILD_TYPE} ANDROID_OUTPUT_FILE=${ANDROID_APP}-${ANDROID_BUILD_TYPE}.apk ANDROID_APK_NAME=HighFidelity-Quest-Beta-${ANDROID_APK_SUFFIX} -./gradlew -PHIFI_ANDROID_PRECOMPILED=${HIFI_ANDROID_PRECOMPILED} -PVERSION_CODE=${VERSION_CODE} -PRELEASE_NUMBER=${RELEASE_NUMBER} -PRELEASE_TYPE=${RELEASE_TYPE} ${ANDROID_APP}:${ANDROID_BUILD_TARGET} -cp ${ANDROID_OUTPUT_DIR}/${ANDROID_OUTPUT_FILE} ./${ANDROID_APK_NAME} +./gradlew -PHIFI_ANDROID_PRECOMPILED=${HIFI_ANDROID_PRECOMPILED} -PVERSION_CODE=${VERSION_CODE} -PRELEASE_NUMBER=${RELEASE_NUMBER} -PRELEASE_TYPE=${RELEASE_TYPE} ${ANDROID_APP}:${ANDROID_BUILD_TARGET} || true +cp ${ANDROID_OUTPUT_DIR}/${ANDROID_OUTPUT_FILE} ./${ANDROID_APK_NAME} || true diff --git a/android/docker/Dockerfile b/android/docker/Dockerfile index fe3a83950a..105bcb7cb0 100644 --- a/android/docker/Dockerfile +++ b/android/docker/Dockerfile @@ -73,7 +73,7 @@ RUN mkdir "$HIFI_BASE" && \ RUN git clone https://github.com/jherico/hifi.git && \ cd ~/hifi && \ - git checkout feature/quest_frame_player + git checkout quest/build WORKDIR /home/jenkins/hifi From 3e6061e4350684f88e2313cbed423a40a324821b Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Fri, 1 Mar 2019 13:45:00 -0800 Subject: [PATCH 289/474] try to not clear my avatar entities on domain switch --- interface/src/Application.cpp | 2 +- .../src/EntityTreeRenderer.cpp | 24 +++++++++---------- .../src/EntityTreeRenderer.h | 6 ++--- libraries/entities/src/EntityTree.cpp | 16 +++++++------ libraries/entities/src/EntityTree.h | 3 ++- libraries/entities/src/EntityTreeElement.cpp | 4 ++-- libraries/entities/src/EntityTreeElement.h | 2 +- libraries/octree/src/Octree.h | 2 +- libraries/octree/src/OctreeProcessor.cpp | 4 ++-- libraries/octree/src/OctreeProcessor.h | 2 +- 10 files changed, 34 insertions(+), 31 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 29d260cb5f..1a64378c36 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -6948,7 +6948,7 @@ void Application::clearDomainOctreeDetails(bool clearAll) { }); // reset the model renderer - clearAll ? getEntities()->clear() : getEntities()->clearNonLocalEntities(); + clearAll ? getEntities()->clear() : getEntities()->clearDomainAndNonOwnedEntities(); auto skyStage = DependencyManager::get()->getSkyStage(); diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index e54258fc3e..1e2c17fa8e 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -196,8 +196,8 @@ void EntityTreeRenderer::resetEntitiesScriptEngine() { }); } -void EntityTreeRenderer::stopNonLocalEntityScripts() { - leaveNonLocalEntities(); +void EntityTreeRenderer::stopDomainAndNonOwnedEntities() { + leaveDomainAndNonOwnedEntities(); // unload and stop the engine if (_entitiesScriptEngine) { QList entitiesWithEntityScripts = _entitiesScriptEngine->getListOfEntityScriptIDs(); @@ -206,7 +206,7 @@ void EntityTreeRenderer::stopNonLocalEntityScripts() { EntityItemPointer entityItem = getTree()->findEntityByEntityItemID(entityID); if (entityItem) { - if (!entityItem->isLocalEntity()) { + if (!(entityItem->isLocalEntity() || (entityItem->isAvatarEntity() && entityItem->getOwningAvatarID() == getTree()->getMyAvatarSessionUUID()))) { _entitiesScriptEngine->unloadEntityScript(entityID, true); } } @@ -214,8 +214,8 @@ void EntityTreeRenderer::stopNonLocalEntityScripts() { } } -void EntityTreeRenderer::clearNonLocalEntities() { - stopNonLocalEntityScripts(); +void EntityTreeRenderer::clearDomainAndNonOwnedEntities() { + stopDomainAndNonOwnedEntities(); std::unordered_map savedEntities; // remove all entities from the scene @@ -225,7 +225,7 @@ void EntityTreeRenderer::clearNonLocalEntities() { for (const auto& entry : _entitiesInScene) { const auto& renderer = entry.second; const EntityItemPointer& entityItem = renderer->getEntity(); - if (!entityItem->isLocalEntity()) { + if (!(entityItem->isLocalEntity() || (entityItem->isAvatarEntity() && entityItem->getOwningAvatarID() == getTree()->getMyAvatarSessionUUID()))) { renderer->removeFromScene(scene, transaction); } else { savedEntities[entry.first] = entry.second; @@ -239,7 +239,7 @@ void EntityTreeRenderer::clearNonLocalEntities() { _layeredZones.clearNonLocalLayeredZones(); - OctreeProcessor::clearNonLocalEntities(); + OctreeProcessor::clearDomainAndNonOwnedEntities(); } void EntityTreeRenderer::clear() { @@ -655,22 +655,22 @@ bool EntityTreeRenderer::checkEnterLeaveEntities() { return didUpdate; } -void EntityTreeRenderer::leaveNonLocalEntities() { +void EntityTreeRenderer::leaveDomainAndNonOwnedEntities() { if (_tree && !_shuttingDown) { - QVector currentLocalEntitiesInside; + QVector currentEntitiesInsideToSave; foreach (const EntityItemID& entityID, _currentEntitiesInside) { EntityItemPointer entityItem = getTree()->findEntityByEntityItemID(entityID); - if (!entityItem->isLocalEntity()) { + if (!(entityItem->isLocalEntity() || (entityItem->isAvatarEntity() && entityItem->getOwningAvatarID() == getTree()->getMyAvatarSessionUUID()))) { emit leaveEntity(entityID); if (_entitiesScriptEngine) { _entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity"); } } else { - currentLocalEntitiesInside.push_back(entityID); + currentEntitiesInsideToSave.push_back(entityID); } } - _currentEntitiesInside = currentLocalEntitiesInside; + _currentEntitiesInside = currentEntitiesInsideToSave; forceRecheckEntities(); } } diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index 51568ab744..b4d3507c17 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -87,7 +87,7 @@ public: virtual void init() override; /// clears the tree - virtual void clearNonLocalEntities() override; + virtual void clearDomainAndNonOwnedEntities() override; virtual void clear() override; /// reloads the entity scripts, calling unload and preload @@ -170,7 +170,7 @@ private: bool findBestZoneAndMaybeContainingEntities(QVector* entitiesContainingAvatar = nullptr); bool applyLayeredZones(); - void stopNonLocalEntityScripts(); + void stopDomainAndNonOwnedEntities(); void checkAndCallPreload(const EntityItemID& entityID, bool reload = false, bool unloadFirst = false); @@ -179,7 +179,7 @@ private: QScriptValueList createEntityArgs(const EntityItemID& entityID); bool checkEnterLeaveEntities(); - void leaveNonLocalEntities(); + void leaveDomainAndNonOwnedEntities(); void leaveAllEntities(); void forceRecheckEntities(); diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 6e404ce690..cdf021638b 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -78,13 +78,14 @@ OctreeElementPointer EntityTree::createNewElement(unsigned char* octalCode) { return std::static_pointer_cast(newElement); } -void EntityTree::eraseNonLocalEntities() { +void EntityTree::eraseDomainAndNonOwnedEntities() { emit clearingEntities(); if (_simulation) { // local entities are not in the simulation, so we clear ALL _simulation->clearEntities(); } + this->withWriteLock([&] { QHash savedEntities; // NOTE: lock the Tree first, then lock the _entityMap. @@ -93,10 +94,10 @@ void EntityTree::eraseNonLocalEntities() { foreach(EntityItemPointer entity, _entityMap) { EntityTreeElementPointer element = entity->getElement(); if (element) { - element->cleanupNonLocalEntities(); + element->cleanupDomainAndNonOwnedEntities(); } - if (entity->isLocalEntity()) { + if (entity->isLocalEntity() || (entity->isAvatarEntity() && entity->getOwningAvatarID() == getMyAvatarSessionUUID())) { savedEntities[entity->getEntityItemID()] = entity; } else { int32_t spaceIndex = entity->getSpaceIndex(); @@ -114,15 +115,16 @@ void EntityTree::eraseNonLocalEntities() { { QWriteLocker locker(&_needsParentFixupLock); - QVector localEntitiesNeedsParentFixup; + QVector needParentFixup; foreach (EntityItemWeakPointer entityItem, _needsParentFixup) { - if (!entityItem.expired() && entityItem.lock()->isLocalEntity()) { - localEntitiesNeedsParentFixup.push_back(entityItem); + auto entity = entityItem.lock(); + if (entity && (entity->isLocalEntity() || (entity->isAvatarEntity() && entity->getOwningAvatarID() == getMyAvatarSessionUUID()))) { + needParentFixup.push_back(entityItem); } } - _needsParentFixup = localEntitiesNeedsParentFixup; + _needsParentFixup = needParentFixup; } } diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index dcce0e4b99..9da0ecedd8 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -75,7 +75,7 @@ public: } - virtual void eraseNonLocalEntities() override; + virtual void eraseDomainAndNonOwnedEntities() override; virtual void eraseAllOctreeElements(bool createNewRoot = true) override; virtual void readBitstreamToTree(const unsigned char* bitstream, @@ -255,6 +255,7 @@ public: QByteArray computeNonce(const QString& certID, const QString ownerKey); bool verifyNonce(const QString& certID, const QString& nonce, EntityItemID& id); + QUuid getMyAvatarSessionUUID() { return _myAvatar ? _myAvatar->getSessionUUID() : QUuid(); } void setMyAvatar(std::shared_ptr myAvatar) { _myAvatar = myAvatar; } void swapStaleProxies(std::vector& proxies) { proxies.swap(_staleProxies); } diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index ce6f20262f..aab98adb52 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -697,11 +697,11 @@ EntityItemPointer EntityTreeElement::getEntityWithEntityItemID(const EntityItemI return foundEntity; } -void EntityTreeElement::cleanupNonLocalEntities() { +void EntityTreeElement::cleanupDomainAndNonOwnedEntities() { withWriteLock([&] { EntityItems savedEntities; foreach(EntityItemPointer entity, _entityItems) { - if (!entity->isLocalEntity()) { + if (!(entity->isLocalEntity() || (entity->isAvatarEntity() && entity->getOwningAvatarID() == getTree()->getMyAvatarSessionUUID()))) { entity->preDelete(); entity->_element = NULL; } else { diff --git a/libraries/entities/src/EntityTreeElement.h b/libraries/entities/src/EntityTreeElement.h index f82eaa7fb1..f94da44138 100644 --- a/libraries/entities/src/EntityTreeElement.h +++ b/libraries/entities/src/EntityTreeElement.h @@ -190,7 +190,7 @@ public: EntityItemPointer getEntityWithEntityItemID(const EntityItemID& id) const; void getEntitiesInside(const AACube& box, QVector& foundEntities); - void cleanupNonLocalEntities(); + void cleanupDomainAndNonOwnedEntities(); void cleanupEntities(); /// called by EntityTree on cleanup this will free all entities bool removeEntityItem(EntityItemPointer entity, bool deletion = false); diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index aac29201f1..82076f618b 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -149,7 +149,7 @@ public: OctreeElementPointer getRoot() { return _rootElement; } - virtual void eraseNonLocalEntities() { _isDirty = true; }; + virtual void eraseDomainAndNonOwnedEntities() { _isDirty = true; }; virtual void eraseAllOctreeElements(bool createNewRoot = true); virtual void readBitstreamToTree(const unsigned char* bitstream, uint64_t bufferSizeBytes, ReadBitstreamToTreeParams& args); diff --git a/libraries/octree/src/OctreeProcessor.cpp b/libraries/octree/src/OctreeProcessor.cpp index 18c8630391..03c8b9ca2f 100644 --- a/libraries/octree/src/OctreeProcessor.cpp +++ b/libraries/octree/src/OctreeProcessor.cpp @@ -198,10 +198,10 @@ void OctreeProcessor::processDatagram(ReceivedMessage& message, SharedNodePointe } -void OctreeProcessor::clearNonLocalEntities() { +void OctreeProcessor::clearDomainAndNonOwnedEntities() { if (_tree) { _tree->withWriteLock([&] { - _tree->eraseNonLocalEntities(); + _tree->eraseDomainAndNonOwnedEntities(); }); } } diff --git a/libraries/octree/src/OctreeProcessor.h b/libraries/octree/src/OctreeProcessor.h index bc5618e657..40af7a39f8 100644 --- a/libraries/octree/src/OctreeProcessor.h +++ b/libraries/octree/src/OctreeProcessor.h @@ -43,7 +43,7 @@ public: virtual void init(); /// clears the tree - virtual void clearNonLocalEntities(); + virtual void clearDomainAndNonOwnedEntities(); virtual void clear(); float getAverageElementsPerPacket() const { return _elementsPerPacket.getAverage(); } From fc3b629cfc3c6e8601b664dbc6937dc410499a89 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Fri, 1 Mar 2019 14:02:18 -0800 Subject: [PATCH 290/474] New name. --- tools/nitpick/src/TestCreator.cpp | 1122 +++++++++++++++++++++++++++++ tools/nitpick/src/TestCreator.h | 170 +++++ 2 files changed, 1292 insertions(+) create mode 100644 tools/nitpick/src/TestCreator.cpp create mode 100644 tools/nitpick/src/TestCreator.h diff --git a/tools/nitpick/src/TestCreator.cpp b/tools/nitpick/src/TestCreator.cpp new file mode 100644 index 0000000000..cf4fe86162 --- /dev/null +++ b/tools/nitpick/src/TestCreator.cpp @@ -0,0 +1,1122 @@ +// +// TestCreator.cpp +// +// Created by Nissim Hadar on 2 Nov 2017. +// Copyright 2013 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 "TestCreator.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "Nitpick.h" +extern Nitpick* nitpick; + +#include + +TestCreator::TestCreator(QProgressBar* progressBar, QCheckBox* checkBoxInteractiveMode) : _awsInterface(NULL) { + _downloader = new Downloader(); + + _progressBar = progressBar; + _checkBoxInteractiveMode = checkBoxInteractiveMode; + + _mismatchWindow.setModal(true); + + if (nitpick) { + nitpick->setUserText(GIT_HUB_DEFAULT_USER); + nitpick->setBranchText(GIT_HUB_DEFAULT_BRANCH); + } +} + +bool TestCreator::createTestResultsFolderPath(const QString& directory) { + QDateTime now = QDateTime::currentDateTime(); + _testResultsFolderPath = directory + "/" + TEST_RESULTS_FOLDER + "--" + now.toString(DATETIME_FORMAT) + "(local)[" + QHostInfo::localHostName() + "]"; + QDir testResultsFolder(_testResultsFolderPath); + + // Create a new test results folder + return QDir().mkdir(_testResultsFolderPath); +} + +QString TestCreator::zipAndDeleteTestResultsFolder() { + QString zippedResultsFileName { _testResultsFolderPath + ".zip" }; + QFileInfo fileInfo(zippedResultsFileName); + if (fileInfo.exists()) { + QFile::remove(zippedResultsFileName); + } + + QDir testResultsFolder(_testResultsFolderPath); + JlCompress::compressDir(_testResultsFolderPath + ".zip", _testResultsFolderPath); + + testResultsFolder.removeRecursively(); + + //In all cases, for the next evaluation + _testResultsFolderPath = ""; + _failureIndex = 1; + _successIndex = 1; + + return zippedResultsFileName; +} + +int TestCreator::compareImageLists() { + _progressBar->setMinimum(0); + _progressBar->setMaximum(_expectedImagesFullFilenames.length() - 1); + _progressBar->setValue(0); + _progressBar->setVisible(true); + + // Loop over both lists and compare each pair of images + // Quit loop if user has aborted due to a failed test. + bool keepOn{ true }; + int numberOfFailures{ 0 }; + for (int i = 0; keepOn && i < _expectedImagesFullFilenames.length(); ++i) { + // First check that images are the same size + QImage resultImage(_resultImagesFullFilenames[i]); + QImage expectedImage(_expectedImagesFullFilenames[i]); + + double similarityIndex; // in [-1.0 .. 1.0], where 1.0 means images are identical + + bool isInteractiveMode = (!_isRunningFromCommandLine && _checkBoxInteractiveMode->isChecked() && !_isRunningInAutomaticTestRun); + + // similarityIndex is set to -100.0 to indicate images are not the same size + if (isInteractiveMode && (resultImage.width() != expectedImage.width() || resultImage.height() != expectedImage.height())) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Images are not the same size"); + similarityIndex = -100.0; + } else { + similarityIndex = _imageComparer.compareImages(resultImage, expectedImage); + } + + TestResult testResult = TestResult{ + (float)similarityIndex, + _expectedImagesFullFilenames[i].left(_expectedImagesFullFilenames[i].lastIndexOf("/") + 1), // path to the test (including trailing /) + QFileInfo(_expectedImagesFullFilenames[i].toStdString().c_str()).fileName(), // filename of expected image + QFileInfo(_resultImagesFullFilenames[i].toStdString().c_str()).fileName() // filename of result image + }; + + _mismatchWindow.setTestResult(testResult); + + if (similarityIndex < THRESHOLD) { + ++numberOfFailures; + + if (!isInteractiveMode) { + appendTestResultsToFile(testResult, _mismatchWindow.getComparisonImage(), true); + } else { + _mismatchWindow.exec(); + + switch (_mismatchWindow.getUserResponse()) { + case USER_RESPONSE_PASS: + break; + case USE_RESPONSE_FAIL: + appendTestResultsToFile(testResult, _mismatchWindow.getComparisonImage(), true); + break; + case USER_RESPONSE_ABORT: + keepOn = false; + break; + default: + assert(false); + break; + } + } + } else { + appendTestResultsToFile(testResult, _mismatchWindow.getComparisonImage(), false); + } + + _progressBar->setValue(i); + } + + _progressBar->setVisible(false); + return numberOfFailures; +} + +int TestCreator::checkTextResults() { + // Create lists of failed and passed tests + QStringList nameFilterFailed; + nameFilterFailed << "*.failed.txt"; + QStringList testsFailed = QDir(_snapshotDirectory).entryList(nameFilterFailed, QDir::Files, QDir::Name); + + QStringList nameFilterPassed; + nameFilterPassed << "*.passed.txt"; + QStringList testsPassed = QDir(_snapshotDirectory).entryList(nameFilterPassed, QDir::Files, QDir::Name); + + // Add results to TestCreator Results folder + foreach(QString currentFilename, testsFailed) { + appendTestResultsToFile(currentFilename, true); + } + + foreach(QString currentFilename, testsPassed) { + appendTestResultsToFile(currentFilename, false); + } + + return testsFailed.length(); +} + +void TestCreator::appendTestResultsToFile(TestResult testResult, QPixmap comparisonImage, bool hasFailed) { + // Critical error if TestCreator Results folder does not exist + if (!QDir().exists(_testResultsFolderPath)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Folder " + _testResultsFolderPath + " not found"); + exit(-1); + } + + // There are separate subfolders for failures and passes + QString resultFolderPath; + if (hasFailed) { + resultFolderPath = _testResultsFolderPath + "/Failure_" + QString::number(_failureIndex) + "--" + + testResult._actualImageFilename.left(testResult._actualImageFilename.length() - 4); + + ++_failureIndex; + } else { + resultFolderPath = _testResultsFolderPath + "/Success_" + QString::number(_successIndex) + "--" + + testResult._actualImageFilename.left(testResult._actualImageFilename.length() - 4); + + ++_successIndex; + } + + if (!QDir().mkdir(resultFolderPath)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Failed to create folder " + resultFolderPath); + exit(-1); + } + + QFile descriptionFile(resultFolderPath + "/" + TEST_RESULTS_FILENAME); + if (!descriptionFile.open(QIODevice::ReadWrite)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create file " + TEST_RESULTS_FILENAME); + exit(-1); + } + + // Create text file describing the failure + QTextStream stream(&descriptionFile); + stream << "TestCreator in folder " << testResult._pathname.left(testResult._pathname.length() - 1) << endl; // remove trailing '/' + stream << "Expected image was " << testResult._expectedImageFilename << endl; + stream << "Actual image was " << testResult._actualImageFilename << endl; + stream << "Similarity index was " << testResult._error << endl; + + descriptionFile.close(); + + // Copy expected and actual images, and save the difference image + QString sourceFile; + QString destinationFile; + + sourceFile = testResult._pathname + testResult._expectedImageFilename; + destinationFile = resultFolderPath + "/" + "Expected Image.png"; + if (!QFile::copy(sourceFile, destinationFile)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to copy " + sourceFile + " to " + destinationFile); + exit(-1); + } + + sourceFile = testResult._pathname + testResult._actualImageFilename; + destinationFile = resultFolderPath + "/" + "Actual Image.png"; + if (!QFile::copy(sourceFile, destinationFile)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to copy " + sourceFile + " to " + destinationFile); + exit(-1); + } + + comparisonImage.save(resultFolderPath + "/" + "Difference Image.png"); +} + +void::TestCreator::appendTestResultsToFile(QString testResultFilename, bool hasFailed) { + // The test name includes everything until the penultimate period + QString testNameTemp = testResultFilename.left(testResultFilename.lastIndexOf('.')); + QString testName = testResultFilename.left(testNameTemp.lastIndexOf('.')); + QString resultFolderPath; + if (hasFailed) { + resultFolderPath = _testResultsFolderPath + "/Failure_" + QString::number(_failureIndex) + "--" + testName; + ++_failureIndex; + } else { + resultFolderPath = _testResultsFolderPath + "/Success_" + QString::number(_successIndex) + "--" + testName; + ++_successIndex; + } + + if (!QDir().mkdir(resultFolderPath)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Failed to create folder " + resultFolderPath); + exit(-1); + } + + QString source = _snapshotDirectory + "/" + testResultFilename; + QString destination = resultFolderPath + "/Result.txt"; + if (!QFile::copy(source, destination)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to copy " + testResultFilename + " to " + resultFolderPath); + exit(-1); + } +} + +void TestCreator::startTestsEvaluation(const bool isRunningFromCommandLine, + const bool isRunningInAutomaticTestRun, + const QString& snapshotDirectory, + const QString& branchFromCommandLine, + const QString& userFromCommandLine +) { + _isRunningFromCommandLine = isRunningFromCommandLine; + _isRunningInAutomaticTestRun = isRunningInAutomaticTestRun; + + if (snapshotDirectory.isNull()) { + // Get list of PNG images in folder, sorted by name + QString previousSelection = _snapshotDirectory; + QString parent = previousSelection.left(previousSelection.lastIndexOf('/')); + if (!parent.isNull() && parent.right(1) != "/") { + parent += "/"; + } + _snapshotDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the snapshots", parent, + QFileDialog::ShowDirsOnly); + + // If user canceled then restore previous selection and return + if (_snapshotDirectory == "") { + _snapshotDirectory = previousSelection; + return; + } + } else { + _snapshotDirectory = snapshotDirectory; + _exitWhenComplete = (isRunningFromCommandLine && !isRunningInAutomaticTestRun); + } + + // Quit if test results folder could not be created + if (!createTestResultsFolderPath(_snapshotDirectory)) { + return; + } + + // Create two lists. The first is the test results, the second is the expected images + // The expected images are represented as a URL to enable download from GitHub + // Images that are in the wrong format are ignored. + + QStringList sortedTestResultsFilenames = createListOfAll_imagesInDirectory("png", _snapshotDirectory); + QStringList expectedImagesURLs; + + _resultImagesFullFilenames.clear(); + _expectedImagesFilenames.clear(); + _expectedImagesFullFilenames.clear(); + + QString branch = (branchFromCommandLine.isNull()) ? nitpick->getSelectedBranch() : branchFromCommandLine; + QString user = (userFromCommandLine.isNull()) ? nitpick->getSelectedUser() : userFromCommandLine; + + foreach(QString currentFilename, sortedTestResultsFilenames) { + QString fullCurrentFilename = _snapshotDirectory + "/" + currentFilename; + if (isInSnapshotFilenameFormat("png", currentFilename)) { + _resultImagesFullFilenames << fullCurrentFilename; + + QString expectedImagePartialSourceDirectory = getExpectedImagePartialSourceDirectory(currentFilename); + + // Images are stored on GitHub as ExpectedImage_ddddd.png + // Extract the digits at the end of the filename (excluding the file extension) + QString expectedImageFilenameTail = currentFilename.left(currentFilename.length() - 4).right(NUM_DIGITS); + QString expectedImageStoredFilename = EXPECTED_IMAGE_PREFIX + expectedImageFilenameTail + ".png"; + + QString imageURLString("https://raw.githubusercontent.com/" + user + "/" + GIT_HUB_REPOSITORY + "/" + branch + "/" + + expectedImagePartialSourceDirectory + "/" + expectedImageStoredFilename); + + expectedImagesURLs << imageURLString; + + // The image retrieved from GitHub needs a unique name + QString expectedImageFilename = currentFilename.replace("/", "_").replace(".png", "_EI.png"); + + _expectedImagesFilenames << expectedImageFilename; + _expectedImagesFullFilenames << _snapshotDirectory + "/" + expectedImageFilename; + } + } + + _downloader->downloadFiles(expectedImagesURLs, _snapshotDirectory, _expectedImagesFilenames, (void *)this); + finishTestsEvaluation(); +} + +void TestCreator::finishTestsEvaluation() { + // First - compare the pairs of images + int numberOfFailures = compareImageLists(); + + // Next - check text results + numberOfFailures += checkTextResults(); + + if (!_isRunningFromCommandLine && !_isRunningInAutomaticTestRun) { + if (numberOfFailures == 0) { + QMessageBox::information(0, "Success", "All images are as expected"); + } else { + QMessageBox::information(0, "Failure", "One or more images are not as expected"); + } + } + + QString zippedFolderName = zipAndDeleteTestResultsFolder(); + + if (_exitWhenComplete) { + exit(0); + } + + if (_isRunningInAutomaticTestRun) { + nitpick->automaticTestRunEvaluationComplete(zippedFolderName, numberOfFailures); + } +} + +bool TestCreator::isAValidDirectory(const QString& pathname) { + // Only process directories + QDir dir(pathname); + if (!dir.exists()) { + return false; + } + + // Ignore '.', '..' directories + if (pathname[pathname.length() - 1] == '.') { + return false; + } + + return true; +} + +QString TestCreator::extractPathFromTestsDown(const QString& fullPath) { + // `fullPath` includes the full path to the test. We need the portion below (and including) `tests` + QStringList pathParts = fullPath.split('/'); + int i{ 0 }; + while (i < pathParts.length() && pathParts[i] != "tests") { + ++i; + } + + if (i == pathParts.length()) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Bad testPathname"); + exit(-1); + } + + QString partialPath; + for (int j = i; j < pathParts.length(); ++j) { + partialPath += "/" + pathParts[j]; + } + + return partialPath; +} + +void TestCreator::includeTest(QTextStream& textStream, const QString& testPathname) { + QString partialPath = extractPathFromTestsDown(testPathname); + QString partialPathWithoutTests = partialPath.right(partialPath.length() - 7); + + textStream << "Script.include(testsRootPath + \"" << partialPathWithoutTests + "\");" << endl; +} + +void TestCreator::createTests(const QString& clientProfile) { + // Rename files sequentially, as ExpectedResult_00000.png, ExpectedResult_00001.png and so on + // Any existing expected result images will be deleted + QString previousSelection = _snapshotDirectory; + QString parent = previousSelection.left(previousSelection.lastIndexOf('/')); + if (!parent.isNull() && parent.right(1) != "/") { + parent += "/"; + } + + _snapshotDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the snapshots", parent, + QFileDialog::ShowDirsOnly); + + // If user canceled then restore previous selection and return + if (_snapshotDirectory == "") { + _snapshotDirectory = previousSelection; + return; + } + + previousSelection = _testsRootDirectory; + parent = previousSelection.left(previousSelection.lastIndexOf('/')); + if (!parent.isNull() && parent.right(1) != "/") { + parent += "/"; + } + + _testsRootDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select test root folder", parent, + QFileDialog::ShowDirsOnly); + + // If user canceled then restore previous selection and return + if (_testsRootDirectory == "") { + _testsRootDirectory = previousSelection; + return; + } + + QStringList sortedImageFilenames = createListOfAll_imagesInDirectory("png", _snapshotDirectory); + + int i = 1; + const int maxImages = pow(10, NUM_DIGITS); + foreach (QString currentFilename, sortedImageFilenames) { + QString fullCurrentFilename = _snapshotDirectory + "/" + currentFilename; + if (isInSnapshotFilenameFormat("png", currentFilename)) { + if (i >= maxImages) { + QMessageBox::critical(0, "Error", "More than " + QString::number(maxImages) + " images not supported"); + exit(-1); + } + + // Path to test is extracted from the file name + // Example: + // filename is tests.engine.interaction.pointer.laser.distanceScaleEnd.00000.jpg + // path is <_testDirectory>/engine/interaction/pointer/laser/distanceScaleEnd + // + // Note: we don't use the first part and the last 2 parts of the filename at this stage + // + QStringList pathParts = currentFilename.split("."); + QString fullNewFileName = _testsRootDirectory; + for (int j = 1; j < pathParts.size() - 2; ++j) { + fullNewFileName += "/" + pathParts[j]; + } + + // The image _index is the penultimate component of the path parts (the last being the file extension) + QString newFilename = "ExpectedImage_" + pathParts[pathParts.size() - 2].rightJustified(5, '0') + ".png"; + fullNewFileName += "/" + newFilename; + + try { + if (QFile::exists(fullNewFileName)) { + QFile::remove(fullNewFileName); + } + QFile::copy(fullCurrentFilename, fullNewFileName); + } catch (...) { + QMessageBox::critical(0, "Error", "Could not copy file: " + fullCurrentFilename + " to " + fullNewFileName + "\n"); + exit(-1); + } + ++i; + } + } + + QMessageBox::information(0, "Success", "Test images have been created"); +} + +ExtractedText TestCreator::getTestScriptLines(QString testFileName) { + ExtractedText relevantTextFromTest; + + QFile inputFile(testFileName); + inputFile.open(QIODevice::ReadOnly); + if (!inputFile.isOpen()) { + QMessageBox::critical(0, + "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Failed to open \"" + testFileName + ); + } + + QTextStream stream(&inputFile); + QString line = stream.readLine(); + + // Name of test is the string in the following line: + // nitpick.perform("Apply Material Entities to Avatars", Script.resolvePath("."), function(testType) {... + const QString ws("\\h*"); //white-space character + const QString functionPerformName(ws + "nitpick" + ws + "\\." + ws + "perform"); + const QString quotedString("\\\".+\\\""); + QString regexTestTitle(ws + functionPerformName + "\\(" + quotedString); + QRegularExpression lineContainingTitle = QRegularExpression(regexTestTitle); + + + // Each step is either of the following forms: + // nitpick.addStepSnapshot("Take snapshot"... + // nitpick.addStep("Clean up after test"... + const QString functionAddStepSnapshotName(ws + "nitpick" + ws + "\\." + ws + "addStepSnapshot"); + const QString regexStepSnapshot(ws + functionAddStepSnapshotName + ws + "\\(" + ws + quotedString + ".*"); + const QRegularExpression lineStepSnapshot = QRegularExpression(regexStepSnapshot); + + const QString functionAddStepName(ws + "nitpick" + ws + "\\." + ws + "addStep"); + const QString regexStep(ws + functionAddStepName + ws + "\\(" + ws + quotedString + ".*"); + const QRegularExpression lineStep = QRegularExpression(regexStep); + + while (!line.isNull()) { + line = stream.readLine(); + if (lineContainingTitle.match(line).hasMatch()) { + QStringList tokens = line.split('"'); + relevantTextFromTest.title = tokens[1]; + } else if (lineStepSnapshot.match(line).hasMatch()) { + QStringList tokens = line.split('"'); + QString nameOfStep = tokens[1]; + + Step *step = new Step(); + step->text = nameOfStep; + step->takeSnapshot = true; + relevantTextFromTest.stepList.emplace_back(step); + + } else if (lineStep.match(line).hasMatch()) { + QStringList tokens = line.split('"'); + QString nameOfStep = tokens[1]; + + Step *step = new Step(); + step->text = nameOfStep; + step->takeSnapshot = false; + relevantTextFromTest.stepList.emplace_back(step); + } + } + + inputFile.close(); + + return relevantTextFromTest; +} + +bool TestCreator::createFileSetup() { + // Folder selection + QString previousSelection = _testDirectory; + QString parent = previousSelection.left(previousSelection.lastIndexOf('/')); + if (!parent.isNull() && parent.right(1) != "/") { + parent += "/"; + } + + _testDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test", parent, + QFileDialog::ShowDirsOnly); + + // If user canceled then restore previous selection and return + if (_testDirectory == "") { + _testDirectory = previousSelection; + return false; + } + + return true; +} + +bool TestCreator::createAllFilesSetup() { + // Select folder to start recursing from + QString previousSelection = _testsRootDirectory; + QString parent = previousSelection.left(previousSelection.lastIndexOf('/')); + if (!parent.isNull() && parent.right(1) != "/") { + parent += "/"; + } + + _testsRootDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the tests root folder", parent, + QFileDialog::ShowDirsOnly); + + // If user cancelled then restore previous selection and return + if (_testsRootDirectory == "") { + _testsRootDirectory = previousSelection; + return false; + } + + return true; +} + +// Create an MD file for a user-selected test. +// The folder selected must contain a script named "test.js", the file produced is named "test.md" +void TestCreator::createMDFile() { + if (!createFileSetup()) { + return; + } + + if (createMDFile(_testDirectory)) { + QMessageBox::information(0, "Success", "MD file has been created"); + } +} + +void TestCreator::createAllMDFiles() { + if (!createAllFilesSetup()) { + return; + } + + // First test if top-level folder has a test.js file + const QString testPathname{ _testsRootDirectory + "/" + TEST_FILENAME }; + QFileInfo fileInfo(testPathname); + if (fileInfo.exists()) { + createMDFile(_testsRootDirectory); + } + + QDirIterator it(_testsRootDirectory, QDirIterator::Subdirectories); + while (it.hasNext()) { + QString directory = it.next(); + + // Only process directories + QDir dir; + if (!isAValidDirectory(directory)) { + continue; + } + + const QString testPathname{ directory + "/" + TEST_FILENAME }; + QFileInfo fileInfo(testPathname); + if (fileInfo.exists()) { + createMDFile(directory); + } + } + + QMessageBox::information(0, "Success", "MD files have been created"); +} + +bool TestCreator::createMDFile(const QString& directory) { + // Verify folder contains test.js file + QString testFileName(directory + "/" + TEST_FILENAME); + QFileInfo testFileInfo(testFileName); + if (!testFileInfo.exists()) { + QMessageBox::critical(0, "Error", "Could not find file: " + TEST_FILENAME); + return false; + } + + ExtractedText testScriptLines = getTestScriptLines(testFileName); + + QString mdFilename(directory + "/" + "test.md"); + QFile mdFile(mdFilename); + if (!mdFile.open(QIODevice::WriteOnly)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create file " + mdFilename); + exit(-1); + } + + QTextStream stream(&mdFile); + + //TestCreator title + QString testName = testScriptLines.title; + stream << "# " << testName << "\n"; + + stream << "## Run this script URL: [Manual](./test.js?raw=true) [Auto](./testAuto.js?raw=true)(from menu/Edit/Open and Run scripts from URL...)." << "\n\n"; + + stream << "## Preconditions" << "\n"; + stream << "- In an empty region of a domain with editing rights." << "\n\n"; + + stream << "## Steps\n"; + stream << "Press '" + ADVANCE_KEY + "' key to advance step by step\n\n"; // note apostrophes surrounding 'ADVANCE_KEY' + + int snapShotIndex { 0 }; + for (size_t i = 0; i < testScriptLines.stepList.size(); ++i) { + stream << "### Step " << QString::number(i + 1) << "\n"; + stream << "- " << testScriptLines.stepList[i]->text << "\n"; + if ((i + 1 < testScriptLines.stepList.size()) && testScriptLines.stepList[i]->takeSnapshot) { + stream << "- ![](./ExpectedImage_" << QString::number(snapShotIndex).rightJustified(5, '0') << ".png)\n"; + ++snapShotIndex; + } + } + + mdFile.close(); + + foreach (auto test, testScriptLines.stepList) { + delete test; + } + testScriptLines.stepList.clear(); + + return true; +} + +void TestCreator::createTestAutoScript() { + if (!createFileSetup()) { + return; + } + + if (createTestAutoScript(_testDirectory)) { + QMessageBox::information(0, "Success", "'testAuto.js` script has been created"); + } +} + +void TestCreator::createAllTestAutoScripts() { + if (!createAllFilesSetup()) { + return; + } + + // First test if top-level folder has a test.js file + const QString testPathname{ _testsRootDirectory + "/" + TEST_FILENAME }; + QFileInfo fileInfo(testPathname); + if (fileInfo.exists()) { + createTestAutoScript(_testsRootDirectory); + } + + QDirIterator it(_testsRootDirectory, QDirIterator::Subdirectories); + while (it.hasNext()) { + QString directory = it.next(); + + // Only process directories + QDir dir; + if (!isAValidDirectory(directory)) { + continue; + } + + const QString testPathname{ directory + "/" + TEST_FILENAME }; + QFileInfo fileInfo(testPathname); + if (fileInfo.exists()) { + createTestAutoScript(directory); + } + } + + QMessageBox::information(0, "Success", "All 'testAuto.js' scripts have been created"); +} + +bool TestCreator::createTestAutoScript(const QString& directory) { + // Verify folder contains test.js file + QString testFileName(directory + "/" + TEST_FILENAME); + QFileInfo testFileInfo(testFileName); + if (!testFileInfo.exists()) { + QMessageBox::critical(0, "Error", "Could not find file: " + TEST_FILENAME); + return false; + } + + QString testAutoScriptFilename(directory + "/" + "testAuto.js"); + QFile testAutoScriptFile(testAutoScriptFilename); + if (!testAutoScriptFile.open(QIODevice::WriteOnly)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Failed to create file " + testAutoScriptFilename); + exit(-1); + } + + QTextStream stream(&testAutoScriptFile); + + stream << "if (typeof PATH_TO_THE_REPO_PATH_UTILS_FILE === 'undefined') PATH_TO_THE_REPO_PATH_UTILS_FILE = 'https://raw.githubusercontent.com/highfidelity/hifi_tests/master/tests/utils/branchUtils.js';\n"; + stream << "Script.include(PATH_TO_THE_REPO_PATH_UTILS_FILE);\n"; + stream << "var nitpick = createNitpick(Script.resolvePath('.'));\n\n"; + stream << "nitpick.enableAuto();\n\n"; + stream << "Script.include('./test.js?raw=true');\n"; + + testAutoScriptFile.close(); + return true; +} + +// Creates a single script in a user-selected folder. +// This script will run all text.js scripts in every applicable sub-folder +void TestCreator::createRecursiveScript() { + if (!createFileSetup()) { + return; + } + + createRecursiveScript(_testDirectory, true); + QMessageBox::information(0, "Success", "'testRecursive.js` script has been created"); +} + +// This method creates a `testRecursive.js` script in every sub-folder. +void TestCreator::createAllRecursiveScripts() { + if (!createAllFilesSetup()) { + return; + } + + createAllRecursiveScripts(_testsRootDirectory); + createRecursiveScript(_testsRootDirectory, false); + QMessageBox::information(0, "Success", "Scripts have been created"); +} + +void TestCreator::createAllRecursiveScripts(const QString& directory) { + QDirIterator it(directory, QDirIterator::Subdirectories); + + while (it.hasNext()) { + QString nextDirectory = it.next(); + if (isAValidDirectory(nextDirectory)) { + createAllRecursiveScripts(nextDirectory); + createRecursiveScript(nextDirectory, false); + } + } +} + +void TestCreator::createRecursiveScript(const QString& directory, bool interactiveMode) { + // If folder contains a test, then we are at a leaf + const QString testPathname{ directory + "/" + TEST_FILENAME }; + if (QFileInfo(testPathname).exists()) { + return; + } + + // Directories are included in reverse order. The nitpick scripts use a stack mechanism, + // so this ensures that the tests run in alphabetical order (a convenience when debugging) + QStringList directories; + QDirIterator it(directory); + while (it.hasNext()) { + QString subDirectory = it.next(); + + // Only process directories + if (!isAValidDirectory(subDirectory)) { + continue; + } + + const QString testPathname{ subDirectory + "/" + TEST_FILENAME }; + if (QFileInfo(testPathname).exists()) { + // Current folder contains a test script + directories.push_front(testPathname); + } + + const QString testRecursivePathname{ subDirectory + "/" + TEST_RECURSIVE_FILENAME }; + if (QFileInfo(testRecursivePathname).exists()) { + // Current folder contains a recursive script + directories.push_front(testRecursivePathname); + } + } + + // If 'directories' is empty, this means that this recursive script has no tests to call, so it is redundant + if (directories.length() == 0) { + return; + } + + // Open the recursive script file + const QString recursiveTestsFilename(directory + "/" + TEST_RECURSIVE_FILENAME); + QFile recursiveTestsFile(recursiveTestsFilename); + if (!recursiveTestsFile.open(QIODevice::WriteOnly | QIODevice::Text)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Failed to create \"" + TEST_RECURSIVE_FILENAME + "\" in directory \"" + directory + "\""); + + exit(-1); + } + + QTextStream textStream(&recursiveTestsFile); + + textStream << "// This is an automatically generated file, created by nitpick" << endl; + + // Include 'nitpick.js' + QString branch = nitpick->getSelectedBranch(); + QString user = nitpick->getSelectedUser(); + + textStream << "PATH_TO_THE_REPO_PATH_UTILS_FILE = \"https://raw.githubusercontent.com/" + user + "/hifi_tests/" + branch + + "/tests/utils/branchUtils.js\";" + << endl; + textStream << "Script.include(PATH_TO_THE_REPO_PATH_UTILS_FILE);" << endl << endl; + + // The 'depth' variable is used to signal when to start running the recursive scripts + textStream << "if (typeof depth === 'undefined') {" << endl; + textStream << " depth = 0;" << endl; + textStream << " nitpick = createNitpick(Script.resolvePath(\".\"));" << endl; + textStream << " testsRootPath = nitpick.getTestsRootPath();" << endl << endl; + textStream << " nitpick.enableRecursive();" << endl; + textStream << " nitpick.enableAuto();" << endl << endl; + textStream << " if (typeof Test !== 'undefined') {" << endl; + textStream << " Test.wait(10000);" << endl; + textStream << " }" << endl; + textStream << "} else {" << endl; + textStream << " depth++" << endl; + textStream << "}" << endl << endl; + + // Now include the test scripts + for (int i = 0; i < directories.length(); ++i) { + includeTest(textStream, directories.at(i)); + } + + textStream << endl; + textStream << "if (depth > 0) {" << endl; + textStream << " depth--;" << endl; + textStream << "} else {" << endl; + textStream << " nitpick.runRecursive();" << endl; + textStream << "}" << endl << endl; + + recursiveTestsFile.close(); +} + +void TestCreator::createTestsOutline() { + QString previousSelection = _testDirectory; + QString parent = previousSelection.left(previousSelection.lastIndexOf('/')); + if (!parent.isNull() && parent.right(1) != "/") { + parent += "/"; + } + + _testDirectory = + QFileDialog::getExistingDirectory(nullptr, "Please select the tests root folder", parent, QFileDialog::ShowDirsOnly); + + // If user canceled then restore previous selection and return + if (_testDirectory == "") { + _testDirectory = previousSelection; + return; + } + + const QString testsOutlineFilename { "testsOutline.md" }; + QString mdFilename(_testDirectory + "/" + testsOutlineFilename); + QFile mdFile(mdFilename); + if (!mdFile.open(QIODevice::WriteOnly)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create file " + mdFilename); + exit(-1); + } + + QTextStream stream(&mdFile); + + //TestCreator title + stream << "# Outline of all tests\n"; + stream << "Directories with an appended (*) have an automatic test\n\n"; + + // We need to know our current depth, as this isn't given by QDirIterator + int rootDepth { _testDirectory.count('/') }; + + // Each test is shown as the folder name linking to the matching GitHub URL, and the path to the associated test.md file + QDirIterator it(_testDirectory, QDirIterator::Subdirectories); + while (it.hasNext()) { + QString directory = it.next(); + + // Only process directories + if (!isAValidDirectory(directory)) { + continue; + } + + // Ignore the utils directory + if (directory.right(5) == "utils") { + continue; + } + + // The prefix is the MarkDown prefix needed for correct indentation + // It consists of 2 spaces for each level of indentation, folled by a dash sign + int currentDepth = directory.count('/') - rootDepth; + QString prefix = QString(" ").repeated(2 * currentDepth - 1) + " - "; + + // The directory name appears after the last slash (we are assured there is at least 1). + QString directoryName = directory.right(directory.length() - directory.lastIndexOf("/") - 1); + + // nitpick is run on a clone of the repository. We use relative paths, so we can use both local disk and GitHub + // For a test in "D:/GitHub/hifi_tests/tests/content/entity/zone/ambientLightInheritance" the + // GitHub URL is "./content/entity/zone/ambientLightInheritance?raw=true" + QString partialPath = directory.right(directory.length() - (directory.lastIndexOf("/tests/") + QString("/tests").length() + 1)); + QString url = "./" + partialPath; + + stream << prefix << "[" << directoryName << "](" << url << "?raw=true" << ")"; + + // note that md files may be named 'test.md' or 'testStory.md' + QFileInfo fileInfo1(directory + "/test.md"); + if (fileInfo1.exists()) { + stream << " [(test description)](" << url << "/test.md)"; + } + + QFileInfo fileInfo2(directory + "/testStory.md"); + if (fileInfo2.exists()) { + stream << " [(test description)](" << url << "/testStory.md)"; + } + + QFileInfo fileInfo3(directory + "/" + TEST_FILENAME); + if (fileInfo3.exists()) { + stream << " (*)"; + } + stream << "\n"; + } + + mdFile.close(); + + QMessageBox::information(0, "Success", "TestCreator outline file " + testsOutlineFilename + " has been created"); +} + +void TestCreator::createTestRailTestCases() { + QString previousSelection = _testDirectory; + QString parent = previousSelection.left(previousSelection.lastIndexOf('/')); + if (!parent.isNull() && parent.right(1) != "/") { + parent += "/"; + } + + _testDirectory = + QFileDialog::getExistingDirectory(nullptr, "Please select the tests root folder", parent, QFileDialog::ShowDirsOnly); + + // If user cancelled then restore previous selection and return + if (_testDirectory.isNull()) { + _testDirectory = previousSelection; + return; + } + + QString outputDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select a folder to store generated files in", + nullptr, QFileDialog::ShowDirsOnly); + + // If user cancelled then return + if (outputDirectory.isNull()) { + return; + } + + if (!_testRailInterface) { + _testRailInterface = new TestRailInterface; + } + + if (_testRailCreateMode == PYTHON) { + _testRailInterface->createTestSuitePython(_testDirectory, outputDirectory, nitpick->getSelectedUser(), + nitpick->getSelectedBranch()); + } else { + _testRailInterface->createTestSuiteXML(_testDirectory, outputDirectory, nitpick->getSelectedUser(), + nitpick->getSelectedBranch()); + } +} + +void TestCreator::createTestRailRun() { + QString outputDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select a folder to store generated files in", + nullptr, QFileDialog::ShowDirsOnly); + + if (outputDirectory.isNull()) { + return; + } + + + if (!_testRailInterface) { + _testRailInterface = new TestRailInterface; + } + + _testRailInterface->createTestRailRun(outputDirectory); +} + +void TestCreator::updateTestRailRunResult() { + QString testResults = QFileDialog::getOpenFileName(nullptr, "Please select the zipped test results to update from", nullptr, + "Zipped TestCreator Results (*.zip)"); + if (testResults.isNull()) { + return; + } + + QString tempDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select a folder to store temporary files in", + nullptr, QFileDialog::ShowDirsOnly); + if (tempDirectory.isNull()) { + return; + } + + + if (!_testRailInterface) { + _testRailInterface = new TestRailInterface; + } + + _testRailInterface->updateTestRailRunResults(testResults, tempDirectory); +} + +QStringList TestCreator::createListOfAll_imagesInDirectory(const QString& imageFormat, const QString& pathToImageDirectory) { + _imageDirectory = QDir(pathToImageDirectory); + QStringList nameFilters; + nameFilters << "*." + imageFormat; + + return _imageDirectory.entryList(nameFilters, QDir::Files, QDir::Name); +} + +// Snapshots are files in the following format: +// Filename (i.e. without extension) contains tests (this is based on all test scripts being within the tests folder) +// Last 5 characters in filename are digits (after removing the extension) +// Extension is 'imageFormat' +bool TestCreator::isInSnapshotFilenameFormat(const QString& imageFormat, const QString& filename) { + bool contains_tests = filename.contains("tests" + PATH_SEPARATOR); + + QString filenameWithoutExtension = filename.left(filename.lastIndexOf('.')); + bool last5CharactersAreDigits; + filenameWithoutExtension.right(5).toInt(&last5CharactersAreDigits, 10); + + bool extensionIsIMAGE_FORMAT = (filename.right(imageFormat.length()) == imageFormat); + + return (contains_tests && last5CharactersAreDigits && extensionIsIMAGE_FORMAT); +} + +// For a file named "D_GitHub_hifi-tests_tests_content_entity_zone_create_0.jpg", the test directory is +// D:/GitHub/hifi-tests/tests/content/entity/zone/create +// This method assumes the filename is in the correct format +QString TestCreator::getExpectedImageDestinationDirectory(const QString& filename) { + QString filenameWithoutExtension = filename.left(filename.length() - 4); + QStringList filenameParts = filenameWithoutExtension.split(PATH_SEPARATOR); + + QString result = filenameParts[0] + ":"; + + for (int i = 1; i < filenameParts.length() - 1; ++i) { + result += "/" + filenameParts[i]; + } + + return result; +} + +// For a file named "D_GitHub_hifi-tests_tests_content_entity_zone_create_0.jpg", the source directory on GitHub +// is ...tests/content/entity/zone/create +// This is used to create the full URL +// This method assumes the filename is in the correct format +QString TestCreator::getExpectedImagePartialSourceDirectory(const QString& filename) { + QString filenameWithoutExtension = filename.left(filename.length() - 4); + QStringList filenameParts = filenameWithoutExtension.split(PATH_SEPARATOR); + + // Note that the bottom-most "tests" folder is assumed to be the root + // This is required because the tests folder is named hifi_tests + int i { filenameParts.length() - 1 }; + while (i >= 0 && filenameParts[i] != "tests") { + --i; + } + + if (i < 0) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Bad filename"); + exit(-1); + } + + QString result = filenameParts[i]; + + for (int j = i + 1; j < filenameParts.length() - 1; ++j) { + result += "/" + filenameParts[j]; + } + + return result; +} + +void TestCreator::setTestRailCreateMode(TestRailCreateMode testRailCreateMode) { + _testRailCreateMode = testRailCreateMode; +} + +void TestCreator::createWebPage(QCheckBox* updateAWSCheckBox, QLineEdit* urlLineEdit) { + QString testResults = QFileDialog::getOpenFileName(nullptr, "Please select the zipped test results to update from", nullptr, + "Zipped TestCreator Results (TestResults--*.zip)"); + if (testResults.isNull()) { + return; + } + + QString workingDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select a folder to store temporary files in", + nullptr, QFileDialog::ShowDirsOnly); + if (workingDirectory.isNull()) { + return; + } + + if (!_awsInterface) { + _awsInterface = new AWSInterface; + } + + _awsInterface->createWebPageFromResults(testResults, workingDirectory, updateAWSCheckBox, urlLineEdit); +} \ No newline at end of file diff --git a/tools/nitpick/src/TestCreator.h b/tools/nitpick/src/TestCreator.h new file mode 100644 index 0000000000..59bbf7bbaf --- /dev/null +++ b/tools/nitpick/src/TestCreator.h @@ -0,0 +1,170 @@ +// +// TestCreator.h +// +// Created by Nissim Hadar on 2 Nov 2017. +// Copyright 2013 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_testCreator_h +#define hifi_testCreator_h + +#include +#include +#include +#include + +#include "AWSInterface.h" +#include "ImageComparer.h" +#include "Downloader.h" +#include "MismatchWindow.h" +#include "TestRailInterface.h" + +class Step { +public: + QString text; + bool takeSnapshot; +}; + +using StepList = std::vector; + +class ExtractedText { +public: + QString title; + StepList stepList; +}; + +enum TestRailCreateMode { + PYTHON, + XML +}; + +class TestCreator { +public: + TestCreator(QProgressBar* progressBar, QCheckBox* checkBoxInteractiveMode); + + void startTestsEvaluation(const bool isRunningFromCommandLine, + const bool isRunningInAutomaticTestRun, + const QString& snapshotDirectory = QString(), + const QString& branchFromCommandLine = QString(), + const QString& userFromCommandLine = QString()); + + void finishTestsEvaluation(); + + void createTests(const QString& clientProfile); + + void createTestsOutline(); + + bool createFileSetup(); + bool createAllFilesSetup(); + + void createMDFile(); + void createAllMDFiles(); + bool createMDFile(const QString& directory); + + void createTestAutoScript(); + void createAllTestAutoScripts(); + bool createTestAutoScript(const QString& directory); + + void createTestRailTestCases(); + void createTestRailRun(); + + void updateTestRailRunResult(); + + void createAllRecursiveScripts(); + void createAllRecursiveScripts(const QString& directory); + + void createRecursiveScript(); + void createRecursiveScript(const QString& directory, bool interactiveMode); + + int compareImageLists(); + int checkTextResults(); + + QStringList createListOfAll_imagesInDirectory(const QString& imageFormat, const QString& pathToImageDirectory); + + bool isInSnapshotFilenameFormat(const QString& imageFormat, const QString& filename); + + void includeTest(QTextStream& textStream, const QString& testPathname); + + void appendTestResultsToFile(TestResult testResult, QPixmap comparisonImage, bool hasFailed); + void appendTestResultsToFile(QString testResultFilename, bool hasFailed); + + bool createTestResultsFolderPath(const QString& directory); + QString zipAndDeleteTestResultsFolder(); + + static bool isAValidDirectory(const QString& pathname); + QString extractPathFromTestsDown(const QString& fullPath); + QString getExpectedImageDestinationDirectory(const QString& filename); + QString getExpectedImagePartialSourceDirectory(const QString& filename); + + ExtractedText getTestScriptLines(QString testFileName); + + void setTestRailCreateMode(TestRailCreateMode testRailCreateMode); + + void createWebPage(QCheckBox* updateAWSCheckBox, QLineEdit* urlLineEdit); + +private: + QProgressBar* _progressBar; + QCheckBox* _checkBoxInteractiveMode; + + bool _isRunningFromCommandLine{ false }; + bool _isRunningInAutomaticTestRun{ false }; + + const QString TEST_FILENAME{ "test.js" }; + const QString TEST_RECURSIVE_FILENAME{ "testRecursive.js" }; + const QString TEST_RESULTS_FOLDER { "TestResults" }; + const QString TEST_RESULTS_FILENAME { "TestResults.txt" }; + + const double THRESHOLD{ 0.965 }; + + QDir _imageDirectory; + + MismatchWindow _mismatchWindow; + + ImageComparer _imageComparer; + + QString _testResultsFolderPath; + int _failureIndex{ 1 }; + int _successIndex{ 1 }; + + // Expected images are in the format ExpectedImage_dddd.jpg (d == decimal digit) + const int NUM_DIGITS { 5 }; + const QString EXPECTED_IMAGE_PREFIX { "ExpectedImage_" }; + + // We have two directories to work with. + // The first is the directory containing the test we are working with + // The second is the root directory of all tests + // The third contains the snapshots taken for test runs that need to be evaluated + QString _testDirectory; + QString _testsRootDirectory; + QString _snapshotDirectory; + + QStringList _expectedImagesFilenames; + QStringList _expectedImagesFullFilenames; + QStringList _resultImagesFullFilenames; + + // Used for accessing GitHub + const QString GIT_HUB_DEFAULT_USER{ "highfidelity" }; + const QString GIT_HUB_DEFAULT_BRANCH{ "master" }; + const QString GIT_HUB_REPOSITORY{ "hifi_tests" }; + + const QString DATETIME_FORMAT{ "yyyy-MM-dd_hh-mm-ss" }; + + // NOTE: these need to match the appropriate var's in nitpick.js + // var advanceKey = "n"; + // var pathSeparator = "."; + const QString ADVANCE_KEY{ "n" }; + const QString PATH_SEPARATOR{ "." }; + + bool _exitWhenComplete{ false }; + + TestRailInterface* _testRailInterface; + TestRailCreateMode _testRailCreateMode { PYTHON }; + + AWSInterface* _awsInterface; + Downloader* _downloader; +}; + +#endif \ No newline at end of file From 695367a3e231954d1561ad0e792428eada0b7e70 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Fri, 1 Mar 2019 14:28:23 -0800 Subject: [PATCH 291/474] Updated for Mac. --- tools/nitpick/README.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tools/nitpick/README.md b/tools/nitpick/README.md index 62e614c214..b4bcff81c8 100644 --- a/tools/nitpick/README.md +++ b/tools/nitpick/README.md @@ -67,10 +67,10 @@ These steps assume the hifi repository has been cloned to `~/hifi`. 1. Copy the downloaded file to (for example) **C:\adb** and extract in place. Verify you see *adb.exe* in **C:\adb\platform-tools\\**. 1. After installation - add the path to adb.exe to the Windows PATH environment variable (note that it is in *adb\platform-tools*). -1. `nitpick` is included in the High Fidelity installer but can also be downloaded from: +1. `nitpick` is included in the High Fidelity installer but can also be downloaded from (change X.X.X to correct version): [here]().* ### Mac -1. (first time) Install brew +1. (First time) Install brew In a terminal: `/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"` Note that you will need to press RETURN again, and will then be asked for your password. @@ -100,6 +100,16 @@ This is needed because the Mac Python supplied no longer links with the deprecat 1. Install the latest release of Boto3 via pip: pip3 install boto3 1. (First time)Install adb (the Android Debug Bridge) - in a terminal: `brew cask install android-platform-tools` +1. (First time) Set terminal privileges + 1. Click on Apple icon (top left) + 1. Select System Preferences... + 1. Select Security & Privacy + 1. Select Accessibility + 1. Click on "Click the lock to make changes" and enter passsword if requested + 1. Set Checkbox near *Terminal* to checked. + 1. Click on "Click the lock to prevent furthur changes" + 1. Close window + 1. `nitpick` is included in the High Fidelity installer but can also be downloaded from: [here]().* # Usage From 618a1d5b8379c7303f195a81c97093082d5234e7 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Fri, 1 Mar 2019 14:03:45 -0800 Subject: [PATCH 292/474] fix tablet button and gizmos rotation --- interface/src/ui/overlays/Overlays.cpp | 23 ++++++++++++----------- scripts/system/libraries/utils.js | 4 ---- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 9b2f741531..08fa04245f 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -413,30 +413,31 @@ EntityItemProperties Overlays::convertOverlayToEntityProperties(QVariantMap& ove overlayProps["dimensions"] = vec3toVariant(ratio * dimensions); } - if (add || overlayProps.contains("rotation")) { + if (add && !overlayProps.contains("rotation") && !overlayProps.contains("localRotation")) { + glm::quat rotation; + overlayProps["rotation"] = quatToVariant(glm::angleAxis(-(float)M_PI_2, rotation * Vectors::RIGHT) * rotation); + } else if (overlayProps.contains("rotation")) { glm::quat rotation; { auto iter = overlayProps.find("rotation"); if (iter != overlayProps.end()) { rotation = quatFromVariant(iter.value()); - } else if (!add) { - EntityPropertyFlags desiredProperties; - desiredProperties += PROP_ROTATION; - rotation = DependencyManager::get()->getEntityProperties(id, desiredProperties).getRotation(); } } overlayProps["rotation"] = quatToVariant(glm::angleAxis(-(float)M_PI_2, rotation * Vectors::RIGHT) * rotation); - } - if (add || overlayProps.contains("localRotation")) { + + if (overlayProps.contains("localRotation")) { + auto iter = overlayProps.find("localRotation"); + if (iter != overlayProps.end()) { + overlayProps.erase(iter); + } + } + } else if (overlayProps.contains("localRotation")) { glm::quat rotation; { auto iter = overlayProps.find("localRotation"); if (iter != overlayProps.end()) { rotation = quatFromVariant(iter.value()); - } else if (!add) { - EntityPropertyFlags desiredProperties; - desiredProperties += PROP_LOCAL_ROTATION; - rotation = DependencyManager::get()->getEntityProperties(id, desiredProperties).getLocalRotation(); } } overlayProps["localRotation"] = quatToVariant(glm::angleAxis(-(float)M_PI_2, rotation * Vectors::RIGHT) * rotation); diff --git a/scripts/system/libraries/utils.js b/scripts/system/libraries/utils.js index 6f74b43a8e..508e8d46e3 100644 --- a/scripts/system/libraries/utils.js +++ b/scripts/system/libraries/utils.js @@ -418,13 +418,11 @@ resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride) var HOME_BUTTON_Z_OFFSET = (tabletDepth / 1.9) * sensorScaleOffsetOverride; Entities.editEntity(HMD.homeButtonID, { localPosition: { x: HOME_BUTTON_X_OFFSET, y: HOME_BUTTON_Y_OFFSET, z: -HOME_BUTTON_Z_OFFSET }, - localRotation: { x: 0, y: 1, z: 0, w: 0 }, dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim } }); Entities.editEntity(HMD.homeButtonHighlightID, { localPosition: { x: -HOME_BUTTON_X_OFFSET, y: HOME_BUTTON_Y_OFFSET, z: -HOME_BUTTON_Z_OFFSET }, - localRotation: { x: 0, y: 1, z: 0, w: 0 }, dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim } }); }; @@ -482,13 +480,11 @@ reparentAndScaleTablet = function(width, reparentProps) { var HOME_BUTTON_Z_OFFSET = (tabletDepth / 1.9) * sensorScaleOffsetOverride; Entities.editEntity(HMD.homeButtonID, { localPosition: { x: HOME_BUTTON_X_OFFSET, y: HOME_BUTTON_Y_OFFSET, z: -HOME_BUTTON_Z_OFFSET }, - localRotation: { x: 0, y: 1, z: 0, w: 0 }, dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim } }); Entities.editEntity(HMD.homeButtonHighlightID, { localPosition: { x: -HOME_BUTTON_X_OFFSET, y: HOME_BUTTON_Y_OFFSET, z: -HOME_BUTTON_Z_OFFSET }, - localRotation: { x: 0, y: 1, z: 0, w: 0 }, dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim } }); } From c046b8ffd35c5aaffd953999eca5c3828d97b2c5 Mon Sep 17 00:00:00 2001 From: amantley Date: Fri, 1 Mar 2019 15:12:37 -0800 Subject: [PATCH 293/474] made is so the boneLengthScale is only computed once per animation clip --- libraries/animation/src/AnimClip.cpp | 80 +++++++++++++--------------- 1 file changed, 36 insertions(+), 44 deletions(-) diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index 9b87d058fd..4fe02e9307 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -123,6 +123,42 @@ void AnimClip::copyFromNetworkAnim() { const int animFrameCount = animModel.animationFrames.size(); _anim.resize(animFrameCount); + // find the size scale factor for translation in the animation. + const int avatarHipsParentIndex = avatarSkeleton->getParentIndex(avatarSkeleton->nameToJointIndex("Hips")); + const int animHipsParentIndex = animSkeleton.getParentIndex(animSkeleton.nameToJointIndex("Hips")); + const AnimPose& avatarHipsAbsoluteDefaultPose = avatarSkeleton->getAbsoluteDefaultPose(avatarSkeleton->nameToJointIndex("Hips")); + const AnimPose& animHipsAbsoluteDefaultPose = animSkeleton.getAbsoluteDefaultPose(animSkeleton.nameToJointIndex("Hips")); + + // the get the units and the heights for the animation and the avatar + const float avatarUnitScale = extractScale(avatarSkeleton->getGeometryOffset()).y; + const float animationUnitScale = extractScale(animModel.offset).y; + const float avatarHeightInMeters = avatarUnitScale * avatarHipsAbsoluteDefaultPose.trans().y; + const float animHeightInMeters = animationUnitScale * animHipsAbsoluteDefaultPose.trans().y; + + // get the parent scales for the avatar and the animation + float avatarHipsParentScale = 1.0f; + if (avatarHipsParentIndex >= 0) { + const AnimPose& avatarHipsParentAbsoluteDefaultPose = avatarSkeleton->getAbsoluteDefaultPose(avatarHipsParentIndex); + avatarHipsParentScale = avatarHipsParentAbsoluteDefaultPose.scale().y; + } + float animHipsParentScale = 1.0f; + if (animHipsParentIndex >= 0) { + const AnimPose& animationHipsParentAbsoluteDefaultPose = animSkeleton.getAbsoluteDefaultPose(animHipsParentIndex); + animHipsParentScale = animationHipsParentAbsoluteDefaultPose.scale().y; + } + + const float EPSILON = 0.0001f; + float boneLengthScale = 1.0f; + // compute the ratios for the units, the heights in meters, and the parent scales + if ((fabsf(animHeightInMeters) > EPSILON) && (animationUnitScale > EPSILON) && (animHipsParentScale > EPSILON)) { + const float avatarToAnimationHeightRatio = avatarHeightInMeters / animHeightInMeters; + const float unitsRatio = 1.0f / (avatarUnitScale / animationUnitScale); + const float parentScaleRatio = 1.0f / (avatarHipsParentScale / animHipsParentScale); + + boneLengthScale = avatarToAnimationHeightRatio * unitsRatio * parentScaleRatio; + } + + for (int frame = 0; frame < animFrameCount; frame++) { const HFMAnimationFrame& animFrame = animModel.animationFrames[frame]; @@ -162,7 +198,6 @@ void AnimClip::copyFromNetworkAnim() { avatarSkeleton->convertAbsoluteRotationsToRelative(avatarRotations); _anim[frame].reserve(avatarJointCount); - for (int avatarJointIndex = 0; avatarJointIndex < avatarJointCount; avatarJointIndex++) { const AnimPose& avatarDefaultPose = avatarSkeleton->getRelativeDefaultPose(avatarJointIndex); @@ -177,49 +212,6 @@ void AnimClip::copyFromNetworkAnim() { // retarget translation from animation to avatar const glm::vec3& animZeroTrans = animModel.animationFrames[0].translations[animJointIndex]; - float boneLengthScale = 1.0f; - const float EPSILON = 0.0001f; - - const int avatarHipsParentIndex = avatarSkeleton->getParentIndex(avatarSkeleton->nameToJointIndex("Hips")); - const int animHipsParentIndex = animSkeleton.getParentIndex(animSkeleton.nameToJointIndex("Hips")); - if (avatarJointIndex == avatarSkeleton->nameToJointIndex("Hips") && (avatarHipsParentIndex >= 0) || (animHipsParentIndex >= 0)) { - - const AnimPose& animHipsAbsoluteDefaultPose = animSkeleton.getAbsoluteDefaultPose(animSkeleton.nameToJointIndex("Hips")); - const AnimPose& avatarHipsAbsoluteDefaultPose = avatarSkeleton->getAbsoluteDefaultPose(avatarSkeleton->nameToJointIndex("Hips")); - - // the get the units and the heights for the animation and the avatar - const float animationUnitScale = extractScale(animModel.offset).y; - const float avatarUnitScale = extractScale(avatarSkeleton->getGeometryOffset()).y; - const float avatarHeightInMeters = avatarUnitScale * avatarHipsAbsoluteDefaultPose.trans().y; - const float animHeightInMeters = animationUnitScale * animHipsAbsoluteDefaultPose.trans().y; - - // get the parent scales for the avatar and the animation - float avatarHipsParentScale = 1.0f; - float animHipsParentScale = 1.0f; - if (avatarHipsParentIndex >= 0) { - const AnimPose& avatarHipsParentAbsoluteDefaultPose = avatarSkeleton->getAbsoluteDefaultPose(avatarHipsParentIndex); - avatarHipsParentScale = avatarHipsParentAbsoluteDefaultPose.scale().y; - } - if (animHipsParentIndex >= 0) { - const AnimPose& animationHipsParentAbsoluteDefaultPose = animSkeleton.getAbsoluteDefaultPose(animHipsParentIndex); - animHipsParentScale = animationHipsParentAbsoluteDefaultPose.scale().y; - } - - // compute the ratios for the units, the heights in meters, and the parent scales - if ((fabsf(animHeightInMeters) > EPSILON) && (animationUnitScale > EPSILON) && (animHipsParentScale > EPSILON)) { - const float avatarToAnimationHeightRatio = avatarHeightInMeters / animHeightInMeters; - const float unitsRatio = 1.0f / (avatarUnitScale / animationUnitScale); - const float parentScaleRatio = 1.0f / (avatarHipsParentScale / animHipsParentScale); - - boneLengthScale = avatarToAnimationHeightRatio * unitsRatio * parentScaleRatio; - } - } else { - - if (fabsf(glm::length(animZeroTrans)) > EPSILON) { - boneLengthScale = glm::length(avatarDefaultPose.trans()) / glm::length(animZeroTrans); - } - } - relativeTranslation = avatarDefaultPose.trans() + boneLengthScale * (animTrans - animZeroTrans); } else { // This joint is NOT in the animation at all. From 8e600adf1e5a997d785b67bd161cf200dfd7a935 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Fri, 1 Mar 2019 15:30:11 -0800 Subject: [PATCH 294/474] making requested changes --- interface/src/ui/overlays/Overlays.cpp | 26 +++----------------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 08fa04245f..8fef2d543d 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -414,32 +414,12 @@ EntityItemProperties Overlays::convertOverlayToEntityProperties(QVariantMap& ove } if (add && !overlayProps.contains("rotation") && !overlayProps.contains("localRotation")) { - glm::quat rotation; - overlayProps["rotation"] = quatToVariant(glm::angleAxis(-(float)M_PI_2, rotation * Vectors::RIGHT) * rotation); + overlayProps["rotation"] = quatToVariant(glm::angleAxis(-(float)M_PI_2, Vectors::RIGHT)); } else if (overlayProps.contains("rotation")) { - glm::quat rotation; - { - auto iter = overlayProps.find("rotation"); - if (iter != overlayProps.end()) { - rotation = quatFromVariant(iter.value()); - } - } + glm::quat rotation = quatFromVariant(overlayProps["rotation"]); overlayProps["rotation"] = quatToVariant(glm::angleAxis(-(float)M_PI_2, rotation * Vectors::RIGHT) * rotation); - - if (overlayProps.contains("localRotation")) { - auto iter = overlayProps.find("localRotation"); - if (iter != overlayProps.end()) { - overlayProps.erase(iter); - } - } } else if (overlayProps.contains("localRotation")) { - glm::quat rotation; - { - auto iter = overlayProps.find("localRotation"); - if (iter != overlayProps.end()) { - rotation = quatFromVariant(iter.value()); - } - } + glm::quat rotation = quatFromVariant(overlayProps["localRotation"]); overlayProps["localRotation"] = quatToVariant(glm::angleAxis(-(float)M_PI_2, rotation * Vectors::RIGHT) * rotation); } From da1ffc15e37ab6c22457a98f129df6d8ec7c425e Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Fri, 1 Mar 2019 15:41:57 -0800 Subject: [PATCH 295/474] lasers scale with avatar --- interface/src/raypick/LaserPointer.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index aeed65fbad..bd746c9090 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -172,7 +172,10 @@ void LaserPointer::RenderState::update(const glm::vec3& origin, const glm::vec3& properties.setVisible(true); properties.setIgnorePickIntersection(doesPathIgnorePicks()); QVector widths; - widths.append(getLineWidth() * parentScale); + float width = getLineWidth() * parentScale; + widths.append(width); + widths.append(width); + properties.setStrokeWidths(widths); DependencyManager::get()->editEntity(getPathID(), properties); } } From 884a64bfa6633a9c2029577e2582f9773fd3f99e Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Fri, 1 Mar 2019 15:50:50 -0800 Subject: [PATCH 296/474] fix resource crash --- libraries/networking/src/ResourceCache.cpp | 23 ++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 7345081380..d5abb27a27 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -353,16 +353,19 @@ QSharedPointer ResourceCache::getResource(const QUrl& url, const QUrl& // We've seen this extra info before resource = resourcesWithExtraHashIter.value().lock(); } else if (resourcesWithExtraHash.size() > 0.0f) { - // We haven't seen this extra info before, but we've already downloaded the resource. We need a new copy of this object (with any old hash). - resource = createResourceCopy(resourcesWithExtraHash.begin().value().lock()); - resource->setExtra(extra); - resource->setExtraHash(extraHash); - resource->setSelf(resource); - resource->setCache(this); - resource->moveToThread(qApp->thread()); - connect(resource.data(), &Resource::updateSize, this, &ResourceCache::updateTotalSize); - resourcesWithExtraHash.insert(extraHash, resource); - resource->ensureLoading(); + auto oldResource = resourcesWithExtraHash.begin().value().lock(); + if (oldResource) { + // We haven't seen this extra info before, but we've already downloaded the resource. We need a new copy of this object (with any old hash). + resource = createResourceCopy(oldResource); + resource->setExtra(extra); + resource->setExtraHash(extraHash); + resource->setSelf(resource); + resource->setCache(this); + resource->moveToThread(qApp->thread()); + connect(resource.data(), &Resource::updateSize, this, &ResourceCache::updateTotalSize); + resourcesWithExtraHash.insert(extraHash, resource); + resource->ensureLoading(); + } } } if (resource) { From 3f65c572586af8b4f854a6625a0674b89c08fa4a Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 1 Mar 2019 15:20:31 -0800 Subject: [PATCH 297/474] Fix CI release builds --- android/containerized_build.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/android/containerized_build.sh b/android/containerized_build.sh index 34e620ad2e..1ca597b2b9 100755 --- a/android/containerized_build.sh +++ b/android/containerized_build.sh @@ -9,6 +9,10 @@ docker build --build-arg BUILD_UID=`id -u` -t "${DOCKER_IMAGE_NAME}" -f docker/D # So make sure we use VERSION_CODE consistently test -z "$VERSION_CODE" && export VERSION_CODE=$VERSION +# PR builds don't populate STABLE_BUILD, but the release builds do, and the build +# bash script requires it, so we need to populate it if it's not present +test -z "$STABLE_BUILD" && export STABLE_BUILD=0 + # FIXME figure out which of these actually need to be forwarded and which can be eliminated docker run \ --rm \ @@ -29,6 +33,7 @@ docker run \ -e OAUTH_CLIENT_ID \ -e OAUTH_REDIRECT_URI \ -e SHA7 \ + -e STABLE_BUILD \ -e VERSION_CODE \ "${DOCKER_IMAGE_NAME}" \ sh -c "./build_android.sh" From 83c9381575eafe3aa43fcfbbf60249506359c6f7 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Fri, 1 Mar 2019 17:25:46 -0800 Subject: [PATCH 298/474] Convert avatarPriority to trivalued (inherit, crowd, hero) Also tweaks from original reviewer comments. --- .../src/avatars/AvatarMixerClientData.cpp | 28 ++++++----- .../src/avatars/AvatarMixerSlave.cpp | 6 +-- assignment-client/src/avatars/MixerAvatar.h | 6 +-- .../entities/src/EntityItemProperties.cpp | 46 +++++++++++++------ libraries/entities/src/EntityItemProperties.h | 2 +- libraries/entities/src/ZoneEntityItem.cpp | 10 ++-- libraries/entities/src/ZoneEntityItem.h | 7 ++- .../system/assets/data/createAppTooltips.json | 2 +- scripts/system/edit.js | 2 +- scripts/system/html/js/entityProperties.js | 3 +- 10 files changed, 69 insertions(+), 43 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index a63a76829b..cef4383aee 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -91,22 +91,26 @@ namespace { struct FindPriorityZone { glm::vec3 position; bool isInPriorityZone { false }; + float zoneVolume { std::numeric_limits::max() }; + static bool operation(const OctreeElementPointer& element, void* extraData) { auto findPriorityZone = static_cast(extraData); if (element->getAACube().contains(findPriorityZone->position)) { const EntityTreeElementPointer entityTreeElement = static_pointer_cast(element); entityTreeElement->forEachEntity([&findPriorityZone](EntityItemPointer item) { if (item->getType() == EntityTypes::Zone - && item->contains(findPriorityZone->position) - && static_pointer_cast(item)->getAvatarPriority()) { - findPriorityZone->isInPriorityZone = true; + && item->contains(findPriorityZone->position)) { + auto zoneItem = static_pointer_cast(item); + if (zoneItem->getAvatarPriority() != COMPONENT_MODE_INHERIT) { + float volume = zoneItem->getVolumeEstimate(); + if (volume < findPriorityZone->zoneVolume) { // Smaller volume wins + findPriorityZone->isInPriorityZone = zoneItem->getAvatarPriority() == COMPONENT_MODE_ENABLED; + findPriorityZone->zoneVolume = volume; + } + } } }); - if (findPriorityZone->isInPriorityZone) { - return false; // For now, stop at first hit. - } else { - return true; - } + return true; // Keep recursing } else { // Position isn't within this subspace, so end recursion. return false; } @@ -144,10 +148,10 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message, const SlaveShared EntityTree& entityTree = *slaveSharedData.entityTree; FindPriorityZone findPriorityZone { newPosition, false } ; entityTree.recurseTreeWithOperation(&FindPriorityZone::operation, &findPriorityZone); - _avatar->setPriorityAvatar(findPriorityZone.isInPriorityZone); - if (findPriorityZone.isInPriorityZone) { - qCWarning(avatars) << "Avatar" << _avatar->getSessionDisplayName() << "in hero zone"; - } + _avatar->setHasPriority(findPriorityZone.isInPriorityZone); + //if (findPriorityZone.isInPriorityZone) { + // qCWarning(avatars) << "Avatar" << _avatar->getSessionDisplayName() << "in hero zone"; + //} #endif } diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 54eefadda1..80600a53ee 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -460,7 +460,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) } } { - if (sourceAvatarNodeData->getConstAvatarData()->getPriorityAvatar() && !sendAvatar) { + if (sourceAvatarNodeData->getConstAvatarData()->getHasPriority() && !sendAvatar) { qCWarning(avatars) << "Hero avatar dropped:" << sourceAvatarNodeData->getConstAvatarData()->getSessionDisplayName() << "lastSeqToReceiver =" << destinationNodeData->getLastBroadcastSequenceNumber(sourceAvatarNode->getLocalID()) << "lastSeqFromSender = " << sourceAvatarNodeData->getLastReceivedSequenceNumber(); @@ -474,7 +474,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) const MixerAvatar* avatarNodeData = sourceAvatarNodeData->getConstAvatarData(); auto lastEncodeTime = destinationNodeData->getLastOtherAvatarEncodeTime(sourceAvatarNode->getLocalID()); - avatarPriorityQueues[avatarNodeData->getPriorityAvatar() ? kHero : kNonhero].push( + avatarPriorityQueues[avatarNodeData->getHasPriority() ? kHero : kNonhero].push( SortableAvatar(avatarNodeData, sourceAvatarNode, lastEncodeTime)); } @@ -601,7 +601,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) if (detail != AvatarData::NoData) { _stats.numOthersIncluded++; - if (sourceAvatar->getPriorityAvatar()) { + if (sourceAvatar->getHasPriority()) { _stats.numHeroesIncluded++; } diff --git a/assignment-client/src/avatars/MixerAvatar.h b/assignment-client/src/avatars/MixerAvatar.h index 4781fdee1b..2444bd541c 100644 --- a/assignment-client/src/avatars/MixerAvatar.h +++ b/assignment-client/src/avatars/MixerAvatar.h @@ -19,11 +19,11 @@ class MixerAvatar : public AvatarData { public: - bool getPriorityAvatar() const { return _bPriorityAvatar; } - void setPriorityAvatar(bool bPriorityAvatar) { _bPriorityAvatar = bPriorityAvatar; } + bool getHasPriority() const { return _bHasPriority; } + void setHasPriority(bool bPriorityAvatar) { _bHasPriority = bPriorityAvatar; } private: - bool _bPriorityAvatar { false }; + bool _bHasPriority { false }; }; using MixerAvatarSharedPointer = std::shared_ptr; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index a7cba157ae..ac9b55df4b 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -225,6 +225,15 @@ QString EntityItemProperties::getBloomModeAsString() const { return getComponentModeAsString(_bloomMode); } +namespace { + const QStringList AVATAR_PRIORITIES_AS_STRING + { "inherit", "crowd", "hero" }; +} + +QString EntityItemProperties::getAvatarPriorityAsString() const { + return AVATAR_PRIORITIES_AS_STRING.value(_avatarPriority); +} + std::array::const_iterator EntityItemProperties::findComponent(const QString& mode) { return std::find_if(COMPONENT_MODES.begin(), COMPONENT_MODES.end(), [&](const ComponentPair& pair) { return (pair.second == mode); @@ -249,6 +258,15 @@ void EntityItemProperties::setBloomModeFromString(const QString& bloomMode) { } } +void EntityItemProperties::setAvatarPriorityFromString(QString const& avatarPriority) { + auto result = AVATAR_PRIORITIES_AS_STRING.indexOf(avatarPriority); + + if (result != -1) { + _avatarPriority = result; + _avatarPriorityChanged = true; + } +} + QString EntityItemProperties::getKeyLightModeAsString() const { return getComponentModeAsString(_keyLightMode); } @@ -616,12 +634,12 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_FLYING_ALLOWED, flyingAllowed); CHECK_PROPERTY_CHANGE(PROP_GHOSTING_ALLOWED, ghostingAllowed); CHECK_PROPERTY_CHANGE(PROP_FILTER_URL, filterURL); - CHECK_PROPERTY_CHANGE(PROP_AVATAR_PRIORITY, avatarPriority); CHECK_PROPERTY_CHANGE(PROP_KEY_LIGHT_MODE, keyLightMode); CHECK_PROPERTY_CHANGE(PROP_AMBIENT_LIGHT_MODE, ambientLightMode); CHECK_PROPERTY_CHANGE(PROP_SKYBOX_MODE, skyboxMode); CHECK_PROPERTY_CHANGE(PROP_HAZE_MODE, hazeMode); CHECK_PROPERTY_CHANGE(PROP_BLOOM_MODE, bloomMode); + CHECK_PROPERTY_CHANGE(PROP_AVATAR_PRIORITY, avatarPriority); // Polyvox CHECK_PROPERTY_CHANGE(PROP_VOXEL_VOLUME_SIZE, voxelVolumeSize); @@ -1422,8 +1440,10 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * zone. It is periodically executed for each entity in the zone. It can, for example, be used to not allow changes to * certain properties.
      * - * @property {boolean} avatarPriority=false - If true avatars within this zone will have their movements distributed to other - * clients with priority over other avatars. Use, for example, on a performance stage with a few presenters. + * @property {string} avatarPriority="inherit" - Configures the update priority of contained avatars to other clients.
      + * "inherit": Priority from enclosing zones is unchanged.
      + * "crowd": Priority in this zone is the normal priority.
      + * "hero": Avatars in this zone will have an increased update priority *
        *
        * function filter(properties) {
      @@ -1753,13 +1773,13 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
               COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_FLYING_ALLOWED, flyingAllowed);
               COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_GHOSTING_ALLOWED, ghostingAllowed);
               COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_FILTER_URL, filterURL);
      -        COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_AVATAR_PRIORITY, avatarPriority);
       
               COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_KEY_LIGHT_MODE, keyLightMode, getKeyLightModeAsString());
               COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_AMBIENT_LIGHT_MODE, ambientLightMode, getAmbientLightModeAsString());
               COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SKYBOX_MODE, skyboxMode, getSkyboxModeAsString());
               COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_HAZE_MODE, hazeMode, getHazeModeAsString());
               COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_BLOOM_MODE, bloomMode, getBloomModeAsString());
      +        COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_AVATAR_PRIORITY, avatarPriority, getAvatarPriorityAsString());
           }
       
           // Web only
      @@ -2114,12 +2134,12 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
           COPY_PROPERTY_FROM_QSCRIPTVALUE(flyingAllowed, bool, setFlyingAllowed);
           COPY_PROPERTY_FROM_QSCRIPTVALUE(ghostingAllowed, bool, setGhostingAllowed);
           COPY_PROPERTY_FROM_QSCRIPTVALUE(filterURL, QString, setFilterURL);
      -    COPY_PROPERTY_FROM_QSCRIPTVALUE(avatarPriority, bool, setAvatarPriority);
           COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(keyLightMode, KeyLightMode);
           COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(ambientLightMode, AmbientLightMode);
           COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(skyboxMode, SkyboxMode);
           COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(hazeMode, HazeMode);
           COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(bloomMode, BloomMode);
      +    COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(avatarPriority, AvatarPriority);
       
           // Polyvox
           COPY_PROPERTY_FROM_QSCRIPTVALUE(voxelVolumeSize, vec3, setVoxelVolumeSize);
      @@ -2393,12 +2413,12 @@ void EntityItemProperties::merge(const EntityItemProperties& other) {
           COPY_PROPERTY_IF_CHANGED(flyingAllowed);
           COPY_PROPERTY_IF_CHANGED(ghostingAllowed);
           COPY_PROPERTY_IF_CHANGED(filterURL);
      -    COPY_PROPERTY_IF_CHANGED(avatarPriority);
           COPY_PROPERTY_IF_CHANGED(keyLightMode);
           COPY_PROPERTY_IF_CHANGED(ambientLightMode);
           COPY_PROPERTY_IF_CHANGED(skyboxMode);
           COPY_PROPERTY_IF_CHANGED(hazeMode);
           COPY_PROPERTY_IF_CHANGED(bloomMode);
      +    COPY_PROPERTY_IF_CHANGED(avatarPriority);
       
           // Polyvox
           COPY_PROPERTY_IF_CHANGED(voxelVolumeSize);
      @@ -2778,12 +2798,12 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr
               ADD_PROPERTY_TO_MAP(PROP_FLYING_ALLOWED, FlyingAllowed, flyingAllowed, bool);
               ADD_PROPERTY_TO_MAP(PROP_GHOSTING_ALLOWED, GhostingAllowed, ghostingAllowed, bool);
               ADD_PROPERTY_TO_MAP(PROP_FILTER_URL, FilterURL, filterURL, QString);
      -        ADD_PROPERTY_TO_MAP(PROP_AVATAR_PRIORITY, AvatarPriority, avatarPriority, bool);
               ADD_PROPERTY_TO_MAP(PROP_KEY_LIGHT_MODE, KeyLightMode, keyLightMode, uint32_t);
               ADD_PROPERTY_TO_MAP(PROP_AMBIENT_LIGHT_MODE, AmbientLightMode, ambientLightMode, uint32_t);
               ADD_PROPERTY_TO_MAP(PROP_SKYBOX_MODE, SkyboxMode, skyboxMode, uint32_t);
               ADD_PROPERTY_TO_MAP(PROP_HAZE_MODE, HazeMode, hazeMode, uint32_t);
               ADD_PROPERTY_TO_MAP(PROP_BLOOM_MODE, BloomMode, bloomMode, uint32_t);
      +        ADD_PROPERTY_TO_MAP(PROP_AVATAR_PRIORITY, AvatarPriority, avatarPriority, uint32_t);
       
               // Polyvox
               ADD_PROPERTY_TO_MAP(PROP_VOXEL_VOLUME_SIZE, VoxelVolumeSize, voxelVolumeSize, vec3);
      @@ -3184,7 +3204,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy
                       APPEND_ENTITY_PROPERTY(PROP_SKYBOX_MODE, (uint32_t)properties.getSkyboxMode());
                       APPEND_ENTITY_PROPERTY(PROP_HAZE_MODE, (uint32_t)properties.getHazeMode());
                       APPEND_ENTITY_PROPERTY(PROP_BLOOM_MODE, (uint32_t)properties.getBloomMode());
      -                APPEND_ENTITY_PROPERTY(PROP_AVATAR_PRIORITY, properties.getAvatarPriority());
      +                APPEND_ENTITY_PROPERTY(PROP_AVATAR_PRIORITY, (uint32_t)properties.getAvatarPriority());
                   }
       
                   if (properties.getType() == EntityTypes::PolyVox) {
      @@ -3641,13 +3661,13 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
               READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_FLYING_ALLOWED, bool, setFlyingAllowed);
               READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_GHOSTING_ALLOWED, bool, setGhostingAllowed);
               READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_FILTER_URL, QString, setFilterURL);
      -        READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_AVATAR_PRIORITY, bool, setAvatarPriority);
       
               READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_KEY_LIGHT_MODE, uint32_t, setKeyLightMode);
               READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_AMBIENT_LIGHT_MODE, uint32_t, setAmbientLightMode);
               READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SKYBOX_MODE, uint32_t, setSkyboxMode);
               READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_HAZE_MODE, uint32_t, setHazeMode);
               READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BLOOM_MODE, uint32_t, setBloomMode);
      +        READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_AVATAR_PRIORITY, uint32_t, setAvatarPriority);
           }
       
           if (properties.getType() == EntityTypes::PolyVox) {
      @@ -4022,13 +4042,13 @@ void EntityItemProperties::markAllChanged() {
           _bloom.markAllChanged();
           _flyingAllowedChanged = true;
           _ghostingAllowedChanged = true;
      -    _avatarPriorityChanged = true;
           _filterURLChanged = true;
           _keyLightModeChanged = true;
           _ambientLightModeChanged = true;
           _skyboxModeChanged = true;
           _hazeModeChanged = true;
           _bloomModeChanged = true;
      +    _avatarPriorityChanged = true;
       
           // Polyvox
           _voxelVolumeSizeChanged = true;
      @@ -4608,9 +4628,6 @@ QList EntityItemProperties::listChangedProperties() {
           if (filterURLChanged()) {
               out += "filterURL";
           }
      -    if (avatarPriorityChanged()) {
      -        out += "avatarPriority";
      -    }
           if (keyLightModeChanged()) {
               out += "keyLightMode";
           }
      @@ -4626,6 +4643,9 @@ QList EntityItemProperties::listChangedProperties() {
           if (bloomModeChanged()) {
               out += "bloomMode";
           }
      +    if (avatarPriorityChanged()) {
      +        out += "avatarPriority";
      +    }
       
           // Polyvox
           if (voxelVolumeSizeChanged()) {
      diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h
      index ff5204efe2..75b2b2aec3 100644
      --- a/libraries/entities/src/EntityItemProperties.h
      +++ b/libraries/entities/src/EntityItemProperties.h
      @@ -315,12 +315,12 @@ public:
           DEFINE_PROPERTY(PROP_FLYING_ALLOWED, FlyingAllowed, flyingAllowed, bool, ZoneEntityItem::DEFAULT_FLYING_ALLOWED);
           DEFINE_PROPERTY(PROP_GHOSTING_ALLOWED, GhostingAllowed, ghostingAllowed, bool, ZoneEntityItem::DEFAULT_GHOSTING_ALLOWED);
           DEFINE_PROPERTY(PROP_FILTER_URL, FilterURL, filterURL, QString, ZoneEntityItem::DEFAULT_FILTER_URL);
      -    DEFINE_PROPERTY(PROP_AVATAR_PRIORITY, AvatarPriority, avatarPriority, bool, ZoneEntityItem::DEFAULT_AVATAR_PRIORITY);
           DEFINE_PROPERTY_REF_ENUM(PROP_KEY_LIGHT_MODE, KeyLightMode, keyLightMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT);
           DEFINE_PROPERTY_REF_ENUM(PROP_SKYBOX_MODE, SkyboxMode, skyboxMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT);
           DEFINE_PROPERTY_REF_ENUM(PROP_AMBIENT_LIGHT_MODE, AmbientLightMode, ambientLightMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT);
           DEFINE_PROPERTY_REF_ENUM(PROP_HAZE_MODE, HazeMode, hazeMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT);
           DEFINE_PROPERTY_REF_ENUM(PROP_BLOOM_MODE, BloomMode, bloomMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT);
      +    DEFINE_PROPERTY_REF_ENUM(PROP_AVATAR_PRIORITY, AvatarPriority, avatarPriority, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT);
       
           // Polyvox
           DEFINE_PROPERTY_REF(PROP_VOXEL_VOLUME_SIZE, VoxelVolumeSize, voxelVolumeSize, glm::vec3, PolyVoxEntityItem::DEFAULT_VOXEL_VOLUME_SIZE);
      diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp
      index 83619caa3c..a6b88ca7c2 100644
      --- a/libraries/entities/src/ZoneEntityItem.cpp
      +++ b/libraries/entities/src/ZoneEntityItem.cpp
      @@ -65,13 +65,13 @@ EntityItemProperties ZoneEntityItem::getProperties(const EntityPropertyFlags& de
           COPY_ENTITY_PROPERTY_TO_PROPERTIES(flyingAllowed, getFlyingAllowed);
           COPY_ENTITY_PROPERTY_TO_PROPERTIES(ghostingAllowed, getGhostingAllowed);
           COPY_ENTITY_PROPERTY_TO_PROPERTIES(filterURL, getFilterURL);
      -    COPY_ENTITY_PROPERTY_TO_PROPERTIES(avatarPriority, getAvatarPriority);
       
           COPY_ENTITY_PROPERTY_TO_PROPERTIES(keyLightMode, getKeyLightMode);
           COPY_ENTITY_PROPERTY_TO_PROPERTIES(ambientLightMode, getAmbientLightMode);
           COPY_ENTITY_PROPERTY_TO_PROPERTIES(skyboxMode, getSkyboxMode);
           COPY_ENTITY_PROPERTY_TO_PROPERTIES(hazeMode, getHazeMode);
           COPY_ENTITY_PROPERTY_TO_PROPERTIES(bloomMode, getBloomMode);
      +    COPY_ENTITY_PROPERTY_TO_PROPERTIES(avatarPriority, getAvatarPriority);
       
           return properties;
       }
      @@ -194,7 +194,7 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
           READ_ENTITY_PROPERTY(PROP_SKYBOX_MODE, uint32_t, setSkyboxMode);
           READ_ENTITY_PROPERTY(PROP_HAZE_MODE, uint32_t, setHazeMode);
           READ_ENTITY_PROPERTY(PROP_BLOOM_MODE, uint32_t, setBloomMode);
      -    READ_ENTITY_PROPERTY(PROP_AVATAR_PRIORITY, bool, setAvatarPriority);
      +    READ_ENTITY_PROPERTY(PROP_AVATAR_PRIORITY, uint32_t, setAvatarPriority);
       
           return bytesRead;
       }
      @@ -476,8 +476,10 @@ bool ZoneEntityItem::matchesJSONFilters(const QJsonObject& jsonFilters) const {
       
           static const QString AVATAR_PRIORITY_PROPERTY = "avatarPriority";
       
      -    if (jsonFilters.contains(AVATAR_PRIORITY_PROPERTY)) {
      -        return (jsonFilters[AVATAR_PRIORITY_PROPERTY].toBool() == _avatarPriority);
      +    // If set ignore only priority-inherit zones:
      +    if (jsonFilters.contains(AVATAR_PRIORITY_PROPERTY) && jsonFilters[AVATAR_PRIORITY_PROPERTY].toBool()
      +        && _avatarPriority != COMPONENT_MODE_INHERIT) {
      +        return true;
           }
       
           // Chain to base:
      diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h
      index a3e668b6f6..1afcfcba47 100644
      --- a/libraries/entities/src/ZoneEntityItem.h
      +++ b/libraries/entities/src/ZoneEntityItem.h
      @@ -98,8 +98,8 @@ public:
           QString getFilterURL() const;
           void setFilterURL(const QString url); 
       
      -    bool getAvatarPriority() const { return _avatarPriority; }
      -    void setAvatarPriority(bool value) { _avatarPriority = value; }
      +    uint32_t getAvatarPriority() const { return _avatarPriority; }
      +    void setAvatarPriority(uint32_t value) { _avatarPriority = value; }
       
           bool keyLightPropertiesChanged() const { return _keyLightPropertiesChanged; }
           bool ambientLightPropertiesChanged() const { return _ambientLightPropertiesChanged; }
      @@ -130,7 +130,6 @@ public:
           static const bool DEFAULT_FLYING_ALLOWED;
           static const bool DEFAULT_GHOSTING_ALLOWED;
           static const QString DEFAULT_FILTER_URL;
      -    static const bool DEFAULT_AVATAR_PRIORITY = false;
       
       protected:
           KeyLightPropertyGroup _keyLightProperties;
      @@ -156,7 +155,7 @@ protected:
           QString _filterURL { DEFAULT_FILTER_URL };
       
           // Avatar-updates priority
      -    bool _avatarPriority { DEFAULT_AVATAR_PRIORITY };
      +    uint32_t _avatarPriority { COMPONENT_MODE_INHERIT };
       
           // Dirty flags turn true when either keylight properties is changing values.
           bool _keyLightPropertiesChanged { false };
      diff --git a/scripts/system/assets/data/createAppTooltips.json b/scripts/system/assets/data/createAppTooltips.json
      index 7e5f2c8659..7201cdecad 100644
      --- a/scripts/system/assets/data/createAppTooltips.json
      +++ b/scripts/system/assets/data/createAppTooltips.json
      @@ -135,7 +135,7 @@
               "tooltip": "The radius of bloom. The higher the value, the larger the bloom."
           },
           "avatarPriority": {
      -        "tooltip":  "Avatars in this zone will have a higher update priority."
      +        "tooltip":  "Alter Avatars' update priorities."
           },
           "modelURL": {
               "tooltip": "A mesh model from an FBX or OBJ file."
      diff --git a/scripts/system/edit.js b/scripts/system/edit.js
      index 67a789c266..2c3785217c 100644
      --- a/scripts/system/edit.js
      +++ b/scripts/system/edit.js
      @@ -383,7 +383,7 @@ const DEFAULT_ENTITY_PROPERTIES = {
               },
               shapeType: "box",
               bloomMode: "inherit",
      -        avatarPriority: false
      +        avatarPriority: "inherit"
           },
           Model: {
               collisionShape: "none",
      diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js
      index 404ded6ae2..b78aee4ad5 100644
      --- a/scripts/system/html/js/entityProperties.js
      +++ b/scripts/system/html/js/entityProperties.js
      @@ -430,7 +430,8 @@ const GROUPS = [
                   },
                   {
                       label: "Avatar Priority",
      -                type: "bool",
      +                type: "dropdown",
      +                options: { inherit: "Inherit", crowd: "Crowd", hero: "Hero" },
                       propertyID: "avatarPriority",
                   },
       
      
      From 12ffa41984730e9ac5ae24f9954368fd152dabb2 Mon Sep 17 00:00:00 2001
      From: r3tk0n 
      Date: Sat, 2 Mar 2019 13:55:06 -0800
      Subject: [PATCH 299/474] Prevent HUD and Web Surface lasers from interrupting
       FarGrab.
      
      ---
       .../controllerModules/hudOverlayPointer.js    | 16 +++++++++++++-
       .../controllerModules/webSurfaceLaserInput.js | 21 ++++++++++++++++++-
       2 files changed, 35 insertions(+), 2 deletions(-)
      
      diff --git a/scripts/system/controllers/controllerModules/hudOverlayPointer.js b/scripts/system/controllers/controllerModules/hudOverlayPointer.js
      index efbca66d72..f7d5b5a2dd 100644
      --- a/scripts/system/controllers/controllerModules/hudOverlayPointer.js
      +++ b/scripts/system/controllers/controllerModules/hudOverlayPointer.js
      @@ -30,6 +30,20 @@
                   100,
                   makeLaserParams((this.hand + HUD_LASER_OFFSET), false));
       
      +        this.getFarGrab = function () {
      +            return getEnabledModuleByName(this.hand === RIGHT_HAND ? ("RightFarGrabEntity") : ("LeftFarGrabEntity"));
      +        }
      +
      +        this.farGrabActive = function () {
      +            var farGrab = this.getFarGrab();
      +            // farGrab will be null if module isn't loaded.
      +            if (farGrab) {
      +                return farGrab.targetIsNull();
      +            } else {
      +                return false;
      +            }
      +        };
      +
               this.getOtherHandController = function() {
                   return (this.hand === RIGHT_HAND) ? Controller.Standard.LeftHand : Controller.Standard.RightHand;
               };
      @@ -79,7 +93,7 @@
       
               this.isReady = function (controllerData) {
                   var otherModuleRunning = this.getOtherModule().running;
      -            if (!otherModuleRunning && HMD.active) {
      +            if (!otherModuleRunning && HMD.active && !this.farGrabActive()) {
                       if (this.processLaser(controllerData)) {
                           this.running = true;
                           return ControllerDispatcherUtils.makeRunningValues(true, [], []);
      diff --git a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js
      index ec35dfe081..4f21b44533 100644
      --- a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js
      +++ b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js
      @@ -37,6 +37,20 @@ Script.include("/~/system/libraries/controllers.js");
                   100,
                   makeLaserParams(hand, true));
       
      +        this.getFarGrab = function () {
      +            return getEnabledModuleByName(this.hand === RIGHT_HAND ? ("RightFarGrabEntity") : ("LeftFarGrabEntity"));
      +        };
      +
      +        this.farGrabActive = function () {
      +            var farGrab = this.getFarGrab();
      +            // farGrab will be null if module isn't loaded.
      +            if (farGrab) {
      +                return farGrab.targetIsNull();
      +            } else {
      +                return false;
      +            }
      +        };
      +
               this.grabModuleWantsNearbyOverlay = function(controllerData) {
                   if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE || controllerData.secondaryValues[this.hand] > BUMPER_ON_VALUE) {
                       var nearGrabName = this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay";
      @@ -184,7 +198,12 @@ Script.include("/~/system/libraries/controllers.js");
       
               this.dominantHandOverride = false;
       
      -        this.isReady = function(controllerData) {
      +        this.isReady = function (controllerData) {
      +            // Trivial rejection for when FarGrab is active.
      +            if (this.farGrabActive()) {
      +                return makeRunningValues(false, [], []);
      +            }
      +
                   var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE &&
                                          controllerData.triggerValues[this.otherHand] <= TRIGGER_OFF_VALUE;
                   var type = this.getInteractableType(controllerData, isTriggerPressed, false);
      
      From 708632ee82ce326c25dc7ee73e9b83fa63998673 Mon Sep 17 00:00:00 2001
      From: SamGondelman 
      Date: Mon, 4 Mar 2019 10:00:26 -0800
      Subject: [PATCH 300/474] possible fix for model crash
      
      ---
       libraries/render-utils/src/Model.cpp | 18 +++++++++++-------
       1 file changed, 11 insertions(+), 7 deletions(-)
      
      diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp
      index 9bb3f72b31..683c517a15 100644
      --- a/libraries/render-utils/src/Model.cpp
      +++ b/libraries/render-utils/src/Model.cpp
      @@ -1346,14 +1346,18 @@ void Model::updateRig(float deltaTime, glm::mat4 parentTransform) {
       }
       
       void Model::computeMeshPartLocalBounds() {
      -    for (auto& part : _modelMeshRenderItems) {
      -        const Model::MeshState& state = _meshStates.at(part->_meshIndex);
      -        if (_useDualQuaternionSkinning) {
      -            part->computeAdjustedLocalBound(state.clusterDualQuaternions);
      -        } else {
      -            part->computeAdjustedLocalBound(state.clusterMatrices);
      -        }
      +    render::Transaction transaction;
      +    for (auto renderItem : _modelMeshRenderItemIDs) {
      +        transaction.updateItem(renderItem, [&](ModelMeshPartPayload& data) {
      +            const Model::MeshState& state = _meshStates.at(data._meshIndex);
      +            if (_useDualQuaternionSkinning) {
      +                data.computeAdjustedLocalBound(state.clusterDualQuaternions);
      +            } else {
      +                data.computeAdjustedLocalBound(state.clusterMatrices);
      +            }
      +        });
           }
      +    AbstractViewStateInterface::instance()->getMain3DScene()->enqueueTransaction(transaction);
       }
       
       // virtual
      
      From a02693381966c82f7f875ce02774aaba71a3a621 Mon Sep 17 00:00:00 2001
      From: danteruiz 
      Date: Mon, 4 Mar 2019 10:56:14 -0800
      Subject: [PATCH 301/474] keyboard handle scaling
      
      ---
       interface/src/ui/Keyboard.cpp | 6 ++++++
       1 file changed, 6 insertions(+)
      
      diff --git a/interface/src/ui/Keyboard.cpp b/interface/src/ui/Keyboard.cpp
      index d344e27d54..252d06bb6e 100644
      --- a/interface/src/ui/Keyboard.cpp
      +++ b/interface/src/ui/Keyboard.cpp
      @@ -373,6 +373,12 @@ void Keyboard::raiseKeyboardAnchor(bool raise) const {
       void Keyboard::scaleKeyboard(float sensorToWorldScale) {
           auto entityScriptingInterface = DependencyManager::get();
       
      +    {
      +        EntityItemProperties properties;
      +        properties.setDimensions(_anchor.originalDimensions * sensorToWorldScale);
      +        entityScriptingInterface->editEntity(_anchor.entityID, properties);
      +    }
      +
           {
               EntityItemProperties properties;
               properties.setLocalPosition(_backPlate.localPosition * sensorToWorldScale);
      
      From 18325ef5e3771f8752f2b541a8e8455dd85e99e1 Mon Sep 17 00:00:00 2001
      From: Oren Hurvitz 
      Date: Mon, 16 Apr 2018 15:26:06 +0300
      Subject: [PATCH 302/474] Save the "Mute Microphone" setting
      
      ---
       interface/src/scripting/Audio.cpp | 12 ++++++++++++
       1 file changed, 12 insertions(+)
      
      diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp
      index 2c4c29ff65..b4e3d7913b 100644
      --- a/interface/src/scripting/Audio.cpp
      +++ b/interface/src/scripting/Audio.cpp
      @@ -25,6 +25,8 @@ QString Audio::DESKTOP { "Desktop" };
       QString Audio::HMD { "VR" };
       
       Setting::Handle enableNoiseReductionSetting { QStringList { Audio::AUDIO, "NoiseReduction" }, true };
      +Setting::Handle mutedSetting { QStringList{ Audio::AUDIO, "MuteMicrophone" }, false };
      +
       
       float Audio::loudnessToLevel(float loudness) {
           float level = loudness * (1/32768.0f);  // level in [0, 1]
      @@ -42,6 +44,7 @@ Audio::Audio() : _devices(_contextIsHMD) {
           connect(this, &Audio::contextChanged, &_devices, &AudioDevices::onContextChanged);
           enableNoiseReduction(enableNoiseReductionSetting.get());
           onContextChanged();
      +    setMuted(mutedSetting.get());
       }
       
       bool Audio::startRecording(const QString& filepath) {
      @@ -89,6 +92,15 @@ bool Audio::noiseReductionEnabled() const {
           });
       }
       
      +void Audio::onMutedChanged() {
      +    bool isMuted = DependencyManager::get()->isMuted();
      +    if (_isMuted != isMuted) {
      +        _isMuted = isMuted;
      +        mutedSetting.set(_isMuted);
      +        emit mutedChanged(_isMuted);
      +    }
      +}
      +
       void Audio::enableNoiseReduction(bool enable) {
           bool changed = false;
           withWriteLock([&] {
      
      From 3f523617535a991463334977b1c4c164dbcfb17d Mon Sep 17 00:00:00 2001
      From: Seth Alves 
      Date: Sun, 17 Feb 2019 14:21:23 -0800
      Subject: [PATCH 303/474] rework audioMuteOverlay.js
      
      ---
       interface/src/scripting/Audio.cpp  |  10 +--
       scripts/defaultScripts.js          |   3 +-
       scripts/system/audioMuteOverlay.js | 140 +++++++++++++----------------
       3 files changed, 63 insertions(+), 90 deletions(-)
      
      diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp
      index b4e3d7913b..bb40f69b0b 100644
      --- a/interface/src/scripting/Audio.cpp
      +++ b/interface/src/scripting/Audio.cpp
      @@ -76,6 +76,7 @@ void Audio::setMuted(bool isMuted) {
           withWriteLock([&] {
               if (_isMuted != isMuted) {
                   _isMuted = isMuted;
      +            mutedSetting.set(_isMuted);
                   auto client = DependencyManager::get().data();
                   QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false));
                   changed = true;
      @@ -92,15 +93,6 @@ bool Audio::noiseReductionEnabled() const {
           });
       }
       
      -void Audio::onMutedChanged() {
      -    bool isMuted = DependencyManager::get()->isMuted();
      -    if (_isMuted != isMuted) {
      -        _isMuted = isMuted;
      -        mutedSetting.set(_isMuted);
      -        emit mutedChanged(_isMuted);
      -    }
      -}
      -
       void Audio::enableNoiseReduction(bool enable) {
           bool changed = false;
           withWriteLock([&] {
      diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js
      index bd7e79dffc..e392680df9 100644
      --- a/scripts/defaultScripts.js
      +++ b/scripts/defaultScripts.js
      @@ -32,7 +32,8 @@ var DEFAULT_SCRIPTS_COMBINED = [
           "system/firstPersonHMD.js",
           "system/tablet-ui/tabletUI.js",
           "system/emote.js",
      -    "system/miniTablet.js"
      +    "system/miniTablet.js",
      +    "system/audioMuteOverlay.js"
       ];
       var DEFAULT_SCRIPTS_SEPARATE = [
           "system/controllers/controllerScripts.js",
      diff --git a/scripts/system/audioMuteOverlay.js b/scripts/system/audioMuteOverlay.js
      index 731d62017d..14ac96c8c6 100644
      --- a/scripts/system/audioMuteOverlay.js
      +++ b/scripts/system/audioMuteOverlay.js
      @@ -1,104 +1,84 @@
      -"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 }] */
       //
       // audioMuteOverlay.js
       //
       // client script that creates an overlay to provide mute feedback
       //
       // Created by Triplelexx on 17/03/09
      +// Reworked by Seth Alves on 2019-2-17
       // 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
       //
       
      +"use strict";
      +
      +/* global Audio, Script, Overlays, Quat, MyAvatar */
      +
       (function() { // BEGIN LOCAL_SCOPE
      -    var utilsPath = Script.resolvePath('../developer/libraries/utils.js');
      -    Script.include(utilsPath);
       
      -    var TWEEN_SPEED = 0.025;
      -    var MIX_AMOUNT = 0.25;
      +   var lastInputLoudness = 0.0;
      +   var sampleRate = 8.0; // Hz
      +   var attackTC =  Math.exp(-1.0 / (sampleRate * 0.500)) // 500 milliseconds attack
      +   var releaseTC =  Math.exp(-1.0 / (sampleRate * 1.000)) // 1000 milliseconds release
      +   var holdReset = 2.0 * sampleRate; // 2 seconds hold
      +   var holdCount = 0;
      +   var warningOverlayID = null;
       
      -    var overlayPosition = Vec3.ZERO;
      -    var tweenPosition = 0;
      -    var startColor = {
      -        red: 170,
      -        green: 170,
      -        blue: 170
      -    };
      -    var endColor = {
      -        red: 255,
      -        green: 0,
      -        blue: 0
      -    };
      -    var overlayID;
      +   function showWarning() {
      +       if (warningOverlayID) {
      +           return;
      +       }
      +       warningOverlayID = Overlays.addOverlay("text3d", {
      +           name: "Muted-Warning",
      +           localPosition: { x: 0.2, y: -0.35, z: -1.0 },
      +           localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 0.0, z: 0.0, w: 1.0 }),
      +           text: "Warning: you are muted",
      +           textAlpha: 1,
      +           color: { red: 226, green: 51, blue: 77 },
      +           backgroundAlpha: 0,
      +           lineHeight: 0.042,
      +           visible: true,
      +           ignoreRayIntersection: true,
      +           drawInFront: true,
      +           grabbable: false,
      +           parentID: MyAvatar.SELF_ID,
      +           parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX")
      +       });
      +   };
       
      -    Script.update.connect(update);
      -    Script.scriptEnding.connect(cleanup);
      +   function hideWarning() {
      +       if (!warningOverlayID) {
      +           return;
      +       }
      +       Overlays.deleteOverlay(warningOverlayID);
      +       warningOverlayID = null;
      +   }
       
      -    function update(dt) {
      -        if (!Audio.muted) {
      -            if (hasOverlay()) {
      -                deleteOverlay();
      -            }
      -        } else if (!hasOverlay()) {
      -            createOverlay();
      -        } else {
      -            updateOverlay();
      -        }
      -    }
      +   function cleanup() {
      +       Overlays.deleteOverlay(warningOverlayID);
      +   }
       
      -    function getOffsetPosition() {
      -        return Vec3.sum(Camera.position, Quat.getFront(Camera.orientation));
      -    }
      +   Script.scriptEnding.connect(cleanup);
       
      -    function createOverlay() {
      -        overlayPosition = getOffsetPosition();
      -        overlayID = Overlays.addOverlay("sphere", {
      -            position: overlayPosition,
      -            rotation: Camera.orientation,
      -            alpha: 0.9,
      -            dimensions: 0.1,
      -            solid: true,
      -            ignoreRayIntersection: true
      -        });
      -    }
      +   Script.setInterval(function() {
       
      -    function hasOverlay() {
      -       return Overlays.getProperty(overlayID, "position") !== undefined;
      -    }
      +       var inputLoudness = Audio.inputLevel;
      +       var tc = (inputLoudness > lastInputLoudness) ? attackTC : releaseTC;
      +       inputLoudness += tc * (lastInputLoudness - inputLoudness);
      +       lastInputLoudness = inputLoudness;
       
      -    function updateOverlay() {
      -        // increase by TWEEN_SPEED until completion
      -        if (tweenPosition < 1) {
      -            tweenPosition += TWEEN_SPEED;
      -        } else {
      -            // after tween completion reset to zero and flip values to ping pong 
      -            tweenPosition = 0;
      -            for (var component in startColor) {
      -                var storedColor = startColor[component];
      -                startColor[component] = endColor[component];
      -                endColor[component] = storedColor;
      -            }
      -        }
      -        // mix previous position with new and mix colors
      -        overlayPosition = Vec3.mix(overlayPosition, getOffsetPosition(), MIX_AMOUNT);
      -        Overlays.editOverlay(overlayID, {
      -            color: colorMix(startColor, endColor, easeIn(tweenPosition)),
      -            position: overlayPosition,
      -            rotation: Camera.orientation
      -        });
      -    }
      +       if (Audio.muted && inputLoudness > 0.3) {
      +           holdCount = holdReset;
      +       } else {
      +           holdCount = Math.max(holdCount - 1, 0);
      +       }
       
      -    function deleteOverlay() {
      -        Overlays.deleteOverlay(overlayID);
      -    }
      +       if (holdCount > 0) {
      +           showWarning();
      +       } else {
      +           hideWarning();
      +       }
      +   }, 1000.0 / sampleRate);
       
      -    function cleanup() {
      -        deleteOverlay();
      -        Audio.muted.disconnect(onMuteToggled);
      -        Script.update.disconnect(update);
      -    }
       }()); // END LOCAL_SCOPE
      
      From 38256df0f24cd710c2e8e32683fa73de9081361f Mon Sep 17 00:00:00 2001
      From: Seth Alves 
      Date: Tue, 19 Feb 2019 09:32:41 -0800
      Subject: [PATCH 304/474] add a way to disble muted warning from audio panel.
       fix positioning of warning.  hide warning when removing timer.
      
      ---
       interface/resources/qml/hifi/audio/Audio.qml |  13 ++
       interface/src/scripting/Audio.cpp            |  25 ++++
       interface/src/scripting/Audio.h              |  14 +-
       libraries/audio-client/src/AudioClient.cpp   |   8 +
       libraries/audio-client/src/AudioClient.h     |   5 +
       scripts/system/audioMuteOverlay.js           | 146 ++++++++++++-------
       6 files changed, 155 insertions(+), 56 deletions(-)
      
      diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml
      index c8dd83cd62..34ae64aee8 100644
      --- a/interface/resources/qml/hifi/audio/Audio.qml
      +++ b/interface/resources/qml/hifi/audio/Audio.qml
      @@ -159,6 +159,19 @@ Rectangle {
                           onXChanged: rightMostInputLevelPos = x + width
                       }
                   }
      +
      +            RowLayout {
      +                spacing: muteMic.spacing*2;
      +                AudioControls.CheckBox {
      +                    spacing: muteMic.spacing
      +                    text: qsTr("Warn when muted");
      +                    checked: AudioScriptingInterface.warnWhenMuted;
      +                    onClicked: {
      +                        AudioScriptingInterface.warnWhenMuted = checked;
      +                        checked = Qt.binding(function() { return AudioScriptingInterface.warnWhenMuted; }); // restore binding
      +                    }
      +                }
      +            }
               }
       
               Separator {}
      diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp
      index bb40f69b0b..4a4b3c146b 100644
      --- a/interface/src/scripting/Audio.cpp
      +++ b/interface/src/scripting/Audio.cpp
      @@ -25,6 +25,7 @@ QString Audio::DESKTOP { "Desktop" };
       QString Audio::HMD { "VR" };
       
       Setting::Handle enableNoiseReductionSetting { QStringList { Audio::AUDIO, "NoiseReduction" }, true };
      +Setting::Handle enableWarnWhenMutedSetting { QStringList { Audio::AUDIO, "WarnWhenMuted" }, true };
       Setting::Handle mutedSetting { QStringList{ Audio::AUDIO, "MuteMicrophone" }, false };
       
       
      @@ -39,10 +40,12 @@ Audio::Audio() : _devices(_contextIsHMD) {
           auto client = DependencyManager::get().data();
           connect(client, &AudioClient::muteToggled, this, &Audio::setMuted);
           connect(client, &AudioClient::noiseReductionChanged, this, &Audio::enableNoiseReduction);
      +    connect(client, &AudioClient::warnWhenMutedChanged, this, &Audio::enableWarnWhenMuted);
           connect(client, &AudioClient::inputLoudnessChanged, this, &Audio::onInputLoudnessChanged);
           connect(client, &AudioClient::inputVolumeChanged, this, &Audio::setInputVolume);
           connect(this, &Audio::contextChanged, &_devices, &AudioDevices::onContextChanged);
           enableNoiseReduction(enableNoiseReductionSetting.get());
      +    enableWarnWhenMuted(enableWarnWhenMutedSetting.get());
           onContextChanged();
           setMuted(mutedSetting.get());
       }
      @@ -109,6 +112,28 @@ void Audio::enableNoiseReduction(bool enable) {
           }
       }
       
      +bool Audio::warnWhenMutedEnabled() const {
      +    return resultWithReadLock([&] {
      +        return _enableWarnWhenMuted;
      +    });
      +}
      +
      +void Audio::enableWarnWhenMuted(bool enable) {
      +    bool changed = false;
      +    withWriteLock([&] {
      +        if (_enableWarnWhenMuted != enable) {
      +            _enableWarnWhenMuted = enable;
      +            auto client = DependencyManager::get().data();
      +            QMetaObject::invokeMethod(client, "setWarnWhenMuted", Q_ARG(bool, enable), Q_ARG(bool, false));
      +            enableWarnWhenMutedSetting.set(enable);
      +            changed = true;
      +        }
      +    });
      +    if (changed) {
      +        emit warnWhenMutedChanged(enable);
      +    }
      +}
      +
       float Audio::getInputVolume() const {
           return resultWithReadLock([&] {
               return _inputVolume;
      diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h
      index e4dcba9130..7e216eb0b2 100644
      --- a/interface/src/scripting/Audio.h
      +++ b/interface/src/scripting/Audio.h
      @@ -58,6 +58,7 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable {
       
           Q_PROPERTY(bool muted READ isMuted WRITE setMuted NOTIFY mutedChanged)
           Q_PROPERTY(bool noiseReduction READ noiseReductionEnabled WRITE enableNoiseReduction NOTIFY noiseReductionChanged)
      +    Q_PROPERTY(bool warnWhenMuted READ warnWhenMutedEnabled WRITE enableWarnWhenMuted NOTIFY warnWhenMutedChanged)
           Q_PROPERTY(float inputVolume READ getInputVolume WRITE setInputVolume NOTIFY inputVolumeChanged)
           Q_PROPERTY(float inputLevel READ getInputLevel NOTIFY inputLevelChanged)
           Q_PROPERTY(bool clipping READ isClipping NOTIFY clippingChanged)
      @@ -75,6 +76,7 @@ public:
       
           bool isMuted() const;
           bool noiseReductionEnabled() const;
      +    bool warnWhenMutedEnabled() const;
           float getInputVolume() const;
           float getInputLevel() const;
           bool isClipping() const;
      @@ -192,7 +194,7 @@ signals:
            * });
            */
           void mutedChanged(bool isMuted);
      -    
      +
           /**jsdoc
            * Triggered when the audio input noise reduction is enabled or disabled.
            * @function Audio.noiseReductionChanged
      @@ -201,6 +203,14 @@ signals:
            */
           void noiseReductionChanged(bool isEnabled);
       
      +    /**jsdoc
      +     * Triggered when "warn when muted" is enabled or disabled.
      +     * @function Audio.warnWhenMutedChanged
      +     * @param {boolean} isEnabled - true if "warn when muted" is enabled, otherwise false.
      +     * @returns {Signal}
      +     */
      +    void warnWhenMutedChanged(bool isEnabled);
      +
           /**jsdoc
            * Triggered when the input audio volume changes.
            * @function Audio.inputVolumeChanged
      @@ -248,6 +258,7 @@ public slots:
       private slots:
           void setMuted(bool muted);
           void enableNoiseReduction(bool enable);
      +    void enableWarnWhenMuted(bool enable);
           void setInputVolume(float volume);
           void onInputLoudnessChanged(float loudness, bool isClipping);
       
      @@ -262,6 +273,7 @@ private:
           bool _isClipping { false };
           bool _isMuted { false };
           bool _enableNoiseReduction { true };  // Match default value of AudioClient::_isNoiseGateEnabled.
      +    bool _enableWarnWhenMuted { true };
           bool _contextIsHMD { false };
           AudioDevices* getDevices() { return &_devices; }
           AudioDevices _devices;
      diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp
      index 8c50a195ee..1c10d24f23 100644
      --- a/libraries/audio-client/src/AudioClient.cpp
      +++ b/libraries/audio-client/src/AudioClient.cpp
      @@ -1531,6 +1531,14 @@ void AudioClient::setNoiseReduction(bool enable, bool emitSignal) {
           }
       }
       
      +void AudioClient::setWarnWhenMuted(bool enable, bool emitSignal) {
      +    if (_warnWhenMuted != enable) {
      +        _warnWhenMuted = enable;
      +        if (emitSignal) {
      +            emit warnWhenMutedChanged(_warnWhenMuted);
      +        }
      +    }
      +}
       
       bool AudioClient::setIsStereoInput(bool isStereoInput) {
           bool stereoInputChanged = false;
      diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h
      index 29036b7c71..6d3483b0f8 100644
      --- a/libraries/audio-client/src/AudioClient.h
      +++ b/libraries/audio-client/src/AudioClient.h
      @@ -210,6 +210,9 @@ public slots:
           void setNoiseReduction(bool isNoiseGateEnabled, bool emitSignal = true);
           bool isNoiseReductionEnabled() const { return _isNoiseGateEnabled; }
       
      +    void setWarnWhenMuted(bool isNoiseGateEnabled, bool emitSignal = true);
      +    bool isWarnWhenMutedEnabled() const { return _warnWhenMuted; }
      +
           bool getLocalEcho() { return _shouldEchoLocally; }
           void setLocalEcho(bool localEcho) { _shouldEchoLocally = localEcho; }
           void toggleLocalEcho() { _shouldEchoLocally = !_shouldEchoLocally; }
      @@ -246,6 +249,7 @@ signals:
           void inputVolumeChanged(float volume);
           void muteToggled(bool muted);
           void noiseReductionChanged(bool noiseReductionEnabled);
      +    void warnWhenMutedChanged(bool warnWhenMutedEnabled);
           void mutedByMixer();
           void inputReceived(const QByteArray& inputSamples);
           void inputLoudnessChanged(float loudness, bool isClipping);
      @@ -365,6 +369,7 @@ private:
           bool _shouldEchoLocally;
           bool _shouldEchoToServer;
           bool _isNoiseGateEnabled;
      +    bool _warnWhenMuted;
       
           bool _reverb;
           AudioEffectOptions _scriptReverbOptions;
      diff --git a/scripts/system/audioMuteOverlay.js b/scripts/system/audioMuteOverlay.js
      index 14ac96c8c6..d759b7d885 100644
      --- a/scripts/system/audioMuteOverlay.js
      +++ b/scripts/system/audioMuteOverlay.js
      @@ -17,68 +17,104 @@
       
       (function() { // BEGIN LOCAL_SCOPE
       
      -   var lastInputLoudness = 0.0;
      -   var sampleRate = 8.0; // Hz
      -   var attackTC =  Math.exp(-1.0 / (sampleRate * 0.500)) // 500 milliseconds attack
      -   var releaseTC =  Math.exp(-1.0 / (sampleRate * 1.000)) // 1000 milliseconds release
      -   var holdReset = 2.0 * sampleRate; // 2 seconds hold
      -   var holdCount = 0;
      -   var warningOverlayID = null;
      +    var lastInputLoudness = 0.0;
      +    var sampleRate = 8.0; // Hz
      +    var attackTC =  Math.exp(-1.0 / (sampleRate * 0.500)); // 500 milliseconds attack
      +    var releaseTC =  Math.exp(-1.0 / (sampleRate * 1.000)); // 1000 milliseconds release
      +    var holdReset = 2.0 * sampleRate; // 2 seconds hold
      +    var holdCount = 0;
      +    var warningOverlayID = null;
      +    var pollInterval = null;
      +    var warningText = "Muted";
      +    var textDimensions = { x: 100, y: 50 };
       
      -   function showWarning() {
      -       if (warningOverlayID) {
      -           return;
      -       }
      -       warningOverlayID = Overlays.addOverlay("text3d", {
      -           name: "Muted-Warning",
      -           localPosition: { x: 0.2, y: -0.35, z: -1.0 },
      -           localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 0.0, z: 0.0, w: 1.0 }),
      -           text: "Warning: you are muted",
      -           textAlpha: 1,
      -           color: { red: 226, green: 51, blue: 77 },
      -           backgroundAlpha: 0,
      -           lineHeight: 0.042,
      -           visible: true,
      -           ignoreRayIntersection: true,
      -           drawInFront: true,
      -           grabbable: false,
      -           parentID: MyAvatar.SELF_ID,
      -           parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX")
      -       });
      -   };
      +    function showWarning() {
      +        if (warningOverlayID) {
      +            return;
      +        }
       
      -   function hideWarning() {
      -       if (!warningOverlayID) {
      -           return;
      -       }
      -       Overlays.deleteOverlay(warningOverlayID);
      -       warningOverlayID = null;
      -   }
      +        var windowWidth;
      +        var windowHeight;
      +        if (HMD.active) {
      +            var viewportDimension = Controller.getViewportDimensions();
      +            windowWidth = viewportDimension.x;
      +            windowHeight = viewportDimension.y;
      +        } else {
      +            windowWidth = Window.innerWidth;
      +            windowHeight = Window.innerHeight;
      +        }
       
      -   function cleanup() {
      -       Overlays.deleteOverlay(warningOverlayID);
      -   }
      +        warningOverlayID = Overlays.addOverlay("text", {
      +            name: "Muted-Warning",
      +            font: { size: 36 },
      +            text: warningText,
      +            x: windowWidth / 2 - textDimensions.x / 2,
      +            y: windowHeight / 2 - textDimensions.y / 2,
      +            width: textDimensions.x,
      +            height: textDimensions.y,
      +            textColor: { red: 226, green: 51, blue: 77 },
      +            backgroundAlpha: 0,
      +            visible: true
      +        });
      +    }
       
      -   Script.scriptEnding.connect(cleanup);
      +    function hideWarning() {
      +        if (!warningOverlayID) {
      +            return;
      +        }
      +        Overlays.deleteOverlay(warningOverlayID);
      +        warningOverlayID = null;
      +    }
       
      -   Script.setInterval(function() {
      +    function startPoll() {
      +        if (pollInterval) {
      +            return;
      +        }
      +        pollInterval = Script.setInterval(function() {
      +            var inputLoudness = Audio.inputLevel;
      +            var tc = (inputLoudness > lastInputLoudness) ? attackTC : releaseTC;
      +            inputLoudness += tc * (lastInputLoudness - inputLoudness);
      +            lastInputLoudness = inputLoudness;
       
      -       var inputLoudness = Audio.inputLevel;
      -       var tc = (inputLoudness > lastInputLoudness) ? attackTC : releaseTC;
      -       inputLoudness += tc * (lastInputLoudness - inputLoudness);
      -       lastInputLoudness = inputLoudness;
      +            if (inputLoudness > 0.1) {
      +                holdCount = holdReset;
      +            } else {
      +                holdCount = Math.max(holdCount - 1, 0);
      +            }
       
      -       if (Audio.muted && inputLoudness > 0.3) {
      -           holdCount = holdReset;
      -       } else {
      -           holdCount = Math.max(holdCount - 1, 0);
      -       }
      +            if (holdCount > 0) {
      +                showWarning();
      +            } else {
      +                hideWarning();
      +            }
      +        }, 1000.0 / sampleRate);
      +    }
       
      -       if (holdCount > 0) {
      -           showWarning();
      -       } else {
      -           hideWarning();
      -       }
      -   }, 1000.0 / sampleRate);
      +    function stopPoll() {
      +        if (!pollInterval) {
      +            return;
      +        }
      +        Script.clearInterval(pollInterval);
      +        pollInterval = null;
      +        hideWarning();
      +    }
      +
      +    function startOrStopPoll() {
      +        if (Audio.warnWhenMuted && Audio.muted) {
      +            startPoll();
      +        } else {
      +            stopPoll();
      +        }
      +    }
      +
      +    function cleanup() {
      +        stopPoll();
      +    }
      +
      +    Script.scriptEnding.connect(cleanup);
      +
      +    startOrStopPoll();
      +    Audio.mutedChanged.connect(startOrStopPoll);
      +    Audio.warnWhenMutedChanged.connect(startOrStopPoll);
       
       }()); // END LOCAL_SCOPE
      
      From 76aa6fb1b9a7416c6c8858c7fc9537a48ff6d14a Mon Sep 17 00:00:00 2001
      From: Seth Alves 
      Date: Wed, 20 Feb 2019 12:53:43 -0800
      Subject: [PATCH 305/474] keep muted warning in center of view for HMD
      
      ---
       scripts/system/audioMuteOverlay.js | 51 ++++++++++++++++++------------
       1 file changed, 30 insertions(+), 21 deletions(-)
      
      diff --git a/scripts/system/audioMuteOverlay.js b/scripts/system/audioMuteOverlay.js
      index d759b7d885..65793d1d87 100644
      --- a/scripts/system/audioMuteOverlay.js
      +++ b/scripts/system/audioMuteOverlay.js
      @@ -26,36 +26,45 @@
           var warningOverlayID = null;
           var pollInterval = null;
           var warningText = "Muted";
      -    var textDimensions = { x: 100, y: 50 };
       
           function showWarning() {
               if (warningOverlayID) {
                   return;
               }
       
      -        var windowWidth;
      -        var windowHeight;
               if (HMD.active) {
      -            var viewportDimension = Controller.getViewportDimensions();
      -            windowWidth = viewportDimension.x;
      -            windowHeight = viewportDimension.y;
      +            warningOverlayID = Overlays.addOverlay("text3d", {
      +                name: "Muted-Warning",
      +                localPosition: { x: 0, y: 0, z: -1.0 },
      +                localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 0.0, z: 0.0, w: 1.0 }),
      +                text: warningText,
      +                textAlpha: 1,
      +                textColor: { red: 226, green: 51, blue: 77 },
      +                backgroundAlpha: 0,
      +                lineHeight: 0.042,
      +                dimensions: { x: 0.11, y: 0.05 },
      +                visible: true,
      +                ignoreRayIntersection: true,
      +                drawInFront: true,
      +                grabbable: false,
      +                parentID: MyAvatar.SELF_ID,
      +                parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX")
      +            });
               } else {
      -            windowWidth = Window.innerWidth;
      -            windowHeight = Window.innerHeight;
      +            var textDimensions = { x: 100, y: 50 };
      +            warningOverlayID = Overlays.addOverlay("text", {
      +                name: "Muted-Warning",
      +                font: { size: 36 },
      +                text: warningText,
      +                x: Window.innerWidth / 2 - textDimensions.x / 2,
      +                y: Window.innerHeight / 2 - textDimensions.y / 2,
      +                width: textDimensions.x,
      +                height: textDimensions.y,
      +                textColor: { red: 226, green: 51, blue: 77 },
      +                backgroundAlpha: 0,
      +                visible: true
      +            });
               }
      -
      -        warningOverlayID = Overlays.addOverlay("text", {
      -            name: "Muted-Warning",
      -            font: { size: 36 },
      -            text: warningText,
      -            x: windowWidth / 2 - textDimensions.x / 2,
      -            y: windowHeight / 2 - textDimensions.y / 2,
      -            width: textDimensions.x,
      -            height: textDimensions.y,
      -            textColor: { red: 226, green: 51, blue: 77 },
      -            backgroundAlpha: 0,
      -            visible: true
      -        });
           }
       
           function hideWarning() {
      
      From bbad6af0d692b176bef20dc87866ccda8848d04c Mon Sep 17 00:00:00 2001
      From: Seth Alves 
      Date: Wed, 20 Feb 2019 13:07:24 -0800
      Subject: [PATCH 306/474] attempt to take background noise into account when
       triggering mute warning
      
      ---
       scripts/system/audioMuteOverlay.js | 33 ++++++++++++++++++++++--------
       1 file changed, 24 insertions(+), 9 deletions(-)
      
      diff --git a/scripts/system/audioMuteOverlay.js b/scripts/system/audioMuteOverlay.js
      index 65793d1d87..96f6d636dc 100644
      --- a/scripts/system/audioMuteOverlay.js
      +++ b/scripts/system/audioMuteOverlay.js
      @@ -13,14 +13,22 @@
       
       "use strict";
       
      -/* global Audio, Script, Overlays, Quat, MyAvatar */
      +/* global Audio, Script, Overlays, Quat, MyAvatar, HMD */
       
       (function() { // BEGIN LOCAL_SCOPE
       
      -    var lastInputLoudness = 0.0;
      +    var lastShortTermInputLoudness = 0.0;
      +    var lastLongTermInputLoudness = 0.0;
           var sampleRate = 8.0; // Hz
      -    var attackTC =  Math.exp(-1.0 / (sampleRate * 0.500)); // 500 milliseconds attack
      -    var releaseTC =  Math.exp(-1.0 / (sampleRate * 1.000)); // 1000 milliseconds release
      +
      +    var shortTermAttackTC =  Math.exp(-1.0 / (sampleRate * 0.500)); // 500 milliseconds attack
      +    var shortTermReleaseTC =  Math.exp(-1.0 / (sampleRate * 1.000)); // 1000 milliseconds release
      +
      +    var longTermAttackTC =  Math.exp(-1.0 / (sampleRate * 5.0)); // 5 second attack
      +    var longTermReleaseTC =  Math.exp(-1.0 / (sampleRate * 10.0)); // 10 seconds release
      +
      +    var activationThreshold = 0.05; // how much louder short-term needs to be than long-term to trigger warning
      +
           var holdReset = 2.0 * sampleRate; // 2 seconds hold
           var holdCount = 0;
           var warningOverlayID = null;
      @@ -80,12 +88,19 @@
                   return;
               }
               pollInterval = Script.setInterval(function() {
      -            var inputLoudness = Audio.inputLevel;
      -            var tc = (inputLoudness > lastInputLoudness) ? attackTC : releaseTC;
      -            inputLoudness += tc * (lastInputLoudness - inputLoudness);
      -            lastInputLoudness = inputLoudness;
      +            var shortTermInputLoudness = Audio.inputLevel;
      +            var longTermInputLoudness = shortTermInputLoudness;
       
      -            if (inputLoudness > 0.1) {
      +            var shortTc = (shortTermInputLoudness > lastShortTermInputLoudness) ? shortTermAttackTC : shortTermReleaseTC;
      +            var longTc = (longTermInputLoudness > lastLongTermInputLoudness) ? longTermAttackTC : longTermReleaseTC;
      +
      +            shortTermInputLoudness += shortTc * (lastShortTermInputLoudness - shortTermInputLoudness);
      +            longTermInputLoudness += longTc * (lastLongTermInputLoudness - longTermInputLoudness);
      +
      +            lastShortTermInputLoudness = shortTermInputLoudness;
      +            lastLongTermInputLoudness = longTermInputLoudness;
      +
      +            if (shortTermInputLoudness > lastLongTermInputLoudness + activationThreshold) {
                       holdCount = holdReset;
                   } else {
                       holdCount = Math.max(holdCount - 1, 0);
      
      From 6f400796214c1f26a15def2f98fe619806298649 Mon Sep 17 00:00:00 2001
      From: Seth Alves 
      Date: Thu, 21 Feb 2019 14:04:08 -0800
      Subject: [PATCH 307/474] added a button to enable server loopback of audio
      
      ---
       interface/resources/qml/hifi/audio/Audio.qml  |  7 ++
       .../qml/hifi/audio/LoopbackAudio.qml          | 75 +++++++++++++++++++
       2 files changed, 82 insertions(+)
       create mode 100644 interface/resources/qml/hifi/audio/LoopbackAudio.qml
      
      diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml
      index 34ae64aee8..e340ec5003 100644
      --- a/interface/resources/qml/hifi/audio/Audio.qml
      +++ b/interface/resources/qml/hifi/audio/Audio.qml
      @@ -313,5 +313,12 @@ Rectangle {
                            (bar.currentIndex === 0 && !isVR);
                   anchors { left: parent.left; leftMargin: margins.paddings }
               }
      +        LoopbackAudio {
      +            x: margins.paddings
      +
      +            visible: (bar.currentIndex === 1 && isVR) ||
      +                (bar.currentIndex === 0 && !isVR);
      +            anchors { left: parent.left; leftMargin: margins.paddings }
      +        }
           }
       }
      diff --git a/interface/resources/qml/hifi/audio/LoopbackAudio.qml b/interface/resources/qml/hifi/audio/LoopbackAudio.qml
      new file mode 100644
      index 0000000000..6d5f8d88fd
      --- /dev/null
      +++ b/interface/resources/qml/hifi/audio/LoopbackAudio.qml
      @@ -0,0 +1,75 @@
      +//
      +//  LoopbackAudio.qml
      +//  qml/hifi/audio
      +//
      +//  Created by Seth Alves on 2019-2-18
      +//  Copyright 2019 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
      +//
      +
      +import QtQuick 2.7
      +import QtQuick.Controls 2.2
      +import QtQuick.Layouts 1.3
      +
      +import stylesUit 1.0
      +import controlsUit 1.0 as HifiControls
      +
      +RowLayout {
      +    property bool audioLoopedBack: false;
      +    function startAudioLoopback() {
      +        if (!audioLoopedBack) {
      +            audioLoopedBack = true;
      +            AudioScope.setServerEcho(true);
      +        }
      +    }
      +    function stopAudioLoopback () {
      +        if (audioLoopedBack) {
      +            audioLoopedBack = false;
      +            AudioScope.setServerEcho(false);
      +        }
      +    }
      +
      +    Component.onDestruction: stopAudioLoopback();
      +    onVisibleChanged: {
      +        if (!visible) {
      +            stopAudioLoopback();
      +        }
      +    }
      +
      +    HifiConstants { id: hifi; }
      +
      +    Button {
      +        id: control
      +        background: Rectangle {
      +            implicitWidth: 20;
      +            implicitHeight: 20;
      +            radius: hifi.buttons.radius;
      +            gradient: Gradient {
      +                GradientStop {
      +                    position: 0.2;
      +                    color: audioLoopedBack ? hifi.buttons.colorStart[hifi.buttons.blue] : hifi.buttons.colorStart[hifi.buttons.black];
      +                }
      +                GradientStop {
      +                    position: 1.0;
      +                    color: audioLoopedBack ? hifi.buttons.colorFinish[hifi.buttons.blue] : hifi.buttons.colorFinish[hifi.buttons.black];
      +                }
      +            }
      +        }
      +        contentItem: HiFiGlyphs {
      +            size: 14;
      +            color: (control.pressed || control.hovered) ? (audioLoopedBack ? "black" : hifi.colors.primaryHighlight) : "white";
      +            text: audioLoopedBack ? hifi.glyphs.stop_square : hifi.glyphs.playback_play;
      +        }
      +
      +        onClicked: audioLoopedBack ? stopAudioLoopback() : startAudioLoopback();
      +    }
      +
      +    RalewayRegular {
      +        Layout.leftMargin: 2;
      +        size: 14;
      +        color: "white";
      +        text: audioLoopedBack ? qsTr("Disable Audio Loopback") : qsTr("Enable Audio Loopback");
      +    }
      +}
      
      From 9ff99c721386cf620dc6dd26d35138f87b295d44 Mon Sep 17 00:00:00 2001
      From: Seth Alves 
      Date: Fri, 22 Feb 2019 09:33:23 -0800
      Subject: [PATCH 308/474] make server-audio-loopback button work in HMDs
      
      ---
       interface/resources/qml/hifi/audio/LoopbackAudio.qml |  4 ++--
       libraries/audio-client/src/AudioClient.h             | 12 ++++++------
       libraries/audio/src/AbstractAudioInterface.h         |  9 ++++++++-
       .../script-engine/src/AudioScriptingInterface.cpp    | 12 ++++++++++++
       .../script-engine/src/AudioScriptingInterface.h      | 12 ++++++++++++
       5 files changed, 40 insertions(+), 9 deletions(-)
      
      diff --git a/interface/resources/qml/hifi/audio/LoopbackAudio.qml b/interface/resources/qml/hifi/audio/LoopbackAudio.qml
      index 6d5f8d88fd..2f0dbe5950 100644
      --- a/interface/resources/qml/hifi/audio/LoopbackAudio.qml
      +++ b/interface/resources/qml/hifi/audio/LoopbackAudio.qml
      @@ -21,13 +21,13 @@ RowLayout {
           function startAudioLoopback() {
               if (!audioLoopedBack) {
                   audioLoopedBack = true;
      -            AudioScope.setServerEcho(true);
      +            AudioScriptingInterface.setServerEcho(true);
               }
           }
           function stopAudioLoopback () {
               if (audioLoopedBack) {
                   audioLoopedBack = false;
      -            AudioScope.setServerEcho(false);
      +            AudioScriptingInterface.setServerEcho(false);
               }
           }
       
      diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h
      index 6d3483b0f8..b9648219a5 100644
      --- a/libraries/audio-client/src/AudioClient.h
      +++ b/libraries/audio-client/src/AudioClient.h
      @@ -213,13 +213,13 @@ public slots:
           void setWarnWhenMuted(bool isNoiseGateEnabled, bool emitSignal = true);
           bool isWarnWhenMutedEnabled() const { return _warnWhenMuted; }
       
      -    bool getLocalEcho() { return _shouldEchoLocally; }
      -    void setLocalEcho(bool localEcho) { _shouldEchoLocally = localEcho; }
      -    void toggleLocalEcho() { _shouldEchoLocally = !_shouldEchoLocally; }
      +    virtual bool getLocalEcho() override { return _shouldEchoLocally; }
      +    virtual void setLocalEcho(bool localEcho) override { _shouldEchoLocally = localEcho; }
      +    virtual void toggleLocalEcho() override { _shouldEchoLocally = !_shouldEchoLocally; }
       
      -    bool getServerEcho() { return _shouldEchoToServer; }
      -    void setServerEcho(bool serverEcho) { _shouldEchoToServer = serverEcho; }
      -    void toggleServerEcho() { _shouldEchoToServer = !_shouldEchoToServer; }
      +    virtual bool getServerEcho() override { return _shouldEchoToServer; }
      +    virtual void setServerEcho(bool serverEcho) override { _shouldEchoToServer = serverEcho; }
      +    virtual void toggleServerEcho() override { _shouldEchoToServer = !_shouldEchoToServer; }
       
           void processReceivedSamples(const QByteArray& inputBuffer, QByteArray& outputBuffer);
           void sendMuteEnvironmentPacket();
      diff --git a/libraries/audio/src/AbstractAudioInterface.h b/libraries/audio/src/AbstractAudioInterface.h
      index 0f075ab224..e9e40e95f9 100644
      --- a/libraries/audio/src/AbstractAudioInterface.h
      +++ b/libraries/audio/src/AbstractAudioInterface.h
      @@ -45,9 +45,16 @@ public slots:
           virtual bool shouldLoopbackInjectors() { return false; }
       
           virtual bool setIsStereoInput(bool stereo) = 0;
      -
           virtual bool isStereoInput() = 0;
       
      +    virtual bool getLocalEcho() = 0;
      +    virtual void setLocalEcho(bool localEcho) = 0;
      +    virtual void toggleLocalEcho() = 0;
      +
      +    virtual bool getServerEcho() = 0;
      +    virtual void setServerEcho(bool serverEcho) = 0;
      +    virtual void toggleServerEcho() = 0;
      +
       signals:
           void isStereoInputChanged(bool isStereo);
       };
      diff --git a/libraries/script-engine/src/AudioScriptingInterface.cpp b/libraries/script-engine/src/AudioScriptingInterface.cpp
      index 8e54d2d5de..b12b55c3f7 100644
      --- a/libraries/script-engine/src/AudioScriptingInterface.cpp
      +++ b/libraries/script-engine/src/AudioScriptingInterface.cpp
      @@ -88,3 +88,15 @@ bool AudioScriptingInterface::isStereoInput() {
           }
           return stereoEnabled;
       }
      +
      +void AudioScriptingInterface::setServerEcho(bool serverEcho) {
      +    if (_localAudioInterface) {
      +        QMetaObject::invokeMethod(_localAudioInterface, "setServerEcho", Q_ARG(bool, serverEcho));
      +    }
      +}
      +
      +void AudioScriptingInterface::setLocalEcho(bool localEcho) {
      +    if (_localAudioInterface) {
      +        QMetaObject::invokeMethod(_localAudioInterface, "setLocalEcho", Q_ARG(bool, localEcho));
      +    }
      +}
      diff --git a/libraries/script-engine/src/AudioScriptingInterface.h b/libraries/script-engine/src/AudioScriptingInterface.h
      index d2f886d2dd..23cc02248d 100644
      --- a/libraries/script-engine/src/AudioScriptingInterface.h
      +++ b/libraries/script-engine/src/AudioScriptingInterface.h
      @@ -66,6 +66,18 @@ public:
               _localAudioInterface->getAudioSolo().reset();
           }
       
      +    /**jsdoc
      +     * @function Audio.setServerEcho
      +     * @parm {boolean} serverEcho
      +     */
      +    Q_INVOKABLE void setServerEcho(bool serverEcho);
      +
      +    /**jsdoc
      +     * @function Audio.setLocalEcho
      +     * @parm {boolean} localEcho
      +     */
      +    Q_INVOKABLE void setLocalEcho(bool localEcho);
      +
       protected:
           AudioScriptingInterface() = default;
       
      
      From 0523a0d06dc6fa751cfb7111c8f1730b96d548fa Mon Sep 17 00:00:00 2001
      From: Seth Alves 
      Date: Sat, 23 Feb 2019 14:23:09 -0800
      Subject: [PATCH 309/474] don't disable server echo when audio qml page is
       closed
      
      ---
       .../qml/hifi/audio/LoopbackAudio.qml          |  9 +-----
       .../src/AudioScriptingInterface.cpp           | 28 +++++++++++++++++++
       .../src/AudioScriptingInterface.h             | 21 ++++++++++++++
       3 files changed, 50 insertions(+), 8 deletions(-)
      
      diff --git a/interface/resources/qml/hifi/audio/LoopbackAudio.qml b/interface/resources/qml/hifi/audio/LoopbackAudio.qml
      index 2f0dbe5950..3ecf09c948 100644
      --- a/interface/resources/qml/hifi/audio/LoopbackAudio.qml
      +++ b/interface/resources/qml/hifi/audio/LoopbackAudio.qml
      @@ -17,7 +17,7 @@ import stylesUit 1.0
       import controlsUit 1.0 as HifiControls
       
       RowLayout {
      -    property bool audioLoopedBack: false;
      +    property bool audioLoopedBack: AudioScriptingInterface.getServerEcho();
           function startAudioLoopback() {
               if (!audioLoopedBack) {
                   audioLoopedBack = true;
      @@ -31,13 +31,6 @@ RowLayout {
               }
           }
       
      -    Component.onDestruction: stopAudioLoopback();
      -    onVisibleChanged: {
      -        if (!visible) {
      -            stopAudioLoopback();
      -        }
      -    }
      -
           HifiConstants { id: hifi; }
       
           Button {
      diff --git a/libraries/script-engine/src/AudioScriptingInterface.cpp b/libraries/script-engine/src/AudioScriptingInterface.cpp
      index b12b55c3f7..65d71e46e6 100644
      --- a/libraries/script-engine/src/AudioScriptingInterface.cpp
      +++ b/libraries/script-engine/src/AudioScriptingInterface.cpp
      @@ -89,14 +89,42 @@ bool AudioScriptingInterface::isStereoInput() {
           return stereoEnabled;
       }
       
      +bool AudioScriptingInterface::getServerEcho() {
      +    bool serverEchoEnabled = false;
      +    if (_localAudioInterface) {
      +        serverEchoEnabled = _localAudioInterface->getServerEcho();
      +    }
      +    return serverEchoEnabled;
      +}
      +
       void AudioScriptingInterface::setServerEcho(bool serverEcho) {
           if (_localAudioInterface) {
               QMetaObject::invokeMethod(_localAudioInterface, "setServerEcho", Q_ARG(bool, serverEcho));
           }
       }
       
      +void AudioScriptingInterface::toggleServerEcho() {
      +    if (_localAudioInterface) {
      +        QMetaObject::invokeMethod(_localAudioInterface, "toggleServerEcho");
      +    }
      +}
      +
      +bool AudioScriptingInterface::getLocalEcho() {
      +    bool localEchoEnabled = false;
      +    if (_localAudioInterface) {
      +        localEchoEnabled = _localAudioInterface->getLocalEcho();
      +    }
      +    return localEchoEnabled;
      +}
      +
       void AudioScriptingInterface::setLocalEcho(bool localEcho) {
           if (_localAudioInterface) {
               QMetaObject::invokeMethod(_localAudioInterface, "setLocalEcho", Q_ARG(bool, localEcho));
           }
       }
      +
      +void AudioScriptingInterface::toggleLocalEcho() {
      +    if (_localAudioInterface) {
      +        QMetaObject::invokeMethod(_localAudioInterface, "toggleLocalEcho");
      +    }
      +}
      diff --git a/libraries/script-engine/src/AudioScriptingInterface.h b/libraries/script-engine/src/AudioScriptingInterface.h
      index 23cc02248d..a6801dcdcb 100644
      --- a/libraries/script-engine/src/AudioScriptingInterface.h
      +++ b/libraries/script-engine/src/AudioScriptingInterface.h
      @@ -66,18 +66,39 @@ public:
               _localAudioInterface->getAudioSolo().reset();
           }
       
      +    /**jsdoc
      +     * @function Audio.getServerEcho
      +     */
      +    Q_INVOKABLE bool getServerEcho();
      +
           /**jsdoc
            * @function Audio.setServerEcho
            * @parm {boolean} serverEcho
            */
           Q_INVOKABLE void setServerEcho(bool serverEcho);
       
      +    /**jsdoc
      +     * @function Audio.toggleServerEcho
      +     */
      +    Q_INVOKABLE void toggleServerEcho();
      +
      +    /**jsdoc
      +     * @function Audio.getLocalEcho
      +     */
      +    Q_INVOKABLE bool getLocalEcho();
      +
           /**jsdoc
            * @function Audio.setLocalEcho
            * @parm {boolean} localEcho
            */
           Q_INVOKABLE void setLocalEcho(bool localEcho);
       
      +    /**jsdoc
      +     * @function Audio.toggleLocalEcho
      +     */
      +    Q_INVOKABLE void toggleLocalEcho();
      +
      +
       protected:
           AudioScriptingInterface() = default;
       
      
      From 90a6e0d9b01446c4f4f0aa28be66c7961b8d3a24 Mon Sep 17 00:00:00 2001
      From: Wayne Chen 
      Date: Mon, 4 Mar 2019 11:42:43 -0800
      Subject: [PATCH 310/474] changing mute warning position
      
      ---
       scripts/system/audioMuteOverlay.js | 6 +++---
       1 file changed, 3 insertions(+), 3 deletions(-)
      
      diff --git a/scripts/system/audioMuteOverlay.js b/scripts/system/audioMuteOverlay.js
      index 96f6d636dc..cd0c99ab6e 100644
      --- a/scripts/system/audioMuteOverlay.js
      +++ b/scripts/system/audioMuteOverlay.js
      @@ -43,7 +43,7 @@
               if (HMD.active) {
                   warningOverlayID = Overlays.addOverlay("text3d", {
                       name: "Muted-Warning",
      -                localPosition: { x: 0, y: 0, z: -1.0 },
      +                localPosition: { x: 0.0, y: -0.5, z: -1.0 },
                       localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 0.0, z: 0.0, w: 1.0 }),
                       text: warningText,
                       textAlpha: 1,
      @@ -64,8 +64,8 @@
                       name: "Muted-Warning",
                       font: { size: 36 },
                       text: warningText,
      -                x: Window.innerWidth / 2 - textDimensions.x / 2,
      -                y: Window.innerHeight / 2 - textDimensions.y / 2,
      +                x: (Window.innerWidth - textDimensions.x) / 2,
      +                y: (Window.innerHeight - textDimensions.y),
                       width: textDimensions.x,
                       height: textDimensions.y,
                       textColor: { red: 226, green: 51, blue: 77 },
      
      From 7860db68eb6ca4f557582fc022f42873b16b4fda Mon Sep 17 00:00:00 2001
      From: Wayne Chen 
      Date: Mon, 4 Mar 2019 13:33:16 -0800
      Subject: [PATCH 311/474] culling hearing oneself and testing sound
      
      ---
       interface/resources/qml/hifi/audio/Audio.qml  |  7 ----
       libraries/audio-client/src/AudioClient.h      | 12 +++---
       libraries/audio/src/AbstractAudioInterface.h  |  8 ----
       .../src/AudioScriptingInterface.cpp           | 42 +------------------
       .../src/AudioScriptingInterface.h             | 33 ---------------
       5 files changed, 7 insertions(+), 95 deletions(-)
      
      diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml
      index e340ec5003..34ae64aee8 100644
      --- a/interface/resources/qml/hifi/audio/Audio.qml
      +++ b/interface/resources/qml/hifi/audio/Audio.qml
      @@ -313,12 +313,5 @@ Rectangle {
                            (bar.currentIndex === 0 && !isVR);
                   anchors { left: parent.left; leftMargin: margins.paddings }
               }
      -        LoopbackAudio {
      -            x: margins.paddings
      -
      -            visible: (bar.currentIndex === 1 && isVR) ||
      -                (bar.currentIndex === 0 && !isVR);
      -            anchors { left: parent.left; leftMargin: margins.paddings }
      -        }
           }
       }
      diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h
      index b9648219a5..6d3483b0f8 100644
      --- a/libraries/audio-client/src/AudioClient.h
      +++ b/libraries/audio-client/src/AudioClient.h
      @@ -213,13 +213,13 @@ public slots:
           void setWarnWhenMuted(bool isNoiseGateEnabled, bool emitSignal = true);
           bool isWarnWhenMutedEnabled() const { return _warnWhenMuted; }
       
      -    virtual bool getLocalEcho() override { return _shouldEchoLocally; }
      -    virtual void setLocalEcho(bool localEcho) override { _shouldEchoLocally = localEcho; }
      -    virtual void toggleLocalEcho() override { _shouldEchoLocally = !_shouldEchoLocally; }
      +    bool getLocalEcho() { return _shouldEchoLocally; }
      +    void setLocalEcho(bool localEcho) { _shouldEchoLocally = localEcho; }
      +    void toggleLocalEcho() { _shouldEchoLocally = !_shouldEchoLocally; }
       
      -    virtual bool getServerEcho() override { return _shouldEchoToServer; }
      -    virtual void setServerEcho(bool serverEcho) override { _shouldEchoToServer = serverEcho; }
      -    virtual void toggleServerEcho() override { _shouldEchoToServer = !_shouldEchoToServer; }
      +    bool getServerEcho() { return _shouldEchoToServer; }
      +    void setServerEcho(bool serverEcho) { _shouldEchoToServer = serverEcho; }
      +    void toggleServerEcho() { _shouldEchoToServer = !_shouldEchoToServer; }
       
           void processReceivedSamples(const QByteArray& inputBuffer, QByteArray& outputBuffer);
           void sendMuteEnvironmentPacket();
      diff --git a/libraries/audio/src/AbstractAudioInterface.h b/libraries/audio/src/AbstractAudioInterface.h
      index e9e40e95f9..dc7d25fdc2 100644
      --- a/libraries/audio/src/AbstractAudioInterface.h
      +++ b/libraries/audio/src/AbstractAudioInterface.h
      @@ -47,14 +47,6 @@ public slots:
           virtual bool setIsStereoInput(bool stereo) = 0;
           virtual bool isStereoInput() = 0;
       
      -    virtual bool getLocalEcho() = 0;
      -    virtual void setLocalEcho(bool localEcho) = 0;
      -    virtual void toggleLocalEcho() = 0;
      -
      -    virtual bool getServerEcho() = 0;
      -    virtual void setServerEcho(bool serverEcho) = 0;
      -    virtual void toggleServerEcho() = 0;
      -
       signals:
           void isStereoInputChanged(bool isStereo);
       };
      diff --git a/libraries/script-engine/src/AudioScriptingInterface.cpp b/libraries/script-engine/src/AudioScriptingInterface.cpp
      index 65d71e46e6..c695d67d91 100644
      --- a/libraries/script-engine/src/AudioScriptingInterface.cpp
      +++ b/libraries/script-engine/src/AudioScriptingInterface.cpp
      @@ -87,44 +87,4 @@ bool AudioScriptingInterface::isStereoInput() {
               stereoEnabled = _localAudioInterface->isStereoInput();
           }
           return stereoEnabled;
      -}
      -
      -bool AudioScriptingInterface::getServerEcho() {
      -    bool serverEchoEnabled = false;
      -    if (_localAudioInterface) {
      -        serverEchoEnabled = _localAudioInterface->getServerEcho();
      -    }
      -    return serverEchoEnabled;
      -}
      -
      -void AudioScriptingInterface::setServerEcho(bool serverEcho) {
      -    if (_localAudioInterface) {
      -        QMetaObject::invokeMethod(_localAudioInterface, "setServerEcho", Q_ARG(bool, serverEcho));
      -    }
      -}
      -
      -void AudioScriptingInterface::toggleServerEcho() {
      -    if (_localAudioInterface) {
      -        QMetaObject::invokeMethod(_localAudioInterface, "toggleServerEcho");
      -    }
      -}
      -
      -bool AudioScriptingInterface::getLocalEcho() {
      -    bool localEchoEnabled = false;
      -    if (_localAudioInterface) {
      -        localEchoEnabled = _localAudioInterface->getLocalEcho();
      -    }
      -    return localEchoEnabled;
      -}
      -
      -void AudioScriptingInterface::setLocalEcho(bool localEcho) {
      -    if (_localAudioInterface) {
      -        QMetaObject::invokeMethod(_localAudioInterface, "setLocalEcho", Q_ARG(bool, localEcho));
      -    }
      -}
      -
      -void AudioScriptingInterface::toggleLocalEcho() {
      -    if (_localAudioInterface) {
      -        QMetaObject::invokeMethod(_localAudioInterface, "toggleLocalEcho");
      -    }
      -}
      +}
      \ No newline at end of file
      diff --git a/libraries/script-engine/src/AudioScriptingInterface.h b/libraries/script-engine/src/AudioScriptingInterface.h
      index a6801dcdcb..d2f886d2dd 100644
      --- a/libraries/script-engine/src/AudioScriptingInterface.h
      +++ b/libraries/script-engine/src/AudioScriptingInterface.h
      @@ -66,39 +66,6 @@ public:
               _localAudioInterface->getAudioSolo().reset();
           }
       
      -    /**jsdoc
      -     * @function Audio.getServerEcho
      -     */
      -    Q_INVOKABLE bool getServerEcho();
      -
      -    /**jsdoc
      -     * @function Audio.setServerEcho
      -     * @parm {boolean} serverEcho
      -     */
      -    Q_INVOKABLE void setServerEcho(bool serverEcho);
      -
      -    /**jsdoc
      -     * @function Audio.toggleServerEcho
      -     */
      -    Q_INVOKABLE void toggleServerEcho();
      -
      -    /**jsdoc
      -     * @function Audio.getLocalEcho
      -     */
      -    Q_INVOKABLE bool getLocalEcho();
      -
      -    /**jsdoc
      -     * @function Audio.setLocalEcho
      -     * @parm {boolean} localEcho
      -     */
      -    Q_INVOKABLE void setLocalEcho(bool localEcho);
      -
      -    /**jsdoc
      -     * @function Audio.toggleLocalEcho
      -     */
      -    Q_INVOKABLE void toggleLocalEcho();
      -
      -
       protected:
           AudioScriptingInterface() = default;
       
      
      From 7780db813da4196f6fc4ce555172acacb70793ff Mon Sep 17 00:00:00 2001
      From: Oren Hurvitz 
      Date: Mon, 16 Apr 2018 15:26:06 +0300
      Subject: [PATCH 312/474] Save the "Mute Microphone" setting
      
      ---
       interface/src/scripting/Audio.cpp | 12 ++++++++++++
       1 file changed, 12 insertions(+)
      
      diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp
      index 2c4c29ff65..b4e3d7913b 100644
      --- a/interface/src/scripting/Audio.cpp
      +++ b/interface/src/scripting/Audio.cpp
      @@ -25,6 +25,8 @@ QString Audio::DESKTOP { "Desktop" };
       QString Audio::HMD { "VR" };
       
       Setting::Handle enableNoiseReductionSetting { QStringList { Audio::AUDIO, "NoiseReduction" }, true };
      +Setting::Handle mutedSetting { QStringList{ Audio::AUDIO, "MuteMicrophone" }, false };
      +
       
       float Audio::loudnessToLevel(float loudness) {
           float level = loudness * (1/32768.0f);  // level in [0, 1]
      @@ -42,6 +44,7 @@ Audio::Audio() : _devices(_contextIsHMD) {
           connect(this, &Audio::contextChanged, &_devices, &AudioDevices::onContextChanged);
           enableNoiseReduction(enableNoiseReductionSetting.get());
           onContextChanged();
      +    setMuted(mutedSetting.get());
       }
       
       bool Audio::startRecording(const QString& filepath) {
      @@ -89,6 +92,15 @@ bool Audio::noiseReductionEnabled() const {
           });
       }
       
      +void Audio::onMutedChanged() {
      +    bool isMuted = DependencyManager::get()->isMuted();
      +    if (_isMuted != isMuted) {
      +        _isMuted = isMuted;
      +        mutedSetting.set(_isMuted);
      +        emit mutedChanged(_isMuted);
      +    }
      +}
      +
       void Audio::enableNoiseReduction(bool enable) {
           bool changed = false;
           withWriteLock([&] {
      
      From d54d0280c2469b2cec8ead405fe754ced5c4604f Mon Sep 17 00:00:00 2001
      From: Seth Alves 
      Date: Sun, 17 Feb 2019 14:21:23 -0800
      Subject: [PATCH 313/474] rework audioMuteOverlay.js
      
      ---
       interface/src/scripting/Audio.cpp  |  10 +--
       scripts/defaultScripts.js          |   3 +-
       scripts/system/audioMuteOverlay.js | 140 +++++++++++++----------------
       3 files changed, 63 insertions(+), 90 deletions(-)
      
      diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp
      index b4e3d7913b..bb40f69b0b 100644
      --- a/interface/src/scripting/Audio.cpp
      +++ b/interface/src/scripting/Audio.cpp
      @@ -76,6 +76,7 @@ void Audio::setMuted(bool isMuted) {
           withWriteLock([&] {
               if (_isMuted != isMuted) {
                   _isMuted = isMuted;
      +            mutedSetting.set(_isMuted);
                   auto client = DependencyManager::get().data();
                   QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false));
                   changed = true;
      @@ -92,15 +93,6 @@ bool Audio::noiseReductionEnabled() const {
           });
       }
       
      -void Audio::onMutedChanged() {
      -    bool isMuted = DependencyManager::get()->isMuted();
      -    if (_isMuted != isMuted) {
      -        _isMuted = isMuted;
      -        mutedSetting.set(_isMuted);
      -        emit mutedChanged(_isMuted);
      -    }
      -}
      -
       void Audio::enableNoiseReduction(bool enable) {
           bool changed = false;
           withWriteLock([&] {
      diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js
      index bd7e79dffc..e392680df9 100644
      --- a/scripts/defaultScripts.js
      +++ b/scripts/defaultScripts.js
      @@ -32,7 +32,8 @@ var DEFAULT_SCRIPTS_COMBINED = [
           "system/firstPersonHMD.js",
           "system/tablet-ui/tabletUI.js",
           "system/emote.js",
      -    "system/miniTablet.js"
      +    "system/miniTablet.js",
      +    "system/audioMuteOverlay.js"
       ];
       var DEFAULT_SCRIPTS_SEPARATE = [
           "system/controllers/controllerScripts.js",
      diff --git a/scripts/system/audioMuteOverlay.js b/scripts/system/audioMuteOverlay.js
      index 731d62017d..14ac96c8c6 100644
      --- a/scripts/system/audioMuteOverlay.js
      +++ b/scripts/system/audioMuteOverlay.js
      @@ -1,104 +1,84 @@
      -"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 }] */
       //
       // audioMuteOverlay.js
       //
       // client script that creates an overlay to provide mute feedback
       //
       // Created by Triplelexx on 17/03/09
      +// Reworked by Seth Alves on 2019-2-17
       // 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
       //
       
      +"use strict";
      +
      +/* global Audio, Script, Overlays, Quat, MyAvatar */
      +
       (function() { // BEGIN LOCAL_SCOPE
      -    var utilsPath = Script.resolvePath('../developer/libraries/utils.js');
      -    Script.include(utilsPath);
       
      -    var TWEEN_SPEED = 0.025;
      -    var MIX_AMOUNT = 0.25;
      +   var lastInputLoudness = 0.0;
      +   var sampleRate = 8.0; // Hz
      +   var attackTC =  Math.exp(-1.0 / (sampleRate * 0.500)) // 500 milliseconds attack
      +   var releaseTC =  Math.exp(-1.0 / (sampleRate * 1.000)) // 1000 milliseconds release
      +   var holdReset = 2.0 * sampleRate; // 2 seconds hold
      +   var holdCount = 0;
      +   var warningOverlayID = null;
       
      -    var overlayPosition = Vec3.ZERO;
      -    var tweenPosition = 0;
      -    var startColor = {
      -        red: 170,
      -        green: 170,
      -        blue: 170
      -    };
      -    var endColor = {
      -        red: 255,
      -        green: 0,
      -        blue: 0
      -    };
      -    var overlayID;
      +   function showWarning() {
      +       if (warningOverlayID) {
      +           return;
      +       }
      +       warningOverlayID = Overlays.addOverlay("text3d", {
      +           name: "Muted-Warning",
      +           localPosition: { x: 0.2, y: -0.35, z: -1.0 },
      +           localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 0.0, z: 0.0, w: 1.0 }),
      +           text: "Warning: you are muted",
      +           textAlpha: 1,
      +           color: { red: 226, green: 51, blue: 77 },
      +           backgroundAlpha: 0,
      +           lineHeight: 0.042,
      +           visible: true,
      +           ignoreRayIntersection: true,
      +           drawInFront: true,
      +           grabbable: false,
      +           parentID: MyAvatar.SELF_ID,
      +           parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX")
      +       });
      +   };
       
      -    Script.update.connect(update);
      -    Script.scriptEnding.connect(cleanup);
      +   function hideWarning() {
      +       if (!warningOverlayID) {
      +           return;
      +       }
      +       Overlays.deleteOverlay(warningOverlayID);
      +       warningOverlayID = null;
      +   }
       
      -    function update(dt) {
      -        if (!Audio.muted) {
      -            if (hasOverlay()) {
      -                deleteOverlay();
      -            }
      -        } else if (!hasOverlay()) {
      -            createOverlay();
      -        } else {
      -            updateOverlay();
      -        }
      -    }
      +   function cleanup() {
      +       Overlays.deleteOverlay(warningOverlayID);
      +   }
       
      -    function getOffsetPosition() {
      -        return Vec3.sum(Camera.position, Quat.getFront(Camera.orientation));
      -    }
      +   Script.scriptEnding.connect(cleanup);
       
      -    function createOverlay() {
      -        overlayPosition = getOffsetPosition();
      -        overlayID = Overlays.addOverlay("sphere", {
      -            position: overlayPosition,
      -            rotation: Camera.orientation,
      -            alpha: 0.9,
      -            dimensions: 0.1,
      -            solid: true,
      -            ignoreRayIntersection: true
      -        });
      -    }
      +   Script.setInterval(function() {
       
      -    function hasOverlay() {
      -       return Overlays.getProperty(overlayID, "position") !== undefined;
      -    }
      +       var inputLoudness = Audio.inputLevel;
      +       var tc = (inputLoudness > lastInputLoudness) ? attackTC : releaseTC;
      +       inputLoudness += tc * (lastInputLoudness - inputLoudness);
      +       lastInputLoudness = inputLoudness;
       
      -    function updateOverlay() {
      -        // increase by TWEEN_SPEED until completion
      -        if (tweenPosition < 1) {
      -            tweenPosition += TWEEN_SPEED;
      -        } else {
      -            // after tween completion reset to zero and flip values to ping pong 
      -            tweenPosition = 0;
      -            for (var component in startColor) {
      -                var storedColor = startColor[component];
      -                startColor[component] = endColor[component];
      -                endColor[component] = storedColor;
      -            }
      -        }
      -        // mix previous position with new and mix colors
      -        overlayPosition = Vec3.mix(overlayPosition, getOffsetPosition(), MIX_AMOUNT);
      -        Overlays.editOverlay(overlayID, {
      -            color: colorMix(startColor, endColor, easeIn(tweenPosition)),
      -            position: overlayPosition,
      -            rotation: Camera.orientation
      -        });
      -    }
      +       if (Audio.muted && inputLoudness > 0.3) {
      +           holdCount = holdReset;
      +       } else {
      +           holdCount = Math.max(holdCount - 1, 0);
      +       }
       
      -    function deleteOverlay() {
      -        Overlays.deleteOverlay(overlayID);
      -    }
      +       if (holdCount > 0) {
      +           showWarning();
      +       } else {
      +           hideWarning();
      +       }
      +   }, 1000.0 / sampleRate);
       
      -    function cleanup() {
      -        deleteOverlay();
      -        Audio.muted.disconnect(onMuteToggled);
      -        Script.update.disconnect(update);
      -    }
       }()); // END LOCAL_SCOPE
      
      From 3ecd86caee33cb72aaadbf2fc3fe90cde635a5c6 Mon Sep 17 00:00:00 2001
      From: Seth Alves 
      Date: Tue, 19 Feb 2019 09:32:41 -0800
      Subject: [PATCH 314/474] add a way to disble muted warning from audio panel.
       fix positioning of warning.  hide warning when removing timer.
      
      ---
       interface/resources/qml/hifi/audio/Audio.qml |  13 ++
       interface/src/scripting/Audio.cpp            |  25 ++++
       interface/src/scripting/Audio.h              |  14 +-
       libraries/audio-client/src/AudioClient.cpp   |   8 +
       libraries/audio-client/src/AudioClient.h     |   5 +
       scripts/system/audioMuteOverlay.js           | 146 ++++++++++++-------
       6 files changed, 155 insertions(+), 56 deletions(-)
      
      diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml
      index c8dd83cd62..34ae64aee8 100644
      --- a/interface/resources/qml/hifi/audio/Audio.qml
      +++ b/interface/resources/qml/hifi/audio/Audio.qml
      @@ -159,6 +159,19 @@ Rectangle {
                           onXChanged: rightMostInputLevelPos = x + width
                       }
                   }
      +
      +            RowLayout {
      +                spacing: muteMic.spacing*2;
      +                AudioControls.CheckBox {
      +                    spacing: muteMic.spacing
      +                    text: qsTr("Warn when muted");
      +                    checked: AudioScriptingInterface.warnWhenMuted;
      +                    onClicked: {
      +                        AudioScriptingInterface.warnWhenMuted = checked;
      +                        checked = Qt.binding(function() { return AudioScriptingInterface.warnWhenMuted; }); // restore binding
      +                    }
      +                }
      +            }
               }
       
               Separator {}
      diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp
      index bb40f69b0b..4a4b3c146b 100644
      --- a/interface/src/scripting/Audio.cpp
      +++ b/interface/src/scripting/Audio.cpp
      @@ -25,6 +25,7 @@ QString Audio::DESKTOP { "Desktop" };
       QString Audio::HMD { "VR" };
       
       Setting::Handle enableNoiseReductionSetting { QStringList { Audio::AUDIO, "NoiseReduction" }, true };
      +Setting::Handle enableWarnWhenMutedSetting { QStringList { Audio::AUDIO, "WarnWhenMuted" }, true };
       Setting::Handle mutedSetting { QStringList{ Audio::AUDIO, "MuteMicrophone" }, false };
       
       
      @@ -39,10 +40,12 @@ Audio::Audio() : _devices(_contextIsHMD) {
           auto client = DependencyManager::get().data();
           connect(client, &AudioClient::muteToggled, this, &Audio::setMuted);
           connect(client, &AudioClient::noiseReductionChanged, this, &Audio::enableNoiseReduction);
      +    connect(client, &AudioClient::warnWhenMutedChanged, this, &Audio::enableWarnWhenMuted);
           connect(client, &AudioClient::inputLoudnessChanged, this, &Audio::onInputLoudnessChanged);
           connect(client, &AudioClient::inputVolumeChanged, this, &Audio::setInputVolume);
           connect(this, &Audio::contextChanged, &_devices, &AudioDevices::onContextChanged);
           enableNoiseReduction(enableNoiseReductionSetting.get());
      +    enableWarnWhenMuted(enableWarnWhenMutedSetting.get());
           onContextChanged();
           setMuted(mutedSetting.get());
       }
      @@ -109,6 +112,28 @@ void Audio::enableNoiseReduction(bool enable) {
           }
       }
       
      +bool Audio::warnWhenMutedEnabled() const {
      +    return resultWithReadLock([&] {
      +        return _enableWarnWhenMuted;
      +    });
      +}
      +
      +void Audio::enableWarnWhenMuted(bool enable) {
      +    bool changed = false;
      +    withWriteLock([&] {
      +        if (_enableWarnWhenMuted != enable) {
      +            _enableWarnWhenMuted = enable;
      +            auto client = DependencyManager::get().data();
      +            QMetaObject::invokeMethod(client, "setWarnWhenMuted", Q_ARG(bool, enable), Q_ARG(bool, false));
      +            enableWarnWhenMutedSetting.set(enable);
      +            changed = true;
      +        }
      +    });
      +    if (changed) {
      +        emit warnWhenMutedChanged(enable);
      +    }
      +}
      +
       float Audio::getInputVolume() const {
           return resultWithReadLock([&] {
               return _inputVolume;
      diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h
      index e4dcba9130..7e216eb0b2 100644
      --- a/interface/src/scripting/Audio.h
      +++ b/interface/src/scripting/Audio.h
      @@ -58,6 +58,7 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable {
       
           Q_PROPERTY(bool muted READ isMuted WRITE setMuted NOTIFY mutedChanged)
           Q_PROPERTY(bool noiseReduction READ noiseReductionEnabled WRITE enableNoiseReduction NOTIFY noiseReductionChanged)
      +    Q_PROPERTY(bool warnWhenMuted READ warnWhenMutedEnabled WRITE enableWarnWhenMuted NOTIFY warnWhenMutedChanged)
           Q_PROPERTY(float inputVolume READ getInputVolume WRITE setInputVolume NOTIFY inputVolumeChanged)
           Q_PROPERTY(float inputLevel READ getInputLevel NOTIFY inputLevelChanged)
           Q_PROPERTY(bool clipping READ isClipping NOTIFY clippingChanged)
      @@ -75,6 +76,7 @@ public:
       
           bool isMuted() const;
           bool noiseReductionEnabled() const;
      +    bool warnWhenMutedEnabled() const;
           float getInputVolume() const;
           float getInputLevel() const;
           bool isClipping() const;
      @@ -192,7 +194,7 @@ signals:
            * });
            */
           void mutedChanged(bool isMuted);
      -    
      +
           /**jsdoc
            * Triggered when the audio input noise reduction is enabled or disabled.
            * @function Audio.noiseReductionChanged
      @@ -201,6 +203,14 @@ signals:
            */
           void noiseReductionChanged(bool isEnabled);
       
      +    /**jsdoc
      +     * Triggered when "warn when muted" is enabled or disabled.
      +     * @function Audio.warnWhenMutedChanged
      +     * @param {boolean} isEnabled - true if "warn when muted" is enabled, otherwise false.
      +     * @returns {Signal}
      +     */
      +    void warnWhenMutedChanged(bool isEnabled);
      +
           /**jsdoc
            * Triggered when the input audio volume changes.
            * @function Audio.inputVolumeChanged
      @@ -248,6 +258,7 @@ public slots:
       private slots:
           void setMuted(bool muted);
           void enableNoiseReduction(bool enable);
      +    void enableWarnWhenMuted(bool enable);
           void setInputVolume(float volume);
           void onInputLoudnessChanged(float loudness, bool isClipping);
       
      @@ -262,6 +273,7 @@ private:
           bool _isClipping { false };
           bool _isMuted { false };
           bool _enableNoiseReduction { true };  // Match default value of AudioClient::_isNoiseGateEnabled.
      +    bool _enableWarnWhenMuted { true };
           bool _contextIsHMD { false };
           AudioDevices* getDevices() { return &_devices; }
           AudioDevices _devices;
      diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp
      index 8c50a195ee..1c10d24f23 100644
      --- a/libraries/audio-client/src/AudioClient.cpp
      +++ b/libraries/audio-client/src/AudioClient.cpp
      @@ -1531,6 +1531,14 @@ void AudioClient::setNoiseReduction(bool enable, bool emitSignal) {
           }
       }
       
      +void AudioClient::setWarnWhenMuted(bool enable, bool emitSignal) {
      +    if (_warnWhenMuted != enable) {
      +        _warnWhenMuted = enable;
      +        if (emitSignal) {
      +            emit warnWhenMutedChanged(_warnWhenMuted);
      +        }
      +    }
      +}
       
       bool AudioClient::setIsStereoInput(bool isStereoInput) {
           bool stereoInputChanged = false;
      diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h
      index 29036b7c71..6d3483b0f8 100644
      --- a/libraries/audio-client/src/AudioClient.h
      +++ b/libraries/audio-client/src/AudioClient.h
      @@ -210,6 +210,9 @@ public slots:
           void setNoiseReduction(bool isNoiseGateEnabled, bool emitSignal = true);
           bool isNoiseReductionEnabled() const { return _isNoiseGateEnabled; }
       
      +    void setWarnWhenMuted(bool isNoiseGateEnabled, bool emitSignal = true);
      +    bool isWarnWhenMutedEnabled() const { return _warnWhenMuted; }
      +
           bool getLocalEcho() { return _shouldEchoLocally; }
           void setLocalEcho(bool localEcho) { _shouldEchoLocally = localEcho; }
           void toggleLocalEcho() { _shouldEchoLocally = !_shouldEchoLocally; }
      @@ -246,6 +249,7 @@ signals:
           void inputVolumeChanged(float volume);
           void muteToggled(bool muted);
           void noiseReductionChanged(bool noiseReductionEnabled);
      +    void warnWhenMutedChanged(bool warnWhenMutedEnabled);
           void mutedByMixer();
           void inputReceived(const QByteArray& inputSamples);
           void inputLoudnessChanged(float loudness, bool isClipping);
      @@ -365,6 +369,7 @@ private:
           bool _shouldEchoLocally;
           bool _shouldEchoToServer;
           bool _isNoiseGateEnabled;
      +    bool _warnWhenMuted;
       
           bool _reverb;
           AudioEffectOptions _scriptReverbOptions;
      diff --git a/scripts/system/audioMuteOverlay.js b/scripts/system/audioMuteOverlay.js
      index 14ac96c8c6..d759b7d885 100644
      --- a/scripts/system/audioMuteOverlay.js
      +++ b/scripts/system/audioMuteOverlay.js
      @@ -17,68 +17,104 @@
       
       (function() { // BEGIN LOCAL_SCOPE
       
      -   var lastInputLoudness = 0.0;
      -   var sampleRate = 8.0; // Hz
      -   var attackTC =  Math.exp(-1.0 / (sampleRate * 0.500)) // 500 milliseconds attack
      -   var releaseTC =  Math.exp(-1.0 / (sampleRate * 1.000)) // 1000 milliseconds release
      -   var holdReset = 2.0 * sampleRate; // 2 seconds hold
      -   var holdCount = 0;
      -   var warningOverlayID = null;
      +    var lastInputLoudness = 0.0;
      +    var sampleRate = 8.0; // Hz
      +    var attackTC =  Math.exp(-1.0 / (sampleRate * 0.500)); // 500 milliseconds attack
      +    var releaseTC =  Math.exp(-1.0 / (sampleRate * 1.000)); // 1000 milliseconds release
      +    var holdReset = 2.0 * sampleRate; // 2 seconds hold
      +    var holdCount = 0;
      +    var warningOverlayID = null;
      +    var pollInterval = null;
      +    var warningText = "Muted";
      +    var textDimensions = { x: 100, y: 50 };
       
      -   function showWarning() {
      -       if (warningOverlayID) {
      -           return;
      -       }
      -       warningOverlayID = Overlays.addOverlay("text3d", {
      -           name: "Muted-Warning",
      -           localPosition: { x: 0.2, y: -0.35, z: -1.0 },
      -           localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 0.0, z: 0.0, w: 1.0 }),
      -           text: "Warning: you are muted",
      -           textAlpha: 1,
      -           color: { red: 226, green: 51, blue: 77 },
      -           backgroundAlpha: 0,
      -           lineHeight: 0.042,
      -           visible: true,
      -           ignoreRayIntersection: true,
      -           drawInFront: true,
      -           grabbable: false,
      -           parentID: MyAvatar.SELF_ID,
      -           parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX")
      -       });
      -   };
      +    function showWarning() {
      +        if (warningOverlayID) {
      +            return;
      +        }
       
      -   function hideWarning() {
      -       if (!warningOverlayID) {
      -           return;
      -       }
      -       Overlays.deleteOverlay(warningOverlayID);
      -       warningOverlayID = null;
      -   }
      +        var windowWidth;
      +        var windowHeight;
      +        if (HMD.active) {
      +            var viewportDimension = Controller.getViewportDimensions();
      +            windowWidth = viewportDimension.x;
      +            windowHeight = viewportDimension.y;
      +        } else {
      +            windowWidth = Window.innerWidth;
      +            windowHeight = Window.innerHeight;
      +        }
       
      -   function cleanup() {
      -       Overlays.deleteOverlay(warningOverlayID);
      -   }
      +        warningOverlayID = Overlays.addOverlay("text", {
      +            name: "Muted-Warning",
      +            font: { size: 36 },
      +            text: warningText,
      +            x: windowWidth / 2 - textDimensions.x / 2,
      +            y: windowHeight / 2 - textDimensions.y / 2,
      +            width: textDimensions.x,
      +            height: textDimensions.y,
      +            textColor: { red: 226, green: 51, blue: 77 },
      +            backgroundAlpha: 0,
      +            visible: true
      +        });
      +    }
       
      -   Script.scriptEnding.connect(cleanup);
      +    function hideWarning() {
      +        if (!warningOverlayID) {
      +            return;
      +        }
      +        Overlays.deleteOverlay(warningOverlayID);
      +        warningOverlayID = null;
      +    }
       
      -   Script.setInterval(function() {
      +    function startPoll() {
      +        if (pollInterval) {
      +            return;
      +        }
      +        pollInterval = Script.setInterval(function() {
      +            var inputLoudness = Audio.inputLevel;
      +            var tc = (inputLoudness > lastInputLoudness) ? attackTC : releaseTC;
      +            inputLoudness += tc * (lastInputLoudness - inputLoudness);
      +            lastInputLoudness = inputLoudness;
       
      -       var inputLoudness = Audio.inputLevel;
      -       var tc = (inputLoudness > lastInputLoudness) ? attackTC : releaseTC;
      -       inputLoudness += tc * (lastInputLoudness - inputLoudness);
      -       lastInputLoudness = inputLoudness;
      +            if (inputLoudness > 0.1) {
      +                holdCount = holdReset;
      +            } else {
      +                holdCount = Math.max(holdCount - 1, 0);
      +            }
       
      -       if (Audio.muted && inputLoudness > 0.3) {
      -           holdCount = holdReset;
      -       } else {
      -           holdCount = Math.max(holdCount - 1, 0);
      -       }
      +            if (holdCount > 0) {
      +                showWarning();
      +            } else {
      +                hideWarning();
      +            }
      +        }, 1000.0 / sampleRate);
      +    }
       
      -       if (holdCount > 0) {
      -           showWarning();
      -       } else {
      -           hideWarning();
      -       }
      -   }, 1000.0 / sampleRate);
      +    function stopPoll() {
      +        if (!pollInterval) {
      +            return;
      +        }
      +        Script.clearInterval(pollInterval);
      +        pollInterval = null;
      +        hideWarning();
      +    }
      +
      +    function startOrStopPoll() {
      +        if (Audio.warnWhenMuted && Audio.muted) {
      +            startPoll();
      +        } else {
      +            stopPoll();
      +        }
      +    }
      +
      +    function cleanup() {
      +        stopPoll();
      +    }
      +
      +    Script.scriptEnding.connect(cleanup);
      +
      +    startOrStopPoll();
      +    Audio.mutedChanged.connect(startOrStopPoll);
      +    Audio.warnWhenMutedChanged.connect(startOrStopPoll);
       
       }()); // END LOCAL_SCOPE
      
      From f83f2b8226b17f2c7446534cd155b917d01a0cc1 Mon Sep 17 00:00:00 2001
      From: Seth Alves 
      Date: Wed, 20 Feb 2019 12:53:43 -0800
      Subject: [PATCH 315/474] keep muted warning in center of view for HMD
      
      ---
       scripts/system/audioMuteOverlay.js | 51 ++++++++++++++++++------------
       1 file changed, 30 insertions(+), 21 deletions(-)
      
      diff --git a/scripts/system/audioMuteOverlay.js b/scripts/system/audioMuteOverlay.js
      index d759b7d885..65793d1d87 100644
      --- a/scripts/system/audioMuteOverlay.js
      +++ b/scripts/system/audioMuteOverlay.js
      @@ -26,36 +26,45 @@
           var warningOverlayID = null;
           var pollInterval = null;
           var warningText = "Muted";
      -    var textDimensions = { x: 100, y: 50 };
       
           function showWarning() {
               if (warningOverlayID) {
                   return;
               }
       
      -        var windowWidth;
      -        var windowHeight;
               if (HMD.active) {
      -            var viewportDimension = Controller.getViewportDimensions();
      -            windowWidth = viewportDimension.x;
      -            windowHeight = viewportDimension.y;
      +            warningOverlayID = Overlays.addOverlay("text3d", {
      +                name: "Muted-Warning",
      +                localPosition: { x: 0, y: 0, z: -1.0 },
      +                localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 0.0, z: 0.0, w: 1.0 }),
      +                text: warningText,
      +                textAlpha: 1,
      +                textColor: { red: 226, green: 51, blue: 77 },
      +                backgroundAlpha: 0,
      +                lineHeight: 0.042,
      +                dimensions: { x: 0.11, y: 0.05 },
      +                visible: true,
      +                ignoreRayIntersection: true,
      +                drawInFront: true,
      +                grabbable: false,
      +                parentID: MyAvatar.SELF_ID,
      +                parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX")
      +            });
               } else {
      -            windowWidth = Window.innerWidth;
      -            windowHeight = Window.innerHeight;
      +            var textDimensions = { x: 100, y: 50 };
      +            warningOverlayID = Overlays.addOverlay("text", {
      +                name: "Muted-Warning",
      +                font: { size: 36 },
      +                text: warningText,
      +                x: Window.innerWidth / 2 - textDimensions.x / 2,
      +                y: Window.innerHeight / 2 - textDimensions.y / 2,
      +                width: textDimensions.x,
      +                height: textDimensions.y,
      +                textColor: { red: 226, green: 51, blue: 77 },
      +                backgroundAlpha: 0,
      +                visible: true
      +            });
               }
      -
      -        warningOverlayID = Overlays.addOverlay("text", {
      -            name: "Muted-Warning",
      -            font: { size: 36 },
      -            text: warningText,
      -            x: windowWidth / 2 - textDimensions.x / 2,
      -            y: windowHeight / 2 - textDimensions.y / 2,
      -            width: textDimensions.x,
      -            height: textDimensions.y,
      -            textColor: { red: 226, green: 51, blue: 77 },
      -            backgroundAlpha: 0,
      -            visible: true
      -        });
           }
       
           function hideWarning() {
      
      From 216b53dcb0975aba559e410393244ce44b0cc691 Mon Sep 17 00:00:00 2001
      From: Seth Alves 
      Date: Wed, 20 Feb 2019 13:07:24 -0800
      Subject: [PATCH 316/474] attempt to take background noise into account when
       triggering mute warning
      
      ---
       scripts/system/audioMuteOverlay.js | 33 ++++++++++++++++++++++--------
       1 file changed, 24 insertions(+), 9 deletions(-)
      
      diff --git a/scripts/system/audioMuteOverlay.js b/scripts/system/audioMuteOverlay.js
      index 65793d1d87..96f6d636dc 100644
      --- a/scripts/system/audioMuteOverlay.js
      +++ b/scripts/system/audioMuteOverlay.js
      @@ -13,14 +13,22 @@
       
       "use strict";
       
      -/* global Audio, Script, Overlays, Quat, MyAvatar */
      +/* global Audio, Script, Overlays, Quat, MyAvatar, HMD */
       
       (function() { // BEGIN LOCAL_SCOPE
       
      -    var lastInputLoudness = 0.0;
      +    var lastShortTermInputLoudness = 0.0;
      +    var lastLongTermInputLoudness = 0.0;
           var sampleRate = 8.0; // Hz
      -    var attackTC =  Math.exp(-1.0 / (sampleRate * 0.500)); // 500 milliseconds attack
      -    var releaseTC =  Math.exp(-1.0 / (sampleRate * 1.000)); // 1000 milliseconds release
      +
      +    var shortTermAttackTC =  Math.exp(-1.0 / (sampleRate * 0.500)); // 500 milliseconds attack
      +    var shortTermReleaseTC =  Math.exp(-1.0 / (sampleRate * 1.000)); // 1000 milliseconds release
      +
      +    var longTermAttackTC =  Math.exp(-1.0 / (sampleRate * 5.0)); // 5 second attack
      +    var longTermReleaseTC =  Math.exp(-1.0 / (sampleRate * 10.0)); // 10 seconds release
      +
      +    var activationThreshold = 0.05; // how much louder short-term needs to be than long-term to trigger warning
      +
           var holdReset = 2.0 * sampleRate; // 2 seconds hold
           var holdCount = 0;
           var warningOverlayID = null;
      @@ -80,12 +88,19 @@
                   return;
               }
               pollInterval = Script.setInterval(function() {
      -            var inputLoudness = Audio.inputLevel;
      -            var tc = (inputLoudness > lastInputLoudness) ? attackTC : releaseTC;
      -            inputLoudness += tc * (lastInputLoudness - inputLoudness);
      -            lastInputLoudness = inputLoudness;
      +            var shortTermInputLoudness = Audio.inputLevel;
      +            var longTermInputLoudness = shortTermInputLoudness;
       
      -            if (inputLoudness > 0.1) {
      +            var shortTc = (shortTermInputLoudness > lastShortTermInputLoudness) ? shortTermAttackTC : shortTermReleaseTC;
      +            var longTc = (longTermInputLoudness > lastLongTermInputLoudness) ? longTermAttackTC : longTermReleaseTC;
      +
      +            shortTermInputLoudness += shortTc * (lastShortTermInputLoudness - shortTermInputLoudness);
      +            longTermInputLoudness += longTc * (lastLongTermInputLoudness - longTermInputLoudness);
      +
      +            lastShortTermInputLoudness = shortTermInputLoudness;
      +            lastLongTermInputLoudness = longTermInputLoudness;
      +
      +            if (shortTermInputLoudness > lastLongTermInputLoudness + activationThreshold) {
                       holdCount = holdReset;
                   } else {
                       holdCount = Math.max(holdCount - 1, 0);
      
      From 6ee236e783e8a1faf2075b0cf462f11770f6a486 Mon Sep 17 00:00:00 2001
      From: Seth Alves 
      Date: Thu, 21 Feb 2019 14:04:08 -0800
      Subject: [PATCH 317/474] added a button to enable server loopback of audio
      
      ---
       interface/resources/qml/hifi/audio/Audio.qml  |  7 ++
       .../qml/hifi/audio/LoopbackAudio.qml          | 75 +++++++++++++++++++
       2 files changed, 82 insertions(+)
       create mode 100644 interface/resources/qml/hifi/audio/LoopbackAudio.qml
      
      diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml
      index 34ae64aee8..e340ec5003 100644
      --- a/interface/resources/qml/hifi/audio/Audio.qml
      +++ b/interface/resources/qml/hifi/audio/Audio.qml
      @@ -313,5 +313,12 @@ Rectangle {
                            (bar.currentIndex === 0 && !isVR);
                   anchors { left: parent.left; leftMargin: margins.paddings }
               }
      +        LoopbackAudio {
      +            x: margins.paddings
      +
      +            visible: (bar.currentIndex === 1 && isVR) ||
      +                (bar.currentIndex === 0 && !isVR);
      +            anchors { left: parent.left; leftMargin: margins.paddings }
      +        }
           }
       }
      diff --git a/interface/resources/qml/hifi/audio/LoopbackAudio.qml b/interface/resources/qml/hifi/audio/LoopbackAudio.qml
      new file mode 100644
      index 0000000000..6d5f8d88fd
      --- /dev/null
      +++ b/interface/resources/qml/hifi/audio/LoopbackAudio.qml
      @@ -0,0 +1,75 @@
      +//
      +//  LoopbackAudio.qml
      +//  qml/hifi/audio
      +//
      +//  Created by Seth Alves on 2019-2-18
      +//  Copyright 2019 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
      +//
      +
      +import QtQuick 2.7
      +import QtQuick.Controls 2.2
      +import QtQuick.Layouts 1.3
      +
      +import stylesUit 1.0
      +import controlsUit 1.0 as HifiControls
      +
      +RowLayout {
      +    property bool audioLoopedBack: false;
      +    function startAudioLoopback() {
      +        if (!audioLoopedBack) {
      +            audioLoopedBack = true;
      +            AudioScope.setServerEcho(true);
      +        }
      +    }
      +    function stopAudioLoopback () {
      +        if (audioLoopedBack) {
      +            audioLoopedBack = false;
      +            AudioScope.setServerEcho(false);
      +        }
      +    }
      +
      +    Component.onDestruction: stopAudioLoopback();
      +    onVisibleChanged: {
      +        if (!visible) {
      +            stopAudioLoopback();
      +        }
      +    }
      +
      +    HifiConstants { id: hifi; }
      +
      +    Button {
      +        id: control
      +        background: Rectangle {
      +            implicitWidth: 20;
      +            implicitHeight: 20;
      +            radius: hifi.buttons.radius;
      +            gradient: Gradient {
      +                GradientStop {
      +                    position: 0.2;
      +                    color: audioLoopedBack ? hifi.buttons.colorStart[hifi.buttons.blue] : hifi.buttons.colorStart[hifi.buttons.black];
      +                }
      +                GradientStop {
      +                    position: 1.0;
      +                    color: audioLoopedBack ? hifi.buttons.colorFinish[hifi.buttons.blue] : hifi.buttons.colorFinish[hifi.buttons.black];
      +                }
      +            }
      +        }
      +        contentItem: HiFiGlyphs {
      +            size: 14;
      +            color: (control.pressed || control.hovered) ? (audioLoopedBack ? "black" : hifi.colors.primaryHighlight) : "white";
      +            text: audioLoopedBack ? hifi.glyphs.stop_square : hifi.glyphs.playback_play;
      +        }
      +
      +        onClicked: audioLoopedBack ? stopAudioLoopback() : startAudioLoopback();
      +    }
      +
      +    RalewayRegular {
      +        Layout.leftMargin: 2;
      +        size: 14;
      +        color: "white";
      +        text: audioLoopedBack ? qsTr("Disable Audio Loopback") : qsTr("Enable Audio Loopback");
      +    }
      +}
      
      From d5e8cba1eec07a12c6cf6a6f8e1919a015b541fb Mon Sep 17 00:00:00 2001
      From: Seth Alves 
      Date: Fri, 22 Feb 2019 09:33:23 -0800
      Subject: [PATCH 318/474] make server-audio-loopback button work in HMDs
      
      ---
       interface/resources/qml/hifi/audio/LoopbackAudio.qml |  4 ++--
       libraries/audio-client/src/AudioClient.h             | 12 ++++++------
       libraries/audio/src/AbstractAudioInterface.h         |  9 ++++++++-
       .../script-engine/src/AudioScriptingInterface.cpp    | 12 ++++++++++++
       .../script-engine/src/AudioScriptingInterface.h      | 12 ++++++++++++
       5 files changed, 40 insertions(+), 9 deletions(-)
      
      diff --git a/interface/resources/qml/hifi/audio/LoopbackAudio.qml b/interface/resources/qml/hifi/audio/LoopbackAudio.qml
      index 6d5f8d88fd..2f0dbe5950 100644
      --- a/interface/resources/qml/hifi/audio/LoopbackAudio.qml
      +++ b/interface/resources/qml/hifi/audio/LoopbackAudio.qml
      @@ -21,13 +21,13 @@ RowLayout {
           function startAudioLoopback() {
               if (!audioLoopedBack) {
                   audioLoopedBack = true;
      -            AudioScope.setServerEcho(true);
      +            AudioScriptingInterface.setServerEcho(true);
               }
           }
           function stopAudioLoopback () {
               if (audioLoopedBack) {
                   audioLoopedBack = false;
      -            AudioScope.setServerEcho(false);
      +            AudioScriptingInterface.setServerEcho(false);
               }
           }
       
      diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h
      index 6d3483b0f8..b9648219a5 100644
      --- a/libraries/audio-client/src/AudioClient.h
      +++ b/libraries/audio-client/src/AudioClient.h
      @@ -213,13 +213,13 @@ public slots:
           void setWarnWhenMuted(bool isNoiseGateEnabled, bool emitSignal = true);
           bool isWarnWhenMutedEnabled() const { return _warnWhenMuted; }
       
      -    bool getLocalEcho() { return _shouldEchoLocally; }
      -    void setLocalEcho(bool localEcho) { _shouldEchoLocally = localEcho; }
      -    void toggleLocalEcho() { _shouldEchoLocally = !_shouldEchoLocally; }
      +    virtual bool getLocalEcho() override { return _shouldEchoLocally; }
      +    virtual void setLocalEcho(bool localEcho) override { _shouldEchoLocally = localEcho; }
      +    virtual void toggleLocalEcho() override { _shouldEchoLocally = !_shouldEchoLocally; }
       
      -    bool getServerEcho() { return _shouldEchoToServer; }
      -    void setServerEcho(bool serverEcho) { _shouldEchoToServer = serverEcho; }
      -    void toggleServerEcho() { _shouldEchoToServer = !_shouldEchoToServer; }
      +    virtual bool getServerEcho() override { return _shouldEchoToServer; }
      +    virtual void setServerEcho(bool serverEcho) override { _shouldEchoToServer = serverEcho; }
      +    virtual void toggleServerEcho() override { _shouldEchoToServer = !_shouldEchoToServer; }
       
           void processReceivedSamples(const QByteArray& inputBuffer, QByteArray& outputBuffer);
           void sendMuteEnvironmentPacket();
      diff --git a/libraries/audio/src/AbstractAudioInterface.h b/libraries/audio/src/AbstractAudioInterface.h
      index 0f075ab224..e9e40e95f9 100644
      --- a/libraries/audio/src/AbstractAudioInterface.h
      +++ b/libraries/audio/src/AbstractAudioInterface.h
      @@ -45,9 +45,16 @@ public slots:
           virtual bool shouldLoopbackInjectors() { return false; }
       
           virtual bool setIsStereoInput(bool stereo) = 0;
      -
           virtual bool isStereoInput() = 0;
       
      +    virtual bool getLocalEcho() = 0;
      +    virtual void setLocalEcho(bool localEcho) = 0;
      +    virtual void toggleLocalEcho() = 0;
      +
      +    virtual bool getServerEcho() = 0;
      +    virtual void setServerEcho(bool serverEcho) = 0;
      +    virtual void toggleServerEcho() = 0;
      +
       signals:
           void isStereoInputChanged(bool isStereo);
       };
      diff --git a/libraries/script-engine/src/AudioScriptingInterface.cpp b/libraries/script-engine/src/AudioScriptingInterface.cpp
      index 8e54d2d5de..b12b55c3f7 100644
      --- a/libraries/script-engine/src/AudioScriptingInterface.cpp
      +++ b/libraries/script-engine/src/AudioScriptingInterface.cpp
      @@ -88,3 +88,15 @@ bool AudioScriptingInterface::isStereoInput() {
           }
           return stereoEnabled;
       }
      +
      +void AudioScriptingInterface::setServerEcho(bool serverEcho) {
      +    if (_localAudioInterface) {
      +        QMetaObject::invokeMethod(_localAudioInterface, "setServerEcho", Q_ARG(bool, serverEcho));
      +    }
      +}
      +
      +void AudioScriptingInterface::setLocalEcho(bool localEcho) {
      +    if (_localAudioInterface) {
      +        QMetaObject::invokeMethod(_localAudioInterface, "setLocalEcho", Q_ARG(bool, localEcho));
      +    }
      +}
      diff --git a/libraries/script-engine/src/AudioScriptingInterface.h b/libraries/script-engine/src/AudioScriptingInterface.h
      index d2f886d2dd..23cc02248d 100644
      --- a/libraries/script-engine/src/AudioScriptingInterface.h
      +++ b/libraries/script-engine/src/AudioScriptingInterface.h
      @@ -66,6 +66,18 @@ public:
               _localAudioInterface->getAudioSolo().reset();
           }
       
      +    /**jsdoc
      +     * @function Audio.setServerEcho
      +     * @parm {boolean} serverEcho
      +     */
      +    Q_INVOKABLE void setServerEcho(bool serverEcho);
      +
      +    /**jsdoc
      +     * @function Audio.setLocalEcho
      +     * @parm {boolean} localEcho
      +     */
      +    Q_INVOKABLE void setLocalEcho(bool localEcho);
      +
       protected:
           AudioScriptingInterface() = default;
       
      
      From 96142160066eea2064789078457015d193390536 Mon Sep 17 00:00:00 2001
      From: Seth Alves 
      Date: Sat, 23 Feb 2019 14:23:09 -0800
      Subject: [PATCH 319/474] don't disable server echo when audio qml page is
       closed
      
      ---
       .../qml/hifi/audio/LoopbackAudio.qml          |  9 +-----
       .../src/AudioScriptingInterface.cpp           | 28 +++++++++++++++++++
       .../src/AudioScriptingInterface.h             | 21 ++++++++++++++
       3 files changed, 50 insertions(+), 8 deletions(-)
      
      diff --git a/interface/resources/qml/hifi/audio/LoopbackAudio.qml b/interface/resources/qml/hifi/audio/LoopbackAudio.qml
      index 2f0dbe5950..3ecf09c948 100644
      --- a/interface/resources/qml/hifi/audio/LoopbackAudio.qml
      +++ b/interface/resources/qml/hifi/audio/LoopbackAudio.qml
      @@ -17,7 +17,7 @@ import stylesUit 1.0
       import controlsUit 1.0 as HifiControls
       
       RowLayout {
      -    property bool audioLoopedBack: false;
      +    property bool audioLoopedBack: AudioScriptingInterface.getServerEcho();
           function startAudioLoopback() {
               if (!audioLoopedBack) {
                   audioLoopedBack = true;
      @@ -31,13 +31,6 @@ RowLayout {
               }
           }
       
      -    Component.onDestruction: stopAudioLoopback();
      -    onVisibleChanged: {
      -        if (!visible) {
      -            stopAudioLoopback();
      -        }
      -    }
      -
           HifiConstants { id: hifi; }
       
           Button {
      diff --git a/libraries/script-engine/src/AudioScriptingInterface.cpp b/libraries/script-engine/src/AudioScriptingInterface.cpp
      index b12b55c3f7..65d71e46e6 100644
      --- a/libraries/script-engine/src/AudioScriptingInterface.cpp
      +++ b/libraries/script-engine/src/AudioScriptingInterface.cpp
      @@ -89,14 +89,42 @@ bool AudioScriptingInterface::isStereoInput() {
           return stereoEnabled;
       }
       
      +bool AudioScriptingInterface::getServerEcho() {
      +    bool serverEchoEnabled = false;
      +    if (_localAudioInterface) {
      +        serverEchoEnabled = _localAudioInterface->getServerEcho();
      +    }
      +    return serverEchoEnabled;
      +}
      +
       void AudioScriptingInterface::setServerEcho(bool serverEcho) {
           if (_localAudioInterface) {
               QMetaObject::invokeMethod(_localAudioInterface, "setServerEcho", Q_ARG(bool, serverEcho));
           }
       }
       
      +void AudioScriptingInterface::toggleServerEcho() {
      +    if (_localAudioInterface) {
      +        QMetaObject::invokeMethod(_localAudioInterface, "toggleServerEcho");
      +    }
      +}
      +
      +bool AudioScriptingInterface::getLocalEcho() {
      +    bool localEchoEnabled = false;
      +    if (_localAudioInterface) {
      +        localEchoEnabled = _localAudioInterface->getLocalEcho();
      +    }
      +    return localEchoEnabled;
      +}
      +
       void AudioScriptingInterface::setLocalEcho(bool localEcho) {
           if (_localAudioInterface) {
               QMetaObject::invokeMethod(_localAudioInterface, "setLocalEcho", Q_ARG(bool, localEcho));
           }
       }
      +
      +void AudioScriptingInterface::toggleLocalEcho() {
      +    if (_localAudioInterface) {
      +        QMetaObject::invokeMethod(_localAudioInterface, "toggleLocalEcho");
      +    }
      +}
      diff --git a/libraries/script-engine/src/AudioScriptingInterface.h b/libraries/script-engine/src/AudioScriptingInterface.h
      index 23cc02248d..a6801dcdcb 100644
      --- a/libraries/script-engine/src/AudioScriptingInterface.h
      +++ b/libraries/script-engine/src/AudioScriptingInterface.h
      @@ -66,18 +66,39 @@ public:
               _localAudioInterface->getAudioSolo().reset();
           }
       
      +    /**jsdoc
      +     * @function Audio.getServerEcho
      +     */
      +    Q_INVOKABLE bool getServerEcho();
      +
           /**jsdoc
            * @function Audio.setServerEcho
            * @parm {boolean} serverEcho
            */
           Q_INVOKABLE void setServerEcho(bool serverEcho);
       
      +    /**jsdoc
      +     * @function Audio.toggleServerEcho
      +     */
      +    Q_INVOKABLE void toggleServerEcho();
      +
      +    /**jsdoc
      +     * @function Audio.getLocalEcho
      +     */
      +    Q_INVOKABLE bool getLocalEcho();
      +
           /**jsdoc
            * @function Audio.setLocalEcho
            * @parm {boolean} localEcho
            */
           Q_INVOKABLE void setLocalEcho(bool localEcho);
       
      +    /**jsdoc
      +     * @function Audio.toggleLocalEcho
      +     */
      +    Q_INVOKABLE void toggleLocalEcho();
      +
      +
       protected:
           AudioScriptingInterface() = default;
       
      
      From 4187104b1788955f050a20304bb1ccce737fd370 Mon Sep 17 00:00:00 2001
      From: Wayne Chen 
      Date: Mon, 4 Mar 2019 14:12:09 -0800
      Subject: [PATCH 320/474] culling mute state
      
      ---
       interface/resources/qml/hifi/audio/Audio.qml |  13 --
       interface/src/scripting/Audio.cpp            |  29 ---
       interface/src/scripting/Audio.h              |  12 --
       libraries/audio-client/src/AudioClient.cpp   |   9 -
       libraries/audio-client/src/AudioClient.h     |   5 -
       scripts/system/audioMuteOverlay.js           | 202 ++++++++-----------
       6 files changed, 81 insertions(+), 189 deletions(-)
      
      diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml
      index e340ec5003..aa64af22a3 100644
      --- a/interface/resources/qml/hifi/audio/Audio.qml
      +++ b/interface/resources/qml/hifi/audio/Audio.qml
      @@ -159,19 +159,6 @@ Rectangle {
                           onXChanged: rightMostInputLevelPos = x + width
                       }
                   }
      -
      -            RowLayout {
      -                spacing: muteMic.spacing*2;
      -                AudioControls.CheckBox {
      -                    spacing: muteMic.spacing
      -                    text: qsTr("Warn when muted");
      -                    checked: AudioScriptingInterface.warnWhenMuted;
      -                    onClicked: {
      -                        AudioScriptingInterface.warnWhenMuted = checked;
      -                        checked = Qt.binding(function() { return AudioScriptingInterface.warnWhenMuted; }); // restore binding
      -                    }
      -                }
      -            }
               }
       
               Separator {}
      diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp
      index 4a4b3c146b..2c4c29ff65 100644
      --- a/interface/src/scripting/Audio.cpp
      +++ b/interface/src/scripting/Audio.cpp
      @@ -25,9 +25,6 @@ QString Audio::DESKTOP { "Desktop" };
       QString Audio::HMD { "VR" };
       
       Setting::Handle enableNoiseReductionSetting { QStringList { Audio::AUDIO, "NoiseReduction" }, true };
      -Setting::Handle enableWarnWhenMutedSetting { QStringList { Audio::AUDIO, "WarnWhenMuted" }, true };
      -Setting::Handle mutedSetting { QStringList{ Audio::AUDIO, "MuteMicrophone" }, false };
      -
       
       float Audio::loudnessToLevel(float loudness) {
           float level = loudness * (1/32768.0f);  // level in [0, 1]
      @@ -40,14 +37,11 @@ Audio::Audio() : _devices(_contextIsHMD) {
           auto client = DependencyManager::get().data();
           connect(client, &AudioClient::muteToggled, this, &Audio::setMuted);
           connect(client, &AudioClient::noiseReductionChanged, this, &Audio::enableNoiseReduction);
      -    connect(client, &AudioClient::warnWhenMutedChanged, this, &Audio::enableWarnWhenMuted);
           connect(client, &AudioClient::inputLoudnessChanged, this, &Audio::onInputLoudnessChanged);
           connect(client, &AudioClient::inputVolumeChanged, this, &Audio::setInputVolume);
           connect(this, &Audio::contextChanged, &_devices, &AudioDevices::onContextChanged);
           enableNoiseReduction(enableNoiseReductionSetting.get());
      -    enableWarnWhenMuted(enableWarnWhenMutedSetting.get());
           onContextChanged();
      -    setMuted(mutedSetting.get());
       }
       
       bool Audio::startRecording(const QString& filepath) {
      @@ -79,7 +73,6 @@ void Audio::setMuted(bool isMuted) {
           withWriteLock([&] {
               if (_isMuted != isMuted) {
                   _isMuted = isMuted;
      -            mutedSetting.set(_isMuted);
                   auto client = DependencyManager::get().data();
                   QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false));
                   changed = true;
      @@ -112,28 +105,6 @@ void Audio::enableNoiseReduction(bool enable) {
           }
       }
       
      -bool Audio::warnWhenMutedEnabled() const {
      -    return resultWithReadLock([&] {
      -        return _enableWarnWhenMuted;
      -    });
      -}
      -
      -void Audio::enableWarnWhenMuted(bool enable) {
      -    bool changed = false;
      -    withWriteLock([&] {
      -        if (_enableWarnWhenMuted != enable) {
      -            _enableWarnWhenMuted = enable;
      -            auto client = DependencyManager::get().data();
      -            QMetaObject::invokeMethod(client, "setWarnWhenMuted", Q_ARG(bool, enable), Q_ARG(bool, false));
      -            enableWarnWhenMutedSetting.set(enable);
      -            changed = true;
      -        }
      -    });
      -    if (changed) {
      -        emit warnWhenMutedChanged(enable);
      -    }
      -}
      -
       float Audio::getInputVolume() const {
           return resultWithReadLock([&] {
               return _inputVolume;
      diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h
      index 7e216eb0b2..fcf3c181da 100644
      --- a/interface/src/scripting/Audio.h
      +++ b/interface/src/scripting/Audio.h
      @@ -58,7 +58,6 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable {
       
           Q_PROPERTY(bool muted READ isMuted WRITE setMuted NOTIFY mutedChanged)
           Q_PROPERTY(bool noiseReduction READ noiseReductionEnabled WRITE enableNoiseReduction NOTIFY noiseReductionChanged)
      -    Q_PROPERTY(bool warnWhenMuted READ warnWhenMutedEnabled WRITE enableWarnWhenMuted NOTIFY warnWhenMutedChanged)
           Q_PROPERTY(float inputVolume READ getInputVolume WRITE setInputVolume NOTIFY inputVolumeChanged)
           Q_PROPERTY(float inputLevel READ getInputLevel NOTIFY inputLevelChanged)
           Q_PROPERTY(bool clipping READ isClipping NOTIFY clippingChanged)
      @@ -76,7 +75,6 @@ public:
       
           bool isMuted() const;
           bool noiseReductionEnabled() const;
      -    bool warnWhenMutedEnabled() const;
           float getInputVolume() const;
           float getInputLevel() const;
           bool isClipping() const;
      @@ -203,14 +201,6 @@ signals:
            */
           void noiseReductionChanged(bool isEnabled);
       
      -    /**jsdoc
      -     * Triggered when "warn when muted" is enabled or disabled.
      -     * @function Audio.warnWhenMutedChanged
      -     * @param {boolean} isEnabled - true if "warn when muted" is enabled, otherwise false.
      -     * @returns {Signal}
      -     */
      -    void warnWhenMutedChanged(bool isEnabled);
      -
           /**jsdoc
            * Triggered when the input audio volume changes.
            * @function Audio.inputVolumeChanged
      @@ -258,7 +248,6 @@ public slots:
       private slots:
           void setMuted(bool muted);
           void enableNoiseReduction(bool enable);
      -    void enableWarnWhenMuted(bool enable);
           void setInputVolume(float volume);
           void onInputLoudnessChanged(float loudness, bool isClipping);
       
      @@ -273,7 +262,6 @@ private:
           bool _isClipping { false };
           bool _isMuted { false };
           bool _enableNoiseReduction { true };  // Match default value of AudioClient::_isNoiseGateEnabled.
      -    bool _enableWarnWhenMuted { true };
           bool _contextIsHMD { false };
           AudioDevices* getDevices() { return &_devices; }
           AudioDevices _devices;
      diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp
      index 1c10d24f23..b2e6167ffa 100644
      --- a/libraries/audio-client/src/AudioClient.cpp
      +++ b/libraries/audio-client/src/AudioClient.cpp
      @@ -1531,15 +1531,6 @@ void AudioClient::setNoiseReduction(bool enable, bool emitSignal) {
           }
       }
       
      -void AudioClient::setWarnWhenMuted(bool enable, bool emitSignal) {
      -    if (_warnWhenMuted != enable) {
      -        _warnWhenMuted = enable;
      -        if (emitSignal) {
      -            emit warnWhenMutedChanged(_warnWhenMuted);
      -        }
      -    }
      -}
      -
       bool AudioClient::setIsStereoInput(bool isStereoInput) {
           bool stereoInputChanged = false;
           if (isStereoInput != _isStereoInput && _inputDeviceInfo.supportedChannelCounts().contains(2)) {
      diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h
      index b9648219a5..87e0f68e72 100644
      --- a/libraries/audio-client/src/AudioClient.h
      +++ b/libraries/audio-client/src/AudioClient.h
      @@ -210,9 +210,6 @@ public slots:
           void setNoiseReduction(bool isNoiseGateEnabled, bool emitSignal = true);
           bool isNoiseReductionEnabled() const { return _isNoiseGateEnabled; }
       
      -    void setWarnWhenMuted(bool isNoiseGateEnabled, bool emitSignal = true);
      -    bool isWarnWhenMutedEnabled() const { return _warnWhenMuted; }
      -
           virtual bool getLocalEcho() override { return _shouldEchoLocally; }
           virtual void setLocalEcho(bool localEcho) override { _shouldEchoLocally = localEcho; }
           virtual void toggleLocalEcho() override { _shouldEchoLocally = !_shouldEchoLocally; }
      @@ -249,7 +246,6 @@ signals:
           void inputVolumeChanged(float volume);
           void muteToggled(bool muted);
           void noiseReductionChanged(bool noiseReductionEnabled);
      -    void warnWhenMutedChanged(bool warnWhenMutedEnabled);
           void mutedByMixer();
           void inputReceived(const QByteArray& inputSamples);
           void inputLoudnessChanged(float loudness, bool isClipping);
      @@ -369,7 +365,6 @@ private:
           bool _shouldEchoLocally;
           bool _shouldEchoToServer;
           bool _isNoiseGateEnabled;
      -    bool _warnWhenMuted;
       
           bool _reverb;
           AudioEffectOptions _scriptReverbOptions;
      diff --git a/scripts/system/audioMuteOverlay.js b/scripts/system/audioMuteOverlay.js
      index 96f6d636dc..c597f75bca 100644
      --- a/scripts/system/audioMuteOverlay.js
      +++ b/scripts/system/audioMuteOverlay.js
      @@ -1,144 +1,104 @@
      +"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 }] */
       //
       // audioMuteOverlay.js
       //
       // client script that creates an overlay to provide mute feedback
       //
       // Created by Triplelexx on 17/03/09
      -// Reworked by Seth Alves on 2019-2-17
       // 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
       //
       
      -"use strict";
      +(function () { // BEGIN LOCAL_SCOPE
      +    var utilsPath = Script.resolvePath('../developer/libraries/utils.js');
      +    Script.include(utilsPath);
       
      -/* global Audio, Script, Overlays, Quat, MyAvatar, HMD */
      +    var TWEEN_SPEED = 0.025;
      +    var MIX_AMOUNT = 0.25;
       
      -(function() { // BEGIN LOCAL_SCOPE
      +    var overlayPosition = Vec3.ZERO;
      +    var tweenPosition = 0;
      +    var startColor = {
      +        red: 170,
      +        green: 170,
      +        blue: 170
      +    };
      +    var endColor = {
      +        red: 255,
      +        green: 0,
      +        blue: 0
      +    };
      +    var overlayID;
       
      -    var lastShortTermInputLoudness = 0.0;
      -    var lastLongTermInputLoudness = 0.0;
      -    var sampleRate = 8.0; // Hz
      +    Script.update.connect(update);
      +    Script.scriptEnding.connect(cleanup);
       
      -    var shortTermAttackTC =  Math.exp(-1.0 / (sampleRate * 0.500)); // 500 milliseconds attack
      -    var shortTermReleaseTC =  Math.exp(-1.0 / (sampleRate * 1.000)); // 1000 milliseconds release
      -
      -    var longTermAttackTC =  Math.exp(-1.0 / (sampleRate * 5.0)); // 5 second attack
      -    var longTermReleaseTC =  Math.exp(-1.0 / (sampleRate * 10.0)); // 10 seconds release
      -
      -    var activationThreshold = 0.05; // how much louder short-term needs to be than long-term to trigger warning
      -
      -    var holdReset = 2.0 * sampleRate; // 2 seconds hold
      -    var holdCount = 0;
      -    var warningOverlayID = null;
      -    var pollInterval = null;
      -    var warningText = "Muted";
      -
      -    function showWarning() {
      -        if (warningOverlayID) {
      -            return;
      -        }
      -
      -        if (HMD.active) {
      -            warningOverlayID = Overlays.addOverlay("text3d", {
      -                name: "Muted-Warning",
      -                localPosition: { x: 0, y: 0, z: -1.0 },
      -                localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 0.0, z: 0.0, w: 1.0 }),
      -                text: warningText,
      -                textAlpha: 1,
      -                textColor: { red: 226, green: 51, blue: 77 },
      -                backgroundAlpha: 0,
      -                lineHeight: 0.042,
      -                dimensions: { x: 0.11, y: 0.05 },
      -                visible: true,
      -                ignoreRayIntersection: true,
      -                drawInFront: true,
      -                grabbable: false,
      -                parentID: MyAvatar.SELF_ID,
      -                parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX")
      -            });
      -        } else {
      -            var textDimensions = { x: 100, y: 50 };
      -            warningOverlayID = Overlays.addOverlay("text", {
      -                name: "Muted-Warning",
      -                font: { size: 36 },
      -                text: warningText,
      -                x: Window.innerWidth / 2 - textDimensions.x / 2,
      -                y: Window.innerHeight / 2 - textDimensions.y / 2,
      -                width: textDimensions.x,
      -                height: textDimensions.y,
      -                textColor: { red: 226, green: 51, blue: 77 },
      -                backgroundAlpha: 0,
      -                visible: true
      -            });
      -        }
      -    }
      -
      -    function hideWarning() {
      -        if (!warningOverlayID) {
      -            return;
      -        }
      -        Overlays.deleteOverlay(warningOverlayID);
      -        warningOverlayID = null;
      -    }
      -
      -    function startPoll() {
      -        if (pollInterval) {
      -            return;
      -        }
      -        pollInterval = Script.setInterval(function() {
      -            var shortTermInputLoudness = Audio.inputLevel;
      -            var longTermInputLoudness = shortTermInputLoudness;
      -
      -            var shortTc = (shortTermInputLoudness > lastShortTermInputLoudness) ? shortTermAttackTC : shortTermReleaseTC;
      -            var longTc = (longTermInputLoudness > lastLongTermInputLoudness) ? longTermAttackTC : longTermReleaseTC;
      -
      -            shortTermInputLoudness += shortTc * (lastShortTermInputLoudness - shortTermInputLoudness);
      -            longTermInputLoudness += longTc * (lastLongTermInputLoudness - longTermInputLoudness);
      -
      -            lastShortTermInputLoudness = shortTermInputLoudness;
      -            lastLongTermInputLoudness = longTermInputLoudness;
      -
      -            if (shortTermInputLoudness > lastLongTermInputLoudness + activationThreshold) {
      -                holdCount = holdReset;
      -            } else {
      -                holdCount = Math.max(holdCount - 1, 0);
      +    function update(dt) {
      +        if (!Audio.muted) {
      +            if (hasOverlay()) {
      +                deleteOverlay();
                   }
      -
      -            if (holdCount > 0) {
      -                showWarning();
      -            } else {
      -                hideWarning();
      -            }
      -        }, 1000.0 / sampleRate);
      -    }
      -
      -    function stopPoll() {
      -        if (!pollInterval) {
      -            return;
      -        }
      -        Script.clearInterval(pollInterval);
      -        pollInterval = null;
      -        hideWarning();
      -    }
      -
      -    function startOrStopPoll() {
      -        if (Audio.warnWhenMuted && Audio.muted) {
      -            startPoll();
      +        } else if (!hasOverlay()) {
      +            createOverlay();
               } else {
      -            stopPoll();
      +            updateOverlay();
               }
           }
       
      +    function getOffsetPosition() {
      +        return Vec3.sum(Camera.position, Quat.getFront(Camera.orientation));
      +    }
      +
      +    function createOverlay() {
      +        overlayPosition = getOffsetPosition();
      +        overlayID = Overlays.addOverlay("sphere", {
      +            position: overlayPosition,
      +            rotation: Camera.orientation,
      +            alpha: 0.9,
      +            dimensions: 0.1,
      +            solid: true,
      +            ignoreRayIntersection: true
      +        });
      +    }
      +
      +    function hasOverlay() {
      +        return Overlays.getProperty(overlayID, "position") !== undefined;
      +    }
      +
      +    function updateOverlay() {
      +        // increase by TWEEN_SPEED until completion
      +        if (tweenPosition < 1) {
      +            tweenPosition += TWEEN_SPEED;
      +        } else {
      +            // after tween completion reset to zero and flip values to ping pong 
      +            tweenPosition = 0;
      +            for (var component in startColor) {
      +                var storedColor = startColor[component];
      +                startColor[component] = endColor[component];
      +                endColor[component] = storedColor;
      +            }
      +        }
      +        // mix previous position with new and mix colors
      +        overlayPosition = Vec3.mix(overlayPosition, getOffsetPosition(), MIX_AMOUNT);
      +        Overlays.editOverlay(overlayID, {
      +            color: colorMix(startColor, endColor, easeIn(tweenPosition)),
      +            position: overlayPosition,
      +            rotation: Camera.orientation
      +        });
      +    }
      +
      +    function deleteOverlay() {
      +        Overlays.deleteOverlay(overlayID);
      +    }
      +
           function cleanup() {
      -        stopPoll();
      +        deleteOverlay();
      +        Audio.muted.disconnect(onMuteToggled);
      +        Script.update.disconnect(update);
           }
      -
      -    Script.scriptEnding.connect(cleanup);
      -
      -    startOrStopPoll();
      -    Audio.mutedChanged.connect(startOrStopPoll);
      -    Audio.warnWhenMutedChanged.connect(startOrStopPoll);
      -
      -}()); // END LOCAL_SCOPE
      +}()); // END LOCAL_SCOPE
      \ No newline at end of file
      
      From 19ac8cae316d620427a8e1847d964526e526548a Mon Sep 17 00:00:00 2001
      From: Wayne Chen 
      Date: Mon, 4 Mar 2019 14:18:10 -0800
      Subject: [PATCH 321/474] culling mute state
      
      ---
       scripts/defaultScripts.js | 3 +--
       1 file changed, 1 insertion(+), 2 deletions(-)
      
      diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js
      index e392680df9..bd7e79dffc 100644
      --- a/scripts/defaultScripts.js
      +++ b/scripts/defaultScripts.js
      @@ -32,8 +32,7 @@ var DEFAULT_SCRIPTS_COMBINED = [
           "system/firstPersonHMD.js",
           "system/tablet-ui/tabletUI.js",
           "system/emote.js",
      -    "system/miniTablet.js",
      -    "system/audioMuteOverlay.js"
      +    "system/miniTablet.js"
       ];
       var DEFAULT_SCRIPTS_SEPARATE = [
           "system/controllers/controllerScripts.js",
      
      From b2d08e9d42f152c4beff350b37842f4af93bcf0c Mon Sep 17 00:00:00 2001
      From: luiscuenca 
      Date: Mon, 4 Mar 2019 15:33:21 -0700
      Subject: [PATCH 322/474] apply axis rotation to translation and meshes
      
      ---
       libraries/fbx/src/FBXSerializer.cpp | 11 ++++++++---
       1 file changed, 8 insertions(+), 3 deletions(-)
      
      diff --git a/libraries/fbx/src/FBXSerializer.cpp b/libraries/fbx/src/FBXSerializer.cpp
      index e022ca8921..5246242a1e 100644
      --- a/libraries/fbx/src/FBXSerializer.cpp
      +++ b/libraries/fbx/src/FBXSerializer.cpp
      @@ -1281,8 +1281,9 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
               joint.geometricScaling = fbxModel.geometricScaling;
               joint.isSkeletonJoint = fbxModel.isLimbNode;
               hfmModel.hasSkeletonJoints = (hfmModel.hasSkeletonJoints || joint.isSkeletonJoint);
      -        if (applyUpAxisZRotation && joint.parentIndex == -1 && !joint.isSkeletonJoint) {
      +        if (applyUpAxisZRotation && joint.parentIndex == -1) {
                   joint.rotation *= upAxisZRotation;
      +            joint.translation = upAxisZRotation * joint.translation;
               }
               glm::quat combinedRotation = joint.preRotation * joint.rotation * joint.postRotation;
               if (joint.parentIndex == -1) {
      @@ -1678,8 +1679,12 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
           }
       
           if (applyUpAxisZRotation) {
      -        hfmModelPtr->meshExtents.rotate(upAxisZRotation);
      -        hfmModelPtr->bindExtents.rotate(upAxisZRotation);
      +        hfmModelPtr->meshExtents.transform(glm::mat4_cast(upAxisZRotation));
      +        hfmModelPtr->bindExtents.transform(glm::mat4_cast(upAxisZRotation));
      +        for (auto &mesh : hfmModelPtr->meshes) {
      +            mesh.modelTransform *= glm::mat4_cast(upAxisZRotation);
      +            mesh.meshExtents.transform(glm::mat4_cast(upAxisZRotation));
      +        }
           }
           return hfmModelPtr;
       }
      
      From 12f5a735d93e61e74c1824d1f5b0f9c88e3a2342 Mon Sep 17 00:00:00 2001
      From: SamGondelman 
      Date: Mon, 4 Mar 2019 14:50:09 -0800
      Subject: [PATCH 323/474] simplifying mouse events and fix mouse on create
      
      ---
       interface/src/Application.cpp                 |  11 +-
       interface/src/ui/overlays/Overlays.cpp        | 239 ++++++------------
       interface/src/ui/overlays/Overlays.h          |  17 +-
       .../src/EntityTreeRenderer.cpp                |  34 +--
       .../src/EntityTreeRenderer.h                  |   2 +-
       scripts/system/edit.js                        |   6 +-
       6 files changed, 103 insertions(+), 206 deletions(-)
      
      diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
      index 2c8d71af00..e41d51de47 100644
      --- a/interface/src/Application.cpp
      +++ b/interface/src/Application.cpp
      @@ -4392,7 +4392,6 @@ void Application::mouseMoveEvent(QMouseEvent* event) {
           if (compositor.getReticleVisible() || !isHMDMode() || !compositor.getReticleOverDesktop() ||
               getOverlays().getOverlayAtPoint(glm::vec2(transformedPos.x(), transformedPos.y())) != UNKNOWN_ENTITY_ID) {
               getEntities()->mouseMoveEvent(&mappedEvent);
      -        getOverlays().mouseMoveEvent(&mappedEvent);
           }
       
           _controllerScriptingInterface->emitMouseMoveEvent(&mappedEvent); // send events to any registered scripts
      @@ -4426,14 +4425,10 @@ void Application::mousePressEvent(QMouseEvent* event) {
       #endif
       
           QMouseEvent mappedEvent(event->type(), transformedPos, event->screenPos(), event->button(), event->buttons(), event->modifiers());
      -    std::pair entityResult;
           if (!_controllerScriptingInterface->areEntityClicksCaptured()) {
      -        entityResult = getEntities()->mousePressEvent(&mappedEvent);
      +        QUuid result = getEntities()->mousePressEvent(&mappedEvent);
      +        setKeyboardFocusEntity(getEntities()->wantsKeyboardFocus(result) ? result : UNKNOWN_ENTITY_ID);
           }
      -    std::pair overlayResult = getOverlays().mousePressEvent(&mappedEvent);
      -
      -    QUuid focusedEntity = entityResult.first < overlayResult.first ? entityResult.second : overlayResult.second;
      -    setKeyboardFocusEntity(getEntities()->wantsKeyboardFocus(focusedEntity) ? focusedEntity : UNKNOWN_ENTITY_ID);
       
           _controllerScriptingInterface->emitMousePressEvent(&mappedEvent); // send events to any registered scripts
       
      @@ -4476,7 +4471,6 @@ void Application::mouseDoublePressEvent(QMouseEvent* event) {
           if (!_controllerScriptingInterface->areEntityClicksCaptured()) {
               getEntities()->mouseDoublePressEvent(&mappedEvent);
           }
      -    getOverlays().mouseDoublePressEvent(&mappedEvent);
       
           // if one of our scripts have asked to capture this event, then stop processing it
           if (_controllerScriptingInterface->isMouseCaptured()) {
      @@ -4501,7 +4495,6 @@ void Application::mouseReleaseEvent(QMouseEvent* event) {
               event->buttons(), event->modifiers());
       
           getEntities()->mouseReleaseEvent(&mappedEvent);
      -    getOverlays().mouseReleaseEvent(&mappedEvent);
       
           _controllerScriptingInterface->emitMouseReleaseEvent(&mappedEvent); // send events to any registered scripts
       
      diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp
      index 5ae3f7d38e..dfd698f6c5 100644
      --- a/interface/src/ui/overlays/Overlays.cpp
      +++ b/interface/src/ui/overlays/Overlays.cpp
      @@ -43,14 +43,6 @@ std::unordered_map Overlays::_entityToOverlayTypes;
       std::unordered_map Overlays::_overlayToEntityTypes;
       
       Overlays::Overlays() {
      -    auto pointerManager = DependencyManager::get();
      -    connect(pointerManager.data(), &PointerManager::hoverBeginOverlay, this, &Overlays::hoverEnterPointerEvent);
      -    connect(pointerManager.data(), &PointerManager::hoverContinueOverlay, this, &Overlays::hoverOverPointerEvent);
      -    connect(pointerManager.data(), &PointerManager::hoverEndOverlay, this, &Overlays::hoverLeavePointerEvent);
      -    connect(pointerManager.data(), &PointerManager::triggerBeginOverlay, this, &Overlays::mousePressPointerEvent);
      -    connect(pointerManager.data(), &PointerManager::triggerContinueOverlay, this, &Overlays::mouseMovePointerEvent);
      -    connect(pointerManager.data(), &PointerManager::triggerEndOverlay, this, &Overlays::mouseReleasePointerEvent);
      -
           ADD_TYPE_MAP(Box, cube);
           ADD_TYPE_MAP(Sphere, sphere);
           _overlayToEntityTypes["rectangle3d"] = "Shape";
      @@ -81,13 +73,23 @@ void Overlays::cleanupAllOverlays() {
       
       void Overlays::init() {
           auto entityScriptingInterface = DependencyManager::get().data();
      -    auto pointerManager = DependencyManager::get();
      -    connect(pointerManager.data(), &PointerManager::hoverBeginOverlay, entityScriptingInterface , &EntityScriptingInterface::hoverEnterEntity);
      -    connect(pointerManager.data(), &PointerManager::hoverContinueOverlay, entityScriptingInterface, &EntityScriptingInterface::hoverOverEntity);
      -    connect(pointerManager.data(), &PointerManager::hoverEndOverlay, entityScriptingInterface, &EntityScriptingInterface::hoverLeaveEntity);
      -    connect(pointerManager.data(), &PointerManager::triggerBeginOverlay, entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity);
      -    connect(pointerManager.data(), &PointerManager::triggerContinueOverlay, entityScriptingInterface, &EntityScriptingInterface::mouseMoveOnEntity);
      -    connect(pointerManager.data(), &PointerManager::triggerEndOverlay, entityScriptingInterface, &EntityScriptingInterface::mouseReleaseOnEntity);
      +    auto pointerManager = DependencyManager::get().data();
      +    connect(pointerManager, &PointerManager::hoverBeginOverlay, entityScriptingInterface , &EntityScriptingInterface::hoverEnterEntity);
      +    connect(pointerManager, &PointerManager::hoverContinueOverlay, entityScriptingInterface, &EntityScriptingInterface::hoverOverEntity);
      +    connect(pointerManager, &PointerManager::hoverEndOverlay, entityScriptingInterface, &EntityScriptingInterface::hoverLeaveEntity);
      +    connect(pointerManager, &PointerManager::triggerBeginOverlay, entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity);
      +    connect(pointerManager, &PointerManager::triggerContinueOverlay, entityScriptingInterface, &EntityScriptingInterface::mouseMoveOnEntity);
      +    connect(pointerManager, &PointerManager::triggerEndOverlay, entityScriptingInterface, &EntityScriptingInterface::mouseReleaseOnEntity);
      +
      +    connect(entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity, this, &Overlays::mousePressOnPointerEvent);
      +    connect(entityScriptingInterface, &EntityScriptingInterface::mousePressOffEntity, this, &Overlays::mousePressOffPointerEvent);
      +    connect(entityScriptingInterface, &EntityScriptingInterface::mouseDoublePressOnEntity, this, &Overlays::mouseDoublePressOnPointerEvent);
      +    connect(entityScriptingInterface, &EntityScriptingInterface::mouseDoublePressOffEntity, this, &Overlays::mouseDoublePressOffPointerEvent);
      +    connect(entityScriptingInterface, &EntityScriptingInterface::mouseReleaseOnEntity, this, &Overlays::mouseReleasePointerEvent);
      +    connect(entityScriptingInterface, &EntityScriptingInterface::mouseMoveOnEntity, this, &Overlays::mouseMovePointerEvent);
      +    connect(entityScriptingInterface, &EntityScriptingInterface::hoverEnterEntity , this, &Overlays::hoverEnterPointerEvent);
      +    connect(entityScriptingInterface, &EntityScriptingInterface::hoverOverEntity, this, &Overlays::hoverOverPointerEvent);
      +    connect(entityScriptingInterface, &EntityScriptingInterface::hoverLeaveEntity, this, &Overlays::hoverLeavePointerEvent);
       }
       
       void Overlays::update(float deltatime) {
      @@ -1159,7 +1161,7 @@ bool Overlays::isAddedOverlay(const QUuid& id) {
       }
       
       void Overlays::sendMousePressOnOverlay(const QUuid& id, const PointerEvent& event) {
      -    mousePressPointerEvent(id, event);
      +    mousePressOnPointerEvent(id, event);
       }
       
       void Overlays::sendMouseReleaseOnOverlay(const QUuid& id, const PointerEvent& event) {
      @@ -1206,57 +1208,66 @@ float Overlays::height() {
           return offscreenUi->getWindow()->size().height();
       }
       
      -static uint32_t toPointerButtons(const QMouseEvent& event) {
      -    uint32_t buttons = 0;
      -    buttons |= event.buttons().testFlag(Qt::LeftButton) ? PointerEvent::PrimaryButton : 0;
      -    buttons |= event.buttons().testFlag(Qt::RightButton) ? PointerEvent::SecondaryButton : 0;
      -    buttons |= event.buttons().testFlag(Qt::MiddleButton) ? PointerEvent::TertiaryButton : 0;
      -    return buttons;
      -}
      -
      -static PointerEvent::Button toPointerButton(const QMouseEvent& event) {
      -    switch (event.button()) {
      -        case Qt::LeftButton:
      -            return PointerEvent::PrimaryButton;
      -        case Qt::RightButton:
      -            return PointerEvent::SecondaryButton;
      -        case Qt::MiddleButton:
      -            return PointerEvent::TertiaryButton;
      -        default:
      -            return PointerEvent::NoButtons;
      -    }
      -}
      -
      -RayToOverlayIntersectionResult getPrevPickResult() {
      -    RayToOverlayIntersectionResult overlayResult;
      -    overlayResult.intersects = false;
      -    auto pickResult = DependencyManager::get()->getPrevPickResultTyped(DependencyManager::get()->getMouseRayPickID());
      -    if (pickResult) {
      -        overlayResult.intersects = pickResult->type == IntersectionType::LOCAL_ENTITY;
      -        if (overlayResult.intersects) {
      -            overlayResult.intersection = pickResult->intersection;
      -            overlayResult.distance = pickResult->distance;
      -            overlayResult.surfaceNormal = pickResult->surfaceNormal;
      -            overlayResult.overlayID = pickResult->objectID;
      -            overlayResult.extraInfo = pickResult->extraInfo;
      +void Overlays::mousePressOnPointerEvent(const QUuid& id, const PointerEvent& event) {
      +    auto keyboard = DependencyManager::get();
      +    // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed
      +    if (!keyboard->getKeyIDs().contains(id)) {
      +        auto entity = DependencyManager::get()->getEntity(id);
      +        if (entity && entity->isLocalEntity()) {
      +            emit mousePressOnOverlay(id, event);
               }
           }
      -    return overlayResult;
       }
       
      -PointerEvent Overlays::calculateOverlayPointerEvent(const QUuid& id, const PickRay& ray,
      -                                                    const RayToOverlayIntersectionResult& rayPickResult, QMouseEvent* event,
      -                                                    PointerEvent::EventType eventType) {
      -    glm::vec2 pos2D = RayPick::projectOntoEntityXYPlane(id, rayPickResult.intersection);
      -    return PointerEvent(eventType, PointerManager::MOUSE_POINTER_ID, pos2D, rayPickResult.intersection, rayPickResult.surfaceNormal,
      -                        ray.direction, toPointerButton(*event), toPointerButtons(*event), event->modifiers());
      +void Overlays::mousePressOffPointerEvent() {
      +    emit mousePressOffOverlay();
      +}
      +
      +void Overlays::mouseDoublePressOnPointerEvent(const QUuid& id, const PointerEvent& event) {
      +    auto keyboard = DependencyManager::get();
      +    // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed
      +    if (!keyboard->getKeyIDs().contains(id)) {
      +        auto entity = DependencyManager::get()->getEntity(id);
      +        if (entity && entity->isLocalEntity()) {
      +            emit mouseDoublePressOnOverlay(id, event);
      +        }
      +    }
      +}
      +
      +void Overlays::mouseDoublePressOffPointerEvent() {
      +    emit mouseDoublePressOffOverlay();
      +}
      +
      +void Overlays::mouseReleasePointerEvent(const QUuid& id, const PointerEvent& event) {
      +    auto keyboard = DependencyManager::get();
      +    // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed
      +    if (!keyboard->getKeyIDs().contains(id)) {
      +        auto entity = DependencyManager::get()->getEntity(id);
      +        if (entity && entity->isLocalEntity()) {
      +            emit mouseReleaseOnOverlay(id, event);
      +        }
      +    }
      +}
      +
      +void Overlays::mouseMovePointerEvent(const QUuid& id, const PointerEvent& event) {
      +    auto keyboard = DependencyManager::get();
      +    // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed
      +    if (!keyboard->getKeyIDs().contains(id)) {
      +        auto entity = DependencyManager::get()->getEntity(id);
      +        if (entity && entity->isLocalEntity()) {
      +            emit mouseMoveOnOverlay(id, event);
      +        }
      +    }
       }
       
       void Overlays::hoverEnterPointerEvent(const QUuid& id, const PointerEvent& event) {
           auto keyboard = DependencyManager::get();
           // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed
           if (!keyboard->getKeyIDs().contains(id)) {
      -        emit hoverEnterOverlay(id, event);
      +        auto entity = DependencyManager::get()->getEntity(id);
      +        if (entity && entity->isLocalEntity()) {
      +            emit hoverEnterOverlay(id, event);
      +        }
           }
       }
       
      @@ -1264,7 +1275,10 @@ void Overlays::hoverOverPointerEvent(const QUuid& id, const PointerEvent& event)
           auto keyboard = DependencyManager::get();
           // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed
           if (!keyboard->getKeyIDs().contains(id)) {
      -        emit hoverOverOverlay(id, event);
      +        auto entity = DependencyManager::get()->getEntity(id);
      +        if (entity && entity->isLocalEntity()) {
      +            emit hoverOverOverlay(id, event);
      +        }
           }
       }
       
      @@ -1272,113 +1286,10 @@ void Overlays::hoverLeavePointerEvent(const QUuid& id, const PointerEvent& event
           auto keyboard = DependencyManager::get();
           // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed
           if (!keyboard->getKeyIDs().contains(id)) {
      -        emit hoverLeaveOverlay(id, event);
      -    }
      -}
      -
      -std::pair Overlays::mousePressEvent(QMouseEvent* event) {
      -    PerformanceTimer perfTimer("Overlays::mousePressEvent");
      -
      -    PickRay ray = qApp->computePickRay(event->x(), event->y());
      -    RayToOverlayIntersectionResult rayPickResult = getPrevPickResult();
      -    if (rayPickResult.intersects) {
      -        _currentClickingOnOverlayID = rayPickResult.overlayID;
      -
      -        PointerEvent pointerEvent = calculateOverlayPointerEvent(_currentClickingOnOverlayID, ray, rayPickResult, event, PointerEvent::Press);
      -        mousePressPointerEvent(_currentClickingOnOverlayID, pointerEvent);
      -        return { rayPickResult.distance, rayPickResult.overlayID };
      -    }
      -    emit mousePressOffOverlay();
      -    return { FLT_MAX, UNKNOWN_ENTITY_ID };
      -}
      -
      -void Overlays::mousePressPointerEvent(const QUuid& id, const PointerEvent& event) {
      -    auto keyboard = DependencyManager::get();
      -    // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed
      -    if (!keyboard->getKeyIDs().contains(id)) {
      -        emit mousePressOnOverlay(id, event);
      -    }
      -}
      -
      -bool Overlays::mouseDoublePressEvent(QMouseEvent* event) {
      -    PerformanceTimer perfTimer("Overlays::mouseDoublePressEvent");
      -
      -    PickRay ray = qApp->computePickRay(event->x(), event->y());
      -    RayToOverlayIntersectionResult rayPickResult = getPrevPickResult();
      -    if (rayPickResult.intersects) {
      -        _currentClickingOnOverlayID = rayPickResult.overlayID;
      -
      -        auto pointerEvent = calculateOverlayPointerEvent(_currentClickingOnOverlayID, ray, rayPickResult, event, PointerEvent::Press);
      -        emit mouseDoublePressOnOverlay(_currentClickingOnOverlayID, pointerEvent);
      -        return true;
      -    }
      -    emit mouseDoublePressOffOverlay();
      -    return false;
      -}
      -
      -bool Overlays::mouseReleaseEvent(QMouseEvent* event) {
      -    PerformanceTimer perfTimer("Overlays::mouseReleaseEvent");
      -
      -    PickRay ray = qApp->computePickRay(event->x(), event->y());
      -    RayToOverlayIntersectionResult rayPickResult = getPrevPickResult();
      -    if (rayPickResult.intersects) {
      -        auto pointerEvent = calculateOverlayPointerEvent(rayPickResult.overlayID, ray, rayPickResult, event, PointerEvent::Release);
      -        mouseReleasePointerEvent(rayPickResult.overlayID, pointerEvent);
      -    }
      -
      -    _currentClickingOnOverlayID = UNKNOWN_ENTITY_ID;
      -    return false;
      -}
      -
      -void Overlays::mouseReleasePointerEvent(const QUuid& id, const PointerEvent& event) {
      -    auto keyboard = DependencyManager::get();
      -    // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed
      -    if (!keyboard->getKeyIDs().contains(id)) {
      -        emit mouseReleaseOnOverlay(id, event);
      -    }
      -}
      -
      -bool Overlays::mouseMoveEvent(QMouseEvent* event) {
      -    PerformanceTimer perfTimer("Overlays::mouseMoveEvent");
      -
      -    PickRay ray = qApp->computePickRay(event->x(), event->y());
      -    RayToOverlayIntersectionResult rayPickResult = getPrevPickResult();
      -    if (rayPickResult.intersects) {
      -        auto pointerEvent = calculateOverlayPointerEvent(rayPickResult.overlayID, ray, rayPickResult, event, PointerEvent::Move);
      -        mouseMovePointerEvent(rayPickResult.overlayID, pointerEvent);
      -
      -        // If previously hovering over a different overlay then leave hover on that overlay.
      -        if (_currentHoverOverOverlayID != UNKNOWN_ENTITY_ID && rayPickResult.overlayID != _currentHoverOverOverlayID) {
      -            auto pointerEvent = calculateOverlayPointerEvent(_currentHoverOverOverlayID, ray, rayPickResult, event, PointerEvent::Move);
      -            hoverLeavePointerEvent(_currentHoverOverOverlayID, pointerEvent);
      +        auto entity = DependencyManager::get()->getEntity(id);
      +        if (entity && entity->isLocalEntity()) {
      +            emit hoverLeaveOverlay(id, event);
               }
      -
      -        // If hovering over a new overlay then enter hover on that overlay.
      -        if (rayPickResult.overlayID != _currentHoverOverOverlayID) {
      -            hoverEnterPointerEvent(rayPickResult.overlayID, pointerEvent);
      -        }
      -
      -        // Hover over current overlay.
      -        hoverOverPointerEvent(rayPickResult.overlayID, pointerEvent);
      -
      -        _currentHoverOverOverlayID = rayPickResult.overlayID;
      -    } else {
      -        // If previously hovering an overlay then leave hover.
      -        if (_currentHoverOverOverlayID != UNKNOWN_ENTITY_ID) {
      -            auto pointerEvent = calculateOverlayPointerEvent(_currentHoverOverOverlayID, ray, rayPickResult, event, PointerEvent::Move);
      -            hoverLeavePointerEvent(_currentHoverOverOverlayID, pointerEvent);
      -
      -            _currentHoverOverOverlayID = UNKNOWN_ENTITY_ID;
      -        }
      -    }
      -    return false;
      -}
      -
      -void Overlays::mouseMovePointerEvent(const QUuid& id, const PointerEvent& event) {
      -    auto keyboard = DependencyManager::get();
      -    // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed
      -    if (!keyboard->getKeyIDs().contains(id)) {
      -        emit mouseMoveOnOverlay(id, event);
           }
       }
       
      diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h
      index 93efc2bc0b..0b2994b872 100644
      --- a/interface/src/ui/overlays/Overlays.h
      +++ b/interface/src/ui/overlays/Overlays.h
      @@ -112,11 +112,6 @@ public:
               const QVector& discard,
               bool visibleOnly = false, bool collidableOnly = false);
       
      -    std::pair mousePressEvent(QMouseEvent* event);
      -    bool mouseDoublePressEvent(QMouseEvent* event);
      -    bool mouseReleaseEvent(QMouseEvent* event);
      -    bool mouseMoveEvent(QMouseEvent* event);
      -
           void cleanupAllOverlays();
       
           mutable QScriptEngine _scriptEngine;
      @@ -719,9 +714,6 @@ private:
           PointerEvent calculateOverlayPointerEvent(const QUuid& id, const PickRay& ray, const RayToOverlayIntersectionResult& rayPickResult,
               QMouseEvent* event, PointerEvent::EventType eventType);
       
      -    QUuid _currentClickingOnOverlayID;
      -    QUuid _currentHoverOverOverlayID;
      -
           static QString entityToOverlayType(const QString& type);
           static QString overlayToEntityType(const QString& type);
           static std::unordered_map _entityToOverlayTypes;
      @@ -732,12 +724,17 @@ private:
           EntityItemProperties convertOverlayToEntityProperties(QVariantMap& overlayProps, std::pair& rotationToSave, const QString& type, bool add, const QUuid& id = QUuid());
       
       private slots:
      -    void mousePressPointerEvent(const QUuid& id, const PointerEvent& event);
      -    void mouseMovePointerEvent(const QUuid& id, const PointerEvent& event);
      +    void mousePressOnPointerEvent(const QUuid& id, const PointerEvent& event);
      +    void mousePressOffPointerEvent();
      +    void mouseDoublePressOnPointerEvent(const QUuid& id, const PointerEvent& event);
      +    void mouseDoublePressOffPointerEvent();
           void mouseReleasePointerEvent(const QUuid& id, const PointerEvent& event);
      +    void mouseMovePointerEvent(const QUuid& id, const PointerEvent& event);
           void hoverEnterPointerEvent(const QUuid& id, const PointerEvent& event);
           void hoverOverPointerEvent(const QUuid& id, const PointerEvent& event);
           void hoverLeavePointerEvent(const QUuid& id, const PointerEvent& event);
      +
      +
       };
       
       #define ADD_TYPE_MAP(entity, overlay) \
      diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp
      index e54258fc3e..c7457c6443 100644
      --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp
      +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp
      @@ -73,14 +73,14 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf
           _currentHoverOverEntityID = UNKNOWN_ENTITY_ID;
           _currentClickingOnEntityID = UNKNOWN_ENTITY_ID;
       
      -    auto entityScriptingInterface = DependencyManager::get();
      +    auto entityScriptingInterface = DependencyManager::get().data();
           auto pointerManager = DependencyManager::get();
      -    connect(pointerManager.data(), &PointerManager::hoverBeginEntity, entityScriptingInterface.data(), &EntityScriptingInterface::hoverEnterEntity);
      -    connect(pointerManager.data(), &PointerManager::hoverContinueEntity, entityScriptingInterface.data(), &EntityScriptingInterface::hoverOverEntity);
      -    connect(pointerManager.data(), &PointerManager::hoverEndEntity, entityScriptingInterface.data(), &EntityScriptingInterface::hoverLeaveEntity);
      -    connect(pointerManager.data(), &PointerManager::triggerBeginEntity, entityScriptingInterface.data(), &EntityScriptingInterface::mousePressOnEntity);
      -    connect(pointerManager.data(), &PointerManager::triggerContinueEntity, entityScriptingInterface.data(), &EntityScriptingInterface::mouseMoveOnEntity);
      -    connect(pointerManager.data(), &PointerManager::triggerEndEntity, entityScriptingInterface.data(), &EntityScriptingInterface::mouseReleaseOnEntity);
      +    connect(pointerManager.data(), &PointerManager::hoverBeginEntity, entityScriptingInterface, &EntityScriptingInterface::hoverEnterEntity);
      +    connect(pointerManager.data(), &PointerManager::hoverContinueEntity, entityScriptingInterface, &EntityScriptingInterface::hoverOverEntity);
      +    connect(pointerManager.data(), &PointerManager::hoverEndEntity, entityScriptingInterface, &EntityScriptingInterface::hoverLeaveEntity);
      +    connect(pointerManager.data(), &PointerManager::triggerBeginEntity, entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity);
      +    connect(pointerManager.data(), &PointerManager::triggerContinueEntity, entityScriptingInterface, &EntityScriptingInterface::mouseMoveOnEntity);
      +    connect(pointerManager.data(), &PointerManager::triggerEndEntity, entityScriptingInterface, &EntityScriptingInterface::mouseReleaseOnEntity);
       
           // Forward mouse events to web entities
           auto handlePointerEvent = [&](const QUuid& entityID, const PointerEvent& event) {
      @@ -93,10 +93,10 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf
                   QMetaObject::invokeMethod(thisEntity.get(), "handlePointerEvent", Q_ARG(const PointerEvent&, event));
               }
           };
      -    connect(entityScriptingInterface.data(), &EntityScriptingInterface::mousePressOnEntity, this, handlePointerEvent);
      -    connect(entityScriptingInterface.data(), &EntityScriptingInterface::mouseMoveOnEntity, this, handlePointerEvent);
      -    connect(entityScriptingInterface.data(), &EntityScriptingInterface::mouseReleaseOnEntity, this, handlePointerEvent);
      -    connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverEnterEntity, this, [&](const QUuid& entityID, const PointerEvent& event) {
      +    connect(entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity, this, handlePointerEvent);
      +    connect(entityScriptingInterface, &EntityScriptingInterface::mouseMoveOnEntity, this, handlePointerEvent);
      +    connect(entityScriptingInterface, &EntityScriptingInterface::mouseReleaseOnEntity, this, handlePointerEvent);
      +    connect(entityScriptingInterface, &EntityScriptingInterface::hoverEnterEntity, this, [&](const QUuid& entityID, const PointerEvent& event) {
               std::shared_ptr thisEntity;
               auto entity = getEntity(entityID);
               if (entity && entity->isVisible() && entity->getType() == EntityTypes::Web) {
      @@ -106,8 +106,8 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf
                   QMetaObject::invokeMethod(thisEntity.get(), "hoverEnterEntity", Q_ARG(const PointerEvent&, event));
               }
           });
      -    connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverOverEntity, this, handlePointerEvent);
      -    connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverLeaveEntity, this, [&](const QUuid& entityID, const PointerEvent& event) {
      +    connect(entityScriptingInterface, &EntityScriptingInterface::hoverOverEntity, this, handlePointerEvent);
      +    connect(entityScriptingInterface, &EntityScriptingInterface::hoverLeaveEntity, this, [&](const QUuid& entityID, const PointerEvent& event) {
               std::shared_ptr thisEntity;
               auto entity = getEntity(entityID);
               if (entity && entity->isVisible() && entity->getType() == EntityTypes::Web) {
      @@ -792,11 +792,11 @@ static PointerEvent::Button toPointerButton(const QMouseEvent& event) {
           }
       }
       
      -std::pair EntityTreeRenderer::mousePressEvent(QMouseEvent* event) {
      +QUuid EntityTreeRenderer::mousePressEvent(QMouseEvent* event) {
           // If we don't have a tree, or we're in the process of shutting down, then don't
           // process these events.
           if (!_tree || _shuttingDown) {
      -        return { FLT_MAX, UNKNOWN_ENTITY_ID };
      +        return UNKNOWN_ENTITY_ID;
           }
       
           PerformanceTimer perfTimer("EntityTreeRenderer::mousePressEvent");
      @@ -827,10 +827,10 @@ std::pair EntityTreeRenderer::mousePressEvent(QMouseEvent* event)
               _lastPointerEvent = pointerEvent;
               _lastPointerEventValid = true;
       
      -        return { rayPickResult.distance, rayPickResult.entityID };
      +        return rayPickResult.entityID;
           }
           emit entityScriptingInterface->mousePressOffEntity();
      -    return { FLT_MAX, UNKNOWN_ENTITY_ID };
      +    return UNKNOWN_ENTITY_ID;
       }
       
       void EntityTreeRenderer::mouseDoublePressEvent(QMouseEvent* event) {
      diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h
      index 51568ab744..8cc34cda10 100644
      --- a/libraries/entities-renderer/src/EntityTreeRenderer.h
      +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h
      @@ -94,7 +94,7 @@ public:
           void reloadEntityScripts();
       
           // event handles which may generate entity related events
      -    std::pair mousePressEvent(QMouseEvent* event);
      +    QUuid mousePressEvent(QMouseEvent* event);
           void mouseReleaseEvent(QMouseEvent* event);
           void mouseDoublePressEvent(QMouseEvent* event);
           void mouseMoveEvent(QMouseEvent* event);
      diff --git a/scripts/system/edit.js b/scripts/system/edit.js
      index 9d807264aa..bce308d5fc 100644
      --- a/scripts/system/edit.js
      +++ b/scripts/system/edit.js
      @@ -924,11 +924,7 @@ var toolBar = (function () {
           that.setActive = function (active) {
               ContextOverlay.enabled = !active;
               Settings.setValue(EDIT_SETTING, active);
      -        if (active) {
      -            Controller.captureEntityClickEvents();
      -        } else {
      -            Controller.releaseEntityClickEvents();
      -
      +        if (!active) {
                   closeExistingDialogWindow();
               }
               if (active === isActive) {
      
      From fd88ec0d16caafe1bef84a4287b3b9e1b5cec846 Mon Sep 17 00:00:00 2001
      From: SamGondelman 
      Date: Mon, 4 Mar 2019 15:25:02 -0800
      Subject: [PATCH 324/474] need to copy meshStates on main thread
      
      ---
       libraries/render-utils/src/Model.cpp | 5 +++--
       1 file changed, 3 insertions(+), 2 deletions(-)
      
      diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp
      index 683c517a15..dd9b0280ca 100644
      --- a/libraries/render-utils/src/Model.cpp
      +++ b/libraries/render-utils/src/Model.cpp
      @@ -1347,9 +1347,10 @@ void Model::updateRig(float deltaTime, glm::mat4 parentTransform) {
       
       void Model::computeMeshPartLocalBounds() {
           render::Transaction transaction;
      +    auto meshStates = _meshStates;
           for (auto renderItem : _modelMeshRenderItemIDs) {
      -        transaction.updateItem(renderItem, [&](ModelMeshPartPayload& data) {
      -            const Model::MeshState& state = _meshStates.at(data._meshIndex);
      +        transaction.updateItem(renderItem, [this, meshStates](ModelMeshPartPayload& data) {
      +            const Model::MeshState& state = meshStates.at(data._meshIndex);
                   if (_useDualQuaternionSkinning) {
                       data.computeAdjustedLocalBound(state.clusterDualQuaternions);
                   } else {
      
      From a68aaaa7a46cf8ab6a4bf090a50bd3ecccc20c48 Mon Sep 17 00:00:00 2001
      From: Wayne Chen 
      Date: Mon, 4 Mar 2019 16:06:50 -0800
      Subject: [PATCH 325/474] adding timer and ui color changes
      
      ---
       interface/resources/qml/hifi/audio/Audio.qml  | 33 ++++++++-----------
       .../qml/hifi/audio/LoopbackAudio.qml          | 32 ++++++++++++++----
       .../qml/hifi/audio/PlaySampleSound.qml        |  6 ++--
       3 files changed, 41 insertions(+), 30 deletions(-)
      
      diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml
      index aa64af22a3..bcbd253e24 100644
      --- a/interface/resources/qml/hifi/audio/Audio.qml
      +++ b/interface/resources/qml/hifi/audio/Audio.qml
      @@ -98,13 +98,6 @@ Rectangle {
       
               Separator { }
       
      -        RalewayRegular {
      -            x: margins.paddings + muteMic.boxSize + muteMic.spacing;
      -            size: 16;
      -            color: "white";
      -            text: qsTr("Input Device Settings")
      -        }
      -
               ColumnLayout {
                   x: margins.paddings;
                   spacing: 16;
      @@ -171,7 +164,7 @@ Rectangle {
                   HiFiGlyphs {
                       width: margins.sizeCheckBox
                       text: hifi.glyphs.mic;
      -                color: hifi.colors.primaryHighlight;
      +                color: hifi.colors.white;
                       anchors.left: parent.left
                       anchors.leftMargin: -size/4 //the glyph has empty space at left about 25%
                       anchors.verticalCenter: parent.verticalCenter;
      @@ -183,8 +176,8 @@ Rectangle {
                       anchors.left: parent.left
                       anchors.leftMargin: margins.sizeCheckBox
                       size: 16;
      -                color: hifi.colors.lightGrayText;
      -                text: qsTr("CHOOSE INPUT DEVICE");
      +                color: hifi.colors.white;
      +                text: qsTr("Choose input device");
                   }
               }
       
      @@ -233,6 +226,13 @@ Rectangle {
                       }
                   }
               }
      +        LoopbackAudio {
      +            x: margins.paddings
      +
      +            visible: (bar.currentIndex === 1 && isVR) ||
      +                (bar.currentIndex === 0 && !isVR);
      +            anchors { left: parent.left; leftMargin: margins.paddings }
      +        }
       
               Separator {}
       
      @@ -247,7 +247,7 @@ Rectangle {
                       anchors.verticalCenter: parent.verticalCenter;
                       width: margins.sizeCheckBox
                       text: hifi.glyphs.unmuted;
      -                color: hifi.colors.primaryHighlight;
      +                color: hifi.colors.white;
                       size: 36;
                   }
       
      @@ -257,8 +257,8 @@ Rectangle {
                       anchors.leftMargin: margins.sizeCheckBox
                       anchors.verticalCenter: parent.verticalCenter;
                       size: 16;
      -                color: hifi.colors.lightGrayText;
      -                text: qsTr("CHOOSE OUTPUT DEVICE");
      +                color: hifi.colors.white;
      +                text: qsTr("Choose output device");
                   }
               }
       
      @@ -300,12 +300,5 @@ Rectangle {
                            (bar.currentIndex === 0 && !isVR);
                   anchors { left: parent.left; leftMargin: margins.paddings }
               }
      -        LoopbackAudio {
      -            x: margins.paddings
      -
      -            visible: (bar.currentIndex === 1 && isVR) ||
      -                (bar.currentIndex === 0 && !isVR);
      -            anchors { left: parent.left; leftMargin: margins.paddings }
      -        }
           }
       }
      diff --git a/interface/resources/qml/hifi/audio/LoopbackAudio.qml b/interface/resources/qml/hifi/audio/LoopbackAudio.qml
      index 3ecf09c948..ed416656b0 100644
      --- a/interface/resources/qml/hifi/audio/LoopbackAudio.qml
      +++ b/interface/resources/qml/hifi/audio/LoopbackAudio.qml
      @@ -24,7 +24,7 @@ RowLayout {
                   AudioScriptingInterface.setServerEcho(true);
               }
           }
      -    function stopAudioLoopback () {
      +    function stopAudioLoopback() {
               if (audioLoopedBack) {
                   audioLoopedBack = false;
                   AudioScriptingInterface.setServerEcho(false);
      @@ -33,6 +33,16 @@ RowLayout {
       
           HifiConstants { id: hifi; }
       
      +    Timer {
      +        id: loopbackTimer
      +        interval: 8000;
      +        running: false;
      +        repeat: false;
      +        onTriggered: {
      +            stopAudioLoopback();
      +        }
      +    }
      +
           Button {
               id: control
               background: Rectangle {
      @@ -42,27 +52,35 @@ RowLayout {
                   gradient: Gradient {
                       GradientStop {
                           position: 0.2;
      -                    color: audioLoopedBack ? hifi.buttons.colorStart[hifi.buttons.blue] : hifi.buttons.colorStart[hifi.buttons.black];
      +                    color: audioLoopedBack ? hifi.buttons.colorStart[hifi.buttons.blue] : "#FFFFFF";
                       }
                       GradientStop {
                           position: 1.0;
      -                    color: audioLoopedBack ? hifi.buttons.colorFinish[hifi.buttons.blue] : hifi.buttons.colorFinish[hifi.buttons.black];
      +                    color: audioLoopedBack ? hifi.buttons.colorFinish[hifi.buttons.blue] : "#AFAFAF";
                       }
                   }
               }
               contentItem: HiFiGlyphs {
                   size: 14;
      -            color: (control.pressed || control.hovered) ? (audioLoopedBack ? "black" : hifi.colors.primaryHighlight) : "white";
      -            text: audioLoopedBack ? hifi.glyphs.stop_square : hifi.glyphs.playback_play;
      +            color: (control.pressed || control.hovered) ? (audioLoopedBack ? "black" : hifi.colors.primaryHighlight) : "#404040";
      +            text: audioLoopedBack ? hifi.glyphs.stop_square : hifi.glyphs.mic;
               }
       
      -        onClicked: audioLoopedBack ? stopAudioLoopback() : startAudioLoopback();
      +        onClicked: {
      +            if (audioLoopedBack) {
      +                loopbackTimer.stop();
      +                stopAudioLoopback();
      +            } else {
      +                loopbackTimer.restart();
      +                startAudioLoopback();
      +            }
      +        }
           }
       
           RalewayRegular {
               Layout.leftMargin: 2;
               size: 14;
               color: "white";
      -        text: audioLoopedBack ? qsTr("Disable Audio Loopback") : qsTr("Enable Audio Loopback");
      +        text: audioLoopedBack ? qsTr("Stop testing your voice") : qsTr("Test your voice");
           }
       }
      diff --git a/interface/resources/qml/hifi/audio/PlaySampleSound.qml b/interface/resources/qml/hifi/audio/PlaySampleSound.qml
      index cfe55af9c4..9889d2c6ca 100644
      --- a/interface/resources/qml/hifi/audio/PlaySampleSound.qml
      +++ b/interface/resources/qml/hifi/audio/PlaySampleSound.qml
      @@ -64,11 +64,11 @@ RowLayout {
                   gradient: Gradient {
                       GradientStop {
                           position: 0.2;
      -                    color: isPlaying ? hifi.buttons.colorStart[hifi.buttons.blue] : hifi.buttons.colorStart[hifi.buttons.black];
      +                    color: isPlaying ? hifi.buttons.colorStart[hifi.buttons.blue] : "#FFFFFF";
                       }
                       GradientStop {
                           position: 1.0;
      -                    color: isPlaying ? hifi.buttons.colorFinish[hifi.buttons.blue] : hifi.buttons.colorFinish[hifi.buttons.black];
      +                    color: isPlaying ? hifi.buttons.colorFinish[hifi.buttons.blue] : "#AFAFAF";
                       }
                   }
               }
      @@ -77,7 +77,7 @@ RowLayout {
       //            x: isPlaying ? 0 : 1;
       //            y: 1;
                   size: 14;
      -            color: (control.pressed || control.hovered) ? (isPlaying ? "black" : hifi.colors.primaryHighlight) : "white";
      +            color: (control.pressed || control.hovered) ? (isPlaying ? "black" : hifi.colors.primaryHighlight) : "#404040";
                   text: isPlaying ? hifi.glyphs.stop_square : hifi.glyphs.playback_play;
               }
       
      
      From f2c248c0a26a56b8c7969a14e274422524322cf3 Mon Sep 17 00:00:00 2001
      From: SamGondelman 
      Date: Mon, 4 Mar 2019 17:08:09 -0800
      Subject: [PATCH 326/474] disable href and entity script events when in edit
       mode
      
      ---
       interface/src/Application.cpp                     | 15 +++++++--------
       .../entities-renderer/src/EntityTreeRenderer.cpp  | 12 +++++++-----
       libraries/entities/src/EntityTree.cpp             |  8 ++++++++
       libraries/entities/src/EntityTree.h               |  4 ++++
       libraries/script-engine/src/ScriptEngine.cpp      |  4 +++-
       scripts/system/edit.js                            |  6 +++++-
       6 files changed, 34 insertions(+), 15 deletions(-)
      
      diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
      index e41d51de47..c85ced5dee 100644
      --- a/interface/src/Application.cpp
      +++ b/interface/src/Application.cpp
      @@ -1061,6 +1061,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
           connect(PluginManager::getInstance().data(), &PluginManager::inputDeviceRunningChanged,
               controllerScriptingInterface, &controller::ScriptingInterface::updateRunningInputDevices);
       
      +    EntityTree::setEntityClicksCapturedOperator([this] {
      +        return _controllerScriptingInterface->areEntityClicksCaptured();
      +    });
      +
           _entityClipboard->createRootElement();
       
       #ifdef Q_OS_WIN
      @@ -4425,10 +4429,8 @@ void Application::mousePressEvent(QMouseEvent* event) {
       #endif
       
           QMouseEvent mappedEvent(event->type(), transformedPos, event->screenPos(), event->button(), event->buttons(), event->modifiers());
      -    if (!_controllerScriptingInterface->areEntityClicksCaptured()) {
      -        QUuid result = getEntities()->mousePressEvent(&mappedEvent);
      -        setKeyboardFocusEntity(getEntities()->wantsKeyboardFocus(result) ? result : UNKNOWN_ENTITY_ID);
      -    }
      +    QUuid result = getEntities()->mousePressEvent(&mappedEvent);
      +    setKeyboardFocusEntity(getEntities()->wantsKeyboardFocus(result) ? result : UNKNOWN_ENTITY_ID);
       
           _controllerScriptingInterface->emitMousePressEvent(&mappedEvent); // send events to any registered scripts
       
      @@ -4467,10 +4469,7 @@ void Application::mouseDoublePressEvent(QMouseEvent* event) {
               transformedPos,
               event->screenPos(), event->button(),
               event->buttons(), event->modifiers());
      -
      -    if (!_controllerScriptingInterface->areEntityClicksCaptured()) {
      -        getEntities()->mouseDoublePressEvent(&mappedEvent);
      -    }
      +    getEntities()->mouseDoublePressEvent(&mappedEvent);
       
           // if one of our scripts have asked to capture this event, then stop processing it
           if (_controllerScriptingInterface->isMouseCaptured()) {
      diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp
      index c7457c6443..576137390e 100644
      --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp
      +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp
      @@ -805,11 +805,13 @@ QUuid EntityTreeRenderer::mousePressEvent(QMouseEvent* event) {
           RayToEntityIntersectionResult rayPickResult = _getPrevRayPickResultOperator(_mouseRayPickID);
           EntityItemPointer entity;
           if (rayPickResult.intersects && (entity = getTree()->findEntityByID(rayPickResult.entityID))) {
      -        auto properties = entity->getProperties();
      -        QString urlString = properties.getHref();
      -        QUrl url = QUrl(urlString, QUrl::StrictMode);
      -        if (url.isValid() && !url.isEmpty()){
      -            DependencyManager::get()->handleLookupString(urlString);
      +        if (!EntityTree::areEntityClicksCaptured()) {
      +            auto properties = entity->getProperties();
      +            QString urlString = properties.getHref();
      +            QUrl url = QUrl(urlString, QUrl::StrictMode);
      +            if (url.isValid() && !url.isEmpty()) {
      +                DependencyManager::get()->handleLookupString(urlString);
      +            }
               }
       
               glm::vec2 pos2D = projectOntoEntityXYPlane(entity, ray, rayPickResult);
      diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp
      index 6e404ce690..644fe0620e 100644
      --- a/libraries/entities/src/EntityTree.cpp
      +++ b/libraries/entities/src/EntityTree.cpp
      @@ -2972,6 +2972,7 @@ QStringList EntityTree::getJointNames(const QUuid& entityID) const {
       
       std::function EntityTree::_getEntityObjectOperator = nullptr;
       std::function EntityTree::_textSizeOperator = nullptr;
      +std::function EntityTree::_areEntityClicksCapturedOperator = nullptr;
       
       QObject* EntityTree::getEntityObject(const QUuid& id) {
           if (_getEntityObjectOperator) {
      @@ -2987,6 +2988,13 @@ QSizeF EntityTree::textSize(const QUuid& id, const QString& text) {
           return QSizeF(0.0f, 0.0f);
       }
       
      +bool EntityTree::areEntityClicksCaptured() {
      +    if (_areEntityClicksCapturedOperator) {
      +        return _areEntityClicksCapturedOperator();
      +    }
      +    return false;
      +}
      +
       void EntityTree::updateEntityQueryAACubeWorker(SpatiallyNestablePointer object, EntityEditPacketSender* packetSender,
                                                      MovingEntitiesOperator& moveOperator, bool force, bool tellServer) {
           // if the queryBox has changed, tell the entity-server
      diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h
      index dcce0e4b99..01829df7f8 100644
      --- a/libraries/entities/src/EntityTree.h
      +++ b/libraries/entities/src/EntityTree.h
      @@ -268,6 +268,9 @@ public:
           static void setTextSizeOperator(std::function textSizeOperator) { _textSizeOperator = textSizeOperator; }
           static QSizeF textSize(const QUuid& id, const QString& text);
       
      +    static void setEntityClicksCapturedOperator(std::function areEntityClicksCapturedOperator) { _areEntityClicksCapturedOperator = areEntityClicksCapturedOperator; }
      +    static bool areEntityClicksCaptured();
      +
           std::map getNamedPaths() const { return _namedPaths; }
       
           void updateEntityQueryAACube(SpatiallyNestablePointer object, EntityEditPacketSender* packetSender,
      @@ -378,6 +381,7 @@ private:
       
           static std::function _getEntityObjectOperator;
           static std::function _textSizeOperator;
      +    static std::function _areEntityClicksCapturedOperator;
       
           std::vector _staleProxies;
       
      diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp
      index 5d33a6a061..825017b1fe 100644
      --- a/libraries/script-engine/src/ScriptEngine.cpp
      +++ b/libraries/script-engine/src/ScriptEngine.cpp
      @@ -976,7 +976,9 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString&
               using PointerHandler = std::function;
               auto makePointerHandler = [this](QString eventName) -> PointerHandler {
                   return [this, eventName](const EntityItemID& entityItemID, const PointerEvent& event) {
      -                forwardHandlerCall(entityItemID, eventName, { entityItemID.toScriptValue(this), event.toScriptValue(this) });
      +                if (!EntityTree::areEntityClicksCaptured()) {
      +                    forwardHandlerCall(entityItemID, eventName, { entityItemID.toScriptValue(this), event.toScriptValue(this) });
      +                }
                   };
               };
       
      diff --git a/scripts/system/edit.js b/scripts/system/edit.js
      index bce308d5fc..9d807264aa 100644
      --- a/scripts/system/edit.js
      +++ b/scripts/system/edit.js
      @@ -924,7 +924,11 @@ var toolBar = (function () {
           that.setActive = function (active) {
               ContextOverlay.enabled = !active;
               Settings.setValue(EDIT_SETTING, active);
      -        if (!active) {
      +        if (active) {
      +            Controller.captureEntityClickEvents();
      +        } else {
      +            Controller.releaseEntityClickEvents();
      +
                   closeExistingDialogWindow();
               }
               if (active === isActive) {
      
      From 122b62a5b82fded9d66450dc6f6b3f251c4ea91b Mon Sep 17 00:00:00 2001
      From: Simon Walton 
      Date: Mon, 4 Mar 2019 17:32:18 -0800
      Subject: [PATCH 327/474] Remove some debug output
      
      ---
       assignment-client/src/avatars/AvatarMixerSlave.cpp | 8 +-------
       1 file changed, 1 insertion(+), 7 deletions(-)
      
      diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp
      index 80600a53ee..b52bf03471 100644
      --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp
      +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp
      @@ -459,13 +459,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
                       ++numAvatarsWithSkippedFrames;
                   }
               }
      -        {
      -            if (sourceAvatarNodeData->getConstAvatarData()->getHasPriority() && !sendAvatar) {
      -                qCWarning(avatars) << "Hero avatar dropped:" << sourceAvatarNodeData->getConstAvatarData()->getSessionDisplayName()
      -                    << "lastSeqToReceiver =" << destinationNodeData->getLastBroadcastSequenceNumber(sourceAvatarNode->getLocalID())
      -                    << "lastSeqFromSender = " << sourceAvatarNodeData->getLastReceivedSequenceNumber();
      -            }
      -        }
      +
               quint64 endIgnoreCalculation = usecTimestampNow();
               _stats.ignoreCalculationElapsedTime += (endIgnoreCalculation - startIgnoreCalculation);
       
      
      From 0f2792930a3d4ba0edda89e8d0330081d16578b5 Mon Sep 17 00:00:00 2001
      From: Wayne Chen 
      Date: Mon, 4 Mar 2019 19:28:12 -0800
      Subject: [PATCH 328/474] adding switches and buttons to testing audio
      
      ---
       .../resources/qml/controlsUit/Switch.qml      |  3 +-
       interface/resources/qml/hifi/audio/Audio.qml  | 81 +++++++++++--------
       .../qml/hifi/audio/LoopbackAudio.qml          | 31 ++-----
       .../qml/hifi/audio/PlaySampleSound.qml        | 35 ++------
       4 files changed, 62 insertions(+), 88 deletions(-)
      
      diff --git a/interface/resources/qml/controlsUit/Switch.qml b/interface/resources/qml/controlsUit/Switch.qml
      index 0961ef2500..0de95a7e70 100644
      --- a/interface/resources/qml/controlsUit/Switch.qml
      +++ b/interface/resources/qml/controlsUit/Switch.qml
      @@ -27,6 +27,7 @@ Item {
           property string labelGlyphOnText: "";
           property int labelGlyphOnSize: 32;
           property alias checked: originalSwitch.checked;
      +    property string backgroundOnColor: "#252525";
           signal onCheckedChanged;
           signal clicked;
       
      @@ -54,7 +55,7 @@ Item {
               }
       
               background: Rectangle {
      -            color: "#252525";
      +            color: checked ? backgroundOnColor : "#252525";
                   implicitWidth: rootSwitch.switchWidth;
                   implicitHeight: rootSwitch.height;
                   radius: rootSwitch.switchRadius;
      diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml
      index bcbd253e24..fc0c4d2d43 100644
      --- a/interface/resources/qml/hifi/audio/Audio.qml
      +++ b/interface/resources/qml/hifi/audio/Audio.qml
      @@ -16,7 +16,7 @@ import QtQuick.Controls 2.2
       import QtQuick.Layouts 1.3
       
       import stylesUit 1.0
      -import controlsUit 1.0 as HifiControls
      +import controlsUit 1.0 as HifiControlsUit
       import "../../windows"
       import "./" as AudioControls
       
      @@ -27,6 +27,8 @@ Rectangle {
       
           property var eventBridge;
           property string title: "Audio Settings"
      +    property int switchHeight: 16
      +    property int switchWidth: 40
           signal sendToScript(var message);
       
           color: hifi.colors.baseGray;
      @@ -38,7 +40,7 @@ Rectangle {
       
       
           property bool isVR: AudioScriptingInterface.context === "VR"
      -    property real rightMostInputLevelPos: 0
      +    property real rightMostInputLevelPos: 450
           //placeholder for control sizes and paddings
           //recalculates dynamically in case of UI size is changed
           QtObject {
      @@ -98,58 +100,68 @@ Rectangle {
       
               Separator { }
       
      -        ColumnLayout {
      -            x: margins.paddings;
      -            spacing: 16;
      +        RowLayout {
      +            x: 2 * margins.paddings;
      +            spacing: columnOne.width;
                   width: parent.width;
       
                   // mute is in its own row
      -            RowLayout {
      -                spacing: (margins.sizeCheckBox - 10.5) * 3;
      -                AudioControls.CheckBox {
      -                    id: muteMic
      -                    text: qsTr("Mute microphone");
      -                    spacing: margins.sizeCheckBox - boxSize
      -                    isRedCheck: true;
      +            ColumnLayout {
      +                id: columnOne
      +                spacing: 12;
      +                x: margins.paddings
      +                HifiControlsUit.Switch {
      +                    id: muteMic;
      +                    height: root.switchHeight;
      +                    switchWidth: root.switchWidth;
      +                    labelTextOn: "Mute microphone";
      +                    backgroundOnColor: "#E3E3E3";
                           checked: AudioScriptingInterface.muted;
      -                    onClicked: {
      +                    onCheckedChanged: {
                               AudioScriptingInterface.muted = checked;
                               checked = Qt.binding(function() { return AudioScriptingInterface.muted; }); // restore binding
                           }
                       }
       
      -                AudioControls.CheckBox {
      -                    id: stereoMic
      -                    spacing: muteMic.spacing;
      -                    text: qsTr("Enable stereo input");
      +                HifiControlsUit.Switch {
      +                    id: stereoInput;
      +                    height: root.switchHeight;
      +                    switchWidth: root.switchWidth;
      +                    labelTextOn:  qsTr("Stereo input");
      +                    backgroundOnColor: "#E3E3E3";
                           checked: AudioScriptingInterface.isStereoInput;
      -                    onClicked: {
      +                    onCheckedChanged: {
                               AudioScriptingInterface.isStereoInput = checked;
                               checked = Qt.binding(function() { return AudioScriptingInterface.isStereoInput; }); // restore binding
                           }
                       }
                   }
       
      -            RowLayout {
      -                spacing: muteMic.spacing*2; //make it visually distinguish
      -                AudioControls.CheckBox {
      -                    spacing: muteMic.spacing
      -                    text: qsTr("Enable noise reduction");
      +            ColumnLayout {
      +                spacing: 12;
      +                HifiControlsUit.Switch {
      +                    height: root.switchHeight;
      +                    switchWidth: root.switchWidth;
      +                    labelTextOn: "Noise Reduction";
      +                    backgroundOnColor: "#E3E3E3";
                           checked: AudioScriptingInterface.noiseReduction;
      -                    onClicked: {
      +                    onCheckedChanged: {
                               AudioScriptingInterface.noiseReduction = checked;
                               checked = Qt.binding(function() { return AudioScriptingInterface.noiseReduction; }); // restore binding
                           }
                       }
      -                AudioControls.CheckBox {
      -                    spacing: muteMic.spacing
      -                    text: qsTr("Show audio level meter");
      +
      +                HifiControlsUit.Switch {
      +                    id: audioLevelSwitch
      +                    height: root.switchHeight;
      +                    switchWidth: root.switchWidth;
      +                    labelTextOn: qsTr("Audio Level Meter");
      +                    backgroundOnColor: "#E3E3E3";
                           checked: AvatarInputs.showAudioTools;
      -                    onClicked: {
      +                    onCheckedChanged: {
                               AvatarInputs.showAudioTools = checked;
                               checked = Qt.binding(function() { return AvatarInputs.showAudioTools; }); // restore binding
                           }
      -                    onXChanged: rightMostInputLevelPos = x + width
                       }
                   }
               }
      @@ -203,7 +215,7 @@ Rectangle {
                           width: parent.width - inputLevel.width
                           clip: true
                           checkable: !checked
      -                    checked: bar.currentIndex === 0 ? selectedDesktop :  selectedHMD;
      +                    checked: bar.currentIndex === 0 ? selectedDesktop : selectedHMD;
                           boxSize: margins.sizeCheckBox / 2
                           isRound: true
                           text: devicename
      @@ -215,7 +227,7 @@ Rectangle {
                               }
                           }
                       }
      -                InputPeak {
      +                AudioControls.InputPeak {
                           id: inputLevel
                           anchors.right: parent.right
                           peak: model.peak;
      @@ -225,8 +237,11 @@ Rectangle {
                                    AudioScriptingInterface.devices.input.peakValuesAvailable;
                       }
                   }
      +            Component.onCompleted: {
      +                console.log("width " + rightMostInputLevelPos);
      +            }
               }
      -        LoopbackAudio {
      +        AudioControls.LoopbackAudio {
                   x: margins.paddings
       
                   visible: (bar.currentIndex === 1 && isVR) ||
      @@ -293,7 +308,7 @@ Rectangle {
                       }
                   }
               }
      -        PlaySampleSound {
      +        AudioControls.PlaySampleSound {
                   x: margins.paddings
       
                   visible: (bar.currentIndex === 1 && isVR) ||
      diff --git a/interface/resources/qml/hifi/audio/LoopbackAudio.qml b/interface/resources/qml/hifi/audio/LoopbackAudio.qml
      index ed416656b0..8ec0ffc496 100644
      --- a/interface/resources/qml/hifi/audio/LoopbackAudio.qml
      +++ b/interface/resources/qml/hifi/audio/LoopbackAudio.qml
      @@ -14,7 +14,7 @@ import QtQuick.Controls 2.2
       import QtQuick.Layouts 1.3
       
       import stylesUit 1.0
      -import controlsUit 1.0 as HifiControls
      +import controlsUit 1.0 as HifiControlsUit
       
       RowLayout {
           property bool audioLoopedBack: AudioScriptingInterface.getServerEcho();
      @@ -43,29 +43,9 @@ RowLayout {
               }
           }
       
      -    Button {
      -        id: control
      -        background: Rectangle {
      -            implicitWidth: 20;
      -            implicitHeight: 20;
      -            radius: hifi.buttons.radius;
      -            gradient: Gradient {
      -                GradientStop {
      -                    position: 0.2;
      -                    color: audioLoopedBack ? hifi.buttons.colorStart[hifi.buttons.blue] : "#FFFFFF";
      -                }
      -                GradientStop {
      -                    position: 1.0;
      -                    color: audioLoopedBack ? hifi.buttons.colorFinish[hifi.buttons.blue] : "#AFAFAF";
      -                }
      -            }
      -        }
      -        contentItem: HiFiGlyphs {
      -            size: 14;
      -            color: (control.pressed || control.hovered) ? (audioLoopedBack ? "black" : hifi.colors.primaryHighlight) : "#404040";
      -            text: audioLoopedBack ? hifi.glyphs.stop_square : hifi.glyphs.mic;
      -        }
      -
      +    HifiControlsUit.Button {
      +        text: audioLoopedBack ? qsTr("STOP TESTING YOUR VOICE") : qsTr("TEST YOUR VOICE");
      +        color: audioLoopedBack ? hifi.buttons.red : hifi.buttons.blue;
               onClicked: {
                   if (audioLoopedBack) {
                       loopbackTimer.stop();
      @@ -81,6 +61,7 @@ RowLayout {
               Layout.leftMargin: 2;
               size: 14;
               color: "white";
      -        text: audioLoopedBack ? qsTr("Stop testing your voice") : qsTr("Test your voice");
      +        font.italic: true
      +        text: audioLoopedBack ? qsTr("Speak in your input") : "";
           }
       }
      diff --git a/interface/resources/qml/hifi/audio/PlaySampleSound.qml b/interface/resources/qml/hifi/audio/PlaySampleSound.qml
      index 9889d2c6ca..b9d9727dab 100644
      --- a/interface/resources/qml/hifi/audio/PlaySampleSound.qml
      +++ b/interface/resources/qml/hifi/audio/PlaySampleSound.qml
      @@ -14,7 +14,7 @@ import QtQuick.Controls 2.2
       import QtQuick.Layouts 1.3
       
       import stylesUit 1.0
      -import controlsUit 1.0 as HifiControls
      +import controlsUit 1.0 as HifiControlsUit
       
       RowLayout {
           property var sound: null;
      @@ -55,32 +55,9 @@ RowLayout {
       
           HifiConstants { id: hifi; }
       
      -    Button {
      -        id: control
      -        background: Rectangle {
      -            implicitWidth: 20;
      -            implicitHeight: 20;
      -            radius: hifi.buttons.radius;
      -            gradient: Gradient {
      -                GradientStop {
      -                    position: 0.2;
      -                    color: isPlaying ? hifi.buttons.colorStart[hifi.buttons.blue] : "#FFFFFF";
      -                }
      -                GradientStop {
      -                    position: 1.0;
      -                    color: isPlaying ? hifi.buttons.colorFinish[hifi.buttons.blue] : "#AFAFAF";
      -                }
      -            }
      -        }
      -        contentItem: HiFiGlyphs {
      -            // absolutely position due to asymmetry in glyph
      -//            x: isPlaying ? 0 : 1;
      -//            y: 1;
      -            size: 14;
      -            color: (control.pressed || control.hovered) ? (isPlaying ? "black" : hifi.colors.primaryHighlight) : "#404040";
      -            text: isPlaying ? hifi.glyphs.stop_square : hifi.glyphs.playback_play;
      -        }
      -
      +    HifiControlsUit.Button {
      +        text: isPlaying ? qsTr("STOP TESTING YOUR SOUND") : qsTr("TEST YOUR SOUND");
      +        color: isPlaying ? hifi.buttons.red : hifi.buttons.blue;
               onClicked: isPlaying ? stopSound() : playSound();
           }
       
      @@ -88,7 +65,7 @@ RowLayout {
               Layout.leftMargin: 2;
               size: 14;
               color: "white";
      -        text: isPlaying ? qsTr("Stop sample sound") : qsTr("Play sample sound");
      +        font.italic: true
      +        text: isPlaying ? qsTr("Listen to your output") : "";
           }
      -
       }
      
      From a00d6ec9f6565e77db30a32211ed5eaa285aba50 Mon Sep 17 00:00:00 2001
      From: Wayne Chen 
      Date: Mon, 4 Mar 2019 19:42:30 -0800
      Subject: [PATCH 329/474] removing debug statement
      
      ---
       interface/resources/qml/controlsUit/Switch.qml | 4 ++--
       interface/resources/qml/hifi/audio/Audio.qml   | 7 ++-----
       2 files changed, 4 insertions(+), 7 deletions(-)
      
      diff --git a/interface/resources/qml/controlsUit/Switch.qml b/interface/resources/qml/controlsUit/Switch.qml
      index 0de95a7e70..4e1c21c456 100644
      --- a/interface/resources/qml/controlsUit/Switch.qml
      +++ b/interface/resources/qml/controlsUit/Switch.qml
      @@ -41,10 +41,10 @@ Item {
               onClicked: rootSwitch.clicked();
               hoverEnabled: true
       
      -        topPadding: 3;
      +        topPadding: 1;
               leftPadding: 3;
               rightPadding: 3;
      -        bottomPadding: 3;
      +        bottomPadding: 1;
       
               onHoveredChanged: {
                   if (hovered) {
      diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml
      index fc0c4d2d43..1869fb9b3e 100644
      --- a/interface/resources/qml/hifi/audio/Audio.qml
      +++ b/interface/resources/qml/hifi/audio/Audio.qml
      @@ -108,7 +108,7 @@ Rectangle {
                   // mute is in its own row
                   ColumnLayout {
                       id: columnOne
      -                spacing: 12;
      +                spacing: 24;
                       x: margins.paddings
                       HifiControlsUit.Switch {
                           id: muteMic;
      @@ -138,7 +138,7 @@ Rectangle {
                   }
       
                   ColumnLayout {
      -                spacing: 12;
      +                spacing: 24;
                       HifiControlsUit.Switch {
                           height: root.switchHeight;
                           switchWidth: root.switchWidth;
      @@ -237,9 +237,6 @@ Rectangle {
                                    AudioScriptingInterface.devices.input.peakValuesAvailable;
                       }
                   }
      -            Component.onCompleted: {
      -                console.log("width " + rightMostInputLevelPos);
      -            }
               }
               AudioControls.LoopbackAudio {
                   x: margins.paddings
      
      From 743d1a58e2c4d07fa26f61b1512190263225fa42 Mon Sep 17 00:00:00 2001
      From: Simon Walton 
      Date: Tue, 5 Mar 2019 09:34:29 -0800
      Subject: [PATCH 330/474] Style tweaks from review
      
      ---
       assignment-client/src/avatars/AvatarMixerSlave.cpp | 6 ------
       assignment-client/src/avatars/MixerAvatar.h        | 6 +++---
       2 files changed, 3 insertions(+), 9 deletions(-)
      
      diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp
      index b52bf03471..e59c81f4b7 100644
      --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp
      +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp
      @@ -355,9 +355,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
           // about avatars they've ignored or that are out of view
           bool PALIsOpen = destinationNodeData->getRequestsDomainListData();
           bool PALWasOpen = destinationNodeData->getPrevRequestsDomainListData();
      -    if (PALIsOpen) {
      -        qCWarning(avatars) << "PAL is open:" << avatar.getSessionDisplayName();
      -    }
       
           // When this is true, the AvatarMixer will send Avatar data to a client about avatars that have ignored them
           bool getsAnyIgnored = PALIsOpen && destinationNode->getCanKick();
      @@ -526,9 +523,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
                           _stats.overBudgetAvatars++;
                           detail = AvatarData::PALMinimum;
                       } else {
      -                    if (currentVariant == kHero) {
      -                        qCWarning(avatars) << "Overbudget break with hero avatars!" << destinationNode->getUUID().toString();
      -                    }
                           _stats.overBudgetAvatars += remainingAvatars;
                           break;
                       }
      diff --git a/assignment-client/src/avatars/MixerAvatar.h b/assignment-client/src/avatars/MixerAvatar.h
      index 2444bd541c..4c3ded4582 100644
      --- a/assignment-client/src/avatars/MixerAvatar.h
      +++ b/assignment-client/src/avatars/MixerAvatar.h
      @@ -19,11 +19,11 @@
       
       class MixerAvatar : public AvatarData {
       public:
      -    bool getHasPriority() const { return  _bHasPriority; }
      -    void setHasPriority(bool bPriorityAvatar) { _bHasPriority = bPriorityAvatar; }
      +    bool getHasPriority() const { return  _hasPriority; }
      +    void setHasPriority(bool hasPriority) { _hasPriority = hasPriority; }
       
       private:
      -    bool _bHasPriority { false };
      +    bool _hasPriority { false };
       };
       
       using MixerAvatarSharedPointer = std::shared_ptr;
      
      From 29a308dcaacfa138b721d183a146b38befe3988a Mon Sep 17 00:00:00 2001
      From: amer cerkic 
      Date: Tue, 5 Mar 2019 10:07:33 -0800
      Subject: [PATCH 331/474] adding modelScale initialization so that it does not
       fail the validScale check assert
      
      ---
       libraries/entities/src/ModelEntityItem.cpp | 1 +
       1 file changed, 1 insertion(+)
      
      diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp
      index 505ee26c0f..87d80ee044 100644
      --- a/libraries/entities/src/ModelEntityItem.cpp
      +++ b/libraries/entities/src/ModelEntityItem.cpp
      @@ -40,6 +40,7 @@ ModelEntityItem::ModelEntityItem(const EntityItemID& entityItemID) : EntityItem(
           _type = EntityTypes::Model;
           _lastKnownCurrentFrame = -1;
           _visuallyReady = false;
      +    _modelScale=glm::vec3(1.0f);
       }
       
       const QString ModelEntityItem::getTextures() const {
      
      From 66125e1f0104e979fed5d248956ee6b01316c259 Mon Sep 17 00:00:00 2001
      From: NissimHadar 
      Date: Tue, 5 Mar 2019 10:21:49 -0800
      Subject: [PATCH 332/474] Updated version.
      
      ---
       tools/nitpick/src/Nitpick.cpp | 2 +-
       1 file changed, 1 insertion(+), 1 deletion(-)
      
      diff --git a/tools/nitpick/src/Nitpick.cpp b/tools/nitpick/src/Nitpick.cpp
      index 03acd4a893..bf9b9c11ba 100644
      --- a/tools/nitpick/src/Nitpick.cpp
      +++ b/tools/nitpick/src/Nitpick.cpp
      @@ -38,7 +38,7 @@ Nitpick::Nitpick(QWidget* parent) : QMainWindow(parent) {
        
           _ui.plainTextEdit->setReadOnly(true);
       
      -    setWindowTitle("Nitpick - v3.1.0");
      +    setWindowTitle("Nitpick - v3.1.1");
       
           clientProfiles << "VR-High" << "Desktop-High" << "Desktop-Low" << "Mobile-Touch" << "VR-Standalone";
           _ui.clientProfileComboBox->insertItems(0, clientProfiles);
      
      From 36e9c604e9e70efde65c5a7f64044d0da052a022 Mon Sep 17 00:00:00 2001
      From: amer cerkic 
      Date: Tue, 5 Mar 2019 10:45:15 -0800
      Subject: [PATCH 333/474] fixed based on comment
      
      ---
       libraries/entities/src/ModelEntityItem.cpp | 1 -
       libraries/entities/src/ModelEntityItem.h   | 2 +-
       2 files changed, 1 insertion(+), 2 deletions(-)
      
      diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp
      index 87d80ee044..505ee26c0f 100644
      --- a/libraries/entities/src/ModelEntityItem.cpp
      +++ b/libraries/entities/src/ModelEntityItem.cpp
      @@ -40,7 +40,6 @@ ModelEntityItem::ModelEntityItem(const EntityItemID& entityItemID) : EntityItem(
           _type = EntityTypes::Model;
           _lastKnownCurrentFrame = -1;
           _visuallyReady = false;
      -    _modelScale=glm::vec3(1.0f);
       }
       
       const QString ModelEntityItem::getTextures() const {
      diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h
      index 0efbbbb16c..89acd6179b 100644
      --- a/libraries/entities/src/ModelEntityItem.h
      +++ b/libraries/entities/src/ModelEntityItem.h
      @@ -164,7 +164,7 @@ protected:
           int _lastKnownCurrentFrame{-1};
       
           glm::u8vec3 _color;
      -    glm::vec3 _modelScale;
      +    glm::vec3 _modelScale {1.0f};
           QString _modelURL;
           bool _relayParentJoints;
           bool _groupCulled { false };
      
      From fab343a1d4d703009c585aca2bc7eba64e983cfe Mon Sep 17 00:00:00 2001
      From: amer cerkic 
      Date: Tue, 5 Mar 2019 10:52:54 -0800
      Subject: [PATCH 334/474] correcting spacing
      
      ---
       libraries/entities/src/ModelEntityItem.h | 2 +-
       1 file changed, 1 insertion(+), 1 deletion(-)
      
      diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h
      index 89acd6179b..08468617ba 100644
      --- a/libraries/entities/src/ModelEntityItem.h
      +++ b/libraries/entities/src/ModelEntityItem.h
      @@ -164,7 +164,7 @@ protected:
           int _lastKnownCurrentFrame{-1};
       
           glm::u8vec3 _color;
      -    glm::vec3 _modelScale {1.0f};
      +    glm::vec3 _modelScale { 1.0f };
           QString _modelURL;
           bool _relayParentJoints;
           bool _groupCulled { false };
      
      From f1cf11de8546cf367671f6ae107efc1401e6c1e0 Mon Sep 17 00:00:00 2001
      From: Wayne Chen 
      Date: Tue, 5 Mar 2019 10:53:55 -0800
      Subject: [PATCH 335/474] moving gain slider to audio settings
      
      ---
       interface/resources/qml/hifi/NameCard.qml    | 28 +++-----
       interface/resources/qml/hifi/audio/Audio.qml | 75 ++++++++++++++++++--
       2 files changed, 77 insertions(+), 26 deletions(-)
      
      diff --git a/interface/resources/qml/hifi/NameCard.qml b/interface/resources/qml/hifi/NameCard.qml
      index 242ca5ab57..646fc881e1 100644
      --- a/interface/resources/qml/hifi/NameCard.qml
      +++ b/interface/resources/qml/hifi/NameCard.qml
      @@ -376,7 +376,7 @@ Item {
           }
           FiraSansRegular {
               id: nameCardConnectionInfoText
      -        visible: selected && !isMyCard && pal.activeTab == "connectionsTab" && !isMyCard
      +        visible: selected && !isMyCard && pal.activeTab == "connectionsTab"
               width: parent.width
               height: displayNameTextPixelSize
               size: displayNameTextPixelSize - 4
      @@ -412,7 +412,7 @@ Item {
           }
           FiraSansRegular {
               id: nameCardRemoveConnectionText
      -        visible: selected && !isMyCard && pal.activeTab == "connectionsTab" && !isMyCard
      +        visible: selected && !isMyCard && pal.activeTab == "connectionsTab"
               width: parent.width
               height: displayNameTextPixelSize
               size: displayNameTextPixelSize - 4
      @@ -425,7 +425,7 @@ Item {
           }
           HifiControls.Button {
               id: visitConnectionButton
      -        visible: selected && !isMyCard && pal.activeTab == "connectionsTab" && !isMyCard
      +        visible: selected && !isMyCard && pal.activeTab == "connectionsTab"
               text: "Visit"
               enabled: thisNameCard.placeName !== ""
               anchors.verticalCenter: nameCardRemoveConnectionImage.verticalCenter
      @@ -450,7 +450,7 @@ Item {
               // Style
               radius: 4
               color: "#c5c5c5"
      -        visible: (isMyCard || (selected && pal.activeTab == "nearbyTab")) && isPresent
      +        visible: (!isMyCard && (selected && pal.activeTab == "nearbyTab")) && isPresent
               // Rectangle for the zero-gain point on the VU meter
               Rectangle {
                   id: vuMeterZeroGain
      @@ -481,7 +481,7 @@ Item {
                   id: vuMeterBase
                   // Anchors
                   anchors.fill: parent
      -            visible: isMyCard || selected
      +            visible: !isMyCard && selected
                   // Style
                   color: parent.color
                   radius: parent.radius
      @@ -489,7 +489,7 @@ Item {
               // Rectangle for the VU meter audio level
               Rectangle {
                   id: vuMeterLevel
      -            visible: isMyCard || selected
      +            visible: !isMyCard && selected
                   // Size
                   width: (thisNameCard.audioLevel) * parent.width
                   // Style
      @@ -525,7 +525,7 @@ Item {
               anchors.verticalCenter: nameCardVUMeter.verticalCenter;
               anchors.left: nameCardVUMeter.left;
               // Properties
      -        visible: (isMyCard || (selected && pal.activeTab == "nearbyTab")) && isPresent;
      +        visible: (!isMyCard && (selected && pal.activeTab == "nearbyTab")) && isPresent;
               minimumValue: -60.0
               maximumValue: 20.0
               stepSize: 5
      @@ -572,19 +572,7 @@ Item {
                       implicitHeight: 16
                   }
               }
      -         RalewayRegular {
      -            // The slider for my card is special, it controls the master gain
      -            id: gainSliderText;
      -            visible: isMyCard;
      -            text: "master volume";
      -            size: hifi.fontSizes.tabularData;
      -            anchors.left: parent.right;
      -            anchors.leftMargin: 8;
      -            color: hifi.colors.baseGrayHighlight;
      -            horizontalAlignment: Text.AlignLeft;
      -            verticalAlignment: Text.AlignTop;
      -        }
      -   }
      +    }
       
           function updateGainFromQML(avatarUuid, sliderValue, isReleased) {
               if (Users.getAvatarGain(avatarUuid) != sliderValue) {
      diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml
      index 1869fb9b3e..efbf663838 100644
      --- a/interface/resources/qml/hifi/audio/Audio.qml
      +++ b/interface/resources/qml/hifi/audio/Audio.qml
      @@ -11,7 +11,7 @@
       //  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
       //
       
      -import QtQuick 2.5
      +import QtQuick 2.7
       import QtQuick.Controls 2.2
       import QtQuick.Layouts 1.3
       
      @@ -26,6 +26,8 @@ Rectangle {
           HifiConstants { id: hifi; }
       
           property var eventBridge;
      +    // leave as blank, this is user's volume for the avatar mixer
      +    property var myAvatarUuid: ""
           property string title: "Audio Settings"
           property int switchHeight: 16
           property int switchWidth: 40
      @@ -82,16 +84,16 @@ Rectangle {
               });
           }
       
      -    function disablePeakValues() {
      -        root.showPeaks = false;
      -        AudioScriptingInterface.devices.input.peakValuesEnabled = false;
      +    function updateMyAvatarGainFromQML(sliderValue, isReleased) {
      +        if (Users.getAvatarGain(myAvatarUuid) != sliderValue) {
      +            Users.setAvatarGain(myAvatarUuid, sliderValue);
      +        }
           }
       
           Component.onCompleted: enablePeakValues();
      -    Component.onDestruction: disablePeakValues();
      -    onVisibleChanged: visible ? enablePeakValues() : disablePeakValues();
       
           Column {
      +        id: column
               spacing: 12;
               anchors.top: bar.bottom
               anchors.bottom: parent.bottom
      @@ -305,6 +307,67 @@ Rectangle {
                       }
                   }
               }
      +
      +        Item {
      +            id: gainContainer
      +            x: margins.paddings;
      +            width: parent.width - margins.paddings*2
      +            height: gainSliderTextMetrics.height
      +
      +            HifiControlsUit.Slider {
      +                id: gainSlider
      +                anchors.right: parent.right
      +                height: parent.height
      +                width: 200
      +                minimumValue: -60.0
      +                maximumValue: 20.0
      +                stepSize: 5
      +                value: Users.getAvatarGain(myAvatarUuid)
      +                onValueChanged: {
      +                    updateMyAvatarGainFromQML(value, false);
      +                }
      +                onPressedChanged: {
      +                    if (!pressed) {
      +                        updateMyAvatarGainFromQML(value, false);
      +                    }
      +                }
      +
      +                MouseArea {
      +                    anchors.fill: parent
      +                    onWheel: {
      +                        // Do nothing.
      +                    }
      +                    onDoubleClicked: {
      +                        gainSlider.value = 0.0
      +                    }
      +                    onPressed: {
      +                        // Pass through to Slider
      +                        mouse.accepted = false
      +                    }
      +                    onReleased: {
      +                        // the above mouse.accepted seems to make this
      +                        // never get called, nonetheless...
      +                        mouse.accepted = false
      +                    }
      +                }
      +            }
      +            TextMetrics {
      +                id: gainSliderTextMetrics
      +                text: gainSliderText.text
      +                font: gainSliderText.font
      +            }
      +            RalewayRegular {
      +                // The slider for my card is special, it controls the master gain
      +                id: gainSliderText;
      +                text: "Avatar volume";
      +                size: 16;
      +                anchors.left: parent.left;
      +                color: hifi.colors.white;
      +                horizontalAlignment: Text.AlignLeft;
      +                verticalAlignment: Text.AlignTop;
      +            }
      +        }
      +
               AudioControls.PlaySampleSound {
                   x: margins.paddings
       
      
      From 2020b757a3b536e070a0a8c9cb1dd25344a88c7d Mon Sep 17 00:00:00 2001
      From: Wayne Chen 
      Date: Tue, 5 Mar 2019 10:58:26 -0800
      Subject: [PATCH 336/474] adding mic glyph
      
      ---
       .../hifi-glyphs-1.34/fonts/hifi-glyphs.woff   | Bin 21976 -> 0 bytes
       .../fonts/hifi-glyphs.eot                     | Bin 34130 -> 34726 bytes
       .../fonts/hifi-glyphs.svg                     |   9 ++-
       .../fonts/hifi-glyphs.ttf                     | Bin 33952 -> 34548 bytes
       .../hifi-glyphs-1.38/fonts/hifi-glyphs.woff   | Bin 0 -> 22380 bytes
       .../icons-reference.html                      |  76 ++++++++++++------
       .../styles.css                                |  27 ++++---
       interface/resources/fonts/hifi-glyphs.ttf     | Bin 34396 -> 34548 bytes
       8 files changed, 74 insertions(+), 38 deletions(-)
       delete mode 100644 interface/resources/fonts/hifi-glyphs-1.34/fonts/hifi-glyphs.woff
       rename interface/resources/fonts/{hifi-glyphs-1.34 => hifi-glyphs-1.38}/fonts/hifi-glyphs.eot (83%)
       rename interface/resources/fonts/{hifi-glyphs-1.34 => hifi-glyphs-1.38}/fonts/hifi-glyphs.svg (96%)
       rename interface/resources/fonts/{hifi-glyphs-1.34 => hifi-glyphs-1.38}/fonts/hifi-glyphs.ttf (83%)
       create mode 100644 interface/resources/fonts/hifi-glyphs-1.38/fonts/hifi-glyphs.woff
       rename interface/resources/fonts/{hifi-glyphs-1.34 => hifi-glyphs-1.38}/icons-reference.html (97%)
       rename interface/resources/fonts/{hifi-glyphs-1.34 => hifi-glyphs-1.38}/styles.css (97%)
      
      diff --git a/interface/resources/fonts/hifi-glyphs-1.34/fonts/hifi-glyphs.woff b/interface/resources/fonts/hifi-glyphs-1.34/fonts/hifi-glyphs.woff
      deleted file mode 100644
      index 6d469701366c67d5aa8123d2be5b314f3f27b65a..0000000000000000000000000000000000000000
      GIT binary patch
      literal 0
      HcmV?d00001
      
      literal 21976
      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
      
      diff --git a/interface/resources/fonts/hifi-glyphs-1.34/fonts/hifi-glyphs.eot b/interface/resources/fonts/hifi-glyphs-1.38/fonts/hifi-glyphs.eot
      similarity index 83%
      rename from interface/resources/fonts/hifi-glyphs-1.34/fonts/hifi-glyphs.eot
      rename to interface/resources/fonts/hifi-glyphs-1.38/fonts/hifi-glyphs.eot
      index 6cdc2487a68d6377f9fdd2fe51ca5c1b9a27b7ad..88936e6c5128623e596c932f5a46a40cd4842a23 100644
      GIT binary patch
      delta 2035
      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
      
      diff --git a/interface/resources/fonts/hifi-glyphs-1.34/fonts/hifi-glyphs.svg b/interface/resources/fonts/hifi-glyphs-1.38/fonts/hifi-glyphs.svg
      similarity index 96%
      rename from interface/resources/fonts/hifi-glyphs-1.34/fonts/hifi-glyphs.svg
      rename to interface/resources/fonts/hifi-glyphs-1.38/fonts/hifi-glyphs.svg
      index de91dcae71..a3a1aac8a9 100644
      --- a/interface/resources/fonts/hifi-glyphs-1.34/fonts/hifi-glyphs.svg
      +++ b/interface/resources/fonts/hifi-glyphs-1.38/fonts/hifi-glyphs.svg
      @@ -14,7 +14,6 @@
       
       
       
      -
       
       
       
      @@ -97,7 +96,6 @@
       
       
       
      -
       
       
       
      @@ -137,7 +135,6 @@
       
       
       
      -
       
       
       
      @@ -156,4 +153,10 @@
       
       
       
      +
      +
      +
      +
      +
      +
       
      diff --git a/interface/resources/fonts/hifi-glyphs-1.34/fonts/hifi-glyphs.ttf b/interface/resources/fonts/hifi-glyphs-1.38/fonts/hifi-glyphs.ttf
      similarity index 83%
      rename from interface/resources/fonts/hifi-glyphs-1.34/fonts/hifi-glyphs.ttf
      rename to interface/resources/fonts/hifi-glyphs-1.38/fonts/hifi-glyphs.ttf
      index c27553333bda29cdfd06e4c1ebdd964b83ff6175..dae388d2eb6798d7d0d0f4135609908d83141764 100644
      GIT binary patch
      delta 2032
      zcmY+FYfM|`8OQ&xZH|wx*v3A-U=w3};n*18?}l7T2oMOQaadat5@HC1*uhDhl%#-Z
      zl9Dx{3FS02O|vPR&{nZDOGmUtQ>C?2R_#NlRcOk(Et8_DtEPR3I+dYynw04n)~QE2
      z|M!26p67j^-}B*lZ@ns5eN9jT0ieJ%gy?B)&2&x9%-#oPJ_TUj(Xzc2zXk=&+y`Lo
      z-s1~)zu!3pkmmsOX7dAu`8!v80l^o5;N0-Q$->j85CSuI0no#_Gl&1Ae)1(i-3DG<
      z92puI)C{}-3g8id=*Y-WX+Y#Bj{%|uU?0gB$7?&Q&j92uK$>%7*@3U#(|#YQC;}v&
      z9~dv-E7H$ZL0Lqrbp$e^))#MI*iXAoLS5kwI~97z^q7gf>7oFIQ
      zT?m(8h7clDfPxq_BzOx_$RLLTN>oAxH8iNgMQEV|13e5dVmErR2N^tzZ{aySj~CE~
      zH?a@<(T@Qf!XSn)j1e5cD2^hBJdR-u1sul-oWv=d#)}xo8JtDnB}`xvQ&>O=(|8%@
      zZ~^D>3TE(ayoOhC370X8E8y@3uHgo*bNwP$e2S5~IB;-F
      zIuQ&7y4%`&Ox2dxug+x;*KD%Z+Uo2Mr>}mVb^9BtZr&9(3q=)_n3lXHmB|&#
      zN|jnuby2Hh^akVZ-aQ%aAEJr*3Kf^HP?ie9N1{Q|QiYPDsgJ~?;yZK)y(EF8Q!*=g
      zC{;@R(hljY^uF{H>Az%+vQgO&Lb5rxEDpNIA
      z^|3asUC{nbcUJcSvyXYetm)JGOZpXq$xtxdFsv9g#wp`%<9*`;;}a88GPRiIO`lgY
      z)os<+&HF4;%Ser;=JcjFtd#Xu?P1%)x@kM?yBw5b%5llD;P|Cu&3VQ7iOc4ib$wRf
      zR{t?O?%w7uxZm->Gu=?r@NC0W!_U1fK7sFw@1}3bPy56E8UJ4b+Q3NQgP=8dD0nY)
      zAxwpDNA^WNkEWw_OR?tIt=QwZHQpJ|#eWiCNwg#$BsIywM(#lg6GDK)=8S}6(NIv&=(J)=XrpvmCK!!`T~0Cg
      zi(x|x5$qg|#M-;U;Yh4KmC2mWxY@lT7pK_-`Tmj!eb`JclMhi14^n6ch|?7bJ3YiErYwZwMXP60Bp7WX
      zVOKa7O_89Uv5<0vUQ2n1&Dli4{BK6*vhm?es2uDhV`5dDrC5tus<2orY>tipUZXKm
      zMvX=&&WVM~_L4DYwA*4_j@H*L&E!Nu|-KOi@BE
      z8|-$2hZng*663RC5(Pg^c#!{)51~LDZ9wcBrP%)mw86EG6f2dXc;K(OLJ_g&G*)`f!1dU<@yBaMXby*|iyz1;L@kRcwu
      zTBX;k)cT&dyOEa5>GQNqMmM_Sa$4=%=98-Aq|c{f7?sb(Hu7tuFkYXg?J|{Iu9Dfs
      zX*NmAh&Ep1cGtvcnH1n!y7zOn6Hb>C7uQENM3CX54POBn{=VTU$cU0VHc{*9M1mjd
      zA_+ubaA(6;fWgX!r@&y1tDMwxznM5t8G#oecoBpj0VKH1lU6$74S9q9K!SU3(#c(&
      zY~%hgX(exS|DL=qf{pCt7N*)L{D>6DaqhFJR*GCB3*=4CUFujKFRcoekDp5t?#Jhs
      z!iwz3Q1+-dd*nnmH#A6{7&`0q2dLaw_UO
      
      delta 1476
      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
      j+LVQgf^blu7y8;nlmGw#c${NkW
           

      HiFi Glyphs

      -

      This font was created for use in High Fidelity

      +

      This font was created withFontastic

      CSS mapping

      • @@ -43,10 +43,6 @@
      • -
      • -
        - -
      • @@ -375,10 +371,6 @@
      • -
      • -
        - -
      • @@ -535,10 +527,6 @@
      • -
      • -
        - -
      • @@ -611,6 +599,30 @@
      • +
      • +
        + +
      • +
      • +
        + +
      • +
      • +
        + +
      • +
      • +
        + +
      • +
      • +
        + +
      • +
      • +
        + +

      Character mapping

        @@ -642,10 +654,6 @@
        -
      • -
        - -
      • @@ -974,10 +982,6 @@
      • -
      • -
        - -
      • @@ -1134,10 +1138,6 @@
      • -
      • -
        - -
      • @@ -1210,6 +1210,30 @@
      • +
      • +
        + +
      • +
      • +
        + +
      • +
      • +
        + +
      • +
      • +
        + +
      • +
      • +
        + +
      • +
      • +
        + +
      - + \ No newline at end of file diff --git a/interface/resources/fonts/hifi-glyphs-1.34/styles.css b/interface/resources/fonts/hifi-glyphs-1.38/styles.css similarity index 97% rename from interface/resources/fonts/hifi-glyphs-1.34/styles.css rename to interface/resources/fonts/hifi-glyphs-1.38/styles.css index 59e38d455c..f6a096189b 100644 --- a/interface/resources/fonts/hifi-glyphs-1.34/styles.css +++ b/interface/resources/fonts/hifi-glyphs-1.38/styles.css @@ -59,9 +59,6 @@ .icon-headphones:before { content: "\68"; } -.icon-mic:before { - content: "\69"; -} .icon-upload:before { content: "\6a"; } @@ -308,9 +305,6 @@ .icon-voxels:before { content: "\e005"; } -.icon-lock:before { - content: "\e006"; -} .icon-visible:before { content: "\e007"; } @@ -428,9 +422,6 @@ .icon-password:before { content: "\e029"; } -.icon-rez:before { - content: "\e025"; -} .icon-keyboard-collapse:before { content: "\e02b"; } @@ -485,3 +476,21 @@ .icon-oculus:before { content: "\e036"; } +.icon-check-circled:before { + content: "\e037"; +} +.icon-rez-01:before { + content: "\e025"; +} +.icon-locked:before { + content: "\e006"; +} +.icon-unlocked:before { + content: "\e039"; +} +.icon-mic:before { + content: "\69"; +} +.icon-92-lock-01:before { + content: "\e038"; +} diff --git a/interface/resources/fonts/hifi-glyphs.ttf b/interface/resources/fonts/hifi-glyphs.ttf index aaeb1d2ace9c0f76f01de9035c05cf1af5f44df3..dae388d2eb6798d7d0d0f4135609908d83141764 100644 GIT binary patch delta 1790 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 From 8e9618d74064dd2ac043b84404cd4d74b2952c58 Mon Sep 17 00:00:00 2001 From: amantley Date: Tue, 5 Mar 2019 11:44:53 -0800 Subject: [PATCH 337/474] added a lower run animation to stop hovering when running --- .../resources/avatar/animations/run_fwd.fbx | Bin 1604368 -> 940272 bytes .../resources/avatar/avatar-animation.json | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/resources/avatar/animations/run_fwd.fbx b/interface/resources/avatar/animations/run_fwd.fbx index 2ea035e6941bfface831586837c6fd1e73b006d6..86add969e59d8c18d5bd6c40dbaa6ebe6eb739c3 100644 GIT binary patch literal 940272 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%0AluyZTKn^i;gerULk{~D9 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 b Date: Tue, 5 Mar 2019 14:24:38 -0800 Subject: [PATCH 338/474] add keyboard for html content --- interface/src/Application.cpp | 4 +++- interface/src/ui/Keyboard.cpp | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 2c8d71af00..3d0012d807 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2342,6 +2342,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo } else { QObject::connect(webSurface.data(), &hifi::qml::OffscreenSurface::rootContextCreated, rootItemLoadedFunctor); } + auto surfaceContext = webSurface->getSurfaceContext(); + surfaceContext->setContextProperty("KeyboardScriptingInterface", DependencyManager::get().data()); } else { // FIXME: the tablet should use the OffscreenQmlSurfaceCache webSurface = QSharedPointer(new OffscreenQmlSurface(), [](OffscreenQmlSurface* webSurface) { @@ -3278,6 +3280,7 @@ void Application::setupQmlSurface(QQmlContext* surfaceContext, bool setAdditiona surfaceContext->setContextProperty("MyAvatar", DependencyManager::get()->getMyAvatar().get()); surfaceContext->setContextProperty("Entities", DependencyManager::get().data()); surfaceContext->setContextProperty("Snapshot", DependencyManager::get().data()); + surfaceContext->setContextProperty("KeyboardScriptingInterface", DependencyManager::get().data()); if (setAdditionalContextProperties) { auto tabletScriptingInterface = DependencyManager::get(); @@ -3288,7 +3291,6 @@ void Application::setupQmlSurface(QQmlContext* surfaceContext, bool setAdditiona surfaceContext->setContextProperty("Settings", SettingsScriptingInterface::getInstance()); surfaceContext->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance()); - surfaceContext->setContextProperty("KeyboardScriptingInterface", DependencyManager::get().data()); surfaceContext->setContextProperty("Account", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED surfaceContext->setContextProperty("GlobalServices", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED diff --git a/interface/src/ui/Keyboard.cpp b/interface/src/ui/Keyboard.cpp index d344e27d54..80b0922a8c 100644 --- a/interface/src/ui/Keyboard.cpp +++ b/interface/src/ui/Keyboard.cpp @@ -65,7 +65,7 @@ static const glm::vec3 KEYBOARD_TABLET_OFFSET{0.30f, -0.38f, -0.04f}; static const glm::vec3 KEYBOARD_TABLET_DEGREES_OFFSET{-45.0f, 0.0f, 0.0f}; static const glm::vec3 KEYBOARD_TABLET_LANDSCAPE_OFFSET{-0.2f, -0.27f, -0.05f}; static const glm::vec3 KEYBOARD_TABLET_LANDSCAPE_DEGREES_OFFSET{-45.0f, 0.0f, -90.0f}; -static const glm::vec3 KEYBOARD_AVATAR_OFFSET{-0.6f, 0.3f, -0.7f}; +static const glm::vec3 KEYBOARD_AVATAR_OFFSET{-0.3f, 0.0f, -0.7f}; static const glm::vec3 KEYBOARD_AVATAR_DEGREES_OFFSET{0.0f, 180.0f, 0.0f}; static const QString SOUND_FILE = PathUtils::resourcesUrl() + "sounds/keyboardPress.mp3"; From 1941e53cc3c62b57704af320ff12a13f56755c68 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Mon, 4 Mar 2019 15:45:47 -0800 Subject: [PATCH 339/474] track oculus controllers while in oculus home --- .../qml/hifi/tablet/OculusConfiguration.qml | 59 +++++++++++++++++++ interface/src/ui/PreferencesDialog.cpp | 1 + .../oculus/src/OculusControllerManager.cpp | 20 ++++++- plugins/oculus/src/OculusControllerManager.h | 6 ++ 4 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 interface/resources/qml/hifi/tablet/OculusConfiguration.qml diff --git a/interface/resources/qml/hifi/tablet/OculusConfiguration.qml b/interface/resources/qml/hifi/tablet/OculusConfiguration.qml new file mode 100644 index 0000000000..6df4fa1b83 --- /dev/null +++ b/interface/resources/qml/hifi/tablet/OculusConfiguration.qml @@ -0,0 +1,59 @@ +// +// Created by Dante Ruiz on 3/4/19. +// Copyright 2019 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 +// + + +import QtQuick 2.5 + +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls + + +Rectangle { + id: root + anchors.fill: parent + property string pluginName: "" + property var displayInformation: null + HifiConstants { id: hifi } + + color: hifi.colors.baseGray + + HifiControls.CheckBox { + id: box + width: 15 + height: 15 + + anchors { + left: root.left + leftMargin: 75 + } + + onClicked: { + sendConfigurationSettings( { trackControllersInOculusHome: checked }); + } + } + + RalewaySemiBold { + id: head + + text: "Track hand controllers in Oculus Home" + size: 12 + + color: "white" + anchors.left: box.right + anchors.leftMargin: 5 + } + + function displayConfiguration() { + var configurationSettings = InputConfiguration.configurationSettings(root.pluginName); + box.checked = configurationSettings.trackControllersInOculusHome; + } + + function sendConfigurationSettings(settings) { + InputConfiguration.setConfigurationSettings(settings, root.pluginName); + } +} diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 3d7971cf57..dbd24573ee 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include "Application.h" diff --git a/plugins/oculus/src/OculusControllerManager.cpp b/plugins/oculus/src/OculusControllerManager.cpp index c2b9145f3c..8d97ff78af 100644 --- a/plugins/oculus/src/OculusControllerManager.cpp +++ b/plugins/oculus/src/OculusControllerManager.cpp @@ -30,6 +30,7 @@ using namespace hifi; const char* OculusControllerManager::NAME = "Oculus"; +const QString OCULUS_LAYOUT = "OculusConfiguration.qml"; const quint64 LOST_TRACKING_DELAY = 3000000; @@ -43,6 +44,22 @@ bool OculusControllerManager::activate() { return true; } +QString OculusControllerManager::configurationLayout() { + return OCULUS_LAYOUT; +} + +void OculusControllerManager::setConfigurationSettings(const QJsonObject configurationSettings) { + if (configurationSettings.contains("trackControllersInOculusHome")) { + _touch->_trackControllersInOculusHome.set(configurationSettings["trackControllersInOculusHome"].toBool()); + } +} + +QJsonObject OculusControllerManager::configurationSettings() { + QJsonObject configurationSettings; + configurationSettings["trackControllersInOculusHome"] = _touch->_trackControllersInOculusHome.get(); + return configurationSettings; +} + void OculusControllerManager::checkForConnectedDevices() { if (_touch && _remote) { return; @@ -215,13 +232,14 @@ void OculusControllerManager::TouchDevice::update(float deltaTime, quint64 currentTime = usecTimestampNow(); static const auto REQUIRED_HAND_STATUS = ovrStatus_OrientationTracked | ovrStatus_PositionTracked; bool hasInputFocus = ovr::hasInputFocus(); + bool trackControllersInOculusHome = _trackControllersInOculusHome.get(); auto tracking = ovr::getTrackingState(); // ovr_GetTrackingState(_parent._session, 0, false); ovr::for_each_hand([&](ovrHandType hand) { ++numTrackedControllers; int controller = (hand == ovrHand_Left ? controller::LEFT_HAND : controller::RIGHT_HAND); // Disable hand tracking while in Oculus Dash (Dash renders it's own hands) - if (!hasInputFocus) { + if (!hasInputFocus && !trackControllersInOculusHome) { _poseStateMap.erase(controller); _poseStateMap[controller].valid = false; return; diff --git a/plugins/oculus/src/OculusControllerManager.h b/plugins/oculus/src/OculusControllerManager.h index ee06115b26..ea32eace61 100644 --- a/plugins/oculus/src/OculusControllerManager.h +++ b/plugins/oculus/src/OculusControllerManager.h @@ -15,6 +15,7 @@ #include +#include #include #include @@ -28,7 +29,11 @@ public: const QString getName() const override { return NAME; } bool isHandController() const override { return _touch != nullptr; } bool isHeadController() const override { return true; } + bool configurable() override { return true; } + QString configurationLayout() override; QStringList getSubdeviceNames() override; + void setConfigurationSettings(const QJsonObject configurationSetting) override; + QJsonObject configurationSettings() override; bool activate() override; void deactivate() override; @@ -93,6 +98,7 @@ private: float _leftHapticStrength { 0.0f }; float _rightHapticDuration { 0.0f }; float _rightHapticStrength { 0.0f }; + Setting::Handle _trackControllersInOculusHome { "trackControllersInOculusHome", false }; mutable std::recursive_mutex _lock; std::map _lostTracking; std::map _regainTrackingDeadline; From ab227f210da939193713c7bb3583c6308d9972b4 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Tue, 5 Mar 2019 16:21:32 -0800 Subject: [PATCH 340/474] Increased threshold to 0.990 --- tools/nitpick/src/Nitpick.cpp | 2 +- tools/nitpick/src/Test.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/nitpick/src/Nitpick.cpp b/tools/nitpick/src/Nitpick.cpp index 39800c6bc6..b50205df7f 100644 --- a/tools/nitpick/src/Nitpick.cpp +++ b/tools/nitpick/src/Nitpick.cpp @@ -40,7 +40,7 @@ Nitpick::Nitpick(QWidget* parent) : QMainWindow(parent) { _ui.plainTextEdit->setReadOnly(true); - setWindowTitle("Nitpick - v3.0.0"); + setWindowTitle("Nitpick - v3.1.2"); clientProfiles << "VR-High" << "Desktop-High" << "Desktop-Low" << "Mobile-Touch" << "VR-Standalone"; _ui.clientProfileComboBox->insertItems(0, clientProfiles); diff --git a/tools/nitpick/src/Test.h b/tools/nitpick/src/Test.h index 23011d0c31..00ba07b051 100644 --- a/tools/nitpick/src/Test.h +++ b/tools/nitpick/src/Test.h @@ -116,7 +116,7 @@ private: const QString TEST_RESULTS_FOLDER { "TestResults" }; const QString TEST_RESULTS_FILENAME { "TestResults.txt" }; - const double THRESHOLD{ 0.935 }; + const double THRESHOLD{ 0.990 }; QDir _imageDirectory; From 272ca5dc1c8f687df11bc36b59474afacf127306 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Tue, 5 Mar 2019 16:23:40 -0800 Subject: [PATCH 341/474] disable entity scaling --- scripts/system/controllers/controllerModules/nearGrabEntity.js | 2 +- scripts/system/controllers/controllerScripts.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/system/controllers/controllerModules/nearGrabEntity.js b/scripts/system/controllers/controllerModules/nearGrabEntity.js index 0f8071677c..763c1a1ce0 100644 --- a/scripts/system/controllers/controllerModules/nearGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearGrabEntity.js @@ -134,7 +134,7 @@ Script.include("/~/system/libraries/controllers.js"); var scaleModuleName = this.hand === RIGHT_HAND ? "RightScaleEntity" : "LeftScaleEntity"; var scaleModule = getEnabledModuleByName(scaleModuleName); - if (scaleModule.grabbedThingID || scaleModule.isReady(controllerData).active) { + if (scaleModule && (scaleModule.grabbedThingID || scaleModule.isReady(controllerData).active)) { // we're rescaling -- don't start a grab. return makeRunningValues(false, [], []); } diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index 86ff7701c3..726e075fcc 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -30,7 +30,6 @@ var CONTOLLER_SCRIPTS = [ "controllerModules/teleport.js", "controllerModules/hudOverlayPointer.js", "controllerModules/mouseHMD.js", - "controllerModules/scaleEntity.js", "controllerModules/nearGrabHyperLinkEntity.js", "controllerModules/nearTabletHighlight.js", "controllerModules/nearGrabEntity.js", From 7ad8b3a610718f86cc1b017c5f53e200219fff93 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Tue, 5 Mar 2019 16:59:36 -0800 Subject: [PATCH 342/474] Fix FST models being copied improperly in the ModelCache --- .../model-networking/src/model-networking/ModelCache.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index 581196b2cc..843d975f7d 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -394,7 +394,11 @@ QSharedPointer ModelCache::createResource(const QUrl& url) { } QSharedPointer ModelCache::createResourceCopy(const QSharedPointer& resource) { - return QSharedPointer(new GeometryDefinitionResource(*resource.staticCast()), &Resource::deleter); + if (resource->getURL().path().toLower().endsWith(".fst")) { + return QSharedPointer(new GeometryMappingResource(*resource.staticCast()), &Resource::deleter); + } else { + return QSharedPointer(new GeometryDefinitionResource(*resource.staticCast()), &Resource::deleter); + } } GeometryResource::Pointer ModelCache::getGeometryResource(const QUrl& url, From 23e8a8bed4c6f086fa5718153cf6733ae218ae68 Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Tue, 5 Mar 2019 17:09:54 -0800 Subject: [PATCH 343/474] Initial implementation (deadlocks still occurring in Audio.cpp) --- interface/resources/qml/hifi/audio/Audio.qml | 19 ++ interface/resources/qml/hifi/audio/MicBar.qml | 9 +- interface/src/Application.cpp | 18 ++ interface/src/Application.h | 2 + interface/src/scripting/Audio.cpp | 174 +++++++++++++++++- interface/src/scripting/Audio.h | 89 ++++++++- scripts/system/audio.js | 3 + 7 files changed, 302 insertions(+), 12 deletions(-) diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index c8dd83cd62..45358f59a2 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -148,6 +148,25 @@ Rectangle { checked = Qt.binding(function() { return AudioScriptingInterface.noiseReduction; }); // restore binding } } + AudioControls.CheckBox { + spacing: muteMic.spacing + text: qsTr("Push To Talk"); + checked: isVR ? AudioScriptingInterface.pushToTalkHMD : AudioScriptingInterface.pushToTalkDesktop; + onClicked: { + if (isVR) { + AudioScriptingInterface.pushToTalkHMD = checked; + } else { + AudioScriptingInterface.pushToTalkDesktop = checked; + } + checked = Qt.binding(function() { + if (isVR) { + return AudioScriptingInterface.pushToTalkHMD; + } else { + return AudioScriptingInterface.pushToTalkDesktop; + } + }); // restore binding + } + } AudioControls.CheckBox { spacing: muteMic.spacing text: qsTr("Show audio level meter"); diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index 39f75a9182..f91058bc3c 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -11,10 +11,13 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 +import stylesUit 1.0 import TabletScriptingInterface 1.0 Rectangle { + HifiConstants { id: hifi; } + readonly property var level: AudioScriptingInterface.inputLevel; property bool gated: false; @@ -131,9 +134,9 @@ Rectangle { Item { id: status; - readonly property string color: AudioScriptingInterface.muted ? colors.muted : colors.unmuted; + readonly property string color: (AudioScriptingInterface.pushingToTalk && AudioScriptingInterface.muted) ? hifi.colors.blueHighlight : AudioScriptingInterface.muted ? colors.muted : colors.unmuted; - visible: AudioScriptingInterface.muted; + visible: AudioScriptingInterface.pushingToTalk || AudioScriptingInterface.muted; anchors { left: parent.left; @@ -152,7 +155,7 @@ Rectangle { color: parent.color; - text: AudioScriptingInterface.muted ? "MUTED" : "MUTE"; + text: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? "SPEAKING" : "PUSH TO TALK") : (AudioScriptingInterface.muted ? "MUTED" : "MUTE"); font.pointSize: 12; } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 2c8d71af00..bcd5367a89 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1431,6 +1431,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo }); connect(this, &Application::activeDisplayPluginChanged, reinterpret_cast(audioScriptingInterface.data()), &scripting::Audio::onContextChanged); + connect(this, &Application::pushedToTalk, + reinterpret_cast(audioScriptingInterface.data()), &scripting::Audio::handlePushedToTalk); } // Create the rendering engine. This can be slow on some machines due to lots of @@ -4195,6 +4197,10 @@ void Application::keyPressEvent(QKeyEvent* event) { } break; + case Qt::Key_T: + emit pushedToTalk(true); + break; + case Qt::Key_P: { if (!isShifted && !isMeta && !isOption && !event->isAutoRepeat()) { AudioInjectorOptions options; @@ -4300,6 +4306,12 @@ void Application::keyReleaseEvent(QKeyEvent* event) { if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->keyReleaseEvent(event); } + + switch (event->key()) { + case Qt::Key_T: + emit pushedToTalk(false); + break; + } } void Application::focusOutEvent(QFocusEvent* event) { @@ -5243,6 +5255,9 @@ void Application::loadSettings() { } } + auto audioScriptingInterface = reinterpret_cast(DependencyManager::get().data()); + audioScriptingInterface->loadData(); + getMyAvatar()->loadData(); _settingsLoaded = true; } @@ -5252,6 +5267,9 @@ void Application::saveSettings() const { DependencyManager::get()->saveSettings(); DependencyManager::get()->saveSettings(); + auto audioScriptingInterface = reinterpret_cast(DependencyManager::get().data()); + audioScriptingInterface->saveData(); + Menu::getInstance()->saveSettings(); getMyAvatar()->saveData(); PluginManager::getInstance()->saveSettings(); diff --git a/interface/src/Application.h b/interface/src/Application.h index a8cc9450c5..1c86326f90 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -358,6 +358,8 @@ signals: void miniTabletEnabledChanged(bool enabled); + void pushedToTalk(bool enabled); + public slots: QVector pasteEntities(float x, float y, float z); bool exportEntities(const QString& filename, const QVector& entityIDs, const glm::vec3* givenOffset = nullptr); diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 2c4c29ff65..fe04ce47ca 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -63,26 +63,163 @@ void Audio::stopRecording() { } bool Audio::isMuted() const { - return resultWithReadLock([&] { - return _isMuted; - }); + bool isHMD = qApp->isHMDMode(); + if (isHMD) { + return getMutedHMD(); + } + else { + return getMutedDesktop(); + } } void Audio::setMuted(bool isMuted) { + withWriteLock([&] { + bool isHMD = qApp->isHMDMode(); + if (isHMD) { + setMutedHMD(isMuted); + } + else { + setMutedDesktop(isMuted); + } + }); +} + +void Audio::setMutedDesktop(bool isMuted) { + bool changed = false; + if (_desktopMuted != isMuted) { + changed = true; + _desktopMuted = isMuted; + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + } + if (changed) { + emit mutedChanged(isMuted); + emit desktopMutedChanged(isMuted); + } +} + +bool Audio::getMutedDesktop() const { + return resultWithReadLock([&] { + return _desktopMuted; + }); +} + +void Audio::setMutedHMD(bool isMuted) { + bool changed = false; + if (_hmdMuted != isMuted) { + changed = true; + _hmdMuted = isMuted; + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + } + if (changed) { + emit mutedChanged(isMuted); + emit hmdMutedChanged(isMuted); + } +} + +bool Audio::getMutedHMD() const { + return resultWithReadLock([&] { + return _hmdMuted; + }); +} + +bool Audio::getPTT() { + bool isHMD = qApp->isHMDMode(); + if (isHMD) { + return getPTTHMD(); + } + else { + return getPTTDesktop(); + } +} + +bool Audio::getPushingToTalk() const { + return resultWithReadLock([&] { + return _pushingToTalk; + }); +} + +void Audio::setPTT(bool enabled) { + bool isHMD = qApp->isHMDMode(); + if (isHMD) { + setPTTHMD(enabled); + } + else { + setPTTDesktop(enabled); + } +} + +void Audio::setPTTDesktop(bool enabled) { bool changed = false; withWriteLock([&] { - if (_isMuted != isMuted) { - _isMuted = isMuted; - auto client = DependencyManager::get().data(); - QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + if (_pttDesktop != enabled) { changed = true; + _pttDesktop = enabled; + if (!enabled) { + // Set to default behavior (unmuted for Desktop) on Push-To-Talk disable. + setMutedDesktop(true); + } + else { + // Should be muted when not pushing to talk while PTT is enabled. + setMutedDesktop(true); + } } }); if (changed) { - emit mutedChanged(isMuted); + emit pushToTalkChanged(enabled); + emit pushToTalkDesktopChanged(enabled); } } +bool Audio::getPTTDesktop() const { + return resultWithReadLock([&] { + return _pttDesktop; + }); +} + +void Audio::setPTTHMD(bool enabled) { + bool changed = false; + withWriteLock([&] { + if (_pttHMD != enabled) { + changed = true; + _pttHMD = enabled; + if (!enabled) { + // Set to default behavior (unmuted for HMD) on Push-To-Talk disable. + setMutedHMD(false); + } + else { + // Should be muted when not pushing to talk while PTT is enabled. + setMutedHMD(true); + } + } + }); + if (changed) { + emit pushToTalkChanged(enabled); + emit pushToTalkHMDChanged(enabled); + } +} + +bool Audio::getPTTHMD() const { + return resultWithReadLock([&] { + return _pttHMD; + }); +} + +void Audio::saveData() { + _desktopMutedSetting.set(getMutedDesktop()); + _hmdMutedSetting.set(getMutedHMD()); + _pttDesktopSetting.set(getPTTDesktop()); + _pttHMDSetting.set(getPTTHMD()); +} + +void Audio::loadData() { + _desktopMuted = _desktopMutedSetting.get(); + _hmdMuted = _hmdMutedSetting.get(); + _pttDesktop = _pttDesktopSetting.get(); + _pttHMD = _pttHMDSetting.get(); +} + bool Audio::noiseReductionEnabled() const { return resultWithReadLock([&] { return _enableNoiseReduction; @@ -179,11 +316,32 @@ void Audio::onContextChanged() { changed = true; } }); + if (isHMD) { + setMuted(getMutedHMD()); + } + else { + setMuted(getMutedDesktop()); + } if (changed) { emit contextChanged(isHMD ? Audio::HMD : Audio::DESKTOP); } } +void Audio::handlePushedToTalk(bool enabled) { + if (getPTT()) { + if (enabled) { + setMuted(false); + } + else { + setMuted(true); + } + if (_pushingToTalk != enabled) { + _pushingToTalk = enabled; + emit pushingToTalkChanged(enabled); + } + } +} + void Audio::setReverb(bool enable) { withWriteLock([&] { DependencyManager::get()->setReverb(enable); diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index e4dcba9130..6aa589e399 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -12,6 +12,7 @@ #ifndef hifi_scripting_Audio_h #define hifi_scripting_Audio_h +#include #include "AudioScriptingInterface.h" #include "AudioDevices.h" #include "AudioEffectOptions.h" @@ -19,6 +20,9 @@ #include "AudioFileWav.h" #include +using MutedGetter = std::function; +using MutedSetter = std::function; + namespace scripting { class Audio : public AudioScriptingInterface, protected ReadWriteLockable { @@ -63,6 +67,12 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { Q_PROPERTY(bool clipping READ isClipping NOTIFY clippingChanged) Q_PROPERTY(QString context READ getContext NOTIFY contextChanged) Q_PROPERTY(AudioDevices* devices READ getDevices NOTIFY nop) + Q_PROPERTY(bool desktopMuted READ getMutedDesktop WRITE setMutedDesktop NOTIFY desktopMutedChanged) + Q_PROPERTY(bool hmdMuted READ getMutedHMD WRITE setMutedHMD NOTIFY hmdMutedChanged) + Q_PROPERTY(bool pushToTalk READ getPTT WRITE setPTT NOTIFY pushToTalkChanged); + Q_PROPERTY(bool pushToTalkDesktop READ getPTTDesktop WRITE setPTTDesktop NOTIFY pushToTalkDesktopChanged) + Q_PROPERTY(bool pushToTalkHMD READ getPTTHMD WRITE setPTTHMD NOTIFY pushToTalkHMDChanged) + Q_PROPERTY(bool pushingToTalk READ getPushingToTalk NOTIFY pushingToTalkChanged) public: static QString AUDIO; @@ -82,6 +92,25 @@ public: void showMicMeter(bool show); + // Mute setting setters and getters + void setMutedDesktop(bool isMuted); + bool getMutedDesktop() const; + void setMutedHMD(bool isMuted); + bool getMutedHMD() const; + void setPTT(bool enabled); + bool getPTT(); + bool getPushingToTalk() const; + + // Push-To-Talk setters and getters + void setPTTDesktop(bool enabled); + bool getPTTDesktop() const; + void setPTTHMD(bool enabled); + bool getPTTHMD() const; + + // Settings handlers + void saveData(); + void loadData(); + /**jsdoc * @function Audio.setInputDevice * @param {object} device @@ -193,6 +222,46 @@ signals: */ void mutedChanged(bool isMuted); + /**jsdoc + * Triggered when desktop audio input is muted or unmuted. + * @function Audio.desktopMutedChanged + * @param {boolean} isMuted - true if the audio input is muted for desktop mode, otherwise false. + * @returns {Signal} + */ + void desktopMutedChanged(bool isMuted); + + /**jsdoc + * Triggered when HMD audio input is muted or unmuted. + * @function Audio.hmdMutedChanged + * @param {boolean} isMuted - true if the audio input is muted for HMD mode, otherwise false. + * @returns {Signal} + */ + void hmdMutedChanged(bool isMuted); + + /** + * Triggered when Push-to-Talk has been enabled or disabled. + * @function Audio.pushToTalkChanged + * @param {boolean} enabled - true if Push-to-Talk is enabled, otherwise false. + * @returns {Signal} + */ + void pushToTalkChanged(bool enabled); + + /** + * Triggered when Push-to-Talk has been enabled or disabled for desktop mode. + * @function Audio.pushToTalkDesktopChanged + * @param {boolean} enabled - true if Push-to-Talk is emabled for Desktop mode, otherwise false. + * @returns {Signal} + */ + void pushToTalkDesktopChanged(bool enabled); + + /** + * Triggered when Push-to-Talk has been enabled or disabled for HMD mode. + * @function Audio.pushToTalkHMDChanged + * @param {boolean} enabled - true if Push-to-Talk is emabled for HMD mode, otherwise false. + * @returns {Signal} + */ + void pushToTalkHMDChanged(bool enabled); + /**jsdoc * Triggered when the audio input noise reduction is enabled or disabled. * @function Audio.noiseReductionChanged @@ -237,6 +306,14 @@ signals: */ void contextChanged(const QString& context); + /**jsdoc + * Triggered when pushing to talk. + * @function Audio.pushingToTalkChanged + * @param {boolean} talking - true if broadcasting with PTT, false otherwise. + * @returns {Signal} + */ + void pushingToTalkChanged(bool talking); + public slots: /**jsdoc @@ -245,6 +322,8 @@ public slots: */ void onContextChanged(); + void handlePushedToTalk(bool enabled); + private slots: void setMuted(bool muted); void enableNoiseReduction(bool enable); @@ -260,11 +339,19 @@ private: float _inputVolume { 1.0f }; float _inputLevel { 0.0f }; bool _isClipping { false }; - bool _isMuted { false }; bool _enableNoiseReduction { true }; // Match default value of AudioClient::_isNoiseGateEnabled. bool _contextIsHMD { false }; AudioDevices* getDevices() { return &_devices; } AudioDevices _devices; + Setting::Handle _desktopMutedSetting{ QStringList { Audio::AUDIO, "desktopMuted" }, true }; + Setting::Handle _hmdMutedSetting{ QStringList { Audio::AUDIO, "hmdMuted" }, true }; + Setting::Handle _pttDesktopSetting{ QStringList { Audio::AUDIO, "pushToTalkDesktop" }, false }; + Setting::Handle _pttHMDSetting{ QStringList { Audio::AUDIO, "pushToTalkHMD" }, false }; + bool _desktopMuted{ true }; + bool _hmdMuted{ false }; + bool _pttDesktop{ false }; + bool _pttHMD{ false }; + bool _pushingToTalk{ false }; }; }; diff --git a/scripts/system/audio.js b/scripts/system/audio.js index ee82c0c6ea..51d070d8cd 100644 --- a/scripts/system/audio.js +++ b/scripts/system/audio.js @@ -28,6 +28,9 @@ var UNMUTE_ICONS = { }; function onMuteToggled() { + if (Audio.pushingToTalk) { + return; + } if (Audio.muted) { button.editProperties(MUTE_ICONS); } else { From 442da665830d90acf6e7ce859569c7023c698af1 Mon Sep 17 00:00:00 2001 From: amantley Date: Tue, 5 Mar 2019 17:44:36 -0800 Subject: [PATCH 344/474] debugging tiny hulk problems --- libraries/animation/src/AnimClip.cpp | 3 ++- libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index 4fe02e9307..e014deca2a 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -134,6 +134,7 @@ void AnimClip::copyFromNetworkAnim() { const float animationUnitScale = extractScale(animModel.offset).y; const float avatarHeightInMeters = avatarUnitScale * avatarHipsAbsoluteDefaultPose.trans().y; const float animHeightInMeters = animationUnitScale * animHipsAbsoluteDefaultPose.trans().y; + qCDebug(animation) << "meters per unit, avatar: " << avatarUnitScale << " and height of avatar " << avatarHeightInMeters; // get the parent scales for the avatar and the animation float avatarHipsParentScale = 1.0f; @@ -154,7 +155,7 @@ void AnimClip::copyFromNetworkAnim() { const float avatarToAnimationHeightRatio = avatarHeightInMeters / animHeightInMeters; const float unitsRatio = 1.0f / (avatarUnitScale / animationUnitScale); const float parentScaleRatio = 1.0f / (avatarHipsParentScale / animHipsParentScale); - + qCDebug(animation) << "height ratio: " << avatarToAnimationHeightRatio << " units ratio " << unitsRatio << " parent Scale Ratio " << parentScaleRatio; boneLengthScale = avatarToAnimationHeightRatio * unitsRatio * parentScaleRatio; } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index d3ae030296..29fc98734e 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -1983,6 +1983,7 @@ float Avatar::getUnscaledEyeHeight() const { void Avatar::buildUnscaledEyeHeightCache() { float skeletonHeight = getUnscaledEyeHeightFromSkeleton(); + qCDebug(avatars_renderer) << "unscaled eye height " << skeletonHeight; // Sanity check by looking at the model extents. Extents meshExtents = _skeletonModel->getUnscaledMeshExtents(); From 76609197e2387f2b5643f43afbb1416e9340f0be Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Tue, 5 Mar 2019 18:50:53 -0700 Subject: [PATCH 345/474] Read flow data from the fst file --- interface/src/avatar/MyAvatar.cpp | 32 +++++++++++++++++++ interface/src/avatar/MyAvatar.h | 1 + libraries/hfm/src/hfm/HFM.h | 9 ++++++ .../model-baker/src/model-baker/Baker.cpp | 9 ++++-- .../src/model-baker/ParseFlowDataTask.cpp | 26 +++++++++++++++ .../src/model-baker/ParseFlowDataTask.h | 24 ++++++++++++++ 6 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 libraries/model-baker/src/model-baker/ParseFlowDataTask.cpp create mode 100644 libraries/model-baker/src/model-baker/ParseFlowDataTask.h diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index af261f490b..ecc51132b9 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2337,6 +2337,7 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { _skeletonModel->setCauterizeBoneSet(_headBoneSet); _fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl(); initAnimGraph(); + initFlow(); _skeletonModelLoaded = true; } QObject::disconnect(*skeletonConnection); @@ -5383,6 +5384,37 @@ void MyAvatar::useFlow(bool isActive, bool isCollidable, const QVariantMap& phys } } +void MyAvatar::initFlow() { + auto &flowData = _skeletonModel->getHFMModel().flowData; + if (flowData._physicsData.size() > 0) { + QVariantMap physicsConfig; + QVariantMap collisionsConfig; + for (auto &data : flowData._physicsData) { + QJsonObject map = QJsonDocument::fromJson(data).object(); + if (!map.isEmpty() && map.keys().size() == 1) { + QString group = map.keys()[0]; + if (map[group].isObject()) { + physicsConfig.insert(group, map[group].toObject().toVariantMap()); + } + } + } + for (auto &data : flowData._collisionsData) { + QJsonObject map = QJsonDocument::fromJson(data).object(); + if (!map.isEmpty() && map.keys().size() == 1) { + QString jointName = map.keys()[0]; + if (map[jointName].isObject()) { + collisionsConfig.insert(jointName, map[jointName].toObject().toVariantMap()); + } + } + } + if (collisionsConfig.size() > 0) { + useFlow(true, true, physicsConfig, collisionsConfig); + } else { + useFlow(true, false, physicsConfig); + } + } +} + void MyAvatar::sendPacket(const QUuid& entityID, const EntityItemProperties& properties) const { auto treeRenderer = DependencyManager::get(); EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 2c8dedd430..c80412c949 100755 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1751,6 +1751,7 @@ private: void updateCollisionSound(const glm::vec3& penetration, float deltaTime, float frequency); void initHeadBones(); void initAnimGraph(); + void initFlow(); // Avatar Preferences QUrl _fullAvatarURLFromPreferences; diff --git a/libraries/hfm/src/hfm/HFM.h b/libraries/hfm/src/hfm/HFM.h index 9f3de3302c..c2d8b14ba1 100644 --- a/libraries/hfm/src/hfm/HFM.h +++ b/libraries/hfm/src/hfm/HFM.h @@ -273,6 +273,13 @@ public: {} }; +class FlowData { +public: + FlowData() {}; + std::vector _physicsData; + std::vector _collisionsData; +}; + /// The runtime model format. class Model { public: @@ -319,6 +326,7 @@ public: QList blendshapeChannelNames; QMap jointRotationOffsets; + FlowData flowData; }; }; @@ -343,6 +351,7 @@ typedef hfm::Mesh HFMMesh; typedef hfm::AnimationFrame HFMAnimationFrame; typedef hfm::Light HFMLight; typedef hfm::Model HFMModel; +typedef hfm::FlowData FlowData; Q_DECLARE_METATYPE(HFMAnimationFrame) Q_DECLARE_METATYPE(QVector) diff --git a/libraries/model-baker/src/model-baker/Baker.cpp b/libraries/model-baker/src/model-baker/Baker.cpp index dfb18eef86..595f2ae0f7 100644 --- a/libraries/model-baker/src/model-baker/Baker.cpp +++ b/libraries/model-baker/src/model-baker/Baker.cpp @@ -20,6 +20,7 @@ #include "CalculateBlendshapeNormalsTask.h" #include "CalculateBlendshapeTangentsTask.h" #include "PrepareJointsTask.h" +#include "ParseFlowDataTask.h" namespace baker { @@ -101,7 +102,7 @@ namespace baker { class BuildModelTask { public: - using Input = VaryingSet5, std::vector, QMap, QHash>; + using Input = VaryingSet6, std::vector, QMap, QHash, FlowData>; using Output = hfm::Model::Pointer; using JobModel = Job::ModelIO; @@ -111,6 +112,7 @@ namespace baker { hfmModelOut->joints = QVector::fromStdVector(input.get2()); hfmModelOut->jointRotationOffsets = input.get3(); hfmModelOut->jointIndices = input.get4(); + hfmModelOut->flowData = input.get5(); output = hfmModelOut; } }; @@ -157,12 +159,15 @@ namespace baker { // Parse material mapping const auto materialMapping = model.addJob("ParseMaterialMapping", mapping); + // Parse flow data + const auto flowData = model.addJob("ParseFlowData", mapping); + // Combine the outputs into a new hfm::Model const auto buildBlendshapesInputs = BuildBlendshapesTask::Input(blendshapesPerMeshIn, normalsPerBlendshapePerMesh, tangentsPerBlendshapePerMesh).asVarying(); const auto blendshapesPerMeshOut = model.addJob("BuildBlendshapes", buildBlendshapesInputs); const auto buildMeshesInputs = BuildMeshesTask::Input(meshesIn, graphicsMeshes, normalsPerMesh, tangentsPerMesh, blendshapesPerMeshOut).asVarying(); const auto meshesOut = model.addJob("BuildMeshes", buildMeshesInputs); - const auto buildModelInputs = BuildModelTask::Input(hfmModelIn, meshesOut, jointsOut, jointRotationOffsets, jointIndices).asVarying(); + const auto buildModelInputs = BuildModelTask::Input(hfmModelIn, meshesOut, jointsOut, jointRotationOffsets, jointIndices, flowData).asVarying(); const auto hfmModelOut = model.addJob("BuildModel", buildModelInputs); output = Output(hfmModelOut, materialMapping); diff --git a/libraries/model-baker/src/model-baker/ParseFlowDataTask.cpp b/libraries/model-baker/src/model-baker/ParseFlowDataTask.cpp new file mode 100644 index 0000000000..1a298b3d4b --- /dev/null +++ b/libraries/model-baker/src/model-baker/ParseFlowDataTask.cpp @@ -0,0 +1,26 @@ +// +// Created by Luis Cuenca on 5/3/2019 +// Copyright 2019 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 "ParseFlowDataTask.h" + +void ParseFlowDataTask::run(const baker::BakeContextPointer& context, const Input& mapping, Output& output) { + FlowData flowData; + static const QString FLOW_PHYSICS_FIELD = "flowPhysicsData"; + static const QString FLOW_COLLISIONS_FIELD = "flowCollisionsData"; + for (auto &mappingIter = mapping.begin(); mappingIter != mapping.end(); mappingIter++) { + if (mappingIter.key() == FLOW_PHYSICS_FIELD || mappingIter.key() == FLOW_COLLISIONS_FIELD) { + QByteArray flowDataValue = mappingIter.value().toByteArray(); + if (mappingIter.key() == FLOW_PHYSICS_FIELD) { + flowData._physicsData.push_back(flowDataValue); + } else { + flowData._collisionsData.push_back(flowDataValue); + } + } + } + output = flowData; +} diff --git a/libraries/model-baker/src/model-baker/ParseFlowDataTask.h b/libraries/model-baker/src/model-baker/ParseFlowDataTask.h new file mode 100644 index 0000000000..3c85430165 --- /dev/null +++ b/libraries/model-baker/src/model-baker/ParseFlowDataTask.h @@ -0,0 +1,24 @@ +// +// Created by Luis Cuenca on 3/7/2019 +// Copyright 2019 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_ParseFlowDataTask_h +#define hifi_ParseFlowDataTask_h + +#include +#include "Engine.h" + +class ParseFlowDataTask { +public: + using Input = QVariantHash; + using Output = FlowData; + using JobModel = baker::Job::ModelIO; + + void run(const baker::BakeContextPointer& context, const Input& input, Output& output); +}; + +#endif // hifi_ParseFlowDataTask_h From beeeb74f17ea740b4bf573a66c86b7e7ddee76d9 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Tue, 5 Mar 2019 18:56:02 -0700 Subject: [PATCH 346/474] Fix creation date --- libraries/model-baker/src/model-baker/ParseFlowDataTask.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/model-baker/src/model-baker/ParseFlowDataTask.h b/libraries/model-baker/src/model-baker/ParseFlowDataTask.h index 3c85430165..7e1bc9fba1 100644 --- a/libraries/model-baker/src/model-baker/ParseFlowDataTask.h +++ b/libraries/model-baker/src/model-baker/ParseFlowDataTask.h @@ -1,5 +1,5 @@ // -// Created by Luis Cuenca on 3/7/2019 +// Created by Luis Cuenca on 5/3/2019 // Copyright 2019 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. From 59200103af72ac2689b3d54443ef6b2c27224a5d Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Wed, 6 Mar 2019 07:06:48 +0100 Subject: [PATCH 347/474] Not for sale explanation --- .../qml/hifi/avatarPackager/AvatarProject.qml | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/avatarPackager/AvatarProject.qml b/interface/resources/qml/hifi/avatarPackager/AvatarProject.qml index a92739cf8a..e1945fad90 100644 --- a/interface/resources/qml/hifi/avatarPackager/AvatarProject.qml +++ b/interface/resources/qml/hifi/avatarPackager/AvatarProject.qml @@ -256,7 +256,7 @@ Item { name: "errors" PropertyChanges { target: doctorStatusMessage - text: "It seems your project has a few issues that will affect how it works in High Fidelity. " + text: "Your avatar has a few issues." } } ] @@ -306,6 +306,29 @@ Item { text: "You can upload your files to our servers to always access them, and to make your avatar visible to other users." } + RalewayRegular { + id: notForSaleMessage + + visible: root.hasSuccessfullyUploaded + + color: 'white' + linkColor: '#00B4EF' + size: 20 + + anchors.left: parent.left + anchors.right: parent.right + anchors.top: infoMessage.bottom + + anchors.bottomMargin: 24 + + wrapMode: Text.Wrap + text: "This item is not for sale yet, learn more." + + onLinkActivated: { + Qt.openUrlExternally("https://docs.highfidelity.com/sell/add-item/upload-avatar.html"); + } + } + RalewayRegular { id: showErrorsLink From dfb434ea09d3eb479117023355551daa6b8cb6ca Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Wed, 6 Mar 2019 09:25:49 -0700 Subject: [PATCH 348/474] Fix warning and refactoring --- interface/src/avatar/MyAvatar.cpp | 65 +++++++------------ interface/src/avatar/MyAvatar.h | 2 +- libraries/hfm/src/hfm/HFM.h | 6 +- .../src/model-baker/ParseFlowDataTask.cpp | 19 ++++-- 4 files changed, 40 insertions(+), 52 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index ecc51132b9..578c1915fd 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2323,24 +2323,25 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { std::shared_ptr skeletonConnection = std::make_shared(); *skeletonConnection = QObject::connect(_skeletonModel.get(), &SkeletonModel::skeletonLoaded, [this, skeletonModelChangeCount, skeletonConnection]() { - if (skeletonModelChangeCount == _skeletonModelChangeCount) { + if (skeletonModelChangeCount == _skeletonModelChangeCount) { - if (_fullAvatarModelName.isEmpty()) { - // Store the FST file name into preferences - const auto& mapping = _skeletonModel->getGeometry()->getMapping(); - if (mapping.value("name").isValid()) { - _fullAvatarModelName = mapping.value("name").toString(); - } - } + if (_fullAvatarModelName.isEmpty()) { + // Store the FST file name into preferences + const auto& mapping = _skeletonModel->getGeometry()->getMapping(); + if (mapping.value("name").isValid()) { + _fullAvatarModelName = mapping.value("name").toString(); + } + } - initHeadBones(); - _skeletonModel->setCauterizeBoneSet(_headBoneSet); - _fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl(); - initAnimGraph(); - initFlow(); - _skeletonModelLoaded = true; - } - QObject::disconnect(*skeletonConnection); + initHeadBones(); + _skeletonModel->setCauterizeBoneSet(_headBoneSet); + _fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl(); + initAnimGraph(); + initFlowFromFST(); + + _skeletonModelLoaded = true; + } + QObject::disconnect(*skeletonConnection); }); saveAvatarUrl(); @@ -5384,33 +5385,11 @@ void MyAvatar::useFlow(bool isActive, bool isCollidable, const QVariantMap& phys } } -void MyAvatar::initFlow() { - auto &flowData = _skeletonModel->getHFMModel().flowData; - if (flowData._physicsData.size() > 0) { - QVariantMap physicsConfig; - QVariantMap collisionsConfig; - for (auto &data : flowData._physicsData) { - QJsonObject map = QJsonDocument::fromJson(data).object(); - if (!map.isEmpty() && map.keys().size() == 1) { - QString group = map.keys()[0]; - if (map[group].isObject()) { - physicsConfig.insert(group, map[group].toObject().toVariantMap()); - } - } - } - for (auto &data : flowData._collisionsData) { - QJsonObject map = QJsonDocument::fromJson(data).object(); - if (!map.isEmpty() && map.keys().size() == 1) { - QString jointName = map.keys()[0]; - if (map[jointName].isObject()) { - collisionsConfig.insert(jointName, map[jointName].toObject().toVariantMap()); - } - } - } - if (collisionsConfig.size() > 0) { - useFlow(true, true, physicsConfig, collisionsConfig); - } else { - useFlow(true, false, physicsConfig); +void MyAvatar::initFlowFromFST() { + if (_skeletonModel->isLoaded()) { + auto &flowData = _skeletonModel->getHFMModel().flowData; + if (flowData.shouldInitFlow()) { + useFlow(true, flowData.shouldInitCollisions(), flowData._physicsConfig, flowData._collisionsConfig); } } } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index c80412c949..46189c4a11 100755 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1751,7 +1751,7 @@ private: void updateCollisionSound(const glm::vec3& penetration, float deltaTime, float frequency); void initHeadBones(); void initAnimGraph(); - void initFlow(); + void initFlowFromFST(); // Avatar Preferences QUrl _fullAvatarURLFromPreferences; diff --git a/libraries/hfm/src/hfm/HFM.h b/libraries/hfm/src/hfm/HFM.h index c2d8b14ba1..4f44595eaa 100644 --- a/libraries/hfm/src/hfm/HFM.h +++ b/libraries/hfm/src/hfm/HFM.h @@ -276,8 +276,10 @@ public: class FlowData { public: FlowData() {}; - std::vector _physicsData; - std::vector _collisionsData; + QVariantMap _physicsConfig; + QVariantMap _collisionsConfig; + bool shouldInitFlow() const { return _physicsConfig.size() > 0; } + bool shouldInitCollisions() const { return _collisionsConfig.size() > 0; } }; /// The runtime model format. diff --git a/libraries/model-baker/src/model-baker/ParseFlowDataTask.cpp b/libraries/model-baker/src/model-baker/ParseFlowDataTask.cpp index 1a298b3d4b..6dff4f8c55 100644 --- a/libraries/model-baker/src/model-baker/ParseFlowDataTask.cpp +++ b/libraries/model-baker/src/model-baker/ParseFlowDataTask.cpp @@ -12,13 +12,20 @@ void ParseFlowDataTask::run(const baker::BakeContextPointer& context, const Inpu FlowData flowData; static const QString FLOW_PHYSICS_FIELD = "flowPhysicsData"; static const QString FLOW_COLLISIONS_FIELD = "flowCollisionsData"; - for (auto &mappingIter = mapping.begin(); mappingIter != mapping.end(); mappingIter++) { + for (auto mappingIter = mapping.begin(); mappingIter != mapping.end(); mappingIter++) { if (mappingIter.key() == FLOW_PHYSICS_FIELD || mappingIter.key() == FLOW_COLLISIONS_FIELD) { - QByteArray flowDataValue = mappingIter.value().toByteArray(); - if (mappingIter.key() == FLOW_PHYSICS_FIELD) { - flowData._physicsData.push_back(flowDataValue); - } else { - flowData._collisionsData.push_back(flowDataValue); + QByteArray data = mappingIter.value().toByteArray(); + QJsonObject dataObject = QJsonDocument::fromJson(data).object(); + if (!dataObject.isEmpty() && dataObject.keys().size() == 1) { + QString key = dataObject.keys()[0]; + if (dataObject[key].isObject()) { + QVariantMap dataMap = dataObject[key].toObject().toVariantMap(); + if (mappingIter.key() == FLOW_PHYSICS_FIELD) { + flowData._physicsConfig.insert(key, dataMap); + } else { + flowData._collisionsConfig.insert(key, dataMap); + } + } } } } From d88235a08f577bc4145bd6b8e518e02e256ae3ce Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Tue, 5 Mar 2019 17:09:54 -0800 Subject: [PATCH 349/474] Initial implementation (deadlocks still occurring in Audio.cpp) --- interface/resources/qml/hifi/audio/Audio.qml | 19 ++ interface/resources/qml/hifi/audio/MicBar.qml | 9 +- interface/src/Application.cpp | 18 ++ interface/src/Application.h | 2 + interface/src/scripting/Audio.cpp | 174 +++++++++++++++++- interface/src/scripting/Audio.h | 89 ++++++++- scripts/system/audio.js | 3 + 7 files changed, 302 insertions(+), 12 deletions(-) diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index c8dd83cd62..45358f59a2 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -148,6 +148,25 @@ Rectangle { checked = Qt.binding(function() { return AudioScriptingInterface.noiseReduction; }); // restore binding } } + AudioControls.CheckBox { + spacing: muteMic.spacing + text: qsTr("Push To Talk"); + checked: isVR ? AudioScriptingInterface.pushToTalkHMD : AudioScriptingInterface.pushToTalkDesktop; + onClicked: { + if (isVR) { + AudioScriptingInterface.pushToTalkHMD = checked; + } else { + AudioScriptingInterface.pushToTalkDesktop = checked; + } + checked = Qt.binding(function() { + if (isVR) { + return AudioScriptingInterface.pushToTalkHMD; + } else { + return AudioScriptingInterface.pushToTalkDesktop; + } + }); // restore binding + } + } AudioControls.CheckBox { spacing: muteMic.spacing text: qsTr("Show audio level meter"); diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index 39f75a9182..f91058bc3c 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -11,10 +11,13 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 +import stylesUit 1.0 import TabletScriptingInterface 1.0 Rectangle { + HifiConstants { id: hifi; } + readonly property var level: AudioScriptingInterface.inputLevel; property bool gated: false; @@ -131,9 +134,9 @@ Rectangle { Item { id: status; - readonly property string color: AudioScriptingInterface.muted ? colors.muted : colors.unmuted; + readonly property string color: (AudioScriptingInterface.pushingToTalk && AudioScriptingInterface.muted) ? hifi.colors.blueHighlight : AudioScriptingInterface.muted ? colors.muted : colors.unmuted; - visible: AudioScriptingInterface.muted; + visible: AudioScriptingInterface.pushingToTalk || AudioScriptingInterface.muted; anchors { left: parent.left; @@ -152,7 +155,7 @@ Rectangle { color: parent.color; - text: AudioScriptingInterface.muted ? "MUTED" : "MUTE"; + text: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? "SPEAKING" : "PUSH TO TALK") : (AudioScriptingInterface.muted ? "MUTED" : "MUTE"); font.pointSize: 12; } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index b8ab4d10db..fa63757560 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1435,6 +1435,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo }); connect(this, &Application::activeDisplayPluginChanged, reinterpret_cast(audioScriptingInterface.data()), &scripting::Audio::onContextChanged); + connect(this, &Application::pushedToTalk, + reinterpret_cast(audioScriptingInterface.data()), &scripting::Audio::handlePushedToTalk); } // Create the rendering engine. This can be slow on some machines due to lots of @@ -4199,6 +4201,10 @@ void Application::keyPressEvent(QKeyEvent* event) { } break; + case Qt::Key_T: + emit pushedToTalk(true); + break; + case Qt::Key_P: { if (!isShifted && !isMeta && !isOption && !event->isAutoRepeat()) { AudioInjectorOptions options; @@ -4304,6 +4310,12 @@ void Application::keyReleaseEvent(QKeyEvent* event) { if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->keyReleaseEvent(event); } + + switch (event->key()) { + case Qt::Key_T: + emit pushedToTalk(false); + break; + } } void Application::focusOutEvent(QFocusEvent* event) { @@ -5235,6 +5247,9 @@ void Application::loadSettings() { } } + auto audioScriptingInterface = reinterpret_cast(DependencyManager::get().data()); + audioScriptingInterface->loadData(); + getMyAvatar()->loadData(); _settingsLoaded = true; } @@ -5244,6 +5259,9 @@ void Application::saveSettings() const { DependencyManager::get()->saveSettings(); DependencyManager::get()->saveSettings(); + auto audioScriptingInterface = reinterpret_cast(DependencyManager::get().data()); + audioScriptingInterface->saveData(); + Menu::getInstance()->saveSettings(); getMyAvatar()->saveData(); PluginManager::getInstance()->saveSettings(); diff --git a/interface/src/Application.h b/interface/src/Application.h index a8cc9450c5..1c86326f90 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -358,6 +358,8 @@ signals: void miniTabletEnabledChanged(bool enabled); + void pushedToTalk(bool enabled); + public slots: QVector pasteEntities(float x, float y, float z); bool exportEntities(const QString& filename, const QVector& entityIDs, const glm::vec3* givenOffset = nullptr); diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 2c4c29ff65..fe04ce47ca 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -63,26 +63,163 @@ void Audio::stopRecording() { } bool Audio::isMuted() const { - return resultWithReadLock([&] { - return _isMuted; - }); + bool isHMD = qApp->isHMDMode(); + if (isHMD) { + return getMutedHMD(); + } + else { + return getMutedDesktop(); + } } void Audio::setMuted(bool isMuted) { + withWriteLock([&] { + bool isHMD = qApp->isHMDMode(); + if (isHMD) { + setMutedHMD(isMuted); + } + else { + setMutedDesktop(isMuted); + } + }); +} + +void Audio::setMutedDesktop(bool isMuted) { + bool changed = false; + if (_desktopMuted != isMuted) { + changed = true; + _desktopMuted = isMuted; + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + } + if (changed) { + emit mutedChanged(isMuted); + emit desktopMutedChanged(isMuted); + } +} + +bool Audio::getMutedDesktop() const { + return resultWithReadLock([&] { + return _desktopMuted; + }); +} + +void Audio::setMutedHMD(bool isMuted) { + bool changed = false; + if (_hmdMuted != isMuted) { + changed = true; + _hmdMuted = isMuted; + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + } + if (changed) { + emit mutedChanged(isMuted); + emit hmdMutedChanged(isMuted); + } +} + +bool Audio::getMutedHMD() const { + return resultWithReadLock([&] { + return _hmdMuted; + }); +} + +bool Audio::getPTT() { + bool isHMD = qApp->isHMDMode(); + if (isHMD) { + return getPTTHMD(); + } + else { + return getPTTDesktop(); + } +} + +bool Audio::getPushingToTalk() const { + return resultWithReadLock([&] { + return _pushingToTalk; + }); +} + +void Audio::setPTT(bool enabled) { + bool isHMD = qApp->isHMDMode(); + if (isHMD) { + setPTTHMD(enabled); + } + else { + setPTTDesktop(enabled); + } +} + +void Audio::setPTTDesktop(bool enabled) { bool changed = false; withWriteLock([&] { - if (_isMuted != isMuted) { - _isMuted = isMuted; - auto client = DependencyManager::get().data(); - QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + if (_pttDesktop != enabled) { changed = true; + _pttDesktop = enabled; + if (!enabled) { + // Set to default behavior (unmuted for Desktop) on Push-To-Talk disable. + setMutedDesktop(true); + } + else { + // Should be muted when not pushing to talk while PTT is enabled. + setMutedDesktop(true); + } } }); if (changed) { - emit mutedChanged(isMuted); + emit pushToTalkChanged(enabled); + emit pushToTalkDesktopChanged(enabled); } } +bool Audio::getPTTDesktop() const { + return resultWithReadLock([&] { + return _pttDesktop; + }); +} + +void Audio::setPTTHMD(bool enabled) { + bool changed = false; + withWriteLock([&] { + if (_pttHMD != enabled) { + changed = true; + _pttHMD = enabled; + if (!enabled) { + // Set to default behavior (unmuted for HMD) on Push-To-Talk disable. + setMutedHMD(false); + } + else { + // Should be muted when not pushing to talk while PTT is enabled. + setMutedHMD(true); + } + } + }); + if (changed) { + emit pushToTalkChanged(enabled); + emit pushToTalkHMDChanged(enabled); + } +} + +bool Audio::getPTTHMD() const { + return resultWithReadLock([&] { + return _pttHMD; + }); +} + +void Audio::saveData() { + _desktopMutedSetting.set(getMutedDesktop()); + _hmdMutedSetting.set(getMutedHMD()); + _pttDesktopSetting.set(getPTTDesktop()); + _pttHMDSetting.set(getPTTHMD()); +} + +void Audio::loadData() { + _desktopMuted = _desktopMutedSetting.get(); + _hmdMuted = _hmdMutedSetting.get(); + _pttDesktop = _pttDesktopSetting.get(); + _pttHMD = _pttHMDSetting.get(); +} + bool Audio::noiseReductionEnabled() const { return resultWithReadLock([&] { return _enableNoiseReduction; @@ -179,11 +316,32 @@ void Audio::onContextChanged() { changed = true; } }); + if (isHMD) { + setMuted(getMutedHMD()); + } + else { + setMuted(getMutedDesktop()); + } if (changed) { emit contextChanged(isHMD ? Audio::HMD : Audio::DESKTOP); } } +void Audio::handlePushedToTalk(bool enabled) { + if (getPTT()) { + if (enabled) { + setMuted(false); + } + else { + setMuted(true); + } + if (_pushingToTalk != enabled) { + _pushingToTalk = enabled; + emit pushingToTalkChanged(enabled); + } + } +} + void Audio::setReverb(bool enable) { withWriteLock([&] { DependencyManager::get()->setReverb(enable); diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index e4dcba9130..6aa589e399 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -12,6 +12,7 @@ #ifndef hifi_scripting_Audio_h #define hifi_scripting_Audio_h +#include #include "AudioScriptingInterface.h" #include "AudioDevices.h" #include "AudioEffectOptions.h" @@ -19,6 +20,9 @@ #include "AudioFileWav.h" #include +using MutedGetter = std::function; +using MutedSetter = std::function; + namespace scripting { class Audio : public AudioScriptingInterface, protected ReadWriteLockable { @@ -63,6 +67,12 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { Q_PROPERTY(bool clipping READ isClipping NOTIFY clippingChanged) Q_PROPERTY(QString context READ getContext NOTIFY contextChanged) Q_PROPERTY(AudioDevices* devices READ getDevices NOTIFY nop) + Q_PROPERTY(bool desktopMuted READ getMutedDesktop WRITE setMutedDesktop NOTIFY desktopMutedChanged) + Q_PROPERTY(bool hmdMuted READ getMutedHMD WRITE setMutedHMD NOTIFY hmdMutedChanged) + Q_PROPERTY(bool pushToTalk READ getPTT WRITE setPTT NOTIFY pushToTalkChanged); + Q_PROPERTY(bool pushToTalkDesktop READ getPTTDesktop WRITE setPTTDesktop NOTIFY pushToTalkDesktopChanged) + Q_PROPERTY(bool pushToTalkHMD READ getPTTHMD WRITE setPTTHMD NOTIFY pushToTalkHMDChanged) + Q_PROPERTY(bool pushingToTalk READ getPushingToTalk NOTIFY pushingToTalkChanged) public: static QString AUDIO; @@ -82,6 +92,25 @@ public: void showMicMeter(bool show); + // Mute setting setters and getters + void setMutedDesktop(bool isMuted); + bool getMutedDesktop() const; + void setMutedHMD(bool isMuted); + bool getMutedHMD() const; + void setPTT(bool enabled); + bool getPTT(); + bool getPushingToTalk() const; + + // Push-To-Talk setters and getters + void setPTTDesktop(bool enabled); + bool getPTTDesktop() const; + void setPTTHMD(bool enabled); + bool getPTTHMD() const; + + // Settings handlers + void saveData(); + void loadData(); + /**jsdoc * @function Audio.setInputDevice * @param {object} device @@ -193,6 +222,46 @@ signals: */ void mutedChanged(bool isMuted); + /**jsdoc + * Triggered when desktop audio input is muted or unmuted. + * @function Audio.desktopMutedChanged + * @param {boolean} isMuted - true if the audio input is muted for desktop mode, otherwise false. + * @returns {Signal} + */ + void desktopMutedChanged(bool isMuted); + + /**jsdoc + * Triggered when HMD audio input is muted or unmuted. + * @function Audio.hmdMutedChanged + * @param {boolean} isMuted - true if the audio input is muted for HMD mode, otherwise false. + * @returns {Signal} + */ + void hmdMutedChanged(bool isMuted); + + /** + * Triggered when Push-to-Talk has been enabled or disabled. + * @function Audio.pushToTalkChanged + * @param {boolean} enabled - true if Push-to-Talk is enabled, otherwise false. + * @returns {Signal} + */ + void pushToTalkChanged(bool enabled); + + /** + * Triggered when Push-to-Talk has been enabled or disabled for desktop mode. + * @function Audio.pushToTalkDesktopChanged + * @param {boolean} enabled - true if Push-to-Talk is emabled for Desktop mode, otherwise false. + * @returns {Signal} + */ + void pushToTalkDesktopChanged(bool enabled); + + /** + * Triggered when Push-to-Talk has been enabled or disabled for HMD mode. + * @function Audio.pushToTalkHMDChanged + * @param {boolean} enabled - true if Push-to-Talk is emabled for HMD mode, otherwise false. + * @returns {Signal} + */ + void pushToTalkHMDChanged(bool enabled); + /**jsdoc * Triggered when the audio input noise reduction is enabled or disabled. * @function Audio.noiseReductionChanged @@ -237,6 +306,14 @@ signals: */ void contextChanged(const QString& context); + /**jsdoc + * Triggered when pushing to talk. + * @function Audio.pushingToTalkChanged + * @param {boolean} talking - true if broadcasting with PTT, false otherwise. + * @returns {Signal} + */ + void pushingToTalkChanged(bool talking); + public slots: /**jsdoc @@ -245,6 +322,8 @@ public slots: */ void onContextChanged(); + void handlePushedToTalk(bool enabled); + private slots: void setMuted(bool muted); void enableNoiseReduction(bool enable); @@ -260,11 +339,19 @@ private: float _inputVolume { 1.0f }; float _inputLevel { 0.0f }; bool _isClipping { false }; - bool _isMuted { false }; bool _enableNoiseReduction { true }; // Match default value of AudioClient::_isNoiseGateEnabled. bool _contextIsHMD { false }; AudioDevices* getDevices() { return &_devices; } AudioDevices _devices; + Setting::Handle _desktopMutedSetting{ QStringList { Audio::AUDIO, "desktopMuted" }, true }; + Setting::Handle _hmdMutedSetting{ QStringList { Audio::AUDIO, "hmdMuted" }, true }; + Setting::Handle _pttDesktopSetting{ QStringList { Audio::AUDIO, "pushToTalkDesktop" }, false }; + Setting::Handle _pttHMDSetting{ QStringList { Audio::AUDIO, "pushToTalkHMD" }, false }; + bool _desktopMuted{ true }; + bool _hmdMuted{ false }; + bool _pttDesktop{ false }; + bool _pttHMD{ false }; + bool _pushingToTalk{ false }; }; }; diff --git a/scripts/system/audio.js b/scripts/system/audio.js index ee82c0c6ea..51d070d8cd 100644 --- a/scripts/system/audio.js +++ b/scripts/system/audio.js @@ -28,6 +28,9 @@ var UNMUTE_ICONS = { }; function onMuteToggled() { + if (Audio.pushingToTalk) { + return; + } if (Audio.muted) { button.editProperties(MUTE_ICONS); } else { From a84a43320c875a05ffcd632802b021a42ae38e39 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Wed, 6 Mar 2019 19:15:01 +0100 Subject: [PATCH 350/474] added spacing between lines --- .../resources/qml/hifi/avatarPackager/AvatarProject.qml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/avatarPackager/AvatarProject.qml b/interface/resources/qml/hifi/avatarPackager/AvatarProject.qml index e1945fad90..bf8c06d1b3 100644 --- a/interface/resources/qml/hifi/avatarPackager/AvatarProject.qml +++ b/interface/resources/qml/hifi/avatarPackager/AvatarProject.qml @@ -318,6 +318,7 @@ Item { anchors.left: parent.left anchors.right: parent.right anchors.top: infoMessage.bottom + anchors.topMargin: 10 anchors.bottomMargin: 24 @@ -338,8 +339,8 @@ Item { visible: AvatarPackagerCore.currentAvatarProject && AvatarPackagerCore.currentAvatarProject.hasErrors anchors { - top: infoMessage.bottom - topMargin: 28 + top: notForSaleMessage.bottom + topMargin: 16 horizontalCenter: parent.horizontalCenter } From d4cea6009f5018d6bb33a486822d2c9a2927f697 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 6 Mar 2019 10:45:13 -0800 Subject: [PATCH 351/474] Add the version code to the Quest android build --- android/apps/interface/build.gradle | 5 ----- android/apps/questInterface/build.gradle | 3 +-- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/android/apps/interface/build.gradle b/android/apps/interface/build.gradle index f954fbc1f4..bb2745ca22 100644 --- a/android/apps/interface/build.gradle +++ b/android/apps/interface/build.gradle @@ -36,11 +36,6 @@ apply plugin: 'com.android.application' android { compileSdkVersion 26 - //buildToolsVersion '27.0.3' - - def appVersionCode = Integer.valueOf(VERSION_CODE ?: 1) - def appVersionName = RELEASE_NUMBER ?: "1.0" - defaultConfig { applicationId "io.highfidelity.hifiinterface" minSdkVersion 24 diff --git a/android/apps/questInterface/build.gradle b/android/apps/questInterface/build.gradle index 6f4f6b7441..c54401a81b 100644 --- a/android/apps/questInterface/build.gradle +++ b/android/apps/questInterface/build.gradle @@ -18,12 +18,11 @@ task renameHifiACTaskRelease(type: Copy) { android { compileSdkVersion 28 - defaultConfig { applicationId "io.highfidelity.questInterface" minSdkVersion 24 targetSdkVersion 28 - versionCode 1 + versionCode appVersionCode versionName appVersionName ndk { abiFilters 'arm64-v8a' } externalNativeBuild { From 8b4123b5dc7a92bedd7a533a2fd6f16b66a1ade5 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 6 Mar 2019 11:00:09 -0800 Subject: [PATCH 352/474] laying groundwork for audio app + fixing deadlocks --- interface/resources/qml/hifi/audio/MicBar.qml | 6 +- interface/src/scripting/Audio.cpp | 108 +++++++++--------- interface/src/scripting/Audio.h | 1 + scripts/system/audio.js | 11 +- 4 files changed, 69 insertions(+), 57 deletions(-) diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index f91058bc3c..2ab1085408 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -134,7 +134,7 @@ Rectangle { Item { id: status; - readonly property string color: (AudioScriptingInterface.pushingToTalk && AudioScriptingInterface.muted) ? hifi.colors.blueHighlight : AudioScriptingInterface.muted ? colors.muted : colors.unmuted; + readonly property string color: AudioScriptingInterface.pushToTalk ? hifi.colors.blueHighlight : AudioScriptingInterface.muted ? colors.muted : colors.unmuted; visible: AudioScriptingInterface.pushingToTalk || AudioScriptingInterface.muted; @@ -165,7 +165,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - width: 50; + width: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? 45: 30) : 50; height: 4; color: parent.color; } @@ -176,7 +176,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - width: 50; + width: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? 45: 30) : 50; height: 4; color: parent.color; } diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index fe04ce47ca..63ce9d2b2e 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -66,32 +66,30 @@ bool Audio::isMuted() const { bool isHMD = qApp->isHMDMode(); if (isHMD) { return getMutedHMD(); - } - else { + } else { return getMutedDesktop(); } } void Audio::setMuted(bool isMuted) { - withWriteLock([&] { - bool isHMD = qApp->isHMDMode(); - if (isHMD) { - setMutedHMD(isMuted); - } - else { - setMutedDesktop(isMuted); - } - }); + bool isHMD = qApp->isHMDMode(); + if (isHMD) { + setMutedHMD(isMuted); + } else { + setMutedDesktop(isMuted); + } } void Audio::setMutedDesktop(bool isMuted) { bool changed = false; - if (_desktopMuted != isMuted) { - changed = true; - _desktopMuted = isMuted; - auto client = DependencyManager::get().data(); - QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); - } + withWriteLock([&] { + if (_desktopMuted != isMuted) { + changed = true; + _desktopMuted = isMuted; + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + } + }); if (changed) { emit mutedChanged(isMuted); emit desktopMutedChanged(isMuted); @@ -106,12 +104,14 @@ bool Audio::getMutedDesktop() const { void Audio::setMutedHMD(bool isMuted) { bool changed = false; - if (_hmdMuted != isMuted) { - changed = true; - _hmdMuted = isMuted; - auto client = DependencyManager::get().data(); - QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); - } + withWriteLock([&] { + if (_hmdMuted != isMuted) { + changed = true; + _hmdMuted = isMuted; + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + } + }); if (changed) { emit mutedChanged(isMuted); emit hmdMutedChanged(isMuted); @@ -128,12 +128,24 @@ bool Audio::getPTT() { bool isHMD = qApp->isHMDMode(); if (isHMD) { return getPTTHMD(); - } - else { + } else { return getPTTDesktop(); } } +void scripting::Audio::setPushingToTalk(bool pushingToTalk) { + bool changed = false; + withWriteLock([&] { + if (_pushingToTalk != pushingToTalk) { + changed = true; + _pushingToTalk = pushingToTalk; + } + }); + if (changed) { + emit pushingToTalkChanged(pushingToTalk); + } +} + bool Audio::getPushingToTalk() const { return resultWithReadLock([&] { return _pushingToTalk; @@ -144,8 +156,7 @@ void Audio::setPTT(bool enabled) { bool isHMD = qApp->isHMDMode(); if (isHMD) { setPTTHMD(enabled); - } - else { + } else { setPTTDesktop(enabled); } } @@ -156,16 +167,16 @@ void Audio::setPTTDesktop(bool enabled) { if (_pttDesktop != enabled) { changed = true; _pttDesktop = enabled; - if (!enabled) { - // Set to default behavior (unmuted for Desktop) on Push-To-Talk disable. - setMutedDesktop(true); - } - else { - // Should be muted when not pushing to talk while PTT is enabled. - setMutedDesktop(true); - } } }); + if (!enabled) { + // Set to default behavior (unmuted for Desktop) on Push-To-Talk disable. + setMutedDesktop(true); + } else { + // Should be muted when not pushing to talk while PTT is enabled. + setMutedDesktop(true); + } + if (changed) { emit pushToTalkChanged(enabled); emit pushToTalkDesktopChanged(enabled); @@ -184,16 +195,16 @@ void Audio::setPTTHMD(bool enabled) { if (_pttHMD != enabled) { changed = true; _pttHMD = enabled; - if (!enabled) { - // Set to default behavior (unmuted for HMD) on Push-To-Talk disable. - setMutedHMD(false); - } - else { - // Should be muted when not pushing to talk while PTT is enabled. - setMutedHMD(true); - } } }); + if (!enabled) { + // Set to default behavior (unmuted for HMD) on Push-To-Talk disable. + setMutedHMD(false); + } else { + // Should be muted when not pushing to talk while PTT is enabled. + setMutedHMD(true); + } + if (changed) { emit pushToTalkChanged(enabled); emit pushToTalkHMDChanged(enabled); @@ -318,8 +329,7 @@ void Audio::onContextChanged() { }); if (isHMD) { setMuted(getMutedHMD()); - } - else { + } else { setMuted(getMutedDesktop()); } if (changed) { @@ -331,14 +341,10 @@ void Audio::handlePushedToTalk(bool enabled) { if (getPTT()) { if (enabled) { setMuted(false); - } - else { + } else { setMuted(true); } - if (_pushingToTalk != enabled) { - _pushingToTalk = enabled; - emit pushingToTalkChanged(enabled); - } + setPushingToTalk(enabled); } } diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index 6aa589e399..94f8a7bf54 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -99,6 +99,7 @@ public: bool getMutedHMD() const; void setPTT(bool enabled); bool getPTT(); + void setPushingToTalk(bool pushingToTalk); bool getPushingToTalk() const; // Push-To-Talk setters and getters diff --git a/scripts/system/audio.js b/scripts/system/audio.js index 51d070d8cd..bf44cfa7cc 100644 --- a/scripts/system/audio.js +++ b/scripts/system/audio.js @@ -26,12 +26,15 @@ var UNMUTE_ICONS = { icon: "icons/tablet-icons/mic-unmute-i.svg", activeIcon: "icons/tablet-icons/mic-unmute-a.svg" }; +var PTT_ICONS = { + icon: "icons/tablet-icons/mic-unmute-i.svg", + activeIcon: "icons/tablet-icons/mic-unmute-a.svg" +}; function onMuteToggled() { if (Audio.pushingToTalk) { - return; - } - if (Audio.muted) { + button.editProperties(PTT_ICONS); + } else if (Audio.muted) { button.editProperties(MUTE_ICONS); } else { button.editProperties(UNMUTE_ICONS); @@ -71,6 +74,7 @@ onMuteToggled(); button.clicked.connect(onClicked); tablet.screenChanged.connect(onScreenChanged); Audio.mutedChanged.connect(onMuteToggled); +Audio.pushingToTalkChanged.connect(onMuteToggled); Script.scriptEnding.connect(function () { if (onAudioScreen) { @@ -79,6 +83,7 @@ Script.scriptEnding.connect(function () { button.clicked.disconnect(onClicked); tablet.screenChanged.disconnect(onScreenChanged); Audio.mutedChanged.disconnect(onMuteToggled); + Audio.pushingToTalkChanged.disconnect(onMuteToggled); tablet.removeButton(button); }); From 7f585587ffb3afb0073855150fc5026ab0976aa1 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 6 Mar 2019 11:11:47 -0800 Subject: [PATCH 353/474] Push to talk changes + rebased with master (#7) Push to talk changes + rebased with master --- interface/resources/qml/hifi/audio/MicBar.qml | 6 +- interface/src/AboutUtil.h | 14 +- interface/src/Application.cpp | 24 +- interface/src/commerce/Wallet.h | 8 +- .../AccountServicesScriptingInterface.cpp | 4 +- .../AccountServicesScriptingInterface.h | 34 +-- interface/src/scripting/Audio.cpp | 128 ++++++---- interface/src/scripting/Audio.h | 1 + .../src/scripting/WalletScriptingInterface.h | 22 +- interface/src/ui/overlays/Overlays.cpp | 239 ++++++------------ interface/src/ui/overlays/Overlays.h | 17 +- .../src/EntityTreeRenderer.cpp | 70 ++--- .../src/EntityTreeRenderer.h | 8 +- libraries/entities/src/EntityTree.cpp | 24 +- libraries/entities/src/EntityTree.h | 7 +- libraries/entities/src/EntityTreeElement.cpp | 4 +- libraries/entities/src/EntityTreeElement.h | 2 +- libraries/entities/src/ModelEntityItem.h | 2 +- libraries/fbx/src/FBXSerializer.cpp | 28 +- libraries/octree/src/Octree.h | 2 +- libraries/octree/src/OctreeProcessor.cpp | 4 +- libraries/octree/src/OctreeProcessor.h | 2 +- libraries/render-utils/src/Model.cpp | 19 +- libraries/script-engine/src/ScriptEngine.cpp | 4 +- scripts/system/audio.js | 11 +- 25 files changed, 331 insertions(+), 353 deletions(-) diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index f91058bc3c..2ab1085408 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -134,7 +134,7 @@ Rectangle { Item { id: status; - readonly property string color: (AudioScriptingInterface.pushingToTalk && AudioScriptingInterface.muted) ? hifi.colors.blueHighlight : AudioScriptingInterface.muted ? colors.muted : colors.unmuted; + readonly property string color: AudioScriptingInterface.pushToTalk ? hifi.colors.blueHighlight : AudioScriptingInterface.muted ? colors.muted : colors.unmuted; visible: AudioScriptingInterface.pushingToTalk || AudioScriptingInterface.muted; @@ -165,7 +165,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - width: 50; + width: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? 45: 30) : 50; height: 4; color: parent.color; } @@ -176,7 +176,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - width: 50; + width: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? 45: 30) : 50; height: 4; color: parent.color; } diff --git a/interface/src/AboutUtil.h b/interface/src/AboutUtil.h index c06255aaa5..4a5074857d 100644 --- a/interface/src/AboutUtil.h +++ b/interface/src/AboutUtil.h @@ -16,8 +16,8 @@ #include /**jsdoc - * The HifiAbout API provides information about the version of Interface that is currently running. It also - * provides the ability to open a Web page in an Interface browser window. + * The HifiAbout API provides information about the version of Interface that is currently running. It also + * has the functionality to open a web page in an Interface browser window. * * @namespace HifiAbout * @@ -30,9 +30,9 @@ * @property {string} qtVersion - The Qt version used in Interface that is currently running. Read-only. * * @example Report build information for the version of Interface currently running. - * print("HiFi build date: " + HifiAbout.buildDate); // 11 Feb 2019 - * print("HiFi version: " + HifiAbout.buildVersion); // 0.78.0 - * print("Qt version: " + HifiAbout.qtVersion); // 5.10.1 + * print("HiFi build date: " + HifiAbout.buildDate); // Returns the build date of the version of Interface currently running on your machine. + * print("HiFi version: " + HifiAbout.buildVersion); // Returns the build version of Interface currently running on your machine. + * print("Qt version: " + HifiAbout.qtVersion); // Returns the Qt version details of the version of Interface currently running on your machine. */ class AboutUtil : public QObject { @@ -52,9 +52,9 @@ public: public slots: /**jsdoc - * Display a Web page in an Interface browser window. + * Display a web page in an Interface browser window. * @function HifiAbout.openUrl - * @param {string} url - The URL of the Web page to display. + * @param {string} url - The URL of the web page you want to view in Interface. */ void openUrl(const QString &url) const; private: diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index bcd5367a89..fa63757560 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1061,6 +1061,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(PluginManager::getInstance().data(), &PluginManager::inputDeviceRunningChanged, controllerScriptingInterface, &controller::ScriptingInterface::updateRunningInputDevices); + EntityTree::setEntityClicksCapturedOperator([this] { + return _controllerScriptingInterface->areEntityClicksCaptured(); + }); + _entityClipboard->createRootElement(); #ifdef Q_OS_WIN @@ -4404,7 +4408,6 @@ void Application::mouseMoveEvent(QMouseEvent* event) { if (compositor.getReticleVisible() || !isHMDMode() || !compositor.getReticleOverDesktop() || getOverlays().getOverlayAtPoint(glm::vec2(transformedPos.x(), transformedPos.y())) != UNKNOWN_ENTITY_ID) { getEntities()->mouseMoveEvent(&mappedEvent); - getOverlays().mouseMoveEvent(&mappedEvent); } _controllerScriptingInterface->emitMouseMoveEvent(&mappedEvent); // send events to any registered scripts @@ -4438,14 +4441,8 @@ void Application::mousePressEvent(QMouseEvent* event) { #endif QMouseEvent mappedEvent(event->type(), transformedPos, event->screenPos(), event->button(), event->buttons(), event->modifiers()); - std::pair entityResult; - if (!_controllerScriptingInterface->areEntityClicksCaptured()) { - entityResult = getEntities()->mousePressEvent(&mappedEvent); - } - std::pair overlayResult = getOverlays().mousePressEvent(&mappedEvent); - - QUuid focusedEntity = entityResult.first < overlayResult.first ? entityResult.second : overlayResult.second; - setKeyboardFocusEntity(getEntities()->wantsKeyboardFocus(focusedEntity) ? focusedEntity : UNKNOWN_ENTITY_ID); + QUuid result = getEntities()->mousePressEvent(&mappedEvent); + setKeyboardFocusEntity(getEntities()->wantsKeyboardFocus(result) ? result : UNKNOWN_ENTITY_ID); _controllerScriptingInterface->emitMousePressEvent(&mappedEvent); // send events to any registered scripts @@ -4484,11 +4481,7 @@ void Application::mouseDoublePressEvent(QMouseEvent* event) { transformedPos, event->screenPos(), event->button(), event->buttons(), event->modifiers()); - - if (!_controllerScriptingInterface->areEntityClicksCaptured()) { - getEntities()->mouseDoublePressEvent(&mappedEvent); - } - getOverlays().mouseDoublePressEvent(&mappedEvent); + getEntities()->mouseDoublePressEvent(&mappedEvent); // if one of our scripts have asked to capture this event, then stop processing it if (_controllerScriptingInterface->isMouseCaptured()) { @@ -4513,7 +4506,6 @@ void Application::mouseReleaseEvent(QMouseEvent* event) { event->buttons(), event->modifiers()); getEntities()->mouseReleaseEvent(&mappedEvent); - getOverlays().mouseReleaseEvent(&mappedEvent); _controllerScriptingInterface->emitMouseReleaseEvent(&mappedEvent); // send events to any registered scripts @@ -6969,7 +6961,7 @@ void Application::clearDomainOctreeDetails(bool clearAll) { }); // reset the model renderer - clearAll ? getEntities()->clear() : getEntities()->clearNonLocalEntities(); + clearAll ? getEntities()->clear() : getEntities()->clearDomainAndNonOwnedEntities(); auto skyStage = DependencyManager::get()->getSkyStage(); diff --git a/interface/src/commerce/Wallet.h b/interface/src/commerce/Wallet.h index 5e5e6c9b4f..fdd6b5e2a6 100644 --- a/interface/src/commerce/Wallet.h +++ b/interface/src/commerce/Wallet.h @@ -62,8 +62,8 @@ public: * ValueMeaningDescription * * - * 0Not logged inThe user isn't logged in. - * 1Not set upThe user's wallet isn't set up. + * 0Not logged inThe user is not logged in. + * 1Not set upThe user's wallet has not been set up. * 2Pre-existingThere is a wallet present on the server but not one * locally. * 3ConflictingThere is a wallet present on the server plus one present locally, @@ -73,8 +73,8 @@ public: * 5ReadyThe wallet is ready for use. * * - *

      Wallets used to be stored locally but now they're stored on the server, unless the computer once had a wallet stored - * locally in which case the wallet may be present in both places.

      + *

      Wallets used to be stored locally but now they're only stored on the server. A wallet is present in both places if + * your computer previously stored its information locally.

      * @typedef {number} WalletScriptingInterface.WalletStatus */ enum WalletStatus { diff --git a/interface/src/scripting/AccountServicesScriptingInterface.cpp b/interface/src/scripting/AccountServicesScriptingInterface.cpp index a3597886e9..5f8fb065ff 100644 --- a/interface/src/scripting/AccountServicesScriptingInterface.cpp +++ b/interface/src/scripting/AccountServicesScriptingInterface.cpp @@ -115,8 +115,8 @@ DownloadInfoResult::DownloadInfoResult() : /**jsdoc * Information on the assets currently being downloaded and pending download. * @typedef {object} AccountServices.DownloadInfoResult - * @property {number[]} downloading - The percentage complete for each asset currently being downloaded. - * @property {number} pending - The number of assets waiting to be download. + * @property {number[]} downloading - The download percentage remaining of each asset currently downloading. + * @property {number} pending - The number of assets pending download. */ QScriptValue DownloadInfoResultToScriptValue(QScriptEngine* engine, const DownloadInfoResult& result) { QScriptValue object = engine->newObject(); diff --git a/interface/src/scripting/AccountServicesScriptingInterface.h b/interface/src/scripting/AccountServicesScriptingInterface.h index c08181d7c9..b188b4e63b 100644 --- a/interface/src/scripting/AccountServicesScriptingInterface.h +++ b/interface/src/scripting/AccountServicesScriptingInterface.h @@ -38,19 +38,19 @@ class AccountServicesScriptingInterface : public QObject { Q_OBJECT /**jsdoc - * The AccountServices API provides functions related to user connectivity, visibility, and asset download - * progress. + * The AccountServices API provides functions that give information on user connectivity, visibility, and + * asset download progress. * * @hifi-interface * @hifi-client-entity * @hifi-avatar * * @namespace AccountServices - * @property {string} username - The user name if the user is logged in, otherwise "Unknown user". - * Read-only. + * @property {string} username - The user name of the user logged in. If there is no user logged in, it is + * "Unknown user". Read-only. * @property {boolean} loggedIn - true if the user is logged in, otherwise false. * Read-only. - * @property {string} findableBy - The user's visibility to other people:
      + * @property {string} findableBy - The user's visibility to other users:
      * "none" - user appears offline.
      * "friends" - user is visible only to friends.
      * "connections" - user is visible to friends and connections.
      @@ -74,23 +74,23 @@ public: public slots: /**jsdoc - * Get information on the progress of downloading assets in the domain. + * Gets information on the download progress of assets in the domain. * @function AccountServices.getDownloadInfo - * @returns {AccountServices.DownloadInfoResult} Information on the progress of assets download. + * @returns {AccountServices.DownloadInfoResult} Information on the download progress of assets. */ DownloadInfoResult getDownloadInfo(); /**jsdoc - * Cause a {@link AccountServices.downloadInfoChanged|downloadInfoChanged} signal to be triggered with information on the - * current progress of the download of assets in the domain. + * Triggers a {@link AccountServices.downloadInfoChanged|downloadInfoChanged} signal with information on the current + * download progress of the assets in the domain. * @function AccountServices.updateDownloadInfo */ void updateDownloadInfo(); /**jsdoc - * Check whether the user is logged in. + * Checks whether the user is logged in. * @function AccountServices.isLoggedIn - * @returns {boolean} true if the user is logged in, false otherwise. + * @returns {boolean} true if the user is logged in, false if not. * @example Report whether you are logged in. * var isLoggedIn = AccountServices.isLoggedIn(); * print("You are logged in: " + isLoggedIn); // true or false @@ -98,9 +98,9 @@ public slots: bool isLoggedIn(); /**jsdoc - * Prompts the user to log in (the login dialog is displayed) if they're not already logged in. + * The function returns the login status of the user and prompts the user to log in (with a login dialog) if they're not already logged in. * @function AccountServices.checkAndSignalForAccessToken - * @returns {boolean} true if the user is already logged in, false otherwise. + * @returns {boolean} true if the user is logged in, false if not. */ bool checkAndSignalForAccessToken(); @@ -140,7 +140,7 @@ signals: /**jsdoc * Triggered when the username logged in with changes, i.e., when the user logs in or out. * @function AccountServices.myUsernameChanged - * @param {string} username - The username logged in with if the user is logged in, otherwise "". + * @param {string} username - The user name of the user logged in. If there is no user logged in, it is "". * @returns {Signal} * @example Report when your username changes. * AccountServices.myUsernameChanged.connect(function (username) { @@ -150,9 +150,9 @@ signals: void myUsernameChanged(const QString& username); /**jsdoc - * Triggered when the progress of the download of assets for the domain changes. + * Triggered when the download progress of the assets in the domain changes. * @function AccountServices.downloadInfoChanged - * @param {AccountServices.DownloadInfoResult} downloadInfo - Information on the progress of assets download. + * @param {AccountServices.DownloadInfoResult} downloadInfo - Information on the download progress of assets. * @returns {Signal} */ void downloadInfoChanged(DownloadInfoResult info); @@ -186,7 +186,7 @@ signals: /**jsdoc * Triggered when the login status of the user changes. * @function AccountServices.loggedInChanged - * @param {boolean} loggedIn - true if the user is logged in, otherwise false. + * @param {boolean} loggedIn - true if the user is logged in, false if not. * @returns {Signal} * @example Report when your login status changes. * AccountServices.loggedInChanged.connect(function(loggedIn) { diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index fe04ce47ca..45bb15f1a3 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -66,32 +66,30 @@ bool Audio::isMuted() const { bool isHMD = qApp->isHMDMode(); if (isHMD) { return getMutedHMD(); - } - else { + } else { return getMutedDesktop(); } } void Audio::setMuted(bool isMuted) { - withWriteLock([&] { - bool isHMD = qApp->isHMDMode(); - if (isHMD) { - setMutedHMD(isMuted); - } - else { - setMutedDesktop(isMuted); - } - }); + bool isHMD = qApp->isHMDMode(); + if (isHMD) { + setMutedHMD(isMuted); + } else { + setMutedDesktop(isMuted); + } } void Audio::setMutedDesktop(bool isMuted) { bool changed = false; - if (_desktopMuted != isMuted) { - changed = true; - _desktopMuted = isMuted; - auto client = DependencyManager::get().data(); - QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); - } + withWriteLock([&] { + if (_desktopMuted != isMuted) { + changed = true; + _desktopMuted = isMuted; + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + } + }); if (changed) { emit mutedChanged(isMuted); emit desktopMutedChanged(isMuted); @@ -106,12 +104,14 @@ bool Audio::getMutedDesktop() const { void Audio::setMutedHMD(bool isMuted) { bool changed = false; - if (_hmdMuted != isMuted) { - changed = true; - _hmdMuted = isMuted; - auto client = DependencyManager::get().data(); - QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); - } + withWriteLock([&] { + if (_hmdMuted != isMuted) { + changed = true; + _hmdMuted = isMuted; + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + } + }); if (changed) { emit mutedChanged(isMuted); emit hmdMutedChanged(isMuted); @@ -128,12 +128,24 @@ bool Audio::getPTT() { bool isHMD = qApp->isHMDMode(); if (isHMD) { return getPTTHMD(); - } - else { + } else { return getPTTDesktop(); } } +void scripting::Audio::setPushingToTalk(bool pushingToTalk) { + bool changed = false; + withWriteLock([&] { + if (_pushingToTalk != pushingToTalk) { + changed = true; + _pushingToTalk = pushingToTalk; + } + }); + if (changed) { + emit pushingToTalkChanged(pushingToTalk); + } +} + bool Audio::getPushingToTalk() const { return resultWithReadLock([&] { return _pushingToTalk; @@ -144,8 +156,7 @@ void Audio::setPTT(bool enabled) { bool isHMD = qApp->isHMDMode(); if (isHMD) { setPTTHMD(enabled); - } - else { + } else { setPTTDesktop(enabled); } } @@ -156,16 +167,16 @@ void Audio::setPTTDesktop(bool enabled) { if (_pttDesktop != enabled) { changed = true; _pttDesktop = enabled; - if (!enabled) { - // Set to default behavior (unmuted for Desktop) on Push-To-Talk disable. - setMutedDesktop(true); - } - else { - // Should be muted when not pushing to talk while PTT is enabled. - setMutedDesktop(true); - } } }); + if (!enabled) { + // Set to default behavior (unmuted for Desktop) on Push-To-Talk disable. + setMutedDesktop(true); + } else { + // Should be muted when not pushing to talk while PTT is enabled. + setMutedDesktop(true); + } + if (changed) { emit pushToTalkChanged(enabled); emit pushToTalkDesktopChanged(enabled); @@ -184,16 +195,16 @@ void Audio::setPTTHMD(bool enabled) { if (_pttHMD != enabled) { changed = true; _pttHMD = enabled; - if (!enabled) { - // Set to default behavior (unmuted for HMD) on Push-To-Talk disable. - setMutedHMD(false); - } - else { - // Should be muted when not pushing to talk while PTT is enabled. - setMutedHMD(true); - } } }); + if (!enabled) { + // Set to default behavior (unmuted for HMD) on Push-To-Talk disable. + setMutedHMD(false); + } else { + // Should be muted when not pushing to talk while PTT is enabled. + setMutedHMD(true); + } + if (changed) { emit pushToTalkChanged(enabled); emit pushToTalkHMDChanged(enabled); @@ -220,6 +231,26 @@ void Audio::loadData() { _pttHMD = _pttHMDSetting.get(); } +bool Audio::getPTTHMD() const { + return resultWithReadLock([&] { + return _pttHMD; + }); +} + +void Audio::saveData() { + _desktopMutedSetting.set(getMutedDesktop()); + _hmdMutedSetting.set(getMutedHMD()); + _pttDesktopSetting.set(getPTTDesktop()); + _pttHMDSetting.set(getPTTHMD()); +} + +void Audio::loadData() { + _desktopMuted = _desktopMutedSetting.get(); + _hmdMuted = _hmdMutedSetting.get(); + _pttDesktop = _pttDesktopSetting.get(); + _pttHMD = _pttHMDSetting.get(); +} + bool Audio::noiseReductionEnabled() const { return resultWithReadLock([&] { return _enableNoiseReduction; @@ -318,8 +349,7 @@ void Audio::onContextChanged() { }); if (isHMD) { setMuted(getMutedHMD()); - } - else { + } else { setMuted(getMutedDesktop()); } if (changed) { @@ -331,14 +361,10 @@ void Audio::handlePushedToTalk(bool enabled) { if (getPTT()) { if (enabled) { setMuted(false); - } - else { + } else { setMuted(true); } - if (_pushingToTalk != enabled) { - _pushingToTalk = enabled; - emit pushingToTalkChanged(enabled); - } + setPushingToTalk(enabled); } } diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index 6aa589e399..94f8a7bf54 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -99,6 +99,7 @@ public: bool getMutedHMD() const; void setPTT(bool enabled); bool getPTT(); + void setPushingToTalk(bool pushingToTalk); bool getPushingToTalk() const; // Push-To-Talk setters and getters diff --git a/interface/src/scripting/WalletScriptingInterface.h b/interface/src/scripting/WalletScriptingInterface.h index 7482b8be00..3ef9c7953a 100644 --- a/interface/src/scripting/WalletScriptingInterface.h +++ b/interface/src/scripting/WalletScriptingInterface.h @@ -41,8 +41,8 @@ public: * * @property {WalletScriptingInterface.WalletStatus} walletStatus - The status of the user's wallet. Read-only. * @property {boolean} limitedCommerce - true if Interface is running in limited commerce mode. In limited commerce - * mode, certain Interface functionality is disabled, e.g., users can't buy non-free items from the Marketplace. The Oculus - * Store version of Interface runs in limited commerce mode. Read-only. + * mode, certain Interface functionalities are disabled, e.g., users can't buy items that are not free from the Marketplace. + * The Oculus Store version of Interface runs in limited commerce mode. Read-only. */ class WalletScriptingInterface : public QObject, public Dependency { Q_OBJECT @@ -55,16 +55,16 @@ public: WalletScriptingInterface(); /**jsdoc - * Check and update the user's wallet status. + * Checks and updates the user's wallet status. * @function WalletScriptingInterface.refreshWalletStatus */ Q_INVOKABLE void refreshWalletStatus(); /**jsdoc - * Get the current status of the user's wallet. + * Gets the current status of the user's wallet. * @function WalletScriptingInterface.getWalletStatus * @returns {WalletScriptingInterface.WalletStatus} - * @example Two ways to report your wallet status. + * @example Use two methods to report your wallet's status. * print("Wallet status: " + WalletScriptingInterface.walletStatus); // Same value as next line. * print("Wallet status: " + WalletScriptingInterface.getWalletStatus()); */ @@ -74,11 +74,11 @@ public: * Check that a certified avatar entity is owned by the avatar whose entity it is. The result of the check is provided via * the {@link WalletScriptingInterface.ownershipVerificationSuccess|ownershipVerificationSuccess} and * {@link WalletScriptingInterface.ownershipVerificationFailed|ownershipVerificationFailed} signals.
      - * Warning: Neither of these signals fire if the entity is not an avatar entity or it's not a certified - * entity. + * Warning: Neither of these signals are triggered if the entity is not an avatar entity or is not + * certified. * @function WalletScriptingInterface.proveAvatarEntityOwnershipVerification - * @param {Uuid} entityID - The ID of the avatar entity to check. - * @example Check ownership of all nearby certified avatar entities. + * @param {Uuid} entityID - The avatar entity's ID. + * @example Check the ownership of all nearby certified avatar entities. * // Set up response handling. * function ownershipSuccess(entityID) { * print("Ownership test succeeded for: " + entityID); @@ -118,7 +118,7 @@ public: signals: /**jsdoc - * Triggered when the status of the user's wallet changes. + * Triggered when the user's wallet status changes. * @function WalletScriptingInterface.walletStatusChanged * @returns {Signal} * @example Report when your wallet status changes, e.g., when you log in and out. @@ -136,7 +136,7 @@ signals: void limitedCommerceChanged(); /**jsdoc - * Triggered when the user rezzes a certified entity but the user's wallet is not ready and so the certified location of the + * Triggered when the user rezzes a certified entity but the user's wallet is not ready. So the certified location of the * entity cannot be updated in the metaverse. * @function WalletScriptingInterface.walletNotSetup * @returns {Signal} diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 5ae3f7d38e..dfd698f6c5 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -43,14 +43,6 @@ std::unordered_map Overlays::_entityToOverlayTypes; std::unordered_map Overlays::_overlayToEntityTypes; Overlays::Overlays() { - auto pointerManager = DependencyManager::get(); - connect(pointerManager.data(), &PointerManager::hoverBeginOverlay, this, &Overlays::hoverEnterPointerEvent); - connect(pointerManager.data(), &PointerManager::hoverContinueOverlay, this, &Overlays::hoverOverPointerEvent); - connect(pointerManager.data(), &PointerManager::hoverEndOverlay, this, &Overlays::hoverLeavePointerEvent); - connect(pointerManager.data(), &PointerManager::triggerBeginOverlay, this, &Overlays::mousePressPointerEvent); - connect(pointerManager.data(), &PointerManager::triggerContinueOverlay, this, &Overlays::mouseMovePointerEvent); - connect(pointerManager.data(), &PointerManager::triggerEndOverlay, this, &Overlays::mouseReleasePointerEvent); - ADD_TYPE_MAP(Box, cube); ADD_TYPE_MAP(Sphere, sphere); _overlayToEntityTypes["rectangle3d"] = "Shape"; @@ -81,13 +73,23 @@ void Overlays::cleanupAllOverlays() { void Overlays::init() { auto entityScriptingInterface = DependencyManager::get().data(); - auto pointerManager = DependencyManager::get(); - connect(pointerManager.data(), &PointerManager::hoverBeginOverlay, entityScriptingInterface , &EntityScriptingInterface::hoverEnterEntity); - connect(pointerManager.data(), &PointerManager::hoverContinueOverlay, entityScriptingInterface, &EntityScriptingInterface::hoverOverEntity); - connect(pointerManager.data(), &PointerManager::hoverEndOverlay, entityScriptingInterface, &EntityScriptingInterface::hoverLeaveEntity); - connect(pointerManager.data(), &PointerManager::triggerBeginOverlay, entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity); - connect(pointerManager.data(), &PointerManager::triggerContinueOverlay, entityScriptingInterface, &EntityScriptingInterface::mouseMoveOnEntity); - connect(pointerManager.data(), &PointerManager::triggerEndOverlay, entityScriptingInterface, &EntityScriptingInterface::mouseReleaseOnEntity); + auto pointerManager = DependencyManager::get().data(); + connect(pointerManager, &PointerManager::hoverBeginOverlay, entityScriptingInterface , &EntityScriptingInterface::hoverEnterEntity); + connect(pointerManager, &PointerManager::hoverContinueOverlay, entityScriptingInterface, &EntityScriptingInterface::hoverOverEntity); + connect(pointerManager, &PointerManager::hoverEndOverlay, entityScriptingInterface, &EntityScriptingInterface::hoverLeaveEntity); + connect(pointerManager, &PointerManager::triggerBeginOverlay, entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity); + connect(pointerManager, &PointerManager::triggerContinueOverlay, entityScriptingInterface, &EntityScriptingInterface::mouseMoveOnEntity); + connect(pointerManager, &PointerManager::triggerEndOverlay, entityScriptingInterface, &EntityScriptingInterface::mouseReleaseOnEntity); + + connect(entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity, this, &Overlays::mousePressOnPointerEvent); + connect(entityScriptingInterface, &EntityScriptingInterface::mousePressOffEntity, this, &Overlays::mousePressOffPointerEvent); + connect(entityScriptingInterface, &EntityScriptingInterface::mouseDoublePressOnEntity, this, &Overlays::mouseDoublePressOnPointerEvent); + connect(entityScriptingInterface, &EntityScriptingInterface::mouseDoublePressOffEntity, this, &Overlays::mouseDoublePressOffPointerEvent); + connect(entityScriptingInterface, &EntityScriptingInterface::mouseReleaseOnEntity, this, &Overlays::mouseReleasePointerEvent); + connect(entityScriptingInterface, &EntityScriptingInterface::mouseMoveOnEntity, this, &Overlays::mouseMovePointerEvent); + connect(entityScriptingInterface, &EntityScriptingInterface::hoverEnterEntity , this, &Overlays::hoverEnterPointerEvent); + connect(entityScriptingInterface, &EntityScriptingInterface::hoverOverEntity, this, &Overlays::hoverOverPointerEvent); + connect(entityScriptingInterface, &EntityScriptingInterface::hoverLeaveEntity, this, &Overlays::hoverLeavePointerEvent); } void Overlays::update(float deltatime) { @@ -1159,7 +1161,7 @@ bool Overlays::isAddedOverlay(const QUuid& id) { } void Overlays::sendMousePressOnOverlay(const QUuid& id, const PointerEvent& event) { - mousePressPointerEvent(id, event); + mousePressOnPointerEvent(id, event); } void Overlays::sendMouseReleaseOnOverlay(const QUuid& id, const PointerEvent& event) { @@ -1206,57 +1208,66 @@ float Overlays::height() { return offscreenUi->getWindow()->size().height(); } -static uint32_t toPointerButtons(const QMouseEvent& event) { - uint32_t buttons = 0; - buttons |= event.buttons().testFlag(Qt::LeftButton) ? PointerEvent::PrimaryButton : 0; - buttons |= event.buttons().testFlag(Qt::RightButton) ? PointerEvent::SecondaryButton : 0; - buttons |= event.buttons().testFlag(Qt::MiddleButton) ? PointerEvent::TertiaryButton : 0; - return buttons; -} - -static PointerEvent::Button toPointerButton(const QMouseEvent& event) { - switch (event.button()) { - case Qt::LeftButton: - return PointerEvent::PrimaryButton; - case Qt::RightButton: - return PointerEvent::SecondaryButton; - case Qt::MiddleButton: - return PointerEvent::TertiaryButton; - default: - return PointerEvent::NoButtons; - } -} - -RayToOverlayIntersectionResult getPrevPickResult() { - RayToOverlayIntersectionResult overlayResult; - overlayResult.intersects = false; - auto pickResult = DependencyManager::get()->getPrevPickResultTyped(DependencyManager::get()->getMouseRayPickID()); - if (pickResult) { - overlayResult.intersects = pickResult->type == IntersectionType::LOCAL_ENTITY; - if (overlayResult.intersects) { - overlayResult.intersection = pickResult->intersection; - overlayResult.distance = pickResult->distance; - overlayResult.surfaceNormal = pickResult->surfaceNormal; - overlayResult.overlayID = pickResult->objectID; - overlayResult.extraInfo = pickResult->extraInfo; +void Overlays::mousePressOnPointerEvent(const QUuid& id, const PointerEvent& event) { + auto keyboard = DependencyManager::get(); + // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed + if (!keyboard->getKeyIDs().contains(id)) { + auto entity = DependencyManager::get()->getEntity(id); + if (entity && entity->isLocalEntity()) { + emit mousePressOnOverlay(id, event); } } - return overlayResult; } -PointerEvent Overlays::calculateOverlayPointerEvent(const QUuid& id, const PickRay& ray, - const RayToOverlayIntersectionResult& rayPickResult, QMouseEvent* event, - PointerEvent::EventType eventType) { - glm::vec2 pos2D = RayPick::projectOntoEntityXYPlane(id, rayPickResult.intersection); - return PointerEvent(eventType, PointerManager::MOUSE_POINTER_ID, pos2D, rayPickResult.intersection, rayPickResult.surfaceNormal, - ray.direction, toPointerButton(*event), toPointerButtons(*event), event->modifiers()); +void Overlays::mousePressOffPointerEvent() { + emit mousePressOffOverlay(); +} + +void Overlays::mouseDoublePressOnPointerEvent(const QUuid& id, const PointerEvent& event) { + auto keyboard = DependencyManager::get(); + // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed + if (!keyboard->getKeyIDs().contains(id)) { + auto entity = DependencyManager::get()->getEntity(id); + if (entity && entity->isLocalEntity()) { + emit mouseDoublePressOnOverlay(id, event); + } + } +} + +void Overlays::mouseDoublePressOffPointerEvent() { + emit mouseDoublePressOffOverlay(); +} + +void Overlays::mouseReleasePointerEvent(const QUuid& id, const PointerEvent& event) { + auto keyboard = DependencyManager::get(); + // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed + if (!keyboard->getKeyIDs().contains(id)) { + auto entity = DependencyManager::get()->getEntity(id); + if (entity && entity->isLocalEntity()) { + emit mouseReleaseOnOverlay(id, event); + } + } +} + +void Overlays::mouseMovePointerEvent(const QUuid& id, const PointerEvent& event) { + auto keyboard = DependencyManager::get(); + // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed + if (!keyboard->getKeyIDs().contains(id)) { + auto entity = DependencyManager::get()->getEntity(id); + if (entity && entity->isLocalEntity()) { + emit mouseMoveOnOverlay(id, event); + } + } } void Overlays::hoverEnterPointerEvent(const QUuid& id, const PointerEvent& event) { auto keyboard = DependencyManager::get(); // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed if (!keyboard->getKeyIDs().contains(id)) { - emit hoverEnterOverlay(id, event); + auto entity = DependencyManager::get()->getEntity(id); + if (entity && entity->isLocalEntity()) { + emit hoverEnterOverlay(id, event); + } } } @@ -1264,7 +1275,10 @@ void Overlays::hoverOverPointerEvent(const QUuid& id, const PointerEvent& event) auto keyboard = DependencyManager::get(); // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed if (!keyboard->getKeyIDs().contains(id)) { - emit hoverOverOverlay(id, event); + auto entity = DependencyManager::get()->getEntity(id); + if (entity && entity->isLocalEntity()) { + emit hoverOverOverlay(id, event); + } } } @@ -1272,113 +1286,10 @@ void Overlays::hoverLeavePointerEvent(const QUuid& id, const PointerEvent& event auto keyboard = DependencyManager::get(); // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed if (!keyboard->getKeyIDs().contains(id)) { - emit hoverLeaveOverlay(id, event); - } -} - -std::pair Overlays::mousePressEvent(QMouseEvent* event) { - PerformanceTimer perfTimer("Overlays::mousePressEvent"); - - PickRay ray = qApp->computePickRay(event->x(), event->y()); - RayToOverlayIntersectionResult rayPickResult = getPrevPickResult(); - if (rayPickResult.intersects) { - _currentClickingOnOverlayID = rayPickResult.overlayID; - - PointerEvent pointerEvent = calculateOverlayPointerEvent(_currentClickingOnOverlayID, ray, rayPickResult, event, PointerEvent::Press); - mousePressPointerEvent(_currentClickingOnOverlayID, pointerEvent); - return { rayPickResult.distance, rayPickResult.overlayID }; - } - emit mousePressOffOverlay(); - return { FLT_MAX, UNKNOWN_ENTITY_ID }; -} - -void Overlays::mousePressPointerEvent(const QUuid& id, const PointerEvent& event) { - auto keyboard = DependencyManager::get(); - // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed - if (!keyboard->getKeyIDs().contains(id)) { - emit mousePressOnOverlay(id, event); - } -} - -bool Overlays::mouseDoublePressEvent(QMouseEvent* event) { - PerformanceTimer perfTimer("Overlays::mouseDoublePressEvent"); - - PickRay ray = qApp->computePickRay(event->x(), event->y()); - RayToOverlayIntersectionResult rayPickResult = getPrevPickResult(); - if (rayPickResult.intersects) { - _currentClickingOnOverlayID = rayPickResult.overlayID; - - auto pointerEvent = calculateOverlayPointerEvent(_currentClickingOnOverlayID, ray, rayPickResult, event, PointerEvent::Press); - emit mouseDoublePressOnOverlay(_currentClickingOnOverlayID, pointerEvent); - return true; - } - emit mouseDoublePressOffOverlay(); - return false; -} - -bool Overlays::mouseReleaseEvent(QMouseEvent* event) { - PerformanceTimer perfTimer("Overlays::mouseReleaseEvent"); - - PickRay ray = qApp->computePickRay(event->x(), event->y()); - RayToOverlayIntersectionResult rayPickResult = getPrevPickResult(); - if (rayPickResult.intersects) { - auto pointerEvent = calculateOverlayPointerEvent(rayPickResult.overlayID, ray, rayPickResult, event, PointerEvent::Release); - mouseReleasePointerEvent(rayPickResult.overlayID, pointerEvent); - } - - _currentClickingOnOverlayID = UNKNOWN_ENTITY_ID; - return false; -} - -void Overlays::mouseReleasePointerEvent(const QUuid& id, const PointerEvent& event) { - auto keyboard = DependencyManager::get(); - // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed - if (!keyboard->getKeyIDs().contains(id)) { - emit mouseReleaseOnOverlay(id, event); - } -} - -bool Overlays::mouseMoveEvent(QMouseEvent* event) { - PerformanceTimer perfTimer("Overlays::mouseMoveEvent"); - - PickRay ray = qApp->computePickRay(event->x(), event->y()); - RayToOverlayIntersectionResult rayPickResult = getPrevPickResult(); - if (rayPickResult.intersects) { - auto pointerEvent = calculateOverlayPointerEvent(rayPickResult.overlayID, ray, rayPickResult, event, PointerEvent::Move); - mouseMovePointerEvent(rayPickResult.overlayID, pointerEvent); - - // If previously hovering over a different overlay then leave hover on that overlay. - if (_currentHoverOverOverlayID != UNKNOWN_ENTITY_ID && rayPickResult.overlayID != _currentHoverOverOverlayID) { - auto pointerEvent = calculateOverlayPointerEvent(_currentHoverOverOverlayID, ray, rayPickResult, event, PointerEvent::Move); - hoverLeavePointerEvent(_currentHoverOverOverlayID, pointerEvent); + auto entity = DependencyManager::get()->getEntity(id); + if (entity && entity->isLocalEntity()) { + emit hoverLeaveOverlay(id, event); } - - // If hovering over a new overlay then enter hover on that overlay. - if (rayPickResult.overlayID != _currentHoverOverOverlayID) { - hoverEnterPointerEvent(rayPickResult.overlayID, pointerEvent); - } - - // Hover over current overlay. - hoverOverPointerEvent(rayPickResult.overlayID, pointerEvent); - - _currentHoverOverOverlayID = rayPickResult.overlayID; - } else { - // If previously hovering an overlay then leave hover. - if (_currentHoverOverOverlayID != UNKNOWN_ENTITY_ID) { - auto pointerEvent = calculateOverlayPointerEvent(_currentHoverOverOverlayID, ray, rayPickResult, event, PointerEvent::Move); - hoverLeavePointerEvent(_currentHoverOverOverlayID, pointerEvent); - - _currentHoverOverOverlayID = UNKNOWN_ENTITY_ID; - } - } - return false; -} - -void Overlays::mouseMovePointerEvent(const QUuid& id, const PointerEvent& event) { - auto keyboard = DependencyManager::get(); - // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed - if (!keyboard->getKeyIDs().contains(id)) { - emit mouseMoveOnOverlay(id, event); } } diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index 93efc2bc0b..0b2994b872 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -112,11 +112,6 @@ public: const QVector& discard, bool visibleOnly = false, bool collidableOnly = false); - std::pair mousePressEvent(QMouseEvent* event); - bool mouseDoublePressEvent(QMouseEvent* event); - bool mouseReleaseEvent(QMouseEvent* event); - bool mouseMoveEvent(QMouseEvent* event); - void cleanupAllOverlays(); mutable QScriptEngine _scriptEngine; @@ -719,9 +714,6 @@ private: PointerEvent calculateOverlayPointerEvent(const QUuid& id, const PickRay& ray, const RayToOverlayIntersectionResult& rayPickResult, QMouseEvent* event, PointerEvent::EventType eventType); - QUuid _currentClickingOnOverlayID; - QUuid _currentHoverOverOverlayID; - static QString entityToOverlayType(const QString& type); static QString overlayToEntityType(const QString& type); static std::unordered_map _entityToOverlayTypes; @@ -732,12 +724,17 @@ private: EntityItemProperties convertOverlayToEntityProperties(QVariantMap& overlayProps, std::pair& rotationToSave, const QString& type, bool add, const QUuid& id = QUuid()); private slots: - void mousePressPointerEvent(const QUuid& id, const PointerEvent& event); - void mouseMovePointerEvent(const QUuid& id, const PointerEvent& event); + void mousePressOnPointerEvent(const QUuid& id, const PointerEvent& event); + void mousePressOffPointerEvent(); + void mouseDoublePressOnPointerEvent(const QUuid& id, const PointerEvent& event); + void mouseDoublePressOffPointerEvent(); void mouseReleasePointerEvent(const QUuid& id, const PointerEvent& event); + void mouseMovePointerEvent(const QUuid& id, const PointerEvent& event); void hoverEnterPointerEvent(const QUuid& id, const PointerEvent& event); void hoverOverPointerEvent(const QUuid& id, const PointerEvent& event); void hoverLeavePointerEvent(const QUuid& id, const PointerEvent& event); + + }; #define ADD_TYPE_MAP(entity, overlay) \ diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index e54258fc3e..143c7fa377 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -73,14 +73,14 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf _currentHoverOverEntityID = UNKNOWN_ENTITY_ID; _currentClickingOnEntityID = UNKNOWN_ENTITY_ID; - auto entityScriptingInterface = DependencyManager::get(); + auto entityScriptingInterface = DependencyManager::get().data(); auto pointerManager = DependencyManager::get(); - connect(pointerManager.data(), &PointerManager::hoverBeginEntity, entityScriptingInterface.data(), &EntityScriptingInterface::hoverEnterEntity); - connect(pointerManager.data(), &PointerManager::hoverContinueEntity, entityScriptingInterface.data(), &EntityScriptingInterface::hoverOverEntity); - connect(pointerManager.data(), &PointerManager::hoverEndEntity, entityScriptingInterface.data(), &EntityScriptingInterface::hoverLeaveEntity); - connect(pointerManager.data(), &PointerManager::triggerBeginEntity, entityScriptingInterface.data(), &EntityScriptingInterface::mousePressOnEntity); - connect(pointerManager.data(), &PointerManager::triggerContinueEntity, entityScriptingInterface.data(), &EntityScriptingInterface::mouseMoveOnEntity); - connect(pointerManager.data(), &PointerManager::triggerEndEntity, entityScriptingInterface.data(), &EntityScriptingInterface::mouseReleaseOnEntity); + connect(pointerManager.data(), &PointerManager::hoverBeginEntity, entityScriptingInterface, &EntityScriptingInterface::hoverEnterEntity); + connect(pointerManager.data(), &PointerManager::hoverContinueEntity, entityScriptingInterface, &EntityScriptingInterface::hoverOverEntity); + connect(pointerManager.data(), &PointerManager::hoverEndEntity, entityScriptingInterface, &EntityScriptingInterface::hoverLeaveEntity); + connect(pointerManager.data(), &PointerManager::triggerBeginEntity, entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity); + connect(pointerManager.data(), &PointerManager::triggerContinueEntity, entityScriptingInterface, &EntityScriptingInterface::mouseMoveOnEntity); + connect(pointerManager.data(), &PointerManager::triggerEndEntity, entityScriptingInterface, &EntityScriptingInterface::mouseReleaseOnEntity); // Forward mouse events to web entities auto handlePointerEvent = [&](const QUuid& entityID, const PointerEvent& event) { @@ -93,10 +93,10 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf QMetaObject::invokeMethod(thisEntity.get(), "handlePointerEvent", Q_ARG(const PointerEvent&, event)); } }; - connect(entityScriptingInterface.data(), &EntityScriptingInterface::mousePressOnEntity, this, handlePointerEvent); - connect(entityScriptingInterface.data(), &EntityScriptingInterface::mouseMoveOnEntity, this, handlePointerEvent); - connect(entityScriptingInterface.data(), &EntityScriptingInterface::mouseReleaseOnEntity, this, handlePointerEvent); - connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverEnterEntity, this, [&](const QUuid& entityID, const PointerEvent& event) { + connect(entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity, this, handlePointerEvent); + connect(entityScriptingInterface, &EntityScriptingInterface::mouseMoveOnEntity, this, handlePointerEvent); + connect(entityScriptingInterface, &EntityScriptingInterface::mouseReleaseOnEntity, this, handlePointerEvent); + connect(entityScriptingInterface, &EntityScriptingInterface::hoverEnterEntity, this, [&](const QUuid& entityID, const PointerEvent& event) { std::shared_ptr thisEntity; auto entity = getEntity(entityID); if (entity && entity->isVisible() && entity->getType() == EntityTypes::Web) { @@ -106,8 +106,8 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf QMetaObject::invokeMethod(thisEntity.get(), "hoverEnterEntity", Q_ARG(const PointerEvent&, event)); } }); - connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverOverEntity, this, handlePointerEvent); - connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverLeaveEntity, this, [&](const QUuid& entityID, const PointerEvent& event) { + connect(entityScriptingInterface, &EntityScriptingInterface::hoverOverEntity, this, handlePointerEvent); + connect(entityScriptingInterface, &EntityScriptingInterface::hoverLeaveEntity, this, [&](const QUuid& entityID, const PointerEvent& event) { std::shared_ptr thisEntity; auto entity = getEntity(entityID); if (entity && entity->isVisible() && entity->getType() == EntityTypes::Web) { @@ -196,8 +196,8 @@ void EntityTreeRenderer::resetEntitiesScriptEngine() { }); } -void EntityTreeRenderer::stopNonLocalEntityScripts() { - leaveNonLocalEntities(); +void EntityTreeRenderer::stopDomainAndNonOwnedEntities() { + leaveDomainAndNonOwnedEntities(); // unload and stop the engine if (_entitiesScriptEngine) { QList entitiesWithEntityScripts = _entitiesScriptEngine->getListOfEntityScriptIDs(); @@ -206,7 +206,7 @@ void EntityTreeRenderer::stopNonLocalEntityScripts() { EntityItemPointer entityItem = getTree()->findEntityByEntityItemID(entityID); if (entityItem) { - if (!entityItem->isLocalEntity()) { + if (!(entityItem->isLocalEntity() || (entityItem->isAvatarEntity() && entityItem->getOwningAvatarID() == getTree()->getMyAvatarSessionUUID()))) { _entitiesScriptEngine->unloadEntityScript(entityID, true); } } @@ -214,8 +214,8 @@ void EntityTreeRenderer::stopNonLocalEntityScripts() { } } -void EntityTreeRenderer::clearNonLocalEntities() { - stopNonLocalEntityScripts(); +void EntityTreeRenderer::clearDomainAndNonOwnedEntities() { + stopDomainAndNonOwnedEntities(); std::unordered_map savedEntities; // remove all entities from the scene @@ -225,7 +225,7 @@ void EntityTreeRenderer::clearNonLocalEntities() { for (const auto& entry : _entitiesInScene) { const auto& renderer = entry.second; const EntityItemPointer& entityItem = renderer->getEntity(); - if (!entityItem->isLocalEntity()) { + if (!(entityItem->isLocalEntity() || (entityItem->isAvatarEntity() && entityItem->getOwningAvatarID() == getTree()->getMyAvatarSessionUUID()))) { renderer->removeFromScene(scene, transaction); } else { savedEntities[entry.first] = entry.second; @@ -239,7 +239,7 @@ void EntityTreeRenderer::clearNonLocalEntities() { _layeredZones.clearNonLocalLayeredZones(); - OctreeProcessor::clearNonLocalEntities(); + OctreeProcessor::clearDomainAndNonOwnedEntities(); } void EntityTreeRenderer::clear() { @@ -655,22 +655,22 @@ bool EntityTreeRenderer::checkEnterLeaveEntities() { return didUpdate; } -void EntityTreeRenderer::leaveNonLocalEntities() { +void EntityTreeRenderer::leaveDomainAndNonOwnedEntities() { if (_tree && !_shuttingDown) { - QVector currentLocalEntitiesInside; + QVector currentEntitiesInsideToSave; foreach (const EntityItemID& entityID, _currentEntitiesInside) { EntityItemPointer entityItem = getTree()->findEntityByEntityItemID(entityID); - if (!entityItem->isLocalEntity()) { + if (!(entityItem->isLocalEntity() || (entityItem->isAvatarEntity() && entityItem->getOwningAvatarID() == getTree()->getMyAvatarSessionUUID()))) { emit leaveEntity(entityID); if (_entitiesScriptEngine) { _entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity"); } } else { - currentLocalEntitiesInside.push_back(entityID); + currentEntitiesInsideToSave.push_back(entityID); } } - _currentEntitiesInside = currentLocalEntitiesInside; + _currentEntitiesInside = currentEntitiesInsideToSave; forceRecheckEntities(); } } @@ -792,11 +792,11 @@ static PointerEvent::Button toPointerButton(const QMouseEvent& event) { } } -std::pair EntityTreeRenderer::mousePressEvent(QMouseEvent* event) { +QUuid EntityTreeRenderer::mousePressEvent(QMouseEvent* event) { // If we don't have a tree, or we're in the process of shutting down, then don't // process these events. if (!_tree || _shuttingDown) { - return { FLT_MAX, UNKNOWN_ENTITY_ID }; + return UNKNOWN_ENTITY_ID; } PerformanceTimer perfTimer("EntityTreeRenderer::mousePressEvent"); @@ -805,11 +805,13 @@ std::pair EntityTreeRenderer::mousePressEvent(QMouseEvent* event) RayToEntityIntersectionResult rayPickResult = _getPrevRayPickResultOperator(_mouseRayPickID); EntityItemPointer entity; if (rayPickResult.intersects && (entity = getTree()->findEntityByID(rayPickResult.entityID))) { - auto properties = entity->getProperties(); - QString urlString = properties.getHref(); - QUrl url = QUrl(urlString, QUrl::StrictMode); - if (url.isValid() && !url.isEmpty()){ - DependencyManager::get()->handleLookupString(urlString); + if (!EntityTree::areEntityClicksCaptured()) { + auto properties = entity->getProperties(); + QString urlString = properties.getHref(); + QUrl url = QUrl(urlString, QUrl::StrictMode); + if (url.isValid() && !url.isEmpty()) { + DependencyManager::get()->handleLookupString(urlString); + } } glm::vec2 pos2D = projectOntoEntityXYPlane(entity, ray, rayPickResult); @@ -827,10 +829,10 @@ std::pair EntityTreeRenderer::mousePressEvent(QMouseEvent* event) _lastPointerEvent = pointerEvent; _lastPointerEventValid = true; - return { rayPickResult.distance, rayPickResult.entityID }; + return rayPickResult.entityID; } emit entityScriptingInterface->mousePressOffEntity(); - return { FLT_MAX, UNKNOWN_ENTITY_ID }; + return UNKNOWN_ENTITY_ID; } void EntityTreeRenderer::mouseDoublePressEvent(QMouseEvent* event) { diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index 51568ab744..a257951ba8 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -87,14 +87,14 @@ public: virtual void init() override; /// clears the tree - virtual void clearNonLocalEntities() override; + virtual void clearDomainAndNonOwnedEntities() override; virtual void clear() override; /// reloads the entity scripts, calling unload and preload void reloadEntityScripts(); // event handles which may generate entity related events - std::pair mousePressEvent(QMouseEvent* event); + QUuid mousePressEvent(QMouseEvent* event); void mouseReleaseEvent(QMouseEvent* event); void mouseDoublePressEvent(QMouseEvent* event); void mouseMoveEvent(QMouseEvent* event); @@ -170,7 +170,7 @@ private: bool findBestZoneAndMaybeContainingEntities(QVector* entitiesContainingAvatar = nullptr); bool applyLayeredZones(); - void stopNonLocalEntityScripts(); + void stopDomainAndNonOwnedEntities(); void checkAndCallPreload(const EntityItemID& entityID, bool reload = false, bool unloadFirst = false); @@ -179,7 +179,7 @@ private: QScriptValueList createEntityArgs(const EntityItemID& entityID); bool checkEnterLeaveEntities(); - void leaveNonLocalEntities(); + void leaveDomainAndNonOwnedEntities(); void leaveAllEntities(); void forceRecheckEntities(); diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 6e404ce690..d4f15fa8b2 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -78,13 +78,14 @@ OctreeElementPointer EntityTree::createNewElement(unsigned char* octalCode) { return std::static_pointer_cast(newElement); } -void EntityTree::eraseNonLocalEntities() { +void EntityTree::eraseDomainAndNonOwnedEntities() { emit clearingEntities(); if (_simulation) { // local entities are not in the simulation, so we clear ALL _simulation->clearEntities(); } + this->withWriteLock([&] { QHash savedEntities; // NOTE: lock the Tree first, then lock the _entityMap. @@ -93,10 +94,10 @@ void EntityTree::eraseNonLocalEntities() { foreach(EntityItemPointer entity, _entityMap) { EntityTreeElementPointer element = entity->getElement(); if (element) { - element->cleanupNonLocalEntities(); + element->cleanupDomainAndNonOwnedEntities(); } - if (entity->isLocalEntity()) { + if (entity->isLocalEntity() || (entity->isAvatarEntity() && entity->getOwningAvatarID() == getMyAvatarSessionUUID())) { savedEntities[entity->getEntityItemID()] = entity; } else { int32_t spaceIndex = entity->getSpaceIndex(); @@ -114,15 +115,16 @@ void EntityTree::eraseNonLocalEntities() { { QWriteLocker locker(&_needsParentFixupLock); - QVector localEntitiesNeedsParentFixup; + QVector needParentFixup; foreach (EntityItemWeakPointer entityItem, _needsParentFixup) { - if (!entityItem.expired() && entityItem.lock()->isLocalEntity()) { - localEntitiesNeedsParentFixup.push_back(entityItem); + auto entity = entityItem.lock(); + if (entity && (entity->isLocalEntity() || (entity->isAvatarEntity() && entity->getOwningAvatarID() == getMyAvatarSessionUUID()))) { + needParentFixup.push_back(entityItem); } } - _needsParentFixup = localEntitiesNeedsParentFixup; + _needsParentFixup = needParentFixup; } } @@ -2972,6 +2974,7 @@ QStringList EntityTree::getJointNames(const QUuid& entityID) const { std::function EntityTree::_getEntityObjectOperator = nullptr; std::function EntityTree::_textSizeOperator = nullptr; +std::function EntityTree::_areEntityClicksCapturedOperator = nullptr; QObject* EntityTree::getEntityObject(const QUuid& id) { if (_getEntityObjectOperator) { @@ -2987,6 +2990,13 @@ QSizeF EntityTree::textSize(const QUuid& id, const QString& text) { return QSizeF(0.0f, 0.0f); } +bool EntityTree::areEntityClicksCaptured() { + if (_areEntityClicksCapturedOperator) { + return _areEntityClicksCapturedOperator(); + } + return false; +} + void EntityTree::updateEntityQueryAACubeWorker(SpatiallyNestablePointer object, EntityEditPacketSender* packetSender, MovingEntitiesOperator& moveOperator, bool force, bool tellServer) { // if the queryBox has changed, tell the entity-server diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index dcce0e4b99..39b3dc57c7 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -75,7 +75,7 @@ public: } - virtual void eraseNonLocalEntities() override; + virtual void eraseDomainAndNonOwnedEntities() override; virtual void eraseAllOctreeElements(bool createNewRoot = true) override; virtual void readBitstreamToTree(const unsigned char* bitstream, @@ -255,6 +255,7 @@ public: QByteArray computeNonce(const QString& certID, const QString ownerKey); bool verifyNonce(const QString& certID, const QString& nonce, EntityItemID& id); + QUuid getMyAvatarSessionUUID() { return _myAvatar ? _myAvatar->getSessionUUID() : QUuid(); } void setMyAvatar(std::shared_ptr myAvatar) { _myAvatar = myAvatar; } void swapStaleProxies(std::vector& proxies) { proxies.swap(_staleProxies); } @@ -268,6 +269,9 @@ public: static void setTextSizeOperator(std::function textSizeOperator) { _textSizeOperator = textSizeOperator; } static QSizeF textSize(const QUuid& id, const QString& text); + static void setEntityClicksCapturedOperator(std::function areEntityClicksCapturedOperator) { _areEntityClicksCapturedOperator = areEntityClicksCapturedOperator; } + static bool areEntityClicksCaptured(); + std::map getNamedPaths() const { return _namedPaths; } void updateEntityQueryAACube(SpatiallyNestablePointer object, EntityEditPacketSender* packetSender, @@ -378,6 +382,7 @@ private: static std::function _getEntityObjectOperator; static std::function _textSizeOperator; + static std::function _areEntityClicksCapturedOperator; std::vector _staleProxies; diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index ce6f20262f..aab98adb52 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -697,11 +697,11 @@ EntityItemPointer EntityTreeElement::getEntityWithEntityItemID(const EntityItemI return foundEntity; } -void EntityTreeElement::cleanupNonLocalEntities() { +void EntityTreeElement::cleanupDomainAndNonOwnedEntities() { withWriteLock([&] { EntityItems savedEntities; foreach(EntityItemPointer entity, _entityItems) { - if (!entity->isLocalEntity()) { + if (!(entity->isLocalEntity() || (entity->isAvatarEntity() && entity->getOwningAvatarID() == getTree()->getMyAvatarSessionUUID()))) { entity->preDelete(); entity->_element = NULL; } else { diff --git a/libraries/entities/src/EntityTreeElement.h b/libraries/entities/src/EntityTreeElement.h index f82eaa7fb1..f94da44138 100644 --- a/libraries/entities/src/EntityTreeElement.h +++ b/libraries/entities/src/EntityTreeElement.h @@ -190,7 +190,7 @@ public: EntityItemPointer getEntityWithEntityItemID(const EntityItemID& id) const; void getEntitiesInside(const AACube& box, QVector& foundEntities); - void cleanupNonLocalEntities(); + void cleanupDomainAndNonOwnedEntities(); void cleanupEntities(); /// called by EntityTree on cleanup this will free all entities bool removeEntityItem(EntityItemPointer entity, bool deletion = false); diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index 0efbbbb16c..08468617ba 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -164,7 +164,7 @@ protected: int _lastKnownCurrentFrame{-1}; glm::u8vec3 _color; - glm::vec3 _modelScale; + glm::vec3 _modelScale { 1.0f }; QString _modelURL; bool _relayParentJoints; bool _groupCulled { false }; diff --git a/libraries/fbx/src/FBXSerializer.cpp b/libraries/fbx/src/FBXSerializer.cpp index 9e7f422b40..5246242a1e 100644 --- a/libraries/fbx/src/FBXSerializer.cpp +++ b/libraries/fbx/src/FBXSerializer.cpp @@ -167,7 +167,6 @@ glm::mat4 getGlobalTransform(const QMultiMap& _connectionParen } } } - return globalTransform; } @@ -436,6 +435,8 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr hfmModel.originalURL = url; float unitScaleFactor = 1.0f; + glm::quat upAxisZRotation; + bool applyUpAxisZRotation = false; glm::vec3 ambientColor; QString hifiGlobalNodeID; unsigned int meshIndex = 0; @@ -473,11 +474,22 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr if (subobject.name == propertyName) { static const QVariant UNIT_SCALE_FACTOR = QByteArray("UnitScaleFactor"); static const QVariant AMBIENT_COLOR = QByteArray("AmbientColor"); + static const QVariant UP_AXIS = QByteArray("UpAxis"); const auto& subpropName = subobject.properties.at(0); if (subpropName == UNIT_SCALE_FACTOR) { unitScaleFactor = subobject.properties.at(index).toFloat(); } else if (subpropName == AMBIENT_COLOR) { ambientColor = getVec3(subobject.properties, index); + } else if (subpropName == UP_AXIS) { + constexpr int UP_AXIS_Y = 1; + constexpr int UP_AXIS_Z = 2; + int upAxis = subobject.properties.at(index).toInt(); + if (upAxis == UP_AXIS_Y) { + // No update necessary, y up is the default + } else if (upAxis == UP_AXIS_Z) { + upAxisZRotation = glm::angleAxis(glm::radians(-90.0f), glm::vec3(1.0f, 0.0f, 0.0f)); + applyUpAxisZRotation = true; + } } } } @@ -1269,9 +1281,11 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr joint.geometricScaling = fbxModel.geometricScaling; joint.isSkeletonJoint = fbxModel.isLimbNode; hfmModel.hasSkeletonJoints = (hfmModel.hasSkeletonJoints || joint.isSkeletonJoint); - + if (applyUpAxisZRotation && joint.parentIndex == -1) { + joint.rotation *= upAxisZRotation; + joint.translation = upAxisZRotation * joint.translation; + } glm::quat combinedRotation = joint.preRotation * joint.rotation * joint.postRotation; - if (joint.parentIndex == -1) { joint.transform = hfmModel.offset * glm::translate(joint.translation) * joint.preTransform * glm::mat4_cast(combinedRotation) * joint.postTransform; @@ -1664,6 +1678,14 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr } } + if (applyUpAxisZRotation) { + hfmModelPtr->meshExtents.transform(glm::mat4_cast(upAxisZRotation)); + hfmModelPtr->bindExtents.transform(glm::mat4_cast(upAxisZRotation)); + for (auto &mesh : hfmModelPtr->meshes) { + mesh.modelTransform *= glm::mat4_cast(upAxisZRotation); + mesh.meshExtents.transform(glm::mat4_cast(upAxisZRotation)); + } + } return hfmModelPtr; } diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index aac29201f1..82076f618b 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -149,7 +149,7 @@ public: OctreeElementPointer getRoot() { return _rootElement; } - virtual void eraseNonLocalEntities() { _isDirty = true; }; + virtual void eraseDomainAndNonOwnedEntities() { _isDirty = true; }; virtual void eraseAllOctreeElements(bool createNewRoot = true); virtual void readBitstreamToTree(const unsigned char* bitstream, uint64_t bufferSizeBytes, ReadBitstreamToTreeParams& args); diff --git a/libraries/octree/src/OctreeProcessor.cpp b/libraries/octree/src/OctreeProcessor.cpp index 18c8630391..03c8b9ca2f 100644 --- a/libraries/octree/src/OctreeProcessor.cpp +++ b/libraries/octree/src/OctreeProcessor.cpp @@ -198,10 +198,10 @@ void OctreeProcessor::processDatagram(ReceivedMessage& message, SharedNodePointe } -void OctreeProcessor::clearNonLocalEntities() { +void OctreeProcessor::clearDomainAndNonOwnedEntities() { if (_tree) { _tree->withWriteLock([&] { - _tree->eraseNonLocalEntities(); + _tree->eraseDomainAndNonOwnedEntities(); }); } } diff --git a/libraries/octree/src/OctreeProcessor.h b/libraries/octree/src/OctreeProcessor.h index bc5618e657..40af7a39f8 100644 --- a/libraries/octree/src/OctreeProcessor.h +++ b/libraries/octree/src/OctreeProcessor.h @@ -43,7 +43,7 @@ public: virtual void init(); /// clears the tree - virtual void clearNonLocalEntities(); + virtual void clearDomainAndNonOwnedEntities(); virtual void clear(); float getAverageElementsPerPacket() const { return _elementsPerPacket.getAverage(); } diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 9bb3f72b31..dd9b0280ca 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1346,14 +1346,19 @@ void Model::updateRig(float deltaTime, glm::mat4 parentTransform) { } void Model::computeMeshPartLocalBounds() { - for (auto& part : _modelMeshRenderItems) { - const Model::MeshState& state = _meshStates.at(part->_meshIndex); - if (_useDualQuaternionSkinning) { - part->computeAdjustedLocalBound(state.clusterDualQuaternions); - } else { - part->computeAdjustedLocalBound(state.clusterMatrices); - } + render::Transaction transaction; + auto meshStates = _meshStates; + for (auto renderItem : _modelMeshRenderItemIDs) { + transaction.updateItem(renderItem, [this, meshStates](ModelMeshPartPayload& data) { + const Model::MeshState& state = meshStates.at(data._meshIndex); + if (_useDualQuaternionSkinning) { + data.computeAdjustedLocalBound(state.clusterDualQuaternions); + } else { + data.computeAdjustedLocalBound(state.clusterMatrices); + } + }); } + AbstractViewStateInterface::instance()->getMain3DScene()->enqueueTransaction(transaction); } // virtual diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 5d33a6a061..825017b1fe 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -976,7 +976,9 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& using PointerHandler = std::function; auto makePointerHandler = [this](QString eventName) -> PointerHandler { return [this, eventName](const EntityItemID& entityItemID, const PointerEvent& event) { - forwardHandlerCall(entityItemID, eventName, { entityItemID.toScriptValue(this), event.toScriptValue(this) }); + if (!EntityTree::areEntityClicksCaptured()) { + forwardHandlerCall(entityItemID, eventName, { entityItemID.toScriptValue(this), event.toScriptValue(this) }); + } }; }; diff --git a/scripts/system/audio.js b/scripts/system/audio.js index 51d070d8cd..bf44cfa7cc 100644 --- a/scripts/system/audio.js +++ b/scripts/system/audio.js @@ -26,12 +26,15 @@ var UNMUTE_ICONS = { icon: "icons/tablet-icons/mic-unmute-i.svg", activeIcon: "icons/tablet-icons/mic-unmute-a.svg" }; +var PTT_ICONS = { + icon: "icons/tablet-icons/mic-unmute-i.svg", + activeIcon: "icons/tablet-icons/mic-unmute-a.svg" +}; function onMuteToggled() { if (Audio.pushingToTalk) { - return; - } - if (Audio.muted) { + button.editProperties(PTT_ICONS); + } else if (Audio.muted) { button.editProperties(MUTE_ICONS); } else { button.editProperties(UNMUTE_ICONS); @@ -71,6 +74,7 @@ onMuteToggled(); button.clicked.connect(onClicked); tablet.screenChanged.connect(onScreenChanged); Audio.mutedChanged.connect(onMuteToggled); +Audio.pushingToTalkChanged.connect(onMuteToggled); Script.scriptEnding.connect(function () { if (onAudioScreen) { @@ -79,6 +83,7 @@ Script.scriptEnding.connect(function () { button.clicked.disconnect(onClicked); tablet.screenChanged.disconnect(onScreenChanged); Audio.mutedChanged.disconnect(onMuteToggled); + Audio.pushingToTalkChanged.disconnect(onMuteToggled); tablet.removeButton(button); }); From 72ec0ea29ad70609972b7e5657aadb96f5614b6a Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 6 Mar 2019 11:16:03 -0800 Subject: [PATCH 354/474] Update Oculus Mobile SDK to latest version --- cmake/macros/TargetOculusMobile.cmake | 2 +- hifi_android.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cmake/macros/TargetOculusMobile.cmake b/cmake/macros/TargetOculusMobile.cmake index 3eaa008b14..f5229845a9 100644 --- a/cmake/macros/TargetOculusMobile.cmake +++ b/cmake/macros/TargetOculusMobile.cmake @@ -1,6 +1,6 @@ macro(target_oculus_mobile) - set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/oculus/VrApi) + set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/oculus_1.22/VrApi) # Mobile SDK set(OVR_MOBILE_INCLUDE_DIRS ${INSTALL_DIR}/Include) diff --git a/hifi_android.py b/hifi_android.py index b8a606a82f..42b472e960 100644 --- a/hifi_android.py +++ b/hifi_android.py @@ -45,10 +45,10 @@ ANDROID_PACKAGES = { 'sharedLibFolder': 'lib', 'includeLibs': ['libnvtt.so', 'libnvmath.so', 'libnvimage.so', 'libnvcore.so'] }, - 'oculus': { - 'file': 'ovr_sdk_mobile_1.19.0.zip', - 'versionId': 's_RN1vlEvUi3pnT7WPxUC4pQ0RJBs27y', - 'checksum': '98f0afb62861f1f02dd8110b31ed30eb', + 'oculus_1.22': { + 'file': 'ovr_sdk_mobile_1.22.zip', + 'versionId': 'InhomR5gwkzyiLAelH3X9k4nvV3iIpA_', + 'checksum': '1ac3c5b0521e5406f287f351015daff8', 'sharedLibFolder': 'VrApi/Libs/Android/arm64-v8a/Release', 'includeLibs': ['libvrapi.so'] }, From 4467567ee6b6dd478f3baef76dc8e089b760fba8 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 6 Mar 2019 11:18:57 -0800 Subject: [PATCH 355/474] changing text display --- interface/resources/qml/hifi/audio/MicBar.qml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index 2ab1085408..9d1cbfbc6c 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -134,9 +134,9 @@ Rectangle { Item { id: status; - readonly property string color: AudioScriptingInterface.pushToTalk ? hifi.colors.blueHighlight : AudioScriptingInterface.muted ? colors.muted : colors.unmuted; + readonly property string color: AudioScriptingInterface.muted ? colors.muted : colors.unmuted; - visible: AudioScriptingInterface.pushingToTalk || AudioScriptingInterface.muted; + visible: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) || AudioScriptingInterface.muted; anchors { left: parent.left; @@ -155,7 +155,7 @@ Rectangle { color: parent.color; - text: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? "SPEAKING" : "PUSH TO TALK") : (AudioScriptingInterface.muted ? "MUTED" : "MUTE"); + text: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? "MUTED-PTT (T)" : (AudioScriptingInterface.muted ? "MUTED" : "MUTE"); font.pointSize: 12; } @@ -165,7 +165,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - width: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? 45: 30) : 50; + width: AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk ? 25 : 50; height: 4; color: parent.color; } @@ -176,7 +176,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - width: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? 45: 30) : 50; + width: AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk ? 25 : 50; height: 4; color: parent.color; } From f5afc2033feed8c932f5715005ea2c8370c1765a Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Thu, 28 Feb 2019 15:10:55 -0800 Subject: [PATCH 356/474] work through stocking vs update issues, and cleanup --- .../qml/hifi/commerce/checkout/Checkout.qml | 41 ++++++++----------- .../hifi/commerce/marketplace/Marketplace.qml | 3 +- .../commerce/marketplace/MarketplaceItem.qml | 19 ++++----- .../hifi/commerce/purchases/PurchasedItem.qml | 2 +- .../qml/hifi/commerce/wallet/Wallet.qml | 4 -- 5 files changed, 27 insertions(+), 42 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index 9589c842e6..93ca366d37 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -41,8 +41,8 @@ Rectangle { property string itemAuthor; property int itemEdition: -1; property bool hasSomethingToTradeIn: alreadyOwned && (itemEdition > 0); // i.e., don't trade in your artist's proof - property bool isTradingIn: isUpdating && hasSomethingToTradeIn; - property bool isStocking: availability === 'not for sale' && creator === Account.username; + property bool isTradingIn: canUpdate && hasSomethingToTradeIn; + property bool isStocking: (availability === 'not for sale') && (creator === Account.username) && ; property string certificateId; property double balanceAfterPurchase; property bool alreadyOwned: false; // Including proofs @@ -59,7 +59,7 @@ Rectangle { property bool canRezCertifiedItems: Entities.canRezCertified() || Entities.canRezTmpCertified(); property string referrer; property bool isInstalled; - property bool isUpdating; + property bool canUpdate; property string availability: "available"; property string creator: ""; property string baseAppURL; @@ -144,6 +144,7 @@ Rectangle { } onAvailableUpdatesResult: { + // Answers the updatable original item cert data still owned by this user that are EITHER instances of this marketplace id, or update to this marketplace id. if (result.status !== 'success') { console.log("Failed to get Available Updates", result.data.message); } else { @@ -155,7 +156,7 @@ Rectangle { if (root.itemEdition !== -1 && root.itemEdition !== parseInt(result.data.updates[i].edition_number)) { continue; } - root.isUpdating = true; + root.canUpdate = true; root.baseItemName = result.data.updates[i].base_item_title; // This CertID is the one corresponding to the base item CertID that the user already owns root.certificateId = result.data.updates[i].certificate_id; @@ -166,7 +167,7 @@ Rectangle { } } - if (result.data.updates.length === 0 || root.isUpdating) { + if (result.data.updates.length === 0 || root.canUpdate) { root.availableUpdatesReceived = true; refreshBuyUI(); } else { @@ -266,13 +267,6 @@ Rectangle { } } } - MouseArea { - enabled: titleBarContainer.usernameDropdownVisible; - anchors.fill: parent; - onClicked: { - titleBarContainer.usernameDropdownVisible = false; - } - } // // TITLE BAR END // @@ -481,7 +475,7 @@ Rectangle { FiraSansSemiBold { id: itemPriceText; text: isTradingIn ? "FREE\nUPDATE" : - (isStocking ? "Free for creator" : + (isStocking ? "Free for creator" : ((root.itemPrice === -1) ? "--" : ((root.itemPrice > 0) ? root.itemPrice : "FREE"))); // Text size size: isTradingIn ? 20 : 26; @@ -580,7 +574,7 @@ Rectangle { // "View in Inventory" button HifiControlsUit.Button { id: viewInMyPurchasesButton; - visible: isCertified && dataReady && (isUpdating ? !hasSomethingToTradeIn : alreadyOwned); + visible: isCertified && dataReady && (isTradingIn ? hasSomethingToTradeIn : alreadyOwned); color: hifi.buttons.blue; colorScheme: hifi.colorSchemes.light; anchors.top: buyTextContainer.visible ? buyTextContainer.bottom : checkoutActionButtonsContainer.top; @@ -588,9 +582,9 @@ Rectangle { height: 50; anchors.left: parent.left; anchors.right: parent.right; - text: root.isUpdating ? "UPDATE TO THIS ITEM FOR FREE" : "VIEW THIS ITEM IN YOUR INVENTORY"; + text: (canUpdate && !isTradingIn) ? "UPDATE TO THIS ITEM FOR FREE" : "VIEW THIS ITEM IN YOUR INVENTORY"; onClicked: { - if (root.isUpdating) { + if (root.canUpdate) { sendToScript({method: 'checkout_goToPurchases', filterText: root.baseItemName}); } else { sendToScript({method: 'checkout_goToPurchases', filterText: root.itemName}); @@ -602,7 +596,11 @@ Rectangle { HifiControlsUit.Button { id: buyButton; visible: isTradingIn || !alreadyOwned || isStocking || !(root.itemType === "avatar" || root.itemType === "app"); - enabled: (root.balanceAfterPurchase >= 0 && dataReady) || (!root.isCertified) || root.isUpdating; + property bool checkBalance: dataReady && (root.availability === "available") + enabled: (checkBalance && (balanceAfterPurchase >= 0)) || !isCertified || isTradingIn || isStocking; + text: isTradingIn ? "Confirm Update" : + (enabled ? (viewInMyPurchasesButton.visible ? "Get It Again" : (dataReady ? "Get Item" : "--")) : + (checkBalance ? "Insufficient Funds" : availability)) color: viewInMyPurchasesButton.visible ? hifi.buttons.white : hifi.buttons.blue; colorScheme: hifi.colorSchemes.light; anchors.top: viewInMyPurchasesButton.visible ? viewInMyPurchasesButton.bottom : @@ -611,13 +609,6 @@ Rectangle { height: 50; anchors.left: parent.left; anchors.right: parent.right; - text: isTradingIn ? - "CONFIRM UPDATE" : - (((root.isCertified) ? - (dataReady ? - ((viewInMyPurchasesButton.visible && !root.isUpdating) ? "Get It Again" : "Confirm") : - "--") : - "Get Item")); onClicked: { if (isTradingIn) { // If we're updating an app, the existing app needs to be uninstalled. @@ -1209,7 +1200,7 @@ Rectangle { buyText.text = ""; // If the user IS on the checkout page for the updated version of an owned item... - if (root.isUpdating) { + if (root.canUpdate) { // If the user HAS already selected a specific edition to update... if (hasSomethingToTradeIn) { buyText.text = "By pressing \"Confirm Update\", you agree to trade in your old item for the updated item that replaces it."; diff --git a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml index ca6838efea..a8b769703d 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml @@ -112,7 +112,7 @@ Rectangle { marketplaceItem.image_url = result.data.thumbnail_url; marketplaceItem.name = result.data.title; marketplaceItem.likes = result.data.likes; - if(result.data.has_liked !== undefined) { + if (result.data.has_liked !== undefined) { marketplaceItem.liked = result.data.has_liked; } marketplaceItem.creator = result.data.creator; @@ -122,6 +122,7 @@ Rectangle { marketplaceItem.attributions = result.data.attributions; marketplaceItem.license = result.data.license; marketplaceItem.availability = result.data.availability; + marketplaceItem.updated_item_id = result.updated_item_id; marketplaceItem.created_at = result.data.created_at; marketplaceItemScrollView.contentHeight = marketplaceItemContent.height; itemsList.visible = false; diff --git a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml index 97e5c10a6b..e909d27e2b 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml @@ -2,7 +2,7 @@ // MarketplaceListItem.qml // qml/hifi/commerce/marketplace // -// MarketplaceListItem +// MarketplaceItem // // Created by Roxanne Skelly on 2019-01-22 // Copyright 2019 High Fidelity, Inc. @@ -34,6 +34,7 @@ Rectangle { property var categories: [] property int price: 0 property string availability: "unknown" + property string updated_item_id: "" property var attributions: [] property string description: "" property string license: "" @@ -42,7 +43,6 @@ Rectangle { property bool isLoggedIn: false; property int edition: -1; property bool supports3DHTML: false; - onCategoriesChanged: { categoriesListModel.clear(); @@ -264,15 +264,12 @@ Rectangle { } height: 50 - property bool isNFS: availability === "not for sale" // Note: server will say "sold out" or "invalidated" before it says NFS - property bool isMine: creator === Account.username - property bool isUpgrade: root.edition >= 0 - property int costToMe: ((isMine && isNFS) || isUpgrade) ? 0 : price - property bool isAvailable: costToMe >= 0 - - text: isUpgrade ? "UPGRADE FOR FREE" : (isAvailable ? (costToMe || "FREE") : availability) - enabled: isAvailable - buttonGlyph: isAvailable ? (costToMe ? hifi.glyphs.hfc : "") : "" + property bool isUpdate: root.edition >= 0 // Special case of updating from a specific older item + property bool isStocking: (creator === Account.username) && (availability === "not for sale") && !updated_item_id // Note: server will say "sold out" or "invalidated" before it says NFS + property bool isFreeSpecial: isStocking || isUpdate + enabled: isFreeSpecial || (availability === 'available') + buttonGlyph: (enabled && !isUpdate && (price > 0)) ? hifi.glyphs.hfc : "" + text: isUpdate ? "UPDATE FOR FREE" : (isStocking ? "FREE STOCK" : (enabled ? (price || "FREE") : availability)) color: hifi.buttons.blue onClicked: root.buy(); diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index df6e216b32..2c2fed1d8f 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -380,7 +380,7 @@ Item { if (updateButton.visible && uninstallButton.visible) { item.itemButtonText = ""; item.glyphSize = 20; - } else { + } else if (item) { item.itemButtonText = "Send to Trash"; item.glyphSize = 30; } diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml index f8e2c9115b..ea74549084 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml @@ -40,10 +40,6 @@ Rectangle { source: "images/wallet-bg.jpg"; } - Component.onDestruction: { - KeyboardScriptingInterface.raised = false; - } - Connections { target: Commerce; From 416cde4c1f4e3efbe80c8d83d77bb3c6e55c82b1 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Thu, 28 Feb 2019 17:31:35 -0800 Subject: [PATCH 357/474] fix uses of updated_item_id that got lost --- interface/resources/qml/hifi/commerce/checkout/Checkout.qml | 4 +++- .../resources/qml/hifi/commerce/marketplace/Marketplace.qml | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index 93ca366d37..60114e3db9 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -42,7 +42,7 @@ Rectangle { property int itemEdition: -1; property bool hasSomethingToTradeIn: alreadyOwned && (itemEdition > 0); // i.e., don't trade in your artist's proof property bool isTradingIn: canUpdate && hasSomethingToTradeIn; - property bool isStocking: (availability === 'not for sale') && (creator === Account.username) && ; + property bool isStocking: (availability === 'not for sale') && (creator === Account.username) && !updated_item_id; property string certificateId; property double balanceAfterPurchase; property bool alreadyOwned: false; // Including proofs @@ -61,6 +61,7 @@ Rectangle { property bool isInstalled; property bool canUpdate; property string availability: "available"; + property string updated_item_id: ""; property string creator: ""; property string baseAppURL; property int currentUpdatesPage: 1; @@ -1101,6 +1102,7 @@ Rectangle { root.itemAuthor = result.data.creator; root.itemType = result.data.item_type || "unknown"; root.availability = result.data.availability; + root.updated_item_id = result.data.updated_item_id || "" root.creator = result.data.creator; if (root.itemType === "unknown") { root.itemHref = result.data.review_url; diff --git a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml index a8b769703d..6e089843dc 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml @@ -122,7 +122,7 @@ Rectangle { marketplaceItem.attributions = result.data.attributions; marketplaceItem.license = result.data.license; marketplaceItem.availability = result.data.availability; - marketplaceItem.updated_item_id = result.updated_item_id; + marketplaceItem.updated_item_id = result.data.updated_item_id || ""; marketplaceItem.created_at = result.data.created_at; marketplaceItemScrollView.contentHeight = marketplaceItemContent.height; itemsList.visible = false; From 366dd935dda30aba1e44a168bdde7bf018cbeeac Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Fri, 1 Mar 2019 12:32:03 -0800 Subject: [PATCH 358/474] trading in has nothing to do with whether you own the new item --- interface/resources/qml/hifi/commerce/checkout/Checkout.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index 60114e3db9..c2bf745527 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -40,7 +40,7 @@ Rectangle { property string itemHref; property string itemAuthor; property int itemEdition: -1; - property bool hasSomethingToTradeIn: alreadyOwned && (itemEdition > 0); // i.e., don't trade in your artist's proof + property bool hasSomethingToTradeIn: itemEdition > 0; // i.e., don't trade in your artist's proof property bool isTradingIn: canUpdate && hasSomethingToTradeIn; property bool isStocking: (availability === 'not for sale') && (creator === Account.username) && !updated_item_id; property string certificateId; From ddfd437696e09de4bfab64bf15e0488c8c91b06a Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Tue, 5 Mar 2019 14:50:06 -0800 Subject: [PATCH 359/474] ensure error message when marketplace update fails --- interface/resources/qml/hifi/commerce/checkout/Checkout.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index c2bf745527..3ace6f381f 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -180,7 +180,7 @@ Rectangle { onUpdateItemResult: { if (result.status !== 'success') { - failureErrorText.text = result.message; + failureErrorText.text = result.data ? (result.data.message || "Unknown Error") : JSON.stringify(result); root.activeView = "checkoutFailure"; } else { root.itemHref = result.data.download_url; From a318b715c3c12ab66cf8c977479c7b6ae1aaadde Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 6 Mar 2019 11:45:43 -0800 Subject: [PATCH 360/474] exposing setting pushingToTalk --- interface/src/scripting/Audio.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index 94f8a7bf54..9ad4aac9c1 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -72,7 +72,7 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { Q_PROPERTY(bool pushToTalk READ getPTT WRITE setPTT NOTIFY pushToTalkChanged); Q_PROPERTY(bool pushToTalkDesktop READ getPTTDesktop WRITE setPTTDesktop NOTIFY pushToTalkDesktopChanged) Q_PROPERTY(bool pushToTalkHMD READ getPTTHMD WRITE setPTTHMD NOTIFY pushToTalkHMDChanged) - Q_PROPERTY(bool pushingToTalk READ getPushingToTalk NOTIFY pushingToTalkChanged) + Q_PROPERTY(bool pushingToTalk READ getPushingToTalk WRITE setPushingToTalk NOTIFY pushingToTalkChanged) public: static QString AUDIO; From e72cc8b16ee41c608aff1091927cb9f912a82557 Mon Sep 17 00:00:00 2001 From: amantley Date: Wed, 6 Mar 2019 12:09:53 -0800 Subject: [PATCH 361/474] put the ik optimized part of the conditional inside a android build define statement --- libraries/animation/src/AnimClip.cpp | 3 +-- libraries/animation/src/Rig.cpp | 27 ++++++++++--------- .../src/avatars-renderer/Avatar.cpp | 1 - 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index e014deca2a..4fe02e9307 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -134,7 +134,6 @@ void AnimClip::copyFromNetworkAnim() { const float animationUnitScale = extractScale(animModel.offset).y; const float avatarHeightInMeters = avatarUnitScale * avatarHipsAbsoluteDefaultPose.trans().y; const float animHeightInMeters = animationUnitScale * animHipsAbsoluteDefaultPose.trans().y; - qCDebug(animation) << "meters per unit, avatar: " << avatarUnitScale << " and height of avatar " << avatarHeightInMeters; // get the parent scales for the avatar and the animation float avatarHipsParentScale = 1.0f; @@ -155,7 +154,7 @@ void AnimClip::copyFromNetworkAnim() { const float avatarToAnimationHeightRatio = avatarHeightInMeters / animHeightInMeters; const float unitsRatio = 1.0f / (avatarUnitScale / animationUnitScale); const float parentScaleRatio = 1.0f / (avatarHipsParentScale / animHipsParentScale); - qCDebug(animation) << "height ratio: " << avatarToAnimationHeightRatio << " units ratio " << unitsRatio << " parent Scale Ratio " << parentScaleRatio; + boneLengthScale = avatarToAnimationHeightRatio * unitsRatio * parentScaleRatio; } diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 9d4c1645d3..344a98f8c4 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1064,17 +1064,18 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos t += deltaTime; - if (_enableInverseKinematics) { - _animVars.set("ikOverlayAlpha", 1.0f); - _animVars.set("splineIKEnabled", true); - _animVars.set("leftHandIKEnabled", true); - _animVars.set("rightHandIKEnabled", true); - _animVars.set("leftFootIKEnabled", true); - _animVars.set("rightFootIKEnabled", true); - _animVars.set("leftFootPoleVectorEnabled", true); - _animVars.set("rightFootPoleVectorEnabled", true); - } else { - _animVars.set("ikOverlayAlpha", 0.0f); + if (_enableInverseKinematics != _lastEnableInverseKinematics) { + if (_enableInverseKinematics) { + _animVars.set("ikOverlayAlpha", 1.0f); + } else { + _animVars.set("ikOverlayAlpha", 0.0f); + } + } + _lastEnableInverseKinematics = _enableInverseKinematics; + +#if defined(Q_OS_ANDROID) || defined(HIFI_USE_OPTIMIZED_IK) + + if (!_enableInverseKinematics) { _animVars.set("splineIKEnabled", false); _animVars.set("leftHandIKEnabled", false); _animVars.set("rightHandIKEnabled", false); @@ -1085,7 +1086,9 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _animVars.set("leftFootPoleVectorEnabled", false); _animVars.set("rightFootPoleVectorEnabled", false); } - _lastEnableInverseKinematics = _enableInverseKinematics; + +#endif + } _lastForward = forward; _lastPosition = worldPosition; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 29fc98734e..d3ae030296 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -1983,7 +1983,6 @@ float Avatar::getUnscaledEyeHeight() const { void Avatar::buildUnscaledEyeHeightCache() { float skeletonHeight = getUnscaledEyeHeightFromSkeleton(); - qCDebug(avatars_renderer) << "unscaled eye height " << skeletonHeight; // Sanity check by looking at the model extents. Extents meshExtents = _skeletonModel->getUnscaledMeshExtents(); From 05ba515c73491ebc9660e34bbb69bfed49ad10a5 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 6 Mar 2019 13:13:45 -0800 Subject: [PATCH 362/474] Support KHR_no_error in the VR context --- libraries/oculusMobile/src/ovr/GLContext.cpp | 11 +++++------ libraries/oculusMobile/src/ovr/GLContext.h | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/libraries/oculusMobile/src/ovr/GLContext.cpp b/libraries/oculusMobile/src/ovr/GLContext.cpp index 449ba67084..8d81df42e5 100644 --- a/libraries/oculusMobile/src/ovr/GLContext.cpp +++ b/libraries/oculusMobile/src/ovr/GLContext.cpp @@ -13,10 +13,7 @@ #include #include - -#if !defined(EGL_OPENGL_ES3_BIT_KHR) -#define EGL_OPENGL_ES3_BIT_KHR 0x0040 -#endif +#include using namespace ovr; @@ -129,7 +126,7 @@ void GLContext::doneCurrent() { eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); } -bool GLContext::create(EGLDisplay display, EGLContext shareContext) { +bool GLContext::create(EGLDisplay display, EGLContext shareContext, bool noError) { this->display = display; auto config = findConfig(display); @@ -139,7 +136,9 @@ bool GLContext::create(EGLDisplay display, EGLContext shareContext) { return false; } - EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE }; + EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 3, + noError ? EGL_CONTEXT_OPENGL_NO_ERROR_KHR : EGL_NONE, EGL_TRUE, + EGL_NONE }; context = eglCreateContext(display, config, shareContext, contextAttribs); if (context == EGL_NO_CONTEXT) { diff --git a/libraries/oculusMobile/src/ovr/GLContext.h b/libraries/oculusMobile/src/ovr/GLContext.h index 04f96e8d47..5ccbf20c42 100644 --- a/libraries/oculusMobile/src/ovr/GLContext.h +++ b/libraries/oculusMobile/src/ovr/GLContext.h @@ -25,7 +25,7 @@ struct GLContext { static EGLConfig findConfig(EGLDisplay display); bool makeCurrent(); void doneCurrent(); - bool create(EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY), EGLContext shareContext = EGL_NO_CONTEXT); + bool create(EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY), EGLContext shareContext = EGL_NO_CONTEXT, bool noError = false); void destroy(); operator bool() const { return context != EGL_NO_CONTEXT; } static void initModule(); From b515a0cceb3633d4872911cf10ba5cf274279df1 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 6 Mar 2019 13:15:12 -0800 Subject: [PATCH 363/474] Support KHR_no_error in the VR wrapper API --- libraries/oculusMobile/src/ovr/VrHandler.cpp | 25 +++++++++++++------- libraries/oculusMobile/src/ovr/VrHandler.h | 2 +- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/libraries/oculusMobile/src/ovr/VrHandler.cpp b/libraries/oculusMobile/src/ovr/VrHandler.cpp index 3fe3901517..6cc2ec9526 100644 --- a/libraries/oculusMobile/src/ovr/VrHandler.cpp +++ b/libraries/oculusMobile/src/ovr/VrHandler.cpp @@ -42,6 +42,8 @@ struct VrSurface : public TaskQueue { uint32_t readFbo{0}; std::atomic presentIndex{1}; double displayTime{0}; + // Not currently set by anything + bool noErrorContext { false }; static constexpr float EYE_BUFFER_SCALE = 1.0f; @@ -71,7 +73,7 @@ struct VrSurface : public TaskQueue { EGLContext currentContext = eglGetCurrentContext(); EGLDisplay currentDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); - vrglContext.create(currentDisplay, currentContext); + vrglContext.create(currentDisplay, currentContext, noErrorContext); vrglContext.makeCurrent(); glm::uvec2 eyeTargetSize; @@ -91,14 +93,17 @@ struct VrSurface : public TaskQueue { } void shutdown() { + noErrorContext = false; + // Destroy the context? } - void setHandler(VrHandler *newHandler) { + void setHandler(VrHandler *newHandler, bool noError) { withLock([&] { isRenderThread = newHandler != nullptr; if (handler != newHandler) { shutdown(); handler = newHandler; + noErrorContext = noError; init(); if (handler) { updateVrMode(); @@ -142,7 +147,6 @@ struct VrSurface : public TaskQueue { __android_log_write(ANDROID_LOG_WARN, "QQQ_OVR", "vrapi_LeaveVrMode"); vrapi_SetClockLevels(session, 1, 1); vrapi_SetExtraLatencyMode(session, VRAPI_EXTRA_LATENCY_MODE_OFF); - vrapi_SetDisplayRefreshRate(session, 60); vrapi_LeaveVrMode(session); session = nullptr; @@ -153,16 +157,19 @@ struct VrSurface : public TaskQueue { ovrJava java{ vm, env, oculusActivity }; ovrModeParms modeParms = vrapi_DefaultModeParms(&java); modeParms.Flags |= VRAPI_MODE_FLAG_NATIVE_WINDOW; + if (noErrorContext) { + modeParms.Flags |= VRAPI_MODE_FLAG_CREATE_CONTEXT_NO_ERROR; + } modeParms.Display = (unsigned long long) vrglContext.display; modeParms.ShareContext = (unsigned long long) vrglContext.context; modeParms.WindowSurface = (unsigned long long) nativeWindow; session = vrapi_EnterVrMode(&modeParms); - ovrPosef trackingTransform = vrapi_GetTrackingTransform( session, VRAPI_TRACKING_TRANSFORM_SYSTEM_CENTER_EYE_LEVEL); - vrapi_SetTrackingTransform( session, trackingTransform ); - vrapi_SetPerfThread(session, VRAPI_PERF_THREAD_TYPE_RENDERER, pthread_self()); + vrapi_SetTrackingSpace( session, VRAPI_TRACKING_SPACE_LOCAL); + vrapi_SetPerfThread(session, VRAPI_PERF_THREAD_TYPE_RENDERER, gettid()); vrapi_SetClockLevels(session, 2, 4); vrapi_SetExtraLatencyMode(session, VRAPI_EXTRA_LATENCY_MODE_DYNAMIC); - vrapi_SetDisplayRefreshRate(session, 72); + // Generates a warning on the quest: "vrapi_SetDisplayRefreshRate: Dynamic Display Refresh Rate not supported" + // vrapi_SetDisplayRefreshRate(session, 72); }); } } @@ -227,8 +234,8 @@ bool VrHandler::vrActive() const { return SURFACE.session != nullptr; } -void VrHandler::setHandler(VrHandler* handler) { - SURFACE.setHandler(handler); +void VrHandler::setHandler(VrHandler* handler, bool noError) { + SURFACE.setHandler(handler, noError); } void VrHandler::pollTask() { diff --git a/libraries/oculusMobile/src/ovr/VrHandler.h b/libraries/oculusMobile/src/ovr/VrHandler.h index 3c36ee8626..46e99f7476 100644 --- a/libraries/oculusMobile/src/ovr/VrHandler.h +++ b/libraries/oculusMobile/src/ovr/VrHandler.h @@ -21,7 +21,7 @@ public: using HandlerTask = std::function; using OvrMobileTask = std::function; using OvrJavaTask = std::function; - static void setHandler(VrHandler* handler); + static void setHandler(VrHandler* handler, bool noError = false); static bool withOvrMobile(const OvrMobileTask& task); protected: From de1513a6dd57d979d67192f8a39c5fc8ee50bbd5 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 6 Mar 2019 13:22:48 -0800 Subject: [PATCH 364/474] Turn on KHR_no_error for the quest frame player --- android/apps/questFramePlayer/src/main/cpp/RenderThread.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/android/apps/questFramePlayer/src/main/cpp/RenderThread.cpp b/android/apps/questFramePlayer/src/main/cpp/RenderThread.cpp index 78a4487284..d5b87af7fa 100644 --- a/android/apps/questFramePlayer/src/main/cpp/RenderThread.cpp +++ b/android/apps/questFramePlayer/src/main/cpp/RenderThread.cpp @@ -117,7 +117,8 @@ void RenderThread::setup() { { std::unique_lock lock(_frameLock); } ovr::VrHandler::initVr(); - ovr::VrHandler::setHandler(this); + // Enable KHR_no_error for this context + ovr::VrHandler::setHandler(this, true); makeCurrent(); From 0dbc83049b71f1207e2dfe30a8e4096cfd0ab656 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Wed, 6 Mar 2019 13:28:14 -0800 Subject: [PATCH 365/474] Make AnimPose from mat4 work better for matrices with negative determinants. Took part of this code from glm::decompose() which references https://opensource.apple.com/source/WebCore/WebCore-514/platform/graphics/transforms/TransformationMatrix.cpp --- libraries/animation/src/AnimPose.cpp | 50 +++++++++++++++++++++++ libraries/animation/src/AnimTwoBoneIK.cpp | 6 +-- tests/animation/CMakeLists.txt | 2 +- 3 files changed, 54 insertions(+), 4 deletions(-) diff --git a/libraries/animation/src/AnimPose.cpp b/libraries/animation/src/AnimPose.cpp index d77514e691..8b030fac39 100644 --- a/libraries/animation/src/AnimPose.cpp +++ b/libraries/animation/src/AnimPose.cpp @@ -13,12 +13,16 @@ #include #include #include "AnimUtil.h" +#include const AnimPose AnimPose::identity = AnimPose(glm::vec3(1.0f), glm::quat(), glm::vec3(0.0f)); +#define NEW_VERSION + AnimPose::AnimPose(const glm::mat4& mat) { +#if defined(ORIGINAL_VERSION) static const float EPSILON = 0.0001f; _scale = extractScale(mat); // quat_cast doesn't work so well with scaled matrices, so cancel it out. @@ -30,6 +34,52 @@ AnimPose::AnimPose(const glm::mat4& mat) { _rot = glm::quat(_rot.w * oneOverLength, _rot.x * oneOverLength, _rot.y * oneOverLength, _rot.z * oneOverLength); } _trans = extractTranslation(mat); +#elif defined(DECOMPOSE_VERSION) + // glm::decompose code + glm::vec3 scale; + glm::quat rotation; + glm::vec3 translation; + glm::vec3 skew; + glm::vec4 perspective; + bool result = glm::decompose(mat, scale, rotation, translation, skew, perspective); + _scale = scale; + _rot = rotation; + _trans = translation; + if (!result) { + // hack + const float HACK_FACTOR = 1000.0f; + glm::mat4 tmp = glm::scale(mat, HACK_FACTOR); + glm::decompose(tmp, scale, rotation, translation, skew, perspective); + _scale = scale / HACK_FACTOR; + _rot = rotation; + _trans = translation; + } +#elif defined(NEW_VERSION) + glm::mat3 m(mat); + _scale = glm::vec3(glm::length(m[0]), glm::length(m[1]), glm::length(m[2])); + float det = glm::determinant(m); + + glm::mat3 tmp; + if (det < 0.0f) { + _scale *= -1.0f; + } + + // quat_cast doesn't work so well with scaled matrices, so cancel out scale. + // also, as a side effect, multiply mirrored matrices by -1 to get the right rotation out. + tmp[0] = m[0] * (1.0f / _scale[0]); + tmp[1] = m[1] * (1.0f / _scale[1]); + tmp[2] = m[2] * (1.0f / _scale[2]); + _rot = glm::quat_cast(tmp); + + // normalize quat if necessary + float lengthSquared = glm::length2(_rot); + if (glm::abs(lengthSquared - 1.0f) > EPSILON) { + float oneOverLength = 1.0f / sqrtf(lengthSquared); + _rot = glm::quat(_rot.w * oneOverLength, _rot.x * oneOverLength, _rot.y * oneOverLength, _rot.z * oneOverLength); + } + + _trans = extractTranslation(mat); +#endif } glm::vec3 AnimPose::operator*(const glm::vec3& rhs) const { diff --git a/libraries/animation/src/AnimTwoBoneIK.cpp b/libraries/animation/src/AnimTwoBoneIK.cpp index 8960b15940..c91518d5db 100644 --- a/libraries/animation/src/AnimTwoBoneIK.cpp +++ b/libraries/animation/src/AnimTwoBoneIK.cpp @@ -156,7 +156,7 @@ const AnimPoseVec& AnimTwoBoneIK::evaluate(const AnimVariantMap& animVars, const glm::quat relMidRot = glm::angleAxis(midAngle, _midHingeAxis); // insert new relative pose into the chain and rebuild it. - ikChain.setRelativePoseAtJointIndex(_midJointIndex, AnimPose(relMidRot, underPoses[_midJointIndex].trans())); + ikChain.setRelativePoseAtJointIndex(_midJointIndex, AnimPose(underPoses[_midJointIndex].scale(), relMidRot, underPoses[_midJointIndex].trans())); ikChain.buildDirtyAbsolutePoses(); // recompute tip pose after mid joint has been rotated @@ -180,7 +180,7 @@ const AnimPoseVec& AnimTwoBoneIK::evaluate(const AnimVariantMap& animVars, const // transform result back into parent relative frame. glm::quat relBaseRot = glm::inverse(baseParentPose.rot()) * absRot; - ikChain.setRelativePoseAtJointIndex(_baseJointIndex, AnimPose(relBaseRot, underPoses[_baseJointIndex].trans())); + ikChain.setRelativePoseAtJointIndex(_baseJointIndex, AnimPose(underPoses[_baseJointIndex].scale(), relBaseRot, underPoses[_baseJointIndex].trans())); } // recompute midJoint pose after base has been rotated. @@ -189,7 +189,7 @@ const AnimPoseVec& AnimTwoBoneIK::evaluate(const AnimVariantMap& animVars, const // transform target rotation in to parent relative frame. glm::quat relTipRot = glm::inverse(midJointPose.rot()) * targetPose.rot(); - ikChain.setRelativePoseAtJointIndex(_tipJointIndex, AnimPose(relTipRot, underPoses[_tipJointIndex].trans())); + ikChain.setRelativePoseAtJointIndex(_tipJointIndex, AnimPose(underPoses[_tipJointIndex].scale(), relTipRot, underPoses[_tipJointIndex].trans())); // blend with the underChain ikChain.blend(underChain, alpha); diff --git a/tests/animation/CMakeLists.txt b/tests/animation/CMakeLists.txt index 2af4d5f2cd..e378750425 100644 --- a/tests/animation/CMakeLists.txt +++ b/tests/animation/CMakeLists.txt @@ -1,7 +1,7 @@ # Declare dependencies macro (setup_testcase_dependencies) # link in the shared libraries - link_hifi_libraries(shared animation gpu fbx hfm graphics networking test-utils) + link_hifi_libraries(shared animation gpu fbx hfm graphics networking test-utils image) package_libraries_for_deployment() endmacro () From c6b2697b44532e1fd06789ca08c6484f6dfd2eb3 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Wed, 6 Mar 2019 13:28:55 -0800 Subject: [PATCH 366/474] cleanup --- .../resources/qml/hifi/commerce/marketplace/Marketplace.qml | 1 - .../qml/hifi/commerce/marketplace/MarketplaceItem.qml | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml index 6e089843dc..3004af6c15 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml @@ -980,7 +980,6 @@ Rectangle { xhr.open("GET", url); xhr.onreadystatechange = function() { if (xhr.readyState == XMLHttpRequest.DONE) { - console.log(xhr.responseText); licenseText.text = xhr.responseText; licenseInfo.visible = true; } diff --git a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml index e909d27e2b..8c9d3c31c8 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml @@ -52,7 +52,7 @@ Rectangle { } onDescriptionChanged: { - + if(root.supports3DHTML) { descriptionTextModel.clear(); descriptionTextModel.append({text: description}); @@ -271,7 +271,7 @@ Rectangle { buttonGlyph: (enabled && !isUpdate && (price > 0)) ? hifi.glyphs.hfc : "" text: isUpdate ? "UPDATE FOR FREE" : (isStocking ? "FREE STOCK" : (enabled ? (price || "FREE") : availability)) color: hifi.buttons.blue - + onClicked: root.buy(); } From d063825269485e8ea09e937fd6e39dd2a17c41ca Mon Sep 17 00:00:00 2001 From: David Back Date: Wed, 6 Mar 2019 12:17:19 -0800 Subject: [PATCH 367/474] export unity material data to materialMap in fst --- .../Assets/Editor/AvatarExporter.cs | 430 +++++++++++++----- tools/unity-avatar-exporter/Assets/README.txt | 2 +- .../avatarExporter.unitypackage | Bin 13667 -> 15949 bytes 3 files changed, 317 insertions(+), 115 deletions(-) diff --git a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs index 7b90145223..40994c8f46 100644 --- a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs +++ b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs @@ -14,12 +14,14 @@ using System.Collections.Generic; class AvatarExporter : MonoBehaviour { // update version number for every PR that changes this file, also set updated version in README file - static readonly string AVATAR_EXPORTER_VERSION = "0.2"; + static readonly string AVATAR_EXPORTER_VERSION = "0.3.1"; static readonly float HIPS_GROUND_MIN_Y = 0.01f; static readonly float HIPS_SPINE_CHEST_MIN_SEPARATION = 0.001f; static readonly int MAXIMUM_USER_BONE_COUNT = 256; static readonly string EMPTY_WARNING_TEXT = "None"; + static readonly string TEXTURES_DIRECTORY = "textures"; + static readonly string DEFAULT_MATERIAL_NAME = "No Name"; // TODO: use regex static readonly string[] RECOMMENDED_UNITY_VERSIONS = new string[] { @@ -195,8 +197,17 @@ class AvatarExporter : MonoBehaviour { " Thumb Intermediate", " Thumb Proximal", }; + + static readonly string STANDARD_SHADER = "Standard"; + static readonly string STANDARD_ROUGHNESS_SHADER = "Standard (Roughness setup)"; + static readonly string STANDARD_SPECULAR_SHADER = "Standard (Specular setup)"; + static readonly string[] SUPPORTED_SHADERS = new string[] { + STANDARD_SHADER, + STANDARD_ROUGHNESS_SHADER, + STANDARD_SPECULAR_SHADER, + }; - enum BoneRule { + enum AvatarRule { RecommendedUnityVersion, SingleRoot, NoDuplicateMapping, @@ -215,14 +226,14 @@ class AvatarExporter : MonoBehaviour { HipsNotOnGround, HipsSpineChestNotCoincident, TotalBoneCountUnderLimit, - BoneRuleEnd, + AvatarRuleEnd, }; // rules that are treated as errors and prevent exporting, otherwise rules will show as warnings - static readonly BoneRule[] EXPORT_BLOCKING_BONE_RULES = new BoneRule[] { - BoneRule.HipsMapped, - BoneRule.SpineMapped, - BoneRule.ChestMapped, - BoneRule.HeadMapped, + static readonly AvatarRule[] EXPORT_BLOCKING_AVATAR_RULES = new AvatarRule[] { + AvatarRule.HipsMapped, + AvatarRule.SpineMapped, + AvatarRule.ChestMapped, + AvatarRule.HeadMapped, }; class UserBoneInformation { @@ -255,15 +266,67 @@ class AvatarExporter : MonoBehaviour { } } - static Dictionary userBoneInfos = new Dictionary(); - static Dictionary humanoidToUserBoneMappings = new Dictionary(); - static BoneTreeNode userBoneTree = new BoneTreeNode(); - static Dictionary failedBoneRules = new Dictionary(); + class MaterialData { + public Color albedo; + public string albedoMap; + public double metallic; + public string metallicMap; + public double roughness; + public string roughnessMap; + public string normalMap; + public string heightMap; + public string occlusionMap; + public Color emissive; + public string emissiveMap; + + public string getJSON() { + string json = "{ \"materialVersion\": 1, \"materials\": { "; + json += "\"albedo\": [" + albedo.r + ", " + albedo.g + ", " + albedo.b + "], "; + if (!string.IsNullOrEmpty(albedoMap)) { + json += "\"albedoMap\": \"" + albedoMap + "\", "; + } + json += "\"metallic\": " + metallic + ", "; + if (!string.IsNullOrEmpty(metallicMap)) { + json += "\"metallicMap\": \"" + metallicMap + "\", "; + } + json += "\"roughness\": " + roughness + ", "; + if (!string.IsNullOrEmpty(roughnessMap)) { + json += "\"roughnessMap\": \"" + roughnessMap + "\", "; + } + if (!string.IsNullOrEmpty(normalMap)) { + json += "\"normalMap\": \"" + normalMap + "\", "; + } + if (!string.IsNullOrEmpty(heightMap)) { + json += "\"heightMap\": \"" + heightMap + "\", "; + } + if (!string.IsNullOrEmpty(occlusionMap)) { + json += "\"occlusionMap\": \"" + occlusionMap + "\", "; + } + json += "\"emissive\": [" + emissive.r + ", " + emissive.g + ", " + emissive.b + "] "; + if (!string.IsNullOrEmpty(emissiveMap)) { + json += "\", emissiveMap\": \"" + emissiveMap + "\""; + } + json += "} }"; + return json; + } + } static string assetPath = ""; static string assetName = ""; + + static ModelImporter modelImporter; static HumanDescription humanDescription; - static Dictionary dependencyTextures = new Dictionary(); + static Dictionary userBoneInfos = new Dictionary(); + static Dictionary humanoidToUserBoneMappings = new Dictionary(); + static BoneTreeNode userBoneTree = new BoneTreeNode(); + static Dictionary failedAvatarRules = new Dictionary(); + + static Dictionary textureDependencies = new Dictionary(); + static Dictionary materialMappings = new Dictionary(); + static Dictionary materialDatas = new Dictionary(); + static List materialAlternateStandardShader = new List(); + static Dictionary materialUnsupportedShader = new Dictionary(); + static List normalMapAndHeightMapNotBoth = new List(); [MenuItem("High Fidelity/Export New Avatar")] static void ExportNewAvatar() { @@ -280,7 +343,10 @@ class AvatarExporter : MonoBehaviour { EditorUtility.DisplayDialog("About", "High Fidelity, Inc.\nAvatar Exporter\nVersion " + AVATAR_EXPORTER_VERSION, "Ok"); } - static void ExportSelectedAvatar(bool updateAvatar) { + static void ExportSelectedAvatar(bool updateAvatar) { + // ensure everything is saved to file before exporting + AssetDatabase.SaveAssets(); + string[] guids = Selection.assetGUIDs; if (guids.Length != 1) { if (guids.Length == 0) { @@ -292,7 +358,7 @@ class AvatarExporter : MonoBehaviour { } assetPath = AssetDatabase.GUIDToAssetPath(Selection.assetGUIDs[0]); assetName = Path.GetFileNameWithoutExtension(assetPath); - ModelImporter modelImporter = ModelImporter.GetAtPath(assetPath) as ModelImporter; + modelImporter = ModelImporter.GetAtPath(assetPath) as ModelImporter; if (Path.GetExtension(assetPath).ToLower() != ".fbx" || modelImporter == null) { EditorUtility.DisplayDialog("Error", "Please select an .fbx model asset to export.", "Ok"); return; @@ -302,26 +368,37 @@ class AvatarExporter : MonoBehaviour { " the Rig section of it's Inspector window.", "Ok"); return; } - + humanDescription = modelImporter.humanDescription; - SetUserBoneInformation(); string textureWarnings = SetTextureDependencies(); + SetBoneAndMaterialInformation(); // check if we should be substituting a bone for a missing UpperChest mapping AdjustUpperChestMapping(); - // format resulting bone rule failure strings - // consider export-blocking bone rules to be errors and show them in an error dialog, - // and also include any other bone rule failures plus texture warnings as warnings in the dialog + // format resulting avatar rule failure strings + // consider export-blocking avatar rules to be errors and show them in an error dialog, + // and also include any other avatar rule failures plus texture warnings as warnings in the dialog string boneErrors = ""; string warnings = ""; - foreach (var failedBoneRule in failedBoneRules) { - if (Array.IndexOf(EXPORT_BLOCKING_BONE_RULES, failedBoneRule.Key) >= 0) { - boneErrors += failedBoneRule.Value + "\n\n"; + foreach (var failedAvatarRule in failedAvatarRules) { + if (Array.IndexOf(EXPORT_BLOCKING_AVATAR_RULES, failedAvatarRule.Key) >= 0) { + boneErrors += failedAvatarRule.Value + "\n\n"; } else { - warnings += failedBoneRule.Value + "\n\n"; + warnings += failedAvatarRule.Value + "\n\n"; } } + foreach (string materialName in materialAlternateStandardShader) { + warnings += "The material " + materialName + " is not using the recommended variation of the Standard shader. " + + "We recommend you change it to Standard (Roughness setup) shader for improved performance.\n\n"; + } + foreach (var material in materialUnsupportedShader) { + warnings += "The material " + material.Key + " is using an unsupported shader " + material.Value + + ". Please change it to a Standard shader type.\n\n"; + } + foreach (string materialName in normalMapAndHeightMapNotBoth) { + warnings += "The material " + materialName + " has both a normal map and a height map assigned but can only use 1.\n\n"; + } warnings += textureWarnings; if (!string.IsNullOrEmpty(boneErrors)) { // if there are both errors and warnings then warnings will be displayed with errors in the error dialog @@ -408,9 +485,9 @@ class AvatarExporter : MonoBehaviour { modelImporter.SaveAndReimport(); // redo parent names, joint mappings, and user bone positions due to the fbx change - // as well as re-check the bone rules for failures + // as well as re-check the avatar rules for failures humanDescription = modelImporter.humanDescription; - SetUserBoneInformation(); + SetBoneAndMaterialInformation(); } } } else { @@ -456,7 +533,7 @@ class AvatarExporter : MonoBehaviour { return; } - // display success dialog with any bone rule warnings + // display success dialog with any avatar rule warnings string successDialog = "Avatar successfully updated!"; if (!string.IsNullOrEmpty(warnings)) { successDialog += "\n\nWarnings:\n" + warnings; @@ -575,6 +652,27 @@ class AvatarExporter : MonoBehaviour { jointOffset.y + ", " + jointOffset.z + ", " + jointOffset.w + ")\n"); } } + + // if there is any material data to save then write out all materials in JSON material format to the materialMap field + if (materialDatas.Count > 0) { + string materialJson = "{ "; + foreach (var materialData in materialDatas) { + // if this is the only material in the mapping and it is the default name No Name mapped to No Name, + // then the avatar has no embedded materials and this material should be applied to all meshes + string materialName = materialData.Key; + if (materialMappings.Count == 1 && materialName == DEFAULT_MATERIAL_NAME && + materialMappings[materialName] == DEFAULT_MATERIAL_NAME) { + materialJson += "\"all\": "; + } else { + materialJson += "\"[mat::" + materialName + "]\": "; + } + materialJson += materialData.Value.getJSON(); + materialJson += ", "; + } + materialJson = materialJson.Substring(0, materialJson.LastIndexOf(", ")); + materialJson += " }"; + File.AppendAllText(exportFstPath, "materialMap = " + materialJson); + } // open File Explorer to the project directory once finished System.Diagnostics.Process.Start("explorer.exe", "/select," + exportFstPath); @@ -582,11 +680,18 @@ class AvatarExporter : MonoBehaviour { return true; } - static void SetUserBoneInformation() { + static void SetBoneAndMaterialInformation() { userBoneInfos.Clear(); humanoidToUserBoneMappings.Clear(); userBoneTree = new BoneTreeNode(); + materialDatas.Clear(); + materialAlternateStandardShader.Clear(); + materialUnsupportedShader.Clear(); + normalMapAndHeightMapNotBoth.Clear(); + + SetMaterialMappings(); + // instantiate a game object of the user avatar to traverse the bone tree to gather // bone parents and positions as well as build a bone tree, then destroy it UnityEngine.Object avatarResource = AssetDatabase.LoadAssetAtPath(assetPath, typeof(UnityEngine.Object)); @@ -610,20 +715,26 @@ class AvatarExporter : MonoBehaviour { } } - // generate the list of bone rule failure strings for any bone rules that are not satisfied by this avatar - SetFailedBoneRules(); + // generate the list of avatar rule failure strings for any avatar rules that are not satisfied by this avatar + SetFailedAvatarRules(); } static void TraverseUserBoneTree(Transform modelBone) { GameObject gameObject = modelBone.gameObject; // check if this transform is a node containing mesh, light, or camera instead of a bone - bool mesh = gameObject.GetComponent() != null || gameObject.GetComponent() != null; + MeshRenderer meshRenderer = gameObject.GetComponent(); + SkinnedMeshRenderer skinnedMeshRenderer = gameObject.GetComponent(); + bool mesh = meshRenderer != null || skinnedMeshRenderer != null; bool light = gameObject.GetComponent() != null; bool camera = gameObject.GetComponent() != null; - // if it is in fact a bone, add it to the bone tree as well as user bone infos list with position and parent name - if (!mesh && !light && !camera) { + // if this is a mesh and the model is using external materials then store its material data to be exported + if (mesh && modelImporter.materialLocation == ModelImporterMaterialLocation.External) { + Material[] materials = skinnedMeshRenderer != null ? skinnedMeshRenderer.sharedMaterials : meshRenderer.sharedMaterials; + StoreMaterialData(materials); + } else if (!light && !camera) { + // if it is in fact a bone, add it to the bone tree as well as user bone infos list with position and parent name UserBoneInformation userBoneInfo = new UserBoneInformation(); userBoneInfo.position = modelBone.position; // bone's absolute position @@ -682,8 +793,8 @@ class AvatarExporter : MonoBehaviour { } } - static void SetFailedBoneRules() { - failedBoneRules.Clear(); + static void SetFailedAvatarRules() { + failedAvatarRules.Clear(); string hipsUserBone = ""; string spineUserBone = ""; @@ -692,60 +803,60 @@ class AvatarExporter : MonoBehaviour { Vector3 hipsPosition = new Vector3(); - // iterate over all bone rules in order and add any rules that fail - // to the failed bone rules map with appropriate error or warning text - for (BoneRule boneRule = 0; boneRule < BoneRule.BoneRuleEnd; ++boneRule) { - switch (boneRule) { - case BoneRule.RecommendedUnityVersion: + // iterate over all avatar rules in order and add any rules that fail + // to the failed avatar rules map with appropriate error or warning text + for (AvatarRule avatarRule = 0; avatarRule < AvatarRule.AvatarRuleEnd; ++avatarRule) { + switch (avatarRule) { + case AvatarRule.RecommendedUnityVersion: if (Array.IndexOf(RECOMMENDED_UNITY_VERSIONS, Application.unityVersion) == -1) { - failedBoneRules.Add(boneRule, "The current version of Unity is not one of the recommended Unity " + - "versions. If you are using a version of Unity later than 2018.2.12f1, " + - "it is recommended to apply Enforce T-Pose under the Pose dropdown " + - "in Humanoid configuration."); + failedAvatarRules.Add(avatarRule, "The current version of Unity is not one of the recommended Unity " + + "versions. If you are using a version of Unity later than 2018.2.12f1, " + + "it is recommended to apply Enforce T-Pose under the Pose dropdown " + + "in Humanoid configuration."); } break; - case BoneRule.SingleRoot: - // bone rule fails if the root bone node has more than one child bone + case AvatarRule.SingleRoot: + // avatar rule fails if the root bone node has more than one child bone if (userBoneTree.children.Count > 1) { - failedBoneRules.Add(boneRule, "There is more than one bone at the top level of the selected avatar's " + - "bone hierarchy. Please ensure all bones for Humanoid mappings are " + - "under the same bone hierarchy."); + failedAvatarRules.Add(avatarRule, "There is more than one bone at the top level of the selected avatar's " + + "bone hierarchy. Please ensure all bones for Humanoid mappings are " + + "under the same bone hierarchy."); } break; - case BoneRule.NoDuplicateMapping: - // bone rule fails if any user bone is mapped to more than one Humanoid bone + case AvatarRule.NoDuplicateMapping: + // avatar rule fails if any user bone is mapped to more than one Humanoid bone foreach (var userBoneInfo in userBoneInfos) { string boneName = userBoneInfo.Key; int mappingCount = userBoneInfo.Value.mappingCount; if (mappingCount > 1) { string text = "The " + boneName + " bone is mapped to more than one bone in Humanoid."; - if (failedBoneRules.ContainsKey(boneRule)) { - failedBoneRules[boneRule] += "\n" + text; + if (failedAvatarRules.ContainsKey(avatarRule)) { + failedAvatarRules[avatarRule] += "\n" + text; } else { - failedBoneRules.Add(boneRule, text); + failedAvatarRules.Add(avatarRule, text); } } } break; - case BoneRule.NoAsymmetricalLegMapping: - CheckAsymmetricalMappingRule(boneRule, LEG_MAPPING_SUFFIXES, "leg"); + case AvatarRule.NoAsymmetricalLegMapping: + CheckAsymmetricalMappingRule(avatarRule, LEG_MAPPING_SUFFIXES, "leg"); break; - case BoneRule.NoAsymmetricalArmMapping: - CheckAsymmetricalMappingRule(boneRule, ARM_MAPPING_SUFFIXES, "arm"); + case AvatarRule.NoAsymmetricalArmMapping: + CheckAsymmetricalMappingRule(avatarRule, ARM_MAPPING_SUFFIXES, "arm"); break; - case BoneRule.NoAsymmetricalHandMapping: - CheckAsymmetricalMappingRule(boneRule, HAND_MAPPING_SUFFIXES, "hand"); + case AvatarRule.NoAsymmetricalHandMapping: + CheckAsymmetricalMappingRule(avatarRule, HAND_MAPPING_SUFFIXES, "hand"); break; - case BoneRule.HipsMapped: - hipsUserBone = CheckHumanBoneMappingRule(boneRule, "Hips"); + case AvatarRule.HipsMapped: + hipsUserBone = CheckHumanBoneMappingRule(avatarRule, "Hips"); break; - case BoneRule.SpineMapped: - spineUserBone = CheckHumanBoneMappingRule(boneRule, "Spine"); + case AvatarRule.SpineMapped: + spineUserBone = CheckHumanBoneMappingRule(avatarRule, "Spine"); break; - case BoneRule.SpineDescendantOfHips: - CheckUserBoneDescendantOfHumanRule(boneRule, spineUserBone, "Hips"); + case AvatarRule.SpineDescendantOfHips: + CheckUserBoneDescendantOfHumanRule(avatarRule, spineUserBone, "Hips"); break; - case BoneRule.ChestMapped: + case AvatarRule.ChestMapped: if (!humanoidToUserBoneMappings.TryGetValue("Chest", out chestUserBone)) { // check to see if there is a child of Spine that we can suggest to be mapped to Chest string spineChild = ""; @@ -755,54 +866,54 @@ class AvatarExporter : MonoBehaviour { spineChild = spineTreeNode.children[0].boneName; } } - failedBoneRules.Add(boneRule, "There is no Chest bone mapped in Humanoid for the selected avatar."); + failedAvatarRules.Add(avatarRule, "There is no Chest bone mapped in Humanoid for the selected avatar."); // if the only found child of Spine is not yet mapped then add it as a suggestion for Chest mapping if (!string.IsNullOrEmpty(spineChild) && !userBoneInfos[spineChild].HasHumanMapping()) { - failedBoneRules[boneRule] += " It is suggested that you map bone " + spineChild + - " to Chest in Humanoid."; + failedAvatarRules[avatarRule] += " It is suggested that you map bone " + spineChild + + " to Chest in Humanoid."; } } break; - case BoneRule.ChestDescendantOfSpine: - CheckUserBoneDescendantOfHumanRule(boneRule, chestUserBone, "Spine"); + case AvatarRule.ChestDescendantOfSpine: + CheckUserBoneDescendantOfHumanRule(avatarRule, chestUserBone, "Spine"); break; - case BoneRule.NeckMapped: - CheckHumanBoneMappingRule(boneRule, "Neck"); + case AvatarRule.NeckMapped: + CheckHumanBoneMappingRule(avatarRule, "Neck"); break; - case BoneRule.HeadMapped: - headUserBone = CheckHumanBoneMappingRule(boneRule, "Head"); + case AvatarRule.HeadMapped: + headUserBone = CheckHumanBoneMappingRule(avatarRule, "Head"); break; - case BoneRule.HeadDescendantOfChest: - CheckUserBoneDescendantOfHumanRule(boneRule, headUserBone, "Chest"); + case AvatarRule.HeadDescendantOfChest: + CheckUserBoneDescendantOfHumanRule(avatarRule, headUserBone, "Chest"); break; - case BoneRule.EyesMapped: + case AvatarRule.EyesMapped: bool leftEyeMapped = humanoidToUserBoneMappings.ContainsKey("LeftEye"); bool rightEyeMapped = humanoidToUserBoneMappings.ContainsKey("RightEye"); if (!leftEyeMapped || !rightEyeMapped) { if (leftEyeMapped && !rightEyeMapped) { - failedBoneRules.Add(boneRule, "There is no RightEye bone mapped in Humanoid " + - "for the selected avatar."); + failedAvatarRules.Add(avatarRule, "There is no RightEye bone mapped in Humanoid " + + "for the selected avatar."); } else if (!leftEyeMapped && rightEyeMapped) { - failedBoneRules.Add(boneRule, "There is no LeftEye bone mapped in Humanoid " + - "for the selected avatar."); + failedAvatarRules.Add(avatarRule, "There is no LeftEye bone mapped in Humanoid " + + "for the selected avatar."); } else { - failedBoneRules.Add(boneRule, "There is no LeftEye or RightEye bone mapped in Humanoid " + - "for the selected avatar."); + failedAvatarRules.Add(avatarRule, "There is no LeftEye or RightEye bone mapped in Humanoid " + + "for the selected avatar."); } } break; - case BoneRule.HipsNotOnGround: + case AvatarRule.HipsNotOnGround: // ensure the absolute Y position for the bone mapped to Hips (if its mapped) is at least HIPS_GROUND_MIN_Y if (!string.IsNullOrEmpty(hipsUserBone)) { UserBoneInformation hipsBoneInfo = userBoneInfos[hipsUserBone]; hipsPosition = hipsBoneInfo.position; if (hipsPosition.y < HIPS_GROUND_MIN_Y) { - failedBoneRules.Add(boneRule, "The bone mapped to Hips in Humanoid (" + hipsUserBone + - ") should not be at ground level."); + failedAvatarRules.Add(avatarRule, "The bone mapped to Hips in Humanoid (" + hipsUserBone + + ") should not be at ground level."); } } break; - case BoneRule.HipsSpineChestNotCoincident: + case AvatarRule.HipsSpineChestNotCoincident: // ensure the bones mapped to Hips, Spine, and Chest are all not in the same position, // check Hips to Spine and Spine to Chest lengths are within HIPS_SPINE_CHEST_MIN_SEPARATION if (!string.IsNullOrEmpty(spineUserBone) && !string.IsNullOrEmpty(chestUserBone) && @@ -813,34 +924,34 @@ class AvatarExporter : MonoBehaviour { Vector3 spineToChest = spineBoneInfo.position - chestBoneInfo.position; if (hipsToSpine.magnitude < HIPS_SPINE_CHEST_MIN_SEPARATION && spineToChest.magnitude < HIPS_SPINE_CHEST_MIN_SEPARATION) { - failedBoneRules.Add(boneRule, "The bone mapped to Hips in Humanoid (" + hipsUserBone + - "), the bone mapped to Spine in Humanoid (" + spineUserBone + - "), and the bone mapped to Chest in Humanoid (" + chestUserBone + - ") should not be coincidental."); + failedAvatarRules.Add(avatarRule, "The bone mapped to Hips in Humanoid (" + hipsUserBone + + "), the bone mapped to Spine in Humanoid (" + spineUserBone + + "), and the bone mapped to Chest in Humanoid (" + chestUserBone + + ") should not be coincidental."); } } break; - case BoneRule.TotalBoneCountUnderLimit: + case AvatarRule.TotalBoneCountUnderLimit: int userBoneCount = userBoneInfos.Count; if (userBoneCount > MAXIMUM_USER_BONE_COUNT) { - failedBoneRules.Add(boneRule, "The total number of bones in the avatar (" + userBoneCount + - ") exceeds the maximum bone limit (" + MAXIMUM_USER_BONE_COUNT + ")."); + failedAvatarRules.Add(avatarRule, "The total number of bones in the avatar (" + userBoneCount + + ") exceeds the maximum bone limit (" + MAXIMUM_USER_BONE_COUNT + ")."); } break; } } } - static string CheckHumanBoneMappingRule(BoneRule boneRule, string humanBoneName) { + static string CheckHumanBoneMappingRule(AvatarRule avatarRule, string humanBoneName) { string userBoneName = ""; - // bone rule fails if bone is not mapped in Humanoid + // avatar rule fails if bone is not mapped in Humanoid if (!humanoidToUserBoneMappings.TryGetValue(humanBoneName, out userBoneName)) { - failedBoneRules.Add(boneRule, "There is no " + humanBoneName + " bone mapped in Humanoid for the selected avatar."); + failedAvatarRules.Add(avatarRule, "There is no " + humanBoneName + " bone mapped in Humanoid for the selected avatar."); } return userBoneName; } - static void CheckUserBoneDescendantOfHumanRule(BoneRule boneRule, string userBoneName, string descendantOfHumanName) { + static void CheckUserBoneDescendantOfHumanRule(AvatarRule avatarRule, string userBoneName, string descendantOfHumanName) { if (string.IsNullOrEmpty(userBoneName)) { return; } @@ -867,13 +978,13 @@ class AvatarExporter : MonoBehaviour { } } - // bone rule fails if no ancestor of given user bone matched the descendant of name (no early return) - failedBoneRules.Add(boneRule, "The bone mapped to " + userBoneInfo.humanName + " in Humanoid (" + userBoneName + - ") is not a child of the bone mapped to " + descendantOfHumanName + " in Humanoid (" + - descendantOfUserBoneName + ")."); + // avatar rule fails if no ancestor of given user bone matched the descendant of name (no early return) + failedAvatarRules.Add(avatarRule, "The bone mapped to " + userBoneInfo.humanName + " in Humanoid (" + userBoneName + + ") is not a child of the bone mapped to " + descendantOfHumanName + " in Humanoid (" + + descendantOfUserBoneName + ")."); } - static void CheckAsymmetricalMappingRule(BoneRule boneRule, string[] mappingSuffixes, string appendage) { + static void CheckAsymmetricalMappingRule(AvatarRule avatarRule, string[] mappingSuffixes, string appendage) { int leftCount = 0; int rightCount = 0; // add Left/Right to each mapping suffix to make Humanoid mapping names, @@ -888,23 +999,23 @@ class AvatarExporter : MonoBehaviour { ++rightCount; } } - // bone rule fails if number of left appendage mappings doesn't match number of right appendage mappings + // avatar rule fails if number of left appendage mappings doesn't match number of right appendage mappings if (leftCount != rightCount) { - failedBoneRules.Add(boneRule, "The number of bones mapped in Humanoid for the left " + appendage + " (" + - leftCount + ") does not match the number of bones mapped in Humanoid for the right " + - appendage + " (" + rightCount + ")."); + failedAvatarRules.Add(avatarRule, "The number of bones mapped in Humanoid for the left " + appendage + " (" + + leftCount + ") does not match the number of bones mapped in Humanoid for the right " + + appendage + " (" + rightCount + ")."); } } static string GetTextureDirectory(string basePath) { - string textureDirectory = Path.GetDirectoryName(basePath) + "\\textures"; + string textureDirectory = Path.GetDirectoryName(basePath) + "\\" + TEXTURES_DIRECTORY; textureDirectory = textureDirectory.Replace("\\\\", "\\"); return textureDirectory; } static string SetTextureDependencies() { string textureWarnings = ""; - dependencyTextures.Clear(); + textureDependencies.Clear(); // build the list of all local asset paths for textures that Unity considers dependencies of the model // for any textures that have duplicate names, return a string of duplicate name warnings @@ -913,11 +1024,11 @@ class AvatarExporter : MonoBehaviour { UnityEngine.Object textureObject = AssetDatabase.LoadAssetAtPath(dependencyPath, typeof(Texture2D)); if (textureObject != null) { string textureName = Path.GetFileName(dependencyPath); - if (dependencyTextures.ContainsKey(textureName)) { + if (textureDependencies.ContainsKey(textureName)) { textureWarnings += "There is more than one texture with the name " + textureName + " referenced in the selected avatar.\n\n"; } else { - dependencyTextures.Add(textureName, dependencyPath); + textureDependencies.Add(textureName, dependencyPath); } } } @@ -927,7 +1038,7 @@ class AvatarExporter : MonoBehaviour { static bool CopyExternalTextures(string texturesDirectory) { // copy the found dependency textures from the local asset folder to the textures folder in the target export project - foreach (var texture in dependencyTextures) { + foreach (var texture in textureDependencies) { string targetPath = texturesDirectory + "\\" + texture.Key; try { File.Copy(texture.Value, targetPath, true); @@ -939,6 +1050,97 @@ class AvatarExporter : MonoBehaviour { } return true; } + + static void StoreMaterialData(Material[] materials) { + // store each material's info in the materialDatas list to be written out later to the FST if it is a supported shader + foreach (Material material in materials) { + string materialName = material.name; + string shaderName = material.shader.name; + + Debug.Log("material1 " + materialName); + + // don't store any material data for unsupported shader types + if (Array.IndexOf(SUPPORTED_SHADERS, shaderName) == -1) { + if (!materialUnsupportedShader.ContainsKey(materialName)) { + materialUnsupportedShader.Add(materialName, shaderName); + } + continue; + } + + MaterialData materialData = new MaterialData(); + materialData.albedo = material.GetColor("_Color"); + materialData.albedoMap = GetMaterialTexture(material, "_MainTex"); + materialData.roughness = material.GetFloat("_Glossiness"); + materialData.roughnessMap = GetMaterialTexture(material, "_SpecGlossMap"); + materialData.normalMap = GetMaterialTexture(material, "_BumpMap"); + materialData.heightMap = GetMaterialTexture(material, "_ParallaxMap"); + materialData.occlusionMap = GetMaterialTexture(material, "_OcclusionMap"); + materialData.emissive = material.GetColor("_EmissionColor"); + materialData.emissiveMap = GetMaterialTexture(material, "_EmissionMap"); + + // for specular setups we will treat the metallic value as the average of the specular RGB intensities + if (shaderName == STANDARD_SPECULAR_SHADER) { + Color specular = material.GetColor("_SpecColor"); + materialData.metallic = (specular.r + specular.g + specular.b) / 3.0f; + } else { + materialData.metallic = material.GetFloat("_Metallic"); + materialData.metallicMap = GetMaterialTexture(material, "_MetallicGlossMap"); + } + + // for non-roughness Standard shaders give a warning that is not the recommended Standard shader, + // and invert smoothness for roughness + if (shaderName == STANDARD_SHADER || shaderName == STANDARD_SPECULAR_SHADER) { + if (!materialAlternateStandardShader.Contains(materialName)) { + materialAlternateStandardShader.Add(materialName); + } + materialData.roughness = 1.0f - materialData.roughness; + } + + // materials can not have both a normal map and a height map set + if (!string.IsNullOrEmpty(materialData.normalMap) && !string.IsNullOrEmpty(materialData.heightMap) && !normalMapAndHeightMapNotBoth.Contains(materialName)) { + normalMapAndHeightMapNotBoth.Add(materialName); + } + + // remap the material name from the Unity material name to the fbx material name that it overrides + if (materialMappings.ContainsKey(materialName)) { + materialName = materialMappings[materialName]; + } + if (!materialDatas.ContainsKey(materialName)) { + materialDatas.Add(materialName, materialData); + } + } + } + + static string GetMaterialTexture(Material material, string textureProperty) { + // ensure the texture property name exists in this material and return its texture directory path if so + string[] textureNames = material.GetTexturePropertyNames(); + if (Array.IndexOf(textureNames, textureProperty) >= 0) { + Texture texture = material.GetTexture(textureProperty); + if (texture) { + foreach (var textureDependency in textureDependencies) { + string textureFile = textureDependency.Key; + if (Path.GetFileNameWithoutExtension(textureFile) == texture.name) { + return TEXTURES_DIRECTORY + "/" + textureFile; + } + } + } + } + return ""; + } + + static void SetMaterialMappings() { + materialMappings.Clear(); + + // store the mappings from fbx material name to the Unity material name overriding it using external fbx mapping + var objectMap = modelImporter.GetExternalObjectMap(); + foreach (var mapping in objectMap) { + var material = mapping.Value as UnityEngine.Material; + if (material != null) { + materialMappings.Add(material.name, mapping.Key.name); + //Debug.Log("materialMapping " + material.name + " " + mapping.Key.name); + } + } + } } class ExportProjectWindow : EditorWindow { diff --git a/tools/unity-avatar-exporter/Assets/README.txt b/tools/unity-avatar-exporter/Assets/README.txt index c84cec2978..2aa1e5f02b 100644 --- a/tools/unity-avatar-exporter/Assets/README.txt +++ b/tools/unity-avatar-exporter/Assets/README.txt @@ -1,6 +1,6 @@ High Fidelity, Inc. Avatar Exporter -Version 0.2 +Version 0.3.1 Note: It is recommended to use Unity versions between 2017.4.17f1 and 2018.2.12f1 for this Avatar Exporter. diff --git a/tools/unity-avatar-exporter/avatarExporter.unitypackage b/tools/unity-avatar-exporter/avatarExporter.unitypackage index 95c000e7c61b7623e0da0a1f9974e0744375014e..ab39e1ed8f7d25ceb210c8ee59897c88aae20abb 100644 GIT binary patch literal 15949 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 B&65BC From 9cc1a7aaaa36838d1a22cfce097065987dfb1157 Mon Sep 17 00:00:00 2001 From: David Back Date: Wed, 6 Mar 2019 13:39:40 -0800 Subject: [PATCH 368/474] CR changes --- .../Assets/Editor/AvatarExporter.cs | 22 ++---------------- .../avatarExporter.unitypackage | Bin 15949 -> 15720 bytes 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs index 40994c8f46..f8728f9e7e 100644 --- a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs +++ b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs @@ -274,7 +274,6 @@ class AvatarExporter : MonoBehaviour { public double roughness; public string roughnessMap; public string normalMap; - public string heightMap; public string occlusionMap; public Color emissive; public string emissiveMap; @@ -296,9 +295,6 @@ class AvatarExporter : MonoBehaviour { if (!string.IsNullOrEmpty(normalMap)) { json += "\"normalMap\": \"" + normalMap + "\", "; } - if (!string.IsNullOrEmpty(heightMap)) { - json += "\"heightMap\": \"" + heightMap + "\", "; - } if (!string.IsNullOrEmpty(occlusionMap)) { json += "\"occlusionMap\": \"" + occlusionMap + "\", "; } @@ -326,7 +322,6 @@ class AvatarExporter : MonoBehaviour { static Dictionary materialDatas = new Dictionary(); static List materialAlternateStandardShader = new List(); static Dictionary materialUnsupportedShader = new Dictionary(); - static List normalMapAndHeightMapNotBoth = new List(); [MenuItem("High Fidelity/Export New Avatar")] static void ExportNewAvatar() { @@ -396,9 +391,6 @@ class AvatarExporter : MonoBehaviour { warnings += "The material " + material.Key + " is using an unsupported shader " + material.Value + ". Please change it to a Standard shader type.\n\n"; } - foreach (string materialName in normalMapAndHeightMapNotBoth) { - warnings += "The material " + materialName + " has both a normal map and a height map assigned but can only use 1.\n\n"; - } warnings += textureWarnings; if (!string.IsNullOrEmpty(boneErrors)) { // if there are both errors and warnings then warnings will be displayed with errors in the error dialog @@ -664,7 +656,7 @@ class AvatarExporter : MonoBehaviour { materialMappings[materialName] == DEFAULT_MATERIAL_NAME) { materialJson += "\"all\": "; } else { - materialJson += "\"[mat::" + materialName + "]\": "; + materialJson += "\"mat::" + materialName + "\": "; } materialJson += materialData.Value.getJSON(); materialJson += ", "; @@ -688,7 +680,6 @@ class AvatarExporter : MonoBehaviour { materialDatas.Clear(); materialAlternateStandardShader.Clear(); materialUnsupportedShader.Clear(); - normalMapAndHeightMapNotBoth.Clear(); SetMaterialMappings(); @@ -1057,8 +1048,6 @@ class AvatarExporter : MonoBehaviour { string materialName = material.name; string shaderName = material.shader.name; - Debug.Log("material1 " + materialName); - // don't store any material data for unsupported shader types if (Array.IndexOf(SUPPORTED_SHADERS, shaderName) == -1) { if (!materialUnsupportedShader.ContainsKey(materialName)) { @@ -1073,7 +1062,6 @@ class AvatarExporter : MonoBehaviour { materialData.roughness = material.GetFloat("_Glossiness"); materialData.roughnessMap = GetMaterialTexture(material, "_SpecGlossMap"); materialData.normalMap = GetMaterialTexture(material, "_BumpMap"); - materialData.heightMap = GetMaterialTexture(material, "_ParallaxMap"); materialData.occlusionMap = GetMaterialTexture(material, "_OcclusionMap"); materialData.emissive = material.GetColor("_EmissionColor"); materialData.emissiveMap = GetMaterialTexture(material, "_EmissionMap"); @@ -1095,12 +1083,7 @@ class AvatarExporter : MonoBehaviour { } materialData.roughness = 1.0f - materialData.roughness; } - - // materials can not have both a normal map and a height map set - if (!string.IsNullOrEmpty(materialData.normalMap) && !string.IsNullOrEmpty(materialData.heightMap) && !normalMapAndHeightMapNotBoth.Contains(materialName)) { - normalMapAndHeightMapNotBoth.Add(materialName); - } - + // remap the material name from the Unity material name to the fbx material name that it overrides if (materialMappings.ContainsKey(materialName)) { materialName = materialMappings[materialName]; @@ -1137,7 +1120,6 @@ class AvatarExporter : MonoBehaviour { var material = mapping.Value as UnityEngine.Material; if (material != null) { materialMappings.Add(material.name, mapping.Key.name); - //Debug.Log("materialMapping " + material.name + " " + mapping.Key.name); } } } diff --git a/tools/unity-avatar-exporter/avatarExporter.unitypackage b/tools/unity-avatar-exporter/avatarExporter.unitypackage index ab39e1ed8f7d25ceb210c8ee59897c88aae20abb..aada1d3515d3ee1a165a97e0eee41c92a058e6b0 100644 GIT binary patch literal 15720 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 literal 15949 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 Date: Wed, 6 Mar 2019 13:55:20 -0800 Subject: [PATCH 369/474] new jointRotationOffset field --- .../Assets/Editor/AvatarExporter.cs | 13 ++++--------- tools/unity-avatar-exporter/Assets/README.txt | 2 +- .../avatarExporter.unitypackage | Bin 13667 -> 13634 bytes 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs index 7b90145223..dcda499387 100644 --- a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs +++ b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs @@ -14,7 +14,7 @@ using System.Collections.Generic; class AvatarExporter : MonoBehaviour { // update version number for every PR that changes this file, also set updated version in README file - static readonly string AVATAR_EXPORTER_VERSION = "0.2"; + static readonly string AVATAR_EXPORTER_VERSION = "0.3.2"; static readonly float HIPS_GROUND_MIN_Y = 0.01f; static readonly float HIPS_SPINE_CHEST_MIN_SEPARATION = 0.001f; @@ -551,13 +551,10 @@ class AvatarExporter : MonoBehaviour { // generate joint rotation offsets for both humanoid-mapped bones as well as extra unmapped bones Quaternion jointOffset = new Quaternion(); - string outputJointName = ""; if (userBoneInfo.HasHumanMapping()) { - outputJointName = HUMANOID_TO_HIFI_JOINT_NAME[userBoneInfo.humanName]; Quaternion rotation = REFERENCE_ROTATIONS[userBoneInfo.humanName]; jointOffset = Quaternion.Inverse(userBoneInfo.rotation) * rotation; } else { - outputJointName = userBoneName; jointOffset = Quaternion.Inverse(userBoneInfo.rotation); string lastRequiredParent = FindLastRequiredAncestorBone(userBoneName); if (lastRequiredParent != "root") { @@ -569,11 +566,9 @@ class AvatarExporter : MonoBehaviour { } // swap from left-handed (Unity) to right-handed (HiFi) coordinates and write out joint rotation offset to fst - if (!string.IsNullOrEmpty(outputJointName)) { - jointOffset = new Quaternion(-jointOffset.x, jointOffset.y, jointOffset.z, -jointOffset.w); - File.AppendAllText(exportFstPath, "jointRotationOffset = " + outputJointName + " = (" + jointOffset.x + ", " + - jointOffset.y + ", " + jointOffset.z + ", " + jointOffset.w + ")\n"); - } + jointOffset = new Quaternion(-jointOffset.x, jointOffset.y, jointOffset.z, -jointOffset.w); + File.AppendAllText(exportFstPath, "jointRotationOffset2 = " + userBoneName + " = (" + jointOffset.x + ", " + + jointOffset.y + ", " + jointOffset.z + ", " + jointOffset.w + ")\n"); } // open File Explorer to the project directory once finished diff --git a/tools/unity-avatar-exporter/Assets/README.txt b/tools/unity-avatar-exporter/Assets/README.txt index c84cec2978..9aea30dc5b 100644 --- a/tools/unity-avatar-exporter/Assets/README.txt +++ b/tools/unity-avatar-exporter/Assets/README.txt @@ -1,6 +1,6 @@ High Fidelity, Inc. Avatar Exporter -Version 0.2 +Version 0.3.2 Note: It is recommended to use Unity versions between 2017.4.17f1 and 2018.2.12f1 for this Avatar Exporter. diff --git a/tools/unity-avatar-exporter/avatarExporter.unitypackage b/tools/unity-avatar-exporter/avatarExporter.unitypackage index 95c000e7c61b7623e0da0a1f9974e0744375014e..ce4ed346f878123160f51e7584f34097ac63e3e1 100644 GIT binary patch delta 12995 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 P7ec=PP;I)t01yEHI%jNC From 5fcfa265d2621a22d7696d8c21305a768fad4a4d Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Wed, 6 Mar 2019 14:02:58 -0800 Subject: [PATCH 370/474] Remove alternate versions of AnimPose(mat4) constructor --- libraries/animation/src/AnimPose.cpp | 36 ---------------------------- 1 file changed, 36 deletions(-) diff --git a/libraries/animation/src/AnimPose.cpp b/libraries/animation/src/AnimPose.cpp index 8b030fac39..56692e763e 100644 --- a/libraries/animation/src/AnimPose.cpp +++ b/libraries/animation/src/AnimPose.cpp @@ -11,9 +11,7 @@ #include "AnimPose.h" #include #include -#include #include "AnimUtil.h" -#include const AnimPose AnimPose::identity = AnimPose(glm::vec3(1.0f), glm::quat(), @@ -22,39 +20,6 @@ const AnimPose AnimPose::identity = AnimPose(glm::vec3(1.0f), #define NEW_VERSION AnimPose::AnimPose(const glm::mat4& mat) { -#if defined(ORIGINAL_VERSION) - static const float EPSILON = 0.0001f; - _scale = extractScale(mat); - // quat_cast doesn't work so well with scaled matrices, so cancel it out. - glm::mat4 tmp = glm::scale(mat, 1.0f / _scale); - _rot = glm::quat_cast(tmp); - float lengthSquared = glm::length2(_rot); - if (glm::abs(lengthSquared - 1.0f) > EPSILON) { - float oneOverLength = 1.0f / sqrtf(lengthSquared); - _rot = glm::quat(_rot.w * oneOverLength, _rot.x * oneOverLength, _rot.y * oneOverLength, _rot.z * oneOverLength); - } - _trans = extractTranslation(mat); -#elif defined(DECOMPOSE_VERSION) - // glm::decompose code - glm::vec3 scale; - glm::quat rotation; - glm::vec3 translation; - glm::vec3 skew; - glm::vec4 perspective; - bool result = glm::decompose(mat, scale, rotation, translation, skew, perspective); - _scale = scale; - _rot = rotation; - _trans = translation; - if (!result) { - // hack - const float HACK_FACTOR = 1000.0f; - glm::mat4 tmp = glm::scale(mat, HACK_FACTOR); - glm::decompose(tmp, scale, rotation, translation, skew, perspective); - _scale = scale / HACK_FACTOR; - _rot = rotation; - _trans = translation; - } -#elif defined(NEW_VERSION) glm::mat3 m(mat); _scale = glm::vec3(glm::length(m[0]), glm::length(m[1]), glm::length(m[2])); float det = glm::determinant(m); @@ -79,7 +44,6 @@ AnimPose::AnimPose(const glm::mat4& mat) { } _trans = extractTranslation(mat); -#endif } glm::vec3 AnimPose::operator*(const glm::vec3& rhs) const { From c5e9a7d1ab2bdacd79602aa7354723370fa616ca Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Wed, 6 Mar 2019 14:26:50 -0800 Subject: [PATCH 371/474] Added unit test for AnimPose() ctor using matrix with negative determinant --- tests/animation/src/AnimTests.cpp | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/animation/src/AnimTests.cpp b/tests/animation/src/AnimTests.cpp index 0cd9571e22..a14ffcf967 100644 --- a/tests/animation/src/AnimTests.cpp +++ b/tests/animation/src/AnimTests.cpp @@ -443,6 +443,28 @@ void AnimTests::testAnimPose() { } } } + + + // test matrix that has a negative determiant. + glm::vec4 col0(-9.91782e-05f, -5.40349e-05f, 0.000724383f, 0.0f); + glm::vec4 col1(-0.000155237f, 0.00071579f, 3.21398e-05f, 0.0f); + glm::vec4 col2(0.000709614f, 0.000149036f, 0.000108273f, 0.0f); + glm::vec4 col3(0.117922f, 0.250457f, 0.102155f, 1.0f); + glm::mat4 m(col0, col1, col2, col3); + AnimPose p(m); + + glm::vec3 resultTrans = glm::vec3(col3); + glm::quat resultRot = glm::quat(0.0530394f, 0.751549f, 0.0949531f, -0.650649f); + glm::vec3 resultScale = glm::vec3(-0.000733135f, -0.000733135f, -0.000733135f); + + const float TEST_EPSILON2 = 0.00001f; + QCOMPARE_WITH_ABS_ERROR(p.trans(), resultTrans, TEST_EPSILON2); + + if (glm::dot(p.rot(), resultRot) < 0.0f) { + resultRot = -resultRot; + } + QCOMPARE_WITH_ABS_ERROR(p.rot(), resultRot, TEST_EPSILON2); + QCOMPARE_WITH_ABS_ERROR(p.scale(), resultScale, TEST_EPSILON2); } void AnimTests::testExpressionTokenizer() { From 0f3e1b91574c1f75fcab5e40f3aa21bc3a524ba1 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 6 Mar 2019 14:53:21 -0800 Subject: [PATCH 372/474] fix wearable scale on position change --- libraries/entities/src/EntityItem.cpp | 2 +- libraries/entities/src/EntityItemProperties.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 5da1c05aa2..df9f12afb9 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1868,7 +1868,7 @@ void EntityItem::setParentID(const QUuid& value) { glm::vec3 EntityItem::getScaledDimensions() const { glm::vec3 scale = getSNScale(); - return _unscaledDimensions * scale; + return getUnscaledDimensions() * scale; } void EntityItem::setScaledDimensions(const glm::vec3& value) { diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 5ae9b30869..ce5b8b558c 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -4177,7 +4177,7 @@ void EntityItemProperties::copySimulationRestrictedProperties(const EntityItemPo setAcceleration(entity->getAcceleration()); } if (!_localDimensionsChanged && !_dimensionsChanged) { - setDimensions(entity->getScaledDimensions()); + setLocalDimensions(entity->getScaledDimensions()); } } From 474a97d0fcb6faf0365a0678ee0656c421e306d1 Mon Sep 17 00:00:00 2001 From: amantley Date: Wed, 6 Mar 2019 15:15:30 -0800 Subject: [PATCH 373/474] now update the ikOverlayAlpha every frame --- libraries/animation/src/Rig.cpp | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 344a98f8c4..0fe03c7074 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1064,18 +1064,10 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos t += deltaTime; - if (_enableInverseKinematics != _lastEnableInverseKinematics) { - if (_enableInverseKinematics) { - _animVars.set("ikOverlayAlpha", 1.0f); - } else { - _animVars.set("ikOverlayAlpha", 0.0f); - } - } - _lastEnableInverseKinematics = _enableInverseKinematics; - -#if defined(Q_OS_ANDROID) || defined(HIFI_USE_OPTIMIZED_IK) - - if (!_enableInverseKinematics) { + if (_enableInverseKinematics) { + _animVars.set("ikOverlayAlpha", 1.0f); + } else { + _animVars.set("ikOverlayAlpha", 0.0f); _animVars.set("splineIKEnabled", false); _animVars.set("leftHandIKEnabled", false); _animVars.set("rightHandIKEnabled", false); @@ -1086,8 +1078,7 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _animVars.set("leftFootPoleVectorEnabled", false); _animVars.set("rightFootPoleVectorEnabled", false); } - -#endif + _lastEnableInverseKinematics = _enableInverseKinematics; } _lastForward = forward; From 96eb137df26917155625d9fa223b18e8a5cea03e Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 6 Mar 2019 15:53:25 -0800 Subject: [PATCH 374/474] fix debugAvatarMixer.js --- interface/src/ui/overlays/Overlays.cpp | 3 ++- .../developer/debugging/debugAvatarMixer.js | 19 ++++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index dfd698f6c5..c776cffd67 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -204,7 +204,8 @@ QString Overlays::overlayToEntityType(const QString& type) { #define RENAME_PROP(o, e) \ { \ auto iter = overlayProps.find(#o); \ - if (iter != overlayProps.end()) { \ + if (iter != overlayProps.end() && \ + !overlayProps.contains(#e)) { \ overlayProps[#e] = iter.value(); \ } \ } diff --git a/scripts/developer/debugging/debugAvatarMixer.js b/scripts/developer/debugging/debugAvatarMixer.js index 90f2de13a9..fad4283f7c 100644 --- a/scripts/developer/debugging/debugAvatarMixer.js +++ b/scripts/developer/debugging/debugAvatarMixer.js @@ -19,6 +19,11 @@ Script.include("/~/system/libraries/controllers.js"); var isShowingOverlays = true; var debugOverlays = {}; +var textSizeOverlay = Overlays.addOverlay("text3d", { + position: MyAvatar.position, + lineHeight: 0.1, + visible: false +}); function removeOverlays() { // enumerate the overlays and remove them @@ -31,6 +36,8 @@ function removeOverlays() { } } + Overlays.deleteOverlay(textSizeOverlay); + debugOverlays = {}; } @@ -60,8 +67,6 @@ function updateOverlays() { var overlayPosition = avatar.getJointPosition("Head"); overlayPosition.y += 1.15; - var rows = 8; - var text = avatarID + "\n" +"--- Data from Mixer ---\n" +"All: " + AvatarManager.getAvatarDataRate(avatarID).toFixed(2) + "kbps (" + AvatarManager.getAvatarUpdateRate(avatarID).toFixed(2) + "hz)" + "\n" @@ -85,9 +90,11 @@ function updateOverlays() { //+" SM: " + AvatarManager.getAvatarSimulationRate(avatarID,"skeletonModel").toFixed(2) + "hz \n" +" JD: " + AvatarManager.getAvatarSimulationRate(avatarID,"jointData").toFixed(2) + "hz \n" + var dimensions = Overlays.textSize(textSizeOverlay, text); if (avatarID in debugOverlays) { // keep the overlay above the current position of this avatar Overlays.editOverlay(debugOverlays[avatarID][0], { + dimensions: { x: 1.1 * dimensions.width, y: 0.6 * dimensions.height }, position: overlayPosition, text: text }); @@ -95,15 +102,9 @@ function updateOverlays() { // add the overlay above this avatar var newOverlay = Overlays.addOverlay("text3d", { position: overlayPosition, - dimensions: { - x: 1.25, - y: rows * 0.13 - }, + dimensions: { x: 1.1 * dimensions.width, y: 0.6 * dimensions.height }, lineHeight: 0.1, - font:{size:0.1}, text: text, - size: 1, - scale: 0.4, color: { red: 255, green: 255, blue: 255}, alpha: 1, solid: true, From a0bd2f67ec4f007fd3b944ce5b45d83bbd0cc6a2 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Wed, 6 Mar 2019 17:20:36 -0800 Subject: [PATCH 375/474] fix stylus gizmo intersection --- interface/src/raypick/RayPick.cpp | 15 +++++++++++++++ interface/src/raypick/RayPick.h | 1 + interface/src/raypick/StylusPick.cpp | 24 +++++++++++++++++++++--- libraries/entities/src/ShapeEntityItem.h | 2 +- 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/interface/src/raypick/RayPick.cpp b/interface/src/raypick/RayPick.cpp index b030a67e17..8157c32a93 100644 --- a/interface/src/raypick/RayPick.cpp +++ b/interface/src/raypick/RayPick.cpp @@ -89,6 +89,8 @@ glm::vec3 RayPick::intersectRayWithEntityXYPlane(const QUuid& entityID, const gl return intersectRayWithXYPlane(origin, direction, props.getPosition(), props.getRotation(), props.getRegistrationPoint()); } + + glm::vec2 RayPick::projectOntoXYPlane(const glm::vec3& worldPos, const glm::vec3& position, const glm::quat& rotation, const glm::vec3& dimensions, const glm::vec3& registrationPoint, bool unNormalized) { glm::quat invRot = glm::inverse(rotation); glm::vec3 localPos = invRot * (worldPos - position); @@ -102,6 +104,19 @@ glm::vec2 RayPick::projectOntoXYPlane(const glm::vec3& worldPos, const glm::vec3 return pos2D; } +glm::vec2 RayPick::projectOntoXZPlane(const glm::vec3& worldPos, const glm::vec3& position, const glm::quat& rotation, const glm::vec3& dimensions, const glm::vec3& registrationPoint, bool unNormalized) { + glm::quat invRot = glm::inverse(rotation); + glm::vec3 localPos = invRot * (worldPos - position); + + glm::vec3 normalizedPos = (localPos / dimensions) + registrationPoint; + + glm::vec2 pos2D = glm::vec2(normalizedPos.x, (1.0f - normalizedPos.z)); + if (unNormalized) { + pos2D *= glm::vec2(dimensions.x, dimensions.z); + } + return pos2D; +} + glm::vec2 RayPick::projectOntoEntityXYPlane(const QUuid& entityID, const glm::vec3& worldPos, bool unNormalized) { EntityPropertyFlags desiredProperties; desiredProperties += PROP_POSITION; diff --git a/interface/src/raypick/RayPick.h b/interface/src/raypick/RayPick.h index 7625146408..161c930fef 100644 --- a/interface/src/raypick/RayPick.h +++ b/interface/src/raypick/RayPick.h @@ -86,6 +86,7 @@ public: static glm::vec2 projectOntoEntityXYPlane(const QUuid& entityID, const glm::vec3& worldPos, bool unNormalized = true); static glm::vec2 projectOntoXYPlane(const glm::vec3& worldPos, const glm::vec3& position, const glm::quat& rotation, const glm::vec3& dimensions, const glm::vec3& registrationPoint, bool unNormalized); + static glm::vec2 projectOntoXZPlane(const glm::vec3& worldPos, const glm::vec3& position, const glm::quat& rotation, const glm::vec3& dimensions, const glm::vec3& registrationPoint, bool unNoemalized); private: static glm::vec3 intersectRayWithXYPlane(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& point, const glm::quat& rotation, const glm::vec3& registration); diff --git a/interface/src/raypick/StylusPick.cpp b/interface/src/raypick/StylusPick.cpp index cf19b68a45..2bd371c506 100644 --- a/interface/src/raypick/StylusPick.cpp +++ b/interface/src/raypick/StylusPick.cpp @@ -155,15 +155,33 @@ PickResultPointer StylusPick::getEntityIntersection(const StylusTip& pick) { const auto entityRotation = entity->getWorldOrientation(); const auto entityPosition = entity->getWorldPosition(); + const auto entityType = entity->getType(); + glm::vec3 normal; - glm::vec3 normal = entityRotation * Vectors::UNIT_Z; + // TODO: Use the xz projection method for Sphere and Quad. + if (entityType == EntityTypes::Gizmo) { + normal = entityRotation * Vectors::UNIT_NEG_Y; + } else { + normal = entityRotation * Vectors::UNIT_Z; + } float distance = glm::dot(pick.position - entityPosition, normal); if (distance < nearestTarget.distance) { const auto entityDimensions = entity->getScaledDimensions(); const auto entityRegistrationPoint = entity->getRegistrationPoint(); glm::vec3 intersection = pick.position - (normal * distance); - glm::vec2 pos2D = RayPick::projectOntoXYPlane(intersection, entityPosition, entityRotation, - entityDimensions, entityRegistrationPoint, false); + glm::vec2 pos2D; + + + auto entityType = entity->getType(); + + if (entityType == EntityTypes::Gizmo) { + pos2D = RayPick::projectOntoXZPlane(intersection, entityPosition, entityRotation, + entityDimensions, entityRegistrationPoint, false); + } else { + pos2D = RayPick::projectOntoXYPlane(intersection, entityPosition, entityRotation, + entityDimensions, entityRegistrationPoint, false); + } + if (pos2D == glm::clamp(pos2D, glm::vec2(0), glm::vec2(1))) { IntersectionType type = IntersectionType::ENTITY; if (getFilter().doesPickLocalEntities()) { diff --git a/libraries/entities/src/ShapeEntityItem.h b/libraries/entities/src/ShapeEntityItem.h index 28edf2e1a2..363a7f39d1 100644 --- a/libraries/entities/src/ShapeEntityItem.h +++ b/libraries/entities/src/ShapeEntityItem.h @@ -85,7 +85,7 @@ public: void setUnscaledDimensions(const glm::vec3& value) override; bool shouldBePhysical() const override { return !isDead(); } - + bool supportsDetailedIntersection() const override; bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, From e218e4bead36d28fa0e4d7de630f7acd72ec1851 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 6 Mar 2019 17:29:32 -0800 Subject: [PATCH 376/474] updating compile failure + icons/settings update --- .../icons/tablet-icons/mic-ptt-a.svg | 1 + .../icons/tablet-icons/mic-ptt-i.svg | 24 +++++++ interface/resources/qml/hifi/audio/Audio.qml | 64 ++++++++++++++++--- interface/resources/qml/hifi/audio/MicBar.qml | 8 ++- interface/src/scripting/Audio.cpp | 20 ------ scripts/system/audio.js | 14 ++-- 6 files changed, 93 insertions(+), 38 deletions(-) create mode 100644 interface/resources/icons/tablet-icons/mic-ptt-a.svg create mode 100644 interface/resources/icons/tablet-icons/mic-ptt-i.svg diff --git a/interface/resources/icons/tablet-icons/mic-ptt-a.svg b/interface/resources/icons/tablet-icons/mic-ptt-a.svg new file mode 100644 index 0000000000..e6df3c69d7 --- /dev/null +++ b/interface/resources/icons/tablet-icons/mic-ptt-a.svg @@ -0,0 +1 @@ +mic-ptt-a \ No newline at end of file diff --git a/interface/resources/icons/tablet-icons/mic-ptt-i.svg b/interface/resources/icons/tablet-icons/mic-ptt-i.svg new file mode 100644 index 0000000000..2141ea5229 --- /dev/null +++ b/interface/resources/icons/tablet-icons/mic-ptt-i.svg @@ -0,0 +1,24 @@ + + + + + + diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index 45358f59a2..d44a9c862e 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -120,6 +120,10 @@ Rectangle { isRedCheck: true; checked: AudioScriptingInterface.muted; onClicked: { + if (AudioScriptingInterface.pushToTalk && !checked) { + // disable push to talk if unmuting + AudioScriptingInterface.pushToTalk = false; + } AudioScriptingInterface.muted = checked; checked = Qt.binding(function() { return AudioScriptingInterface.muted; }); // restore binding } @@ -150,7 +154,23 @@ Rectangle { } AudioControls.CheckBox { spacing: muteMic.spacing - text: qsTr("Push To Talk"); + text: qsTr("Show audio level meter"); + checked: AvatarInputs.showAudioTools; + onClicked: { + AvatarInputs.showAudioTools = checked; + checked = Qt.binding(function() { return AvatarInputs.showAudioTools; }); // restore binding + } + onXChanged: rightMostInputLevelPos = x + width + } + } + + Separator {} + + ColumnLayout { + spacing: muteMic.spacing; + AudioControls.CheckBox { + spacing: muteMic.spacing + text: qsTr("Push To Talk (T)"); checked: isVR ? AudioScriptingInterface.pushToTalkHMD : AudioScriptingInterface.pushToTalkDesktop; onClicked: { if (isVR) { @@ -167,15 +187,41 @@ Rectangle { }); // restore binding } } - AudioControls.CheckBox { - spacing: muteMic.spacing - text: qsTr("Show audio level meter"); - checked: AvatarInputs.showAudioTools; - onClicked: { - AvatarInputs.showAudioTools = checked; - checked = Qt.binding(function() { return AvatarInputs.showAudioTools; }); // restore binding + Item { + id: pttTextContainer + x: margins.paddings; + width: rightMostInputLevelPos + height: pttTextMetrics.height + visible: true + TextMetrics { + id: pttTextMetrics + text: pttText.text + font: pttText.font + } + RalewayRegular { + id: pttText + wrapMode: Text.WordWrap + color: hifi.colors.white; + width: parent.width; + font.italic: true + size: 16; + text: isVR ? qsTr("Press and hold grip triggers on both of your controllers to unmute.") : + qsTr("Press and hold the button \"T\" to unmute."); + onTextChanged: { + if (pttTextMetrics.width > rightMostInputLevelPos) { + pttTextContainer.height = Math.ceil(pttTextMetrics.width / rightMostInputLevelPos) * pttTextMetrics.height; + } else { + pttTextContainer.height = pttTextMetrics.height; + } + } + } + Component.onCompleted: { + if (pttTextMetrics.width > rightMostInputLevelPos) { + pttTextContainer.height = Math.ceil(pttTextMetrics.width / rightMostInputLevelPos) * pttTextMetrics.height; + } else { + pttTextContainer.height = pttTextMetrics.height; + } } - onXChanged: rightMostInputLevelPos = x + width } } } diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index 9d1cbfbc6c..50477b82f8 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -67,6 +67,9 @@ Rectangle { hoverEnabled: true; scrollGestureEnabled: false; onClicked: { + if (AudioScriptingInterface.pushToTalk) { + return; + } AudioScriptingInterface.muted = !AudioScriptingInterface.muted; Tablet.playSound(TabletEnums.ButtonClick); } @@ -109,9 +112,10 @@ Rectangle { Image { readonly property string unmutedIcon: "../../../icons/tablet-icons/mic-unmute-i.svg"; readonly property string mutedIcon: "../../../icons/tablet-icons/mic-mute-i.svg"; + readonly property string pushToTalkIcon: "../../../icons/tablet-icons/mic-ptt-i.svg"; id: image; - source: AudioScriptingInterface.muted ? mutedIcon : unmutedIcon; + source: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? pushToTalkIcon : AudioScriptingInterface.muted ? mutedIcon : unmutedIcon; width: 30; height: 30; @@ -155,7 +159,7 @@ Rectangle { color: parent.color; - text: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? "MUTED-PTT (T)" : (AudioScriptingInterface.muted ? "MUTED" : "MUTE"); + text: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? "MUTED PTT-(T)" : (AudioScriptingInterface.muted ? "MUTED" : "MUTE"); font.pointSize: 12; } diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 45bb15f1a3..63ce9d2b2e 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -231,26 +231,6 @@ void Audio::loadData() { _pttHMD = _pttHMDSetting.get(); } -bool Audio::getPTTHMD() const { - return resultWithReadLock([&] { - return _pttHMD; - }); -} - -void Audio::saveData() { - _desktopMutedSetting.set(getMutedDesktop()); - _hmdMutedSetting.set(getMutedHMD()); - _pttDesktopSetting.set(getPTTDesktop()); - _pttHMDSetting.set(getPTTHMD()); -} - -void Audio::loadData() { - _desktopMuted = _desktopMutedSetting.get(); - _hmdMuted = _hmdMutedSetting.get(); - _pttDesktop = _pttDesktopSetting.get(); - _pttHMD = _pttHMDSetting.get(); -} - bool Audio::noiseReductionEnabled() const { return resultWithReadLock([&] { return _enableNoiseReduction; diff --git a/scripts/system/audio.js b/scripts/system/audio.js index bf44cfa7cc..19ed3faef2 100644 --- a/scripts/system/audio.js +++ b/scripts/system/audio.js @@ -27,12 +27,12 @@ var UNMUTE_ICONS = { activeIcon: "icons/tablet-icons/mic-unmute-a.svg" }; var PTT_ICONS = { - icon: "icons/tablet-icons/mic-unmute-i.svg", - activeIcon: "icons/tablet-icons/mic-unmute-a.svg" + icon: "icons/tablet-icons/mic-ptt-i.svg", + activeIcon: "icons/tablet-icons/mic-ptt-a.svg" }; function onMuteToggled() { - if (Audio.pushingToTalk) { + if (Audio.pushToTalk) { button.editProperties(PTT_ICONS); } else if (Audio.muted) { button.editProperties(MUTE_ICONS); @@ -63,8 +63,8 @@ function onScreenChanged(type, url) { var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var button = tablet.addButton({ - icon: Audio.muted ? MUTE_ICONS.icon : UNMUTE_ICONS.icon, - activeIcon: Audio.muted ? MUTE_ICONS.activeIcon : UNMUTE_ICONS.activeIcon, + icon: Audio.pushToTalk ? PTT_ICONS.icon : Audio.muted ? MUTE_ICONS.icon : UNMUTE_ICONS.icon, + activeIcon: Audio.pushToTalk ? PTT_ICONS.activeIcon : Audio.muted ? MUTE_ICONS.activeIcon : UNMUTE_ICONS.activeIcon, text: TABLET_BUTTON_NAME, sortOrder: 1 }); @@ -74,7 +74,7 @@ onMuteToggled(); button.clicked.connect(onClicked); tablet.screenChanged.connect(onScreenChanged); Audio.mutedChanged.connect(onMuteToggled); -Audio.pushingToTalkChanged.connect(onMuteToggled); +Audio.pushToTalkChanged.connect(onMuteToggled); Script.scriptEnding.connect(function () { if (onAudioScreen) { @@ -83,7 +83,7 @@ Script.scriptEnding.connect(function () { button.clicked.disconnect(onClicked); tablet.screenChanged.disconnect(onScreenChanged); Audio.mutedChanged.disconnect(onMuteToggled); - Audio.pushingToTalkChanged.disconnect(onMuteToggled); + Audio.pushToTalkChanged.disconnect(onMuteToggled); tablet.removeButton(button); }); From 380df059ffb62a8f723f18c4454af2b272bff3f7 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 6 Mar 2019 18:18:39 -0800 Subject: [PATCH 377/474] fix shapes app --- interface/src/avatar/MyAvatar.cpp | 3 +- interface/src/ui/overlays/Overlays.cpp | 29 +++++++++++++++---- .../src/RenderableModelEntityItem.cpp | 8 ++--- .../src/RenderableModelEntityItem.h | 2 +- .../src/RenderableZoneEntityItem.h | 2 +- libraries/entities/src/EntityItem.cpp | 4 +-- libraries/entities/src/EntityItem.h | 2 +- libraries/entities/src/EntityTree.cpp | 23 ++++++++++----- libraries/entities/src/LightEntityItem.cpp | 4 +-- libraries/entities/src/LightEntityItem.h | 2 +- libraries/shared/src/SpatiallyNestable.cpp | 10 ++++--- libraries/shared/src/SpatiallyNestable.h | 2 +- 12 files changed, 60 insertions(+), 31 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index faa9f88ae9..bfcb9269d5 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -910,7 +910,7 @@ void MyAvatar::simulate(float deltaTime, bool inView) { recorder->recordFrame(FRAME_TYPE, toFrame(*this)); } - locationChanged(); + locationChanged(true, false); // if a entity-child of this avatar has moved outside of its queryAACube, update the cube and tell the entity server. auto entityTreeRenderer = qApp->getEntities(); EntityTreePointer entityTree = entityTreeRenderer ? entityTreeRenderer->getTree() : nullptr; @@ -920,6 +920,7 @@ void MyAvatar::simulate(float deltaTime, bool inView) { zoneInteractionProperties = entityTreeRenderer->getZoneInteractionProperties(); EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender(); forEachDescendant([&](SpatiallyNestablePointer object) { + locationChanged(true, false); // we need to update attached queryAACubes in our own local tree so point-select always works // however we don't want to flood the update pipeline with AvatarEntity updates, so we assume // others have all info required to properly update queryAACube of AvatarEntities on their end diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index c776cffd67..1bcb040a77 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -494,15 +494,34 @@ EntityItemProperties Overlays::convertOverlayToEntityProperties(QVariantMap& ove RENAME_PROP_CONVERT(p1, p1, [](const QVariant& v) { return vec3toVariant(glm::vec3(0.0f)); }); RENAME_PROP_CONVERT(p2, p2, [=](const QVariant& v) { glm::vec3 position; + bool hasPosition = false; + glm::quat rotation; + bool hasRotation = false; + auto iter2 = overlayProps.find("position"); if (iter2 != overlayProps.end()) { position = vec3FromVariant(iter2.value()); - } else if (!add) { - EntityPropertyFlags desiredProperties; - desiredProperties += PROP_POSITION; - position = DependencyManager::get()->getEntityProperties(id, desiredProperties).getPosition(); + hasPosition = true; } - return vec3toVariant(vec3FromVariant(v) - position); + iter2 = overlayProps.find("rotation"); + if (iter2 != overlayProps.end()) { + rotation = quatFromVariant(iter2.value()); + hasRotation = true; + } + + if (!add && !(hasPosition && hasRotation)) { + auto entity = DependencyManager::get()->getEntity(id); + if (entity) { + if (!hasPosition) { + position = entity->getWorldPosition(); + } + if (!hasRotation) { + rotation = entity->getWorldOrientation(); + } + } + } + + return vec3toVariant(glm::inverse(rotation) * (vec3FromVariant(v) - position)); }); RENAME_PROP(localStart, p1); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index e842d98714..03c50008a0 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -928,9 +928,9 @@ void RenderableModelEntityItem::setJointTranslationsSet(const QVector& tra _needsJointSimulation = true; } -void RenderableModelEntityItem::locationChanged(bool tellPhysics) { +void RenderableModelEntityItem::locationChanged(bool tellPhysics, bool tellChildren) { DETAILED_PERFORMANCE_TIMER("locationChanged"); - EntityItem::locationChanged(tellPhysics); + EntityItem::locationChanged(tellPhysics, tellChildren); auto model = getModel(); if (model && model->isLoaded()) { model->updateRenderItems(); @@ -1032,9 +1032,7 @@ void RenderableModelEntityItem::copyAnimationJointDataToModel() { }); if (changed) { - forEachChild([&](SpatiallyNestablePointer object) { - object->locationChanged(false); - }); + locationChanged(false, true); } } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 9adff9ca01..2fd1041c5f 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -108,7 +108,7 @@ public: virtual void setJointTranslations(const QVector& translations) override; virtual void setJointTranslationsSet(const QVector& translationsSet) override; - virtual void locationChanged(bool tellPhysics = true) override; + virtual void locationChanged(bool tellPhysics = true, bool tellChildren = true) override; virtual int getJointIndex(const QString& name) const override; virtual QStringList getJointNames() const override; diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.h b/libraries/entities-renderer/src/RenderableZoneEntityItem.h index 32b5cf94a0..2fa55f1540 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.h +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.h @@ -150,7 +150,7 @@ public: virtual bool addToScene(const EntityItemPointer& self, const render::ScenePointer& scene, render::Transaction& transaction) override; virtual void removeFromScene(const EntityItemPointer& self, const render::ScenePointer& scene, render::Transaction& transaction) override; private: - virtual void locationChanged(bool tellPhysics = true) override { EntityItem::locationChanged(tellPhysics); notifyBoundChanged(); } + virtual void locationChanged(bool tellPhysics = true, bool tellChildren = true) override { EntityItem::locationChanged(tellPhysics, tellChildren); notifyBoundChanged(); } virtual void dimensionsChanged() override { EntityItem::dimensionsChanged(); notifyBoundChanged(); } void notifyBoundChanged(); void notifyChangedRenderItem(); diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index d9614a0918..80db432fd0 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -2593,7 +2593,7 @@ QList EntityItem::getActionsOfType(EntityDynamicType typeT return result; } -void EntityItem::locationChanged(bool tellPhysics) { +void EntityItem::locationChanged(bool tellPhysics, bool tellChildren) { requiresRecalcBoxes(); if (tellPhysics) { _flags |= Simulation::DIRTY_TRANSFORM; @@ -2602,7 +2602,7 @@ void EntityItem::locationChanged(bool tellPhysics) { tree->entityChanged(getThisPointer()); } } - SpatiallyNestable::locationChanged(tellPhysics); // tell all the children, also + SpatiallyNestable::locationChanged(tellPhysics, tellChildren); std::pair data(_spaceIndex, glm::vec4(getWorldPosition(), _boundingRadius)); emit spaceUpdate(data); somethingChangedNotification(); diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 3fbfde6b24..f8c9c3b6f7 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -518,7 +518,7 @@ public: virtual bool getMeshes(MeshProxyList& result) { return true; } - virtual void locationChanged(bool tellPhysics = true) override; + virtual void locationChanged(bool tellPhysics = true, bool tellChildren = true) override; virtual bool getScalesWithParent() const override; diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index d4f15fa8b2..5234e77075 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -2039,6 +2039,8 @@ void EntityTree::fixupNeedsParentFixups() { Simulation::DIRTY_COLLISION_GROUP | Simulation::DIRTY_TRANSFORM); entityChanged(entity); + entity->locationChanged(true, false); + entity->forEachDescendant([&](SpatiallyNestablePointer object) { if (object->getNestableType() == NestableType::Entity) { EntityItemPointer descendantEntity = std::static_pointer_cast(object); @@ -2047,8 +2049,8 @@ void EntityTree::fixupNeedsParentFixups() { Simulation::DIRTY_TRANSFORM); entityChanged(descendantEntity); } + object->locationChanged(true, false); }); - entity->locationChanged(true); // Update our parent's bounding box bool success = false; @@ -3002,8 +3004,19 @@ void EntityTree::updateEntityQueryAACubeWorker(SpatiallyNestablePointer object, // if the queryBox has changed, tell the entity-server EntityItemPointer entity = std::dynamic_pointer_cast(object); if (entity) { - // NOTE: we rely on side-effects of the entity->updateQueryAACube() call in the following if() conditional: - if (entity->updateQueryAACube() || force) { + bool queryAACubeChanged = false; + if (!entity->hasChildren()) { + // updateQueryAACube will also update all ancestors' AACubes, so we only need to call this for leaf nodes + queryAACubeChanged = entity->updateQueryAACube(); + } else { + AACube oldCube = entity->getQueryAACube(); + object->forEachDescendant([&](SpatiallyNestablePointer descendant) { + updateEntityQueryAACubeWorker(descendant, packetSender, moveOperator, force, tellServer); + }); + queryAACubeChanged = oldCube != entity->getQueryAACube(); + } + + if (queryAACubeChanged || force) { bool success; AACube newCube = entity->getQueryAACube(success); if (success) { @@ -3027,10 +3040,6 @@ void EntityTree::updateEntityQueryAACubeWorker(SpatiallyNestablePointer object, entityChanged(entity); } } - - object->forEachDescendant([&](SpatiallyNestablePointer descendant) { - updateEntityQueryAACubeWorker(descendant, packetSender, moveOperator, force, tellServer); - }); } void EntityTree::updateEntityQueryAACube(SpatiallyNestablePointer object, EntityEditPacketSender* packetSender, diff --git a/libraries/entities/src/LightEntityItem.cpp b/libraries/entities/src/LightEntityItem.cpp index 88aae9691c..fcfda66319 100644 --- a/libraries/entities/src/LightEntityItem.cpp +++ b/libraries/entities/src/LightEntityItem.cpp @@ -55,8 +55,8 @@ void LightEntityItem::setUnscaledDimensions(const glm::vec3& value) { } } -void LightEntityItem::locationChanged(bool tellPhysics) { - EntityItem::locationChanged(tellPhysics); +void LightEntityItem::locationChanged(bool tellPhysics, bool tellChildren) { + EntityItem::locationChanged(tellPhysics, tellChildren); withWriteLock([&] { _lightPropertiesChanged = true; }); diff --git a/libraries/entities/src/LightEntityItem.h b/libraries/entities/src/LightEntityItem.h index 26b74f02cd..cc64121cb3 100644 --- a/libraries/entities/src/LightEntityItem.h +++ b/libraries/entities/src/LightEntityItem.h @@ -74,7 +74,7 @@ public: static bool getLightsArePickable() { return _lightsArePickable; } static void setLightsArePickable(bool value) { _lightsArePickable = value; } - virtual void locationChanged(bool tellPhysics) override; + virtual void locationChanged(bool tellPhysics, bool tellChildren) override; virtual void dimensionsChanged() override; bool lightPropertiesChanged() const { return _lightPropertiesChanged; } diff --git a/libraries/shared/src/SpatiallyNestable.cpp b/libraries/shared/src/SpatiallyNestable.cpp index d202afc59f..4d97f43e6a 100644 --- a/libraries/shared/src/SpatiallyNestable.cpp +++ b/libraries/shared/src/SpatiallyNestable.cpp @@ -1100,10 +1100,12 @@ void SpatiallyNestable::forEachDescendantTest(const ChildLambdaTest& actor) cons } } -void SpatiallyNestable::locationChanged(bool tellPhysics) { - forEachChild([&](SpatiallyNestablePointer object) { - object->locationChanged(tellPhysics); - }); +void SpatiallyNestable::locationChanged(bool tellPhysics, bool tellChildren) { + if (tellChildren) { + forEachChild([&](SpatiallyNestablePointer object) { + object->locationChanged(tellPhysics, tellChildren); + }); + } } AACube SpatiallyNestable::getMaximumAACube(bool& success) const { diff --git a/libraries/shared/src/SpatiallyNestable.h b/libraries/shared/src/SpatiallyNestable.h index 6b709f0352..f52dc4bf8b 100644 --- a/libraries/shared/src/SpatiallyNestable.h +++ b/libraries/shared/src/SpatiallyNestable.h @@ -211,7 +211,7 @@ public: void dump(const QString& prefix = "") const; - virtual void locationChanged(bool tellPhysics = true); // called when a this object's location has changed + virtual void locationChanged(bool tellPhysics = true, bool tellChildren = true); // called when a this object's location has changed virtual void dimensionsChanged() { _queryAACubeSet = false; } // called when a this object's dimensions have changed virtual void parentDeleted() { } // called on children of a deleted parent From dc4e2645da1439e193e3cbf88e346f4d524a3916 Mon Sep 17 00:00:00 2001 From: Sam Gondelman Date: Thu, 7 Mar 2019 08:05:32 -0800 Subject: [PATCH 378/474] even better! --- libraries/entities/src/EntityTree.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 5234e77075..fffcd943c3 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -3010,7 +3010,7 @@ void EntityTree::updateEntityQueryAACubeWorker(SpatiallyNestablePointer object, queryAACubeChanged = entity->updateQueryAACube(); } else { AACube oldCube = entity->getQueryAACube(); - object->forEachDescendant([&](SpatiallyNestablePointer descendant) { + object->forEachChild([&](SpatiallyNestablePointer descendant) { updateEntityQueryAACubeWorker(descendant, packetSender, moveOperator, force, tellServer); }); queryAACubeChanged = oldCube != entity->getQueryAACube(); From 97bfa538ff4c25f5512b62de983d0377ad31d025 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Thu, 7 Mar 2019 09:51:20 -0800 Subject: [PATCH 379/474] unlimited certified copies in a domain --- libraries/entities/src/EntityItem.cpp | 6 ++++++ libraries/entities/src/EntityItem.h | 3 +++ libraries/entities/src/EntityItemProperties.cpp | 14 ++++++++++++++ libraries/entities/src/EntityItemProperties.h | 2 ++ .../entities/src/EntityItemPropertiesDefaults.h | 1 + libraries/entities/src/EntityPropertyFlags.h | 1 + libraries/entities/src/EntityTree.cpp | 5 +++-- libraries/networking/src/udt/PacketHeaders.h | 1 + scripts/system/libraries/cloneEntityUtils.js | 5 +++-- 9 files changed, 34 insertions(+), 4 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index d9614a0918..6962feb71c 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -147,6 +147,7 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param requestedProperties += PROP_EDITION_NUMBER; requestedProperties += PROP_ENTITY_INSTANCE_NUMBER; requestedProperties += PROP_CERTIFICATE_ID; + requestedProperties += PROP_CERTIFICATE_TYPE; requestedProperties += PROP_STATIC_CERTIFICATE_VERSION; return requestedProperties; @@ -337,6 +338,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet APPEND_ENTITY_PROPERTY(PROP_EDITION_NUMBER, getEditionNumber()); APPEND_ENTITY_PROPERTY(PROP_ENTITY_INSTANCE_NUMBER, getEntityInstanceNumber()); APPEND_ENTITY_PROPERTY(PROP_CERTIFICATE_ID, getCertificateID()); + APPEND_ENTITY_PROPERTY(PROP_CERTIFICATE_TYPE, getCertificateType()); APPEND_ENTITY_PROPERTY(PROP_STATIC_CERTIFICATE_VERSION, getStaticCertificateVersion()); appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, @@ -942,6 +944,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef READ_ENTITY_PROPERTY(PROP_EDITION_NUMBER, quint32, setEditionNumber); READ_ENTITY_PROPERTY(PROP_ENTITY_INSTANCE_NUMBER, quint32, setEntityInstanceNumber); READ_ENTITY_PROPERTY(PROP_CERTIFICATE_ID, QString, setCertificateID); + READ_ENTITY_PROPERTY(PROP_CERTIFICATE_TYPE, QString, setCertificateType); READ_ENTITY_PROPERTY(PROP_STATIC_CERTIFICATE_VERSION, quint32, setStaticCertificateVersion); bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, @@ -1381,6 +1384,7 @@ EntityItemProperties EntityItem::getProperties(const EntityPropertyFlags& desire COPY_ENTITY_PROPERTY_TO_PROPERTIES(editionNumber, getEditionNumber); COPY_ENTITY_PROPERTY_TO_PROPERTIES(entityInstanceNumber, getEntityInstanceNumber); COPY_ENTITY_PROPERTY_TO_PROPERTIES(certificateID, getCertificateID); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(certificateType, getCertificateType); COPY_ENTITY_PROPERTY_TO_PROPERTIES(staticCertificateVersion, getStaticCertificateVersion); // Script local data @@ -1529,6 +1533,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(editionNumber, setEditionNumber); SET_ENTITY_PROPERTY_FROM_PROPERTIES(entityInstanceNumber, setEntityInstanceNumber); SET_ENTITY_PROPERTY_FROM_PROPERTIES(certificateID, setCertificateID); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(certificateType, setCertificateType); SET_ENTITY_PROPERTY_FROM_PROPERTIES(staticCertificateVersion, setStaticCertificateVersion); if (updateQueryAACube()) { @@ -3150,6 +3155,7 @@ DEFINE_PROPERTY_ACCESSOR(QString, MarketplaceID, marketplaceID) DEFINE_PROPERTY_ACCESSOR(quint32, EditionNumber, editionNumber) DEFINE_PROPERTY_ACCESSOR(quint32, EntityInstanceNumber, entityInstanceNumber) DEFINE_PROPERTY_ACCESSOR(QString, CertificateID, certificateID) +DEFINE_PROPERTY_ACCESSOR(QString, CertificateType, certificateType) DEFINE_PROPERTY_ACCESSOR(quint32, StaticCertificateVersion, staticCertificateVersion) uint32_t EntityItem::getDirtyFlags() const { diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 3fbfde6b24..79dbe1c41a 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -369,6 +369,8 @@ public: void setEntityInstanceNumber(const quint32&); QString getCertificateID() const; void setCertificateID(const QString& value); + QString getCertificateType() const; + void setCertificateType(const QString& value); quint32 getStaticCertificateVersion() const; void setStaticCertificateVersion(const quint32&); @@ -653,6 +655,7 @@ protected: QString _itemLicense { ENTITY_ITEM_DEFAULT_ITEM_LICENSE }; quint32 _limitedRun { ENTITY_ITEM_DEFAULT_LIMITED_RUN }; QString _certificateID { ENTITY_ITEM_DEFAULT_CERTIFICATE_ID }; + QString _certificateType { ENTITY_ITEM_DEFAULT_CERTIFICATE_TYPE }; quint32 _editionNumber { ENTITY_ITEM_DEFAULT_EDITION_NUMBER }; quint32 _entityInstanceNumber { ENTITY_ITEM_DEFAULT_ENTITY_INSTANCE_NUMBER }; QString _marketplaceID { ENTITY_ITEM_DEFAULT_MARKETPLACE_ID }; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 3d14206c95..18237f7de2 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -545,6 +545,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_EDITION_NUMBER, editionNumber); CHECK_PROPERTY_CHANGE(PROP_ENTITY_INSTANCE_NUMBER, entityInstanceNumber); CHECK_PROPERTY_CHANGE(PROP_CERTIFICATE_ID, certificateID); + CHECK_PROPERTY_CHANGE(PROP_CERTIFICATE_TYPE, certificateType); CHECK_PROPERTY_CHANGE(PROP_STATIC_CERTIFICATE_VERSION, staticCertificateVersion); // Location data for scripts @@ -1644,6 +1645,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EDITION_NUMBER, editionNumber); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ENTITY_INSTANCE_NUMBER, entityInstanceNumber); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CERTIFICATE_ID, certificateID); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CERTIFICATE_TYPE, certificateType); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_STATIC_CERTIFICATE_VERSION, staticCertificateVersion); // Local props for scripts @@ -2054,6 +2056,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(editionNumber, quint32, setEditionNumber); COPY_PROPERTY_FROM_QSCRIPTVALUE(entityInstanceNumber, quint32, setEntityInstanceNumber); COPY_PROPERTY_FROM_QSCRIPTVALUE(certificateID, QString, setCertificateID); + COPY_PROPERTY_FROM_QSCRIPTVALUE(certificateType, QString, setCertificateType); COPY_PROPERTY_FROM_QSCRIPTVALUE(staticCertificateVersion, quint32, setStaticCertificateVersion); // Script location data @@ -2335,6 +2338,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(editionNumber); COPY_PROPERTY_IF_CHANGED(entityInstanceNumber); COPY_PROPERTY_IF_CHANGED(certificateID); + COPY_PROPERTY_IF_CHANGED(certificateType); COPY_PROPERTY_IF_CHANGED(staticCertificateVersion); // Local props for scripts @@ -2649,6 +2653,7 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr ADD_PROPERTY_TO_MAP(PROP_EDITION_NUMBER, EditionNumber, editionNumber, quint32); ADD_PROPERTY_TO_MAP(PROP_ENTITY_INSTANCE_NUMBER, EntityInstanceNumber, entityInstanceNumber, quint32); ADD_PROPERTY_TO_MAP(PROP_CERTIFICATE_ID, CertificateID, certificateID, QString); + ADD_PROPERTY_TO_MAP(PROP_CERTIFICATE_TYPE, CertificateType, certificateType, QString); ADD_PROPERTY_TO_MAP(PROP_STATIC_CERTIFICATE_VERSION, StaticCertificateVersion, staticCertificateVersion, quint32); // Local script props @@ -3094,6 +3099,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_EDITION_NUMBER, properties.getEditionNumber()); APPEND_ENTITY_PROPERTY(PROP_ENTITY_INSTANCE_NUMBER, properties.getEntityInstanceNumber()); APPEND_ENTITY_PROPERTY(PROP_CERTIFICATE_ID, properties.getCertificateID()); + APPEND_ENTITY_PROPERTY(PROP_CERTIFICATE_TYPE, properties.getCertificateType()); APPEND_ENTITY_PROPERTY(PROP_STATIC_CERTIFICATE_VERSION, properties.getStaticCertificateVersion()); if (properties.getType() == EntityTypes::ParticleEffect) { @@ -3573,6 +3579,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EDITION_NUMBER, quint32, setEditionNumber); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ENTITY_INSTANCE_NUMBER, quint32, setEntityInstanceNumber); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CERTIFICATE_ID, QString, setCertificateID); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CERTIFICATE_TYPE, QString, setCertificateType); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_STATIC_CERTIFICATE_VERSION, quint32, setStaticCertificateVersion); if (properties.getType() == EntityTypes::ParticleEffect) { @@ -3982,6 +3989,7 @@ void EntityItemProperties::markAllChanged() { _editionNumberChanged = true; _entityInstanceNumberChanged = true; _certificateIDChanged = true; + _certificateTypeChanged = true; _staticCertificateVersionChanged = true; // Common @@ -4443,6 +4451,9 @@ QList EntityItemProperties::listChangedProperties() { if (certificateIDChanged()) { out += "certificateID"; } + if (certificateTypeChanged()) { + out += "certificateType"; + } if (staticCertificateVersionChanged()) { out += "staticCertificateVersion"; } @@ -4879,6 +4890,9 @@ QByteArray EntityItemProperties::getStaticCertificateJSON() const { if (!getAnimation().getURL().isEmpty()) { json["animationURL"] = getAnimation().getURL(); } + if (staticCertificateVersion >= 3) { + ADD_STRING_PROPERTY(certificateType, CertificateType); + } ADD_STRING_PROPERTY(collisionSoundURL, CollisionSoundURL); ADD_STRING_PROPERTY(compoundShapeURL, CompoundShapeURL); ADD_INT_PROPERTY(editionNumber, EditionNumber); diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index bc1784c93b..d030f4f2e4 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -226,6 +226,7 @@ public: DEFINE_PROPERTY_REF(PROP_EDITION_NUMBER, EditionNumber, editionNumber, quint32, ENTITY_ITEM_DEFAULT_EDITION_NUMBER); DEFINE_PROPERTY_REF(PROP_ENTITY_INSTANCE_NUMBER, EntityInstanceNumber, entityInstanceNumber, quint32, ENTITY_ITEM_DEFAULT_ENTITY_INSTANCE_NUMBER); DEFINE_PROPERTY_REF(PROP_CERTIFICATE_ID, CertificateID, certificateID, QString, ENTITY_ITEM_DEFAULT_CERTIFICATE_ID); + DEFINE_PROPERTY_REF(PROP_CERTIFICATE_TYPE, CertificateType, certificateType, QString, ENTITY_ITEM_DEFAULT_CERTIFICATE_TYPE); DEFINE_PROPERTY_REF(PROP_STATIC_CERTIFICATE_VERSION, StaticCertificateVersion, staticCertificateVersion, quint32, ENTITY_ITEM_DEFAULT_STATIC_CERTIFICATE_VERSION); // these are used when bouncing location data into and out of scripts @@ -630,6 +631,7 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, EditionNumber, editionNumber, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, EntityInstanceNumber, entityInstanceNumber, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, CertificateID, certificateID, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, CertificateType, certificateType, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, StaticCertificateVersion, staticCertificateVersion, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, LocalPosition, localPosition, ""); diff --git a/libraries/entities/src/EntityItemPropertiesDefaults.h b/libraries/entities/src/EntityItemPropertiesDefaults.h index 4d7a34bea5..be3c566724 100644 --- a/libraries/entities/src/EntityItemPropertiesDefaults.h +++ b/libraries/entities/src/EntityItemPropertiesDefaults.h @@ -41,6 +41,7 @@ const QString ENTITY_ITEM_DEFAULT_MARKETPLACE_ID = QString(""); const quint32 ENTITY_ITEM_DEFAULT_EDITION_NUMBER = 0; const quint32 ENTITY_ITEM_DEFAULT_ENTITY_INSTANCE_NUMBER = 0; const QString ENTITY_ITEM_DEFAULT_CERTIFICATE_ID = QString(""); +const QString ENTITY_ITEM_DEFAULT_CERTIFICATE_TYPE = QString(""); const quint32 ENTITY_ITEM_DEFAULT_STATIC_CERTIFICATE_VERSION = 0; const glm::u8vec3 ENTITY_ITEM_DEFAULT_COLOR = { 255, 255, 255 }; diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 6d1c3a1df8..969fd007f1 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -98,6 +98,7 @@ enum EntityPropertyList { PROP_EDITION_NUMBER, PROP_ENTITY_INSTANCE_NUMBER, PROP_CERTIFICATE_ID, + PROP_CERTIFICATE_TYPE, PROP_STATIC_CERTIFICATE_VERSION, // Used to convert values to and from scripts diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index d4f15fa8b2..619cd9781f 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -42,6 +42,7 @@ static const quint64 DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER = USECS_PER_MSEC * 50; const float EntityTree::DEFAULT_MAX_TMP_ENTITY_LIFETIME = 60 * 60; // 1 hour +static const QString DOMAIN_UNLIMITED = "domainUnlimitied"; EntityTree::EntityTree(bool shouldReaverage) : Octree(shouldReaverage) @@ -292,7 +293,7 @@ void EntityTree::postAddEntity(EntityItemPointer entity) { { QWriteLocker locker(&_entityCertificateIDMapLock); existingEntityItemID = _entityCertificateIDMap.value(certID); - if (!certID.isEmpty()) { + if (!certID.isEmpty() && !entity->getCertificateType().contains(DOMAIN_UNLIMITED)) { _entityCertificateIDMap.insert(certID, entityItemID); qCDebug(entities) << "Certificate ID" << certID << "belongs to" << entityItemID; } @@ -1870,7 +1871,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c failedAdd = true; qCDebug(entities) << "User without 'certified rez rights' [" << senderNode->getUUID() << "] attempted to add a certified entity with ID:" << entityItemID; - } else if (isClone && isCertified) { + } else if (isClone && isCertified && !properties.getCertificateType().contains(DOMAIN_UNLIMITED)) { failedAdd = true; qCDebug(entities) << "User attempted to clone certified entity from entity ID:" << entityIDToClone; } else if (isClone && !isCloneable) { diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 1489f8e16c..0ec7c40ca4 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -265,6 +265,7 @@ enum class EntityVersion : PacketVersion { WebBillboardMode, ModelScale, ReOrderParentIDProperties, + CertificateTypeProperty, // Add new versions above here NUM_PACKET_TYPE, diff --git a/scripts/system/libraries/cloneEntityUtils.js b/scripts/system/libraries/cloneEntityUtils.js index 5381d39116..e0f4aba84a 100644 --- a/scripts/system/libraries/cloneEntityUtils.js +++ b/scripts/system/libraries/cloneEntityUtils.js @@ -48,10 +48,11 @@ propsAreCloneDynamic = function(props) { cloneEntity = function(props) { var entityToClone = props.id; - var certificateID = Entities.getEntityProperties(entityToClone, ['certificateID']).certificateID; + var props = Entities.getEntityProperties(entityToClone, ['certificateID', 'certificateType']) + var certificateID = props.certificateID; // ensure entity is cloneable and does not have a certificate ID, whereas cloneable limits // will now be handled by the server where the entity add will fail if limit reached - if (entityIsCloneable(props) && (certificateID === undefined || certificateID.length === 0)) { + if (entityIsCloneable(props) && (!!certificateID || props.certificateType.indexOf('domainUnlimited') >= 0)) { var cloneID = Entities.cloneEntity(entityToClone); return cloneID; } From 9ca38054999b581323d0c7da416e5d0a9d72007e Mon Sep 17 00:00:00 2001 From: danteruiz Date: Thu, 7 Mar 2019 10:27:16 -0800 Subject: [PATCH 380/474] making requested changes --- interface/src/raypick/StylusPick.cpp | 2 +- scripts/system/libraries/WebTablet.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/interface/src/raypick/StylusPick.cpp b/interface/src/raypick/StylusPick.cpp index 2bd371c506..9139a16c6d 100644 --- a/interface/src/raypick/StylusPick.cpp +++ b/interface/src/raypick/StylusPick.cpp @@ -160,7 +160,7 @@ PickResultPointer StylusPick::getEntityIntersection(const StylusTip& pick) { // TODO: Use the xz projection method for Sphere and Quad. if (entityType == EntityTypes::Gizmo) { - normal = entityRotation * Vectors::UNIT_NEG_Y; + normal = entityRotation * Vectors::UNIT_Y; } else { normal = entityRotation * Vectors::UNIT_Z; } diff --git a/scripts/system/libraries/WebTablet.js b/scripts/system/libraries/WebTablet.js index c5ae9730f2..ceb7d8e77e 100644 --- a/scripts/system/libraries/WebTablet.js +++ b/scripts/system/libraries/WebTablet.js @@ -176,10 +176,10 @@ WebTablet = function (url, width, dpi, hand, location, visible) { this.homeButtonID = Overlays.addOverlay("circle3d", { name: "homeButton", localPosition: { x: HOME_BUTTON_X_OFFSET, y: HOME_BUTTON_Y_OFFSET, z: -HOME_BUTTON_Z_OFFSET }, - localRotation: { x: 0, y: 1, z: 0, w: 0}, + localRotation: Quat.fromVec3Degrees({ x: 180, y: 180, z: 0}), dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim }, solid: true, - alpha: 0.0, + alpha: 1.0, visible: visible, drawInFront: false, parentID: this.tabletEntityID, @@ -189,7 +189,7 @@ WebTablet = function (url, width, dpi, hand, location, visible) { this.homeButtonHighlightID = Overlays.addOverlay("circle3d", { name: "homeButtonHighlight", localPosition: { x: -HOME_BUTTON_X_OFFSET, y: HOME_BUTTON_Y_OFFSET, z: -HOME_BUTTON_Z_OFFSET }, - localRotation: { x: 0, y: 1, z: 0, w: 0}, + localRotation: { x: 0, y: -1, z: 0, w: 0}, dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim }, color: {red: 255, green: 255, blue: 255}, solid: true, From 05dc30a740270c176b51809b6d346415fd733710 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Thu, 7 Mar 2019 10:48:47 -0800 Subject: [PATCH 381/474] more requested changes --- scripts/system/libraries/WebTablet.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/system/libraries/WebTablet.js b/scripts/system/libraries/WebTablet.js index ceb7d8e77e..9d333a1ae4 100644 --- a/scripts/system/libraries/WebTablet.js +++ b/scripts/system/libraries/WebTablet.js @@ -179,7 +179,7 @@ WebTablet = function (url, width, dpi, hand, location, visible) { localRotation: Quat.fromVec3Degrees({ x: 180, y: 180, z: 0}), dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim }, solid: true, - alpha: 1.0, + alpha: 0.0, visible: visible, drawInFront: false, parentID: this.tabletEntityID, @@ -189,7 +189,7 @@ WebTablet = function (url, width, dpi, hand, location, visible) { this.homeButtonHighlightID = Overlays.addOverlay("circle3d", { name: "homeButtonHighlight", localPosition: { x: -HOME_BUTTON_X_OFFSET, y: HOME_BUTTON_Y_OFFSET, z: -HOME_BUTTON_Z_OFFSET }, - localRotation: { x: 0, y: -1, z: 0, w: 0}, + localRotation: Quat.fromVec3Degrees({ x: 180, y: 180, z: 0}), dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim }, color: {red: 255, green: 255, blue: 255}, solid: true, From 36f62f05d1b8d96199419aacab89c204bee6a58d Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Thu, 7 Mar 2019 11:31:28 -0800 Subject: [PATCH 382/474] Add pushToTalk.js controllerModule. --- .../controllerModules/pushToTalk.js | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 scripts/system/controllers/controllerModules/pushToTalk.js diff --git a/scripts/system/controllers/controllerModules/pushToTalk.js b/scripts/system/controllers/controllerModules/pushToTalk.js new file mode 100644 index 0000000000..e764b228c9 --- /dev/null +++ b/scripts/system/controllers/controllerModules/pushToTalk.js @@ -0,0 +1,75 @@ +"use strict"; + +// Created by Jason C. Najera on 3/7/2019 +// Copyright 2019 High Fidelity, Inc. +// +// Handles Push-to-Talk functionality for HMD mode. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +Script.include("/~/system/libraries/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/controllers.js"); + +(function() { // BEGIN LOCAL_SCOPE + function PushToTalkHandler() { + var _this = this; + this.active = false; + //var pttMapping, mappingName; + + this.setup = function() { + //mappingName = 'Hifi-PTT-Dev-' + Math.random(); + //pttMapping = Controller.newMapping(mappingName); + //pttMapping.enable(); + }; + + this.shouldTalk = function (controllerData) { + // Set up test against controllerData here... + var gripVal = controllerData.secondaryValues[this.hand]; + return (gripVal) ? true : false; + }; + + this.shouldStopTalking = function (controllerData) { + var gripVal = controllerData.secondaryValues[this.hand]; + return (gripVal) ? false : true; + }; + + this.isReady = function (controllerData, deltaTime) { + if (HMD.active() && Audio.pushToTalk && this.shouldTalk(controllerData)) { + Audio.pushingToTalk = true; + returnMakeRunningValues(true, [], []); + } + + return makeRunningValues(false, [], []); + }; + + this.run = function (controllerData, deltaTime) { + if (this.shouldStopTalking(controllerData) || !Audio.pushToTalk) { + Audio.pushingToTalk = false; + return makeRunningValues(false, [], []); + } + + return makeRunningValues(true, [], []); + }; + + this.cleanup = function () { + //pttMapping.disable(); + }; + + this.parameters = makeDispatcherModuleParameters( + 950, + ["head"], + [], + 100); + } + + var pushToTalk = new PushToTalkHandler(); + enableDispatcherModule("PushToTalk", pushToTalk); + + function cleanup () { + pushToTalk.cleanup(); + disableDispatcherModule("PushToTalk"); + }; + + Script.scriptEnding.connect(cleanup); +}()); // END LOCAL_SCOPE \ No newline at end of file From f444f383eba17e47a39032411ce08b33d6df2b57 Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Thu, 7 Mar 2019 11:31:48 -0800 Subject: [PATCH 383/474] Enable pushToTalk.js controller module. --- scripts/system/controllers/controllerDispatcher.js | 1 + scripts/system/controllers/controllerScripts.js | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 28c3e2a299..f4c0cbb0d6 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -58,6 +58,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); // not set to false (not in use), a module cannot start. When a module is using a slot, that module's name // is stored as the value, rather than false. this.activitySlots = { + head: false, leftHand: false, rightHand: false, rightHandTrigger: false, diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index 86ff7701c3..d236d6b12a 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -34,7 +34,8 @@ var CONTOLLER_SCRIPTS = [ "controllerModules/nearGrabHyperLinkEntity.js", "controllerModules/nearTabletHighlight.js", "controllerModules/nearGrabEntity.js", - "controllerModules/farGrabEntity.js" + "controllerModules/farGrabEntity.js", + "controllerModules/pushToTalk.js" ]; var DEBUG_MENU_ITEM = "Debug defaultScripts.js"; From d8478e0b17d56500ff1fd4debdd322fbeec77a4c Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Tue, 5 Mar 2019 17:09:54 -0800 Subject: [PATCH 384/474] Initial implementation (deadlocks still occurring in Audio.cpp) --- interface/resources/qml/hifi/audio/Audio.qml | 19 ++ interface/resources/qml/hifi/audio/MicBar.qml | 9 +- interface/src/Application.cpp | 18 ++ interface/src/Application.h | 2 + interface/src/scripting/Audio.cpp | 174 +++++++++++++++++- interface/src/scripting/Audio.h | 89 ++++++++- scripts/system/audio.js | 3 + 7 files changed, 302 insertions(+), 12 deletions(-) diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index c8dd83cd62..45358f59a2 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -148,6 +148,25 @@ Rectangle { checked = Qt.binding(function() { return AudioScriptingInterface.noiseReduction; }); // restore binding } } + AudioControls.CheckBox { + spacing: muteMic.spacing + text: qsTr("Push To Talk"); + checked: isVR ? AudioScriptingInterface.pushToTalkHMD : AudioScriptingInterface.pushToTalkDesktop; + onClicked: { + if (isVR) { + AudioScriptingInterface.pushToTalkHMD = checked; + } else { + AudioScriptingInterface.pushToTalkDesktop = checked; + } + checked = Qt.binding(function() { + if (isVR) { + return AudioScriptingInterface.pushToTalkHMD; + } else { + return AudioScriptingInterface.pushToTalkDesktop; + } + }); // restore binding + } + } AudioControls.CheckBox { spacing: muteMic.spacing text: qsTr("Show audio level meter"); diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index 39f75a9182..f91058bc3c 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -11,10 +11,13 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 +import stylesUit 1.0 import TabletScriptingInterface 1.0 Rectangle { + HifiConstants { id: hifi; } + readonly property var level: AudioScriptingInterface.inputLevel; property bool gated: false; @@ -131,9 +134,9 @@ Rectangle { Item { id: status; - readonly property string color: AudioScriptingInterface.muted ? colors.muted : colors.unmuted; + readonly property string color: (AudioScriptingInterface.pushingToTalk && AudioScriptingInterface.muted) ? hifi.colors.blueHighlight : AudioScriptingInterface.muted ? colors.muted : colors.unmuted; - visible: AudioScriptingInterface.muted; + visible: AudioScriptingInterface.pushingToTalk || AudioScriptingInterface.muted; anchors { left: parent.left; @@ -152,7 +155,7 @@ Rectangle { color: parent.color; - text: AudioScriptingInterface.muted ? "MUTED" : "MUTE"; + text: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? "SPEAKING" : "PUSH TO TALK") : (AudioScriptingInterface.muted ? "MUTED" : "MUTE"); font.pointSize: 12; } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index b8ab4d10db..fa63757560 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1435,6 +1435,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo }); connect(this, &Application::activeDisplayPluginChanged, reinterpret_cast(audioScriptingInterface.data()), &scripting::Audio::onContextChanged); + connect(this, &Application::pushedToTalk, + reinterpret_cast(audioScriptingInterface.data()), &scripting::Audio::handlePushedToTalk); } // Create the rendering engine. This can be slow on some machines due to lots of @@ -4199,6 +4201,10 @@ void Application::keyPressEvent(QKeyEvent* event) { } break; + case Qt::Key_T: + emit pushedToTalk(true); + break; + case Qt::Key_P: { if (!isShifted && !isMeta && !isOption && !event->isAutoRepeat()) { AudioInjectorOptions options; @@ -4304,6 +4310,12 @@ void Application::keyReleaseEvent(QKeyEvent* event) { if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->keyReleaseEvent(event); } + + switch (event->key()) { + case Qt::Key_T: + emit pushedToTalk(false); + break; + } } void Application::focusOutEvent(QFocusEvent* event) { @@ -5235,6 +5247,9 @@ void Application::loadSettings() { } } + auto audioScriptingInterface = reinterpret_cast(DependencyManager::get().data()); + audioScriptingInterface->loadData(); + getMyAvatar()->loadData(); _settingsLoaded = true; } @@ -5244,6 +5259,9 @@ void Application::saveSettings() const { DependencyManager::get()->saveSettings(); DependencyManager::get()->saveSettings(); + auto audioScriptingInterface = reinterpret_cast(DependencyManager::get().data()); + audioScriptingInterface->saveData(); + Menu::getInstance()->saveSettings(); getMyAvatar()->saveData(); PluginManager::getInstance()->saveSettings(); diff --git a/interface/src/Application.h b/interface/src/Application.h index a8cc9450c5..1c86326f90 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -358,6 +358,8 @@ signals: void miniTabletEnabledChanged(bool enabled); + void pushedToTalk(bool enabled); + public slots: QVector pasteEntities(float x, float y, float z); bool exportEntities(const QString& filename, const QVector& entityIDs, const glm::vec3* givenOffset = nullptr); diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 2c4c29ff65..fe04ce47ca 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -63,26 +63,163 @@ void Audio::stopRecording() { } bool Audio::isMuted() const { - return resultWithReadLock([&] { - return _isMuted; - }); + bool isHMD = qApp->isHMDMode(); + if (isHMD) { + return getMutedHMD(); + } + else { + return getMutedDesktop(); + } } void Audio::setMuted(bool isMuted) { + withWriteLock([&] { + bool isHMD = qApp->isHMDMode(); + if (isHMD) { + setMutedHMD(isMuted); + } + else { + setMutedDesktop(isMuted); + } + }); +} + +void Audio::setMutedDesktop(bool isMuted) { + bool changed = false; + if (_desktopMuted != isMuted) { + changed = true; + _desktopMuted = isMuted; + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + } + if (changed) { + emit mutedChanged(isMuted); + emit desktopMutedChanged(isMuted); + } +} + +bool Audio::getMutedDesktop() const { + return resultWithReadLock([&] { + return _desktopMuted; + }); +} + +void Audio::setMutedHMD(bool isMuted) { + bool changed = false; + if (_hmdMuted != isMuted) { + changed = true; + _hmdMuted = isMuted; + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + } + if (changed) { + emit mutedChanged(isMuted); + emit hmdMutedChanged(isMuted); + } +} + +bool Audio::getMutedHMD() const { + return resultWithReadLock([&] { + return _hmdMuted; + }); +} + +bool Audio::getPTT() { + bool isHMD = qApp->isHMDMode(); + if (isHMD) { + return getPTTHMD(); + } + else { + return getPTTDesktop(); + } +} + +bool Audio::getPushingToTalk() const { + return resultWithReadLock([&] { + return _pushingToTalk; + }); +} + +void Audio::setPTT(bool enabled) { + bool isHMD = qApp->isHMDMode(); + if (isHMD) { + setPTTHMD(enabled); + } + else { + setPTTDesktop(enabled); + } +} + +void Audio::setPTTDesktop(bool enabled) { bool changed = false; withWriteLock([&] { - if (_isMuted != isMuted) { - _isMuted = isMuted; - auto client = DependencyManager::get().data(); - QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + if (_pttDesktop != enabled) { changed = true; + _pttDesktop = enabled; + if (!enabled) { + // Set to default behavior (unmuted for Desktop) on Push-To-Talk disable. + setMutedDesktop(true); + } + else { + // Should be muted when not pushing to talk while PTT is enabled. + setMutedDesktop(true); + } } }); if (changed) { - emit mutedChanged(isMuted); + emit pushToTalkChanged(enabled); + emit pushToTalkDesktopChanged(enabled); } } +bool Audio::getPTTDesktop() const { + return resultWithReadLock([&] { + return _pttDesktop; + }); +} + +void Audio::setPTTHMD(bool enabled) { + bool changed = false; + withWriteLock([&] { + if (_pttHMD != enabled) { + changed = true; + _pttHMD = enabled; + if (!enabled) { + // Set to default behavior (unmuted for HMD) on Push-To-Talk disable. + setMutedHMD(false); + } + else { + // Should be muted when not pushing to talk while PTT is enabled. + setMutedHMD(true); + } + } + }); + if (changed) { + emit pushToTalkChanged(enabled); + emit pushToTalkHMDChanged(enabled); + } +} + +bool Audio::getPTTHMD() const { + return resultWithReadLock([&] { + return _pttHMD; + }); +} + +void Audio::saveData() { + _desktopMutedSetting.set(getMutedDesktop()); + _hmdMutedSetting.set(getMutedHMD()); + _pttDesktopSetting.set(getPTTDesktop()); + _pttHMDSetting.set(getPTTHMD()); +} + +void Audio::loadData() { + _desktopMuted = _desktopMutedSetting.get(); + _hmdMuted = _hmdMutedSetting.get(); + _pttDesktop = _pttDesktopSetting.get(); + _pttHMD = _pttHMDSetting.get(); +} + bool Audio::noiseReductionEnabled() const { return resultWithReadLock([&] { return _enableNoiseReduction; @@ -179,11 +316,32 @@ void Audio::onContextChanged() { changed = true; } }); + if (isHMD) { + setMuted(getMutedHMD()); + } + else { + setMuted(getMutedDesktop()); + } if (changed) { emit contextChanged(isHMD ? Audio::HMD : Audio::DESKTOP); } } +void Audio::handlePushedToTalk(bool enabled) { + if (getPTT()) { + if (enabled) { + setMuted(false); + } + else { + setMuted(true); + } + if (_pushingToTalk != enabled) { + _pushingToTalk = enabled; + emit pushingToTalkChanged(enabled); + } + } +} + void Audio::setReverb(bool enable) { withWriteLock([&] { DependencyManager::get()->setReverb(enable); diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index e4dcba9130..6aa589e399 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -12,6 +12,7 @@ #ifndef hifi_scripting_Audio_h #define hifi_scripting_Audio_h +#include #include "AudioScriptingInterface.h" #include "AudioDevices.h" #include "AudioEffectOptions.h" @@ -19,6 +20,9 @@ #include "AudioFileWav.h" #include +using MutedGetter = std::function; +using MutedSetter = std::function; + namespace scripting { class Audio : public AudioScriptingInterface, protected ReadWriteLockable { @@ -63,6 +67,12 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { Q_PROPERTY(bool clipping READ isClipping NOTIFY clippingChanged) Q_PROPERTY(QString context READ getContext NOTIFY contextChanged) Q_PROPERTY(AudioDevices* devices READ getDevices NOTIFY nop) + Q_PROPERTY(bool desktopMuted READ getMutedDesktop WRITE setMutedDesktop NOTIFY desktopMutedChanged) + Q_PROPERTY(bool hmdMuted READ getMutedHMD WRITE setMutedHMD NOTIFY hmdMutedChanged) + Q_PROPERTY(bool pushToTalk READ getPTT WRITE setPTT NOTIFY pushToTalkChanged); + Q_PROPERTY(bool pushToTalkDesktop READ getPTTDesktop WRITE setPTTDesktop NOTIFY pushToTalkDesktopChanged) + Q_PROPERTY(bool pushToTalkHMD READ getPTTHMD WRITE setPTTHMD NOTIFY pushToTalkHMDChanged) + Q_PROPERTY(bool pushingToTalk READ getPushingToTalk NOTIFY pushingToTalkChanged) public: static QString AUDIO; @@ -82,6 +92,25 @@ public: void showMicMeter(bool show); + // Mute setting setters and getters + void setMutedDesktop(bool isMuted); + bool getMutedDesktop() const; + void setMutedHMD(bool isMuted); + bool getMutedHMD() const; + void setPTT(bool enabled); + bool getPTT(); + bool getPushingToTalk() const; + + // Push-To-Talk setters and getters + void setPTTDesktop(bool enabled); + bool getPTTDesktop() const; + void setPTTHMD(bool enabled); + bool getPTTHMD() const; + + // Settings handlers + void saveData(); + void loadData(); + /**jsdoc * @function Audio.setInputDevice * @param {object} device @@ -193,6 +222,46 @@ signals: */ void mutedChanged(bool isMuted); + /**jsdoc + * Triggered when desktop audio input is muted or unmuted. + * @function Audio.desktopMutedChanged + * @param {boolean} isMuted - true if the audio input is muted for desktop mode, otherwise false. + * @returns {Signal} + */ + void desktopMutedChanged(bool isMuted); + + /**jsdoc + * Triggered when HMD audio input is muted or unmuted. + * @function Audio.hmdMutedChanged + * @param {boolean} isMuted - true if the audio input is muted for HMD mode, otherwise false. + * @returns {Signal} + */ + void hmdMutedChanged(bool isMuted); + + /** + * Triggered when Push-to-Talk has been enabled or disabled. + * @function Audio.pushToTalkChanged + * @param {boolean} enabled - true if Push-to-Talk is enabled, otherwise false. + * @returns {Signal} + */ + void pushToTalkChanged(bool enabled); + + /** + * Triggered when Push-to-Talk has been enabled or disabled for desktop mode. + * @function Audio.pushToTalkDesktopChanged + * @param {boolean} enabled - true if Push-to-Talk is emabled for Desktop mode, otherwise false. + * @returns {Signal} + */ + void pushToTalkDesktopChanged(bool enabled); + + /** + * Triggered when Push-to-Talk has been enabled or disabled for HMD mode. + * @function Audio.pushToTalkHMDChanged + * @param {boolean} enabled - true if Push-to-Talk is emabled for HMD mode, otherwise false. + * @returns {Signal} + */ + void pushToTalkHMDChanged(bool enabled); + /**jsdoc * Triggered when the audio input noise reduction is enabled or disabled. * @function Audio.noiseReductionChanged @@ -237,6 +306,14 @@ signals: */ void contextChanged(const QString& context); + /**jsdoc + * Triggered when pushing to talk. + * @function Audio.pushingToTalkChanged + * @param {boolean} talking - true if broadcasting with PTT, false otherwise. + * @returns {Signal} + */ + void pushingToTalkChanged(bool talking); + public slots: /**jsdoc @@ -245,6 +322,8 @@ public slots: */ void onContextChanged(); + void handlePushedToTalk(bool enabled); + private slots: void setMuted(bool muted); void enableNoiseReduction(bool enable); @@ -260,11 +339,19 @@ private: float _inputVolume { 1.0f }; float _inputLevel { 0.0f }; bool _isClipping { false }; - bool _isMuted { false }; bool _enableNoiseReduction { true }; // Match default value of AudioClient::_isNoiseGateEnabled. bool _contextIsHMD { false }; AudioDevices* getDevices() { return &_devices; } AudioDevices _devices; + Setting::Handle _desktopMutedSetting{ QStringList { Audio::AUDIO, "desktopMuted" }, true }; + Setting::Handle _hmdMutedSetting{ QStringList { Audio::AUDIO, "hmdMuted" }, true }; + Setting::Handle _pttDesktopSetting{ QStringList { Audio::AUDIO, "pushToTalkDesktop" }, false }; + Setting::Handle _pttHMDSetting{ QStringList { Audio::AUDIO, "pushToTalkHMD" }, false }; + bool _desktopMuted{ true }; + bool _hmdMuted{ false }; + bool _pttDesktop{ false }; + bool _pttHMD{ false }; + bool _pushingToTalk{ false }; }; }; diff --git a/scripts/system/audio.js b/scripts/system/audio.js index ee82c0c6ea..51d070d8cd 100644 --- a/scripts/system/audio.js +++ b/scripts/system/audio.js @@ -28,6 +28,9 @@ var UNMUTE_ICONS = { }; function onMuteToggled() { + if (Audio.pushingToTalk) { + return; + } if (Audio.muted) { button.editProperties(MUTE_ICONS); } else { From eeb900b76165b0277ef17435d5e8c774ce85d9b5 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 6 Mar 2019 11:00:09 -0800 Subject: [PATCH 385/474] laying groundwork for audio app + fixing deadlocks --- interface/resources/qml/hifi/audio/MicBar.qml | 6 +- interface/src/scripting/Audio.cpp | 108 +++++++++--------- interface/src/scripting/Audio.h | 1 + scripts/system/audio.js | 11 +- 4 files changed, 69 insertions(+), 57 deletions(-) diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index f91058bc3c..2ab1085408 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -134,7 +134,7 @@ Rectangle { Item { id: status; - readonly property string color: (AudioScriptingInterface.pushingToTalk && AudioScriptingInterface.muted) ? hifi.colors.blueHighlight : AudioScriptingInterface.muted ? colors.muted : colors.unmuted; + readonly property string color: AudioScriptingInterface.pushToTalk ? hifi.colors.blueHighlight : AudioScriptingInterface.muted ? colors.muted : colors.unmuted; visible: AudioScriptingInterface.pushingToTalk || AudioScriptingInterface.muted; @@ -165,7 +165,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - width: 50; + width: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? 45: 30) : 50; height: 4; color: parent.color; } @@ -176,7 +176,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - width: 50; + width: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? 45: 30) : 50; height: 4; color: parent.color; } diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index fe04ce47ca..63ce9d2b2e 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -66,32 +66,30 @@ bool Audio::isMuted() const { bool isHMD = qApp->isHMDMode(); if (isHMD) { return getMutedHMD(); - } - else { + } else { return getMutedDesktop(); } } void Audio::setMuted(bool isMuted) { - withWriteLock([&] { - bool isHMD = qApp->isHMDMode(); - if (isHMD) { - setMutedHMD(isMuted); - } - else { - setMutedDesktop(isMuted); - } - }); + bool isHMD = qApp->isHMDMode(); + if (isHMD) { + setMutedHMD(isMuted); + } else { + setMutedDesktop(isMuted); + } } void Audio::setMutedDesktop(bool isMuted) { bool changed = false; - if (_desktopMuted != isMuted) { - changed = true; - _desktopMuted = isMuted; - auto client = DependencyManager::get().data(); - QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); - } + withWriteLock([&] { + if (_desktopMuted != isMuted) { + changed = true; + _desktopMuted = isMuted; + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + } + }); if (changed) { emit mutedChanged(isMuted); emit desktopMutedChanged(isMuted); @@ -106,12 +104,14 @@ bool Audio::getMutedDesktop() const { void Audio::setMutedHMD(bool isMuted) { bool changed = false; - if (_hmdMuted != isMuted) { - changed = true; - _hmdMuted = isMuted; - auto client = DependencyManager::get().data(); - QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); - } + withWriteLock([&] { + if (_hmdMuted != isMuted) { + changed = true; + _hmdMuted = isMuted; + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + } + }); if (changed) { emit mutedChanged(isMuted); emit hmdMutedChanged(isMuted); @@ -128,12 +128,24 @@ bool Audio::getPTT() { bool isHMD = qApp->isHMDMode(); if (isHMD) { return getPTTHMD(); - } - else { + } else { return getPTTDesktop(); } } +void scripting::Audio::setPushingToTalk(bool pushingToTalk) { + bool changed = false; + withWriteLock([&] { + if (_pushingToTalk != pushingToTalk) { + changed = true; + _pushingToTalk = pushingToTalk; + } + }); + if (changed) { + emit pushingToTalkChanged(pushingToTalk); + } +} + bool Audio::getPushingToTalk() const { return resultWithReadLock([&] { return _pushingToTalk; @@ -144,8 +156,7 @@ void Audio::setPTT(bool enabled) { bool isHMD = qApp->isHMDMode(); if (isHMD) { setPTTHMD(enabled); - } - else { + } else { setPTTDesktop(enabled); } } @@ -156,16 +167,16 @@ void Audio::setPTTDesktop(bool enabled) { if (_pttDesktop != enabled) { changed = true; _pttDesktop = enabled; - if (!enabled) { - // Set to default behavior (unmuted for Desktop) on Push-To-Talk disable. - setMutedDesktop(true); - } - else { - // Should be muted when not pushing to talk while PTT is enabled. - setMutedDesktop(true); - } } }); + if (!enabled) { + // Set to default behavior (unmuted for Desktop) on Push-To-Talk disable. + setMutedDesktop(true); + } else { + // Should be muted when not pushing to talk while PTT is enabled. + setMutedDesktop(true); + } + if (changed) { emit pushToTalkChanged(enabled); emit pushToTalkDesktopChanged(enabled); @@ -184,16 +195,16 @@ void Audio::setPTTHMD(bool enabled) { if (_pttHMD != enabled) { changed = true; _pttHMD = enabled; - if (!enabled) { - // Set to default behavior (unmuted for HMD) on Push-To-Talk disable. - setMutedHMD(false); - } - else { - // Should be muted when not pushing to talk while PTT is enabled. - setMutedHMD(true); - } } }); + if (!enabled) { + // Set to default behavior (unmuted for HMD) on Push-To-Talk disable. + setMutedHMD(false); + } else { + // Should be muted when not pushing to talk while PTT is enabled. + setMutedHMD(true); + } + if (changed) { emit pushToTalkChanged(enabled); emit pushToTalkHMDChanged(enabled); @@ -318,8 +329,7 @@ void Audio::onContextChanged() { }); if (isHMD) { setMuted(getMutedHMD()); - } - else { + } else { setMuted(getMutedDesktop()); } if (changed) { @@ -331,14 +341,10 @@ void Audio::handlePushedToTalk(bool enabled) { if (getPTT()) { if (enabled) { setMuted(false); - } - else { + } else { setMuted(true); } - if (_pushingToTalk != enabled) { - _pushingToTalk = enabled; - emit pushingToTalkChanged(enabled); - } + setPushingToTalk(enabled); } } diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index 6aa589e399..94f8a7bf54 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -99,6 +99,7 @@ public: bool getMutedHMD() const; void setPTT(bool enabled); bool getPTT(); + void setPushingToTalk(bool pushingToTalk); bool getPushingToTalk() const; // Push-To-Talk setters and getters diff --git a/scripts/system/audio.js b/scripts/system/audio.js index 51d070d8cd..bf44cfa7cc 100644 --- a/scripts/system/audio.js +++ b/scripts/system/audio.js @@ -26,12 +26,15 @@ var UNMUTE_ICONS = { icon: "icons/tablet-icons/mic-unmute-i.svg", activeIcon: "icons/tablet-icons/mic-unmute-a.svg" }; +var PTT_ICONS = { + icon: "icons/tablet-icons/mic-unmute-i.svg", + activeIcon: "icons/tablet-icons/mic-unmute-a.svg" +}; function onMuteToggled() { if (Audio.pushingToTalk) { - return; - } - if (Audio.muted) { + button.editProperties(PTT_ICONS); + } else if (Audio.muted) { button.editProperties(MUTE_ICONS); } else { button.editProperties(UNMUTE_ICONS); @@ -71,6 +74,7 @@ onMuteToggled(); button.clicked.connect(onClicked); tablet.screenChanged.connect(onScreenChanged); Audio.mutedChanged.connect(onMuteToggled); +Audio.pushingToTalkChanged.connect(onMuteToggled); Script.scriptEnding.connect(function () { if (onAudioScreen) { @@ -79,6 +83,7 @@ Script.scriptEnding.connect(function () { button.clicked.disconnect(onClicked); tablet.screenChanged.disconnect(onScreenChanged); Audio.mutedChanged.disconnect(onMuteToggled); + Audio.pushingToTalkChanged.disconnect(onMuteToggled); tablet.removeButton(button); }); From 066d1797cf6f96c722274b18b33d4010ed60405a Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 6 Mar 2019 11:11:47 -0800 Subject: [PATCH 386/474] Push to talk changes + rebased with master (#7) Push to talk changes + rebased with master --- interface/src/scripting/Audio.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 63ce9d2b2e..45bb15f1a3 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -231,6 +231,26 @@ void Audio::loadData() { _pttHMD = _pttHMDSetting.get(); } +bool Audio::getPTTHMD() const { + return resultWithReadLock([&] { + return _pttHMD; + }); +} + +void Audio::saveData() { + _desktopMutedSetting.set(getMutedDesktop()); + _hmdMutedSetting.set(getMutedHMD()); + _pttDesktopSetting.set(getPTTDesktop()); + _pttHMDSetting.set(getPTTHMD()); +} + +void Audio::loadData() { + _desktopMuted = _desktopMutedSetting.get(); + _hmdMuted = _hmdMutedSetting.get(); + _pttDesktop = _pttDesktopSetting.get(); + _pttHMD = _pttHMDSetting.get(); +} + bool Audio::noiseReductionEnabled() const { return resultWithReadLock([&] { return _enableNoiseReduction; From 9613cfb6b9da73248c95151ddc15d002b85a87b2 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 6 Mar 2019 11:18:57 -0800 Subject: [PATCH 387/474] changing text display --- interface/resources/qml/hifi/audio/MicBar.qml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index 2ab1085408..9d1cbfbc6c 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -134,9 +134,9 @@ Rectangle { Item { id: status; - readonly property string color: AudioScriptingInterface.pushToTalk ? hifi.colors.blueHighlight : AudioScriptingInterface.muted ? colors.muted : colors.unmuted; + readonly property string color: AudioScriptingInterface.muted ? colors.muted : colors.unmuted; - visible: AudioScriptingInterface.pushingToTalk || AudioScriptingInterface.muted; + visible: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) || AudioScriptingInterface.muted; anchors { left: parent.left; @@ -155,7 +155,7 @@ Rectangle { color: parent.color; - text: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? "SPEAKING" : "PUSH TO TALK") : (AudioScriptingInterface.muted ? "MUTED" : "MUTE"); + text: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? "MUTED-PTT (T)" : (AudioScriptingInterface.muted ? "MUTED" : "MUTE"); font.pointSize: 12; } @@ -165,7 +165,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - width: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? 45: 30) : 50; + width: AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk ? 25 : 50; height: 4; color: parent.color; } @@ -176,7 +176,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - width: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? 45: 30) : 50; + width: AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk ? 25 : 50; height: 4; color: parent.color; } From 0cb9e7eb3609189d305df8cb700c5225e801b951 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 6 Mar 2019 11:45:43 -0800 Subject: [PATCH 388/474] exposing setting pushingToTalk --- interface/src/scripting/Audio.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index 94f8a7bf54..9ad4aac9c1 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -72,7 +72,7 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { Q_PROPERTY(bool pushToTalk READ getPTT WRITE setPTT NOTIFY pushToTalkChanged); Q_PROPERTY(bool pushToTalkDesktop READ getPTTDesktop WRITE setPTTDesktop NOTIFY pushToTalkDesktopChanged) Q_PROPERTY(bool pushToTalkHMD READ getPTTHMD WRITE setPTTHMD NOTIFY pushToTalkHMDChanged) - Q_PROPERTY(bool pushingToTalk READ getPushingToTalk NOTIFY pushingToTalkChanged) + Q_PROPERTY(bool pushingToTalk READ getPushingToTalk WRITE setPushingToTalk NOTIFY pushingToTalkChanged) public: static QString AUDIO; From 7b197c975c9b5926e48cb2d2847e490d8f9ad0d8 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 6 Mar 2019 17:29:32 -0800 Subject: [PATCH 389/474] updating compile failure + icons/settings update --- .../icons/tablet-icons/mic-ptt-a.svg | 1 + .../icons/tablet-icons/mic-ptt-i.svg | 24 +++++++ interface/resources/qml/hifi/audio/Audio.qml | 64 ++++++++++++++++--- interface/resources/qml/hifi/audio/MicBar.qml | 8 ++- interface/src/scripting/Audio.cpp | 20 ------ scripts/system/audio.js | 14 ++-- 6 files changed, 93 insertions(+), 38 deletions(-) create mode 100644 interface/resources/icons/tablet-icons/mic-ptt-a.svg create mode 100644 interface/resources/icons/tablet-icons/mic-ptt-i.svg diff --git a/interface/resources/icons/tablet-icons/mic-ptt-a.svg b/interface/resources/icons/tablet-icons/mic-ptt-a.svg new file mode 100644 index 0000000000..e6df3c69d7 --- /dev/null +++ b/interface/resources/icons/tablet-icons/mic-ptt-a.svg @@ -0,0 +1 @@ +mic-ptt-a \ No newline at end of file diff --git a/interface/resources/icons/tablet-icons/mic-ptt-i.svg b/interface/resources/icons/tablet-icons/mic-ptt-i.svg new file mode 100644 index 0000000000..2141ea5229 --- /dev/null +++ b/interface/resources/icons/tablet-icons/mic-ptt-i.svg @@ -0,0 +1,24 @@ + + + + + + diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index 45358f59a2..d44a9c862e 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -120,6 +120,10 @@ Rectangle { isRedCheck: true; checked: AudioScriptingInterface.muted; onClicked: { + if (AudioScriptingInterface.pushToTalk && !checked) { + // disable push to talk if unmuting + AudioScriptingInterface.pushToTalk = false; + } AudioScriptingInterface.muted = checked; checked = Qt.binding(function() { return AudioScriptingInterface.muted; }); // restore binding } @@ -150,7 +154,23 @@ Rectangle { } AudioControls.CheckBox { spacing: muteMic.spacing - text: qsTr("Push To Talk"); + text: qsTr("Show audio level meter"); + checked: AvatarInputs.showAudioTools; + onClicked: { + AvatarInputs.showAudioTools = checked; + checked = Qt.binding(function() { return AvatarInputs.showAudioTools; }); // restore binding + } + onXChanged: rightMostInputLevelPos = x + width + } + } + + Separator {} + + ColumnLayout { + spacing: muteMic.spacing; + AudioControls.CheckBox { + spacing: muteMic.spacing + text: qsTr("Push To Talk (T)"); checked: isVR ? AudioScriptingInterface.pushToTalkHMD : AudioScriptingInterface.pushToTalkDesktop; onClicked: { if (isVR) { @@ -167,15 +187,41 @@ Rectangle { }); // restore binding } } - AudioControls.CheckBox { - spacing: muteMic.spacing - text: qsTr("Show audio level meter"); - checked: AvatarInputs.showAudioTools; - onClicked: { - AvatarInputs.showAudioTools = checked; - checked = Qt.binding(function() { return AvatarInputs.showAudioTools; }); // restore binding + Item { + id: pttTextContainer + x: margins.paddings; + width: rightMostInputLevelPos + height: pttTextMetrics.height + visible: true + TextMetrics { + id: pttTextMetrics + text: pttText.text + font: pttText.font + } + RalewayRegular { + id: pttText + wrapMode: Text.WordWrap + color: hifi.colors.white; + width: parent.width; + font.italic: true + size: 16; + text: isVR ? qsTr("Press and hold grip triggers on both of your controllers to unmute.") : + qsTr("Press and hold the button \"T\" to unmute."); + onTextChanged: { + if (pttTextMetrics.width > rightMostInputLevelPos) { + pttTextContainer.height = Math.ceil(pttTextMetrics.width / rightMostInputLevelPos) * pttTextMetrics.height; + } else { + pttTextContainer.height = pttTextMetrics.height; + } + } + } + Component.onCompleted: { + if (pttTextMetrics.width > rightMostInputLevelPos) { + pttTextContainer.height = Math.ceil(pttTextMetrics.width / rightMostInputLevelPos) * pttTextMetrics.height; + } else { + pttTextContainer.height = pttTextMetrics.height; + } } - onXChanged: rightMostInputLevelPos = x + width } } } diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index 9d1cbfbc6c..50477b82f8 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -67,6 +67,9 @@ Rectangle { hoverEnabled: true; scrollGestureEnabled: false; onClicked: { + if (AudioScriptingInterface.pushToTalk) { + return; + } AudioScriptingInterface.muted = !AudioScriptingInterface.muted; Tablet.playSound(TabletEnums.ButtonClick); } @@ -109,9 +112,10 @@ Rectangle { Image { readonly property string unmutedIcon: "../../../icons/tablet-icons/mic-unmute-i.svg"; readonly property string mutedIcon: "../../../icons/tablet-icons/mic-mute-i.svg"; + readonly property string pushToTalkIcon: "../../../icons/tablet-icons/mic-ptt-i.svg"; id: image; - source: AudioScriptingInterface.muted ? mutedIcon : unmutedIcon; + source: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? pushToTalkIcon : AudioScriptingInterface.muted ? mutedIcon : unmutedIcon; width: 30; height: 30; @@ -155,7 +159,7 @@ Rectangle { color: parent.color; - text: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? "MUTED-PTT (T)" : (AudioScriptingInterface.muted ? "MUTED" : "MUTE"); + text: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? "MUTED PTT-(T)" : (AudioScriptingInterface.muted ? "MUTED" : "MUTE"); font.pointSize: 12; } diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 45bb15f1a3..63ce9d2b2e 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -231,26 +231,6 @@ void Audio::loadData() { _pttHMD = _pttHMDSetting.get(); } -bool Audio::getPTTHMD() const { - return resultWithReadLock([&] { - return _pttHMD; - }); -} - -void Audio::saveData() { - _desktopMutedSetting.set(getMutedDesktop()); - _hmdMutedSetting.set(getMutedHMD()); - _pttDesktopSetting.set(getPTTDesktop()); - _pttHMDSetting.set(getPTTHMD()); -} - -void Audio::loadData() { - _desktopMuted = _desktopMutedSetting.get(); - _hmdMuted = _hmdMutedSetting.get(); - _pttDesktop = _pttDesktopSetting.get(); - _pttHMD = _pttHMDSetting.get(); -} - bool Audio::noiseReductionEnabled() const { return resultWithReadLock([&] { return _enableNoiseReduction; diff --git a/scripts/system/audio.js b/scripts/system/audio.js index bf44cfa7cc..19ed3faef2 100644 --- a/scripts/system/audio.js +++ b/scripts/system/audio.js @@ -27,12 +27,12 @@ var UNMUTE_ICONS = { activeIcon: "icons/tablet-icons/mic-unmute-a.svg" }; var PTT_ICONS = { - icon: "icons/tablet-icons/mic-unmute-i.svg", - activeIcon: "icons/tablet-icons/mic-unmute-a.svg" + icon: "icons/tablet-icons/mic-ptt-i.svg", + activeIcon: "icons/tablet-icons/mic-ptt-a.svg" }; function onMuteToggled() { - if (Audio.pushingToTalk) { + if (Audio.pushToTalk) { button.editProperties(PTT_ICONS); } else if (Audio.muted) { button.editProperties(MUTE_ICONS); @@ -63,8 +63,8 @@ function onScreenChanged(type, url) { var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var button = tablet.addButton({ - icon: Audio.muted ? MUTE_ICONS.icon : UNMUTE_ICONS.icon, - activeIcon: Audio.muted ? MUTE_ICONS.activeIcon : UNMUTE_ICONS.activeIcon, + icon: Audio.pushToTalk ? PTT_ICONS.icon : Audio.muted ? MUTE_ICONS.icon : UNMUTE_ICONS.icon, + activeIcon: Audio.pushToTalk ? PTT_ICONS.activeIcon : Audio.muted ? MUTE_ICONS.activeIcon : UNMUTE_ICONS.activeIcon, text: TABLET_BUTTON_NAME, sortOrder: 1 }); @@ -74,7 +74,7 @@ onMuteToggled(); button.clicked.connect(onClicked); tablet.screenChanged.connect(onScreenChanged); Audio.mutedChanged.connect(onMuteToggled); -Audio.pushingToTalkChanged.connect(onMuteToggled); +Audio.pushToTalkChanged.connect(onMuteToggled); Script.scriptEnding.connect(function () { if (onAudioScreen) { @@ -83,7 +83,7 @@ Script.scriptEnding.connect(function () { button.clicked.disconnect(onClicked); tablet.screenChanged.disconnect(onScreenChanged); Audio.mutedChanged.disconnect(onMuteToggled); - Audio.pushingToTalkChanged.disconnect(onMuteToggled); + Audio.pushToTalkChanged.disconnect(onMuteToggled); tablet.removeButton(button); }); From 2536fcaa1794c30813544e616f1ca4666c7a05a3 Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Thu, 7 Mar 2019 11:31:28 -0800 Subject: [PATCH 390/474] Add pushToTalk.js controllerModule. --- .../controllerModules/pushToTalk.js | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 scripts/system/controllers/controllerModules/pushToTalk.js diff --git a/scripts/system/controllers/controllerModules/pushToTalk.js b/scripts/system/controllers/controllerModules/pushToTalk.js new file mode 100644 index 0000000000..e764b228c9 --- /dev/null +++ b/scripts/system/controllers/controllerModules/pushToTalk.js @@ -0,0 +1,75 @@ +"use strict"; + +// Created by Jason C. Najera on 3/7/2019 +// Copyright 2019 High Fidelity, Inc. +// +// Handles Push-to-Talk functionality for HMD mode. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +Script.include("/~/system/libraries/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/controllers.js"); + +(function() { // BEGIN LOCAL_SCOPE + function PushToTalkHandler() { + var _this = this; + this.active = false; + //var pttMapping, mappingName; + + this.setup = function() { + //mappingName = 'Hifi-PTT-Dev-' + Math.random(); + //pttMapping = Controller.newMapping(mappingName); + //pttMapping.enable(); + }; + + this.shouldTalk = function (controllerData) { + // Set up test against controllerData here... + var gripVal = controllerData.secondaryValues[this.hand]; + return (gripVal) ? true : false; + }; + + this.shouldStopTalking = function (controllerData) { + var gripVal = controllerData.secondaryValues[this.hand]; + return (gripVal) ? false : true; + }; + + this.isReady = function (controllerData, deltaTime) { + if (HMD.active() && Audio.pushToTalk && this.shouldTalk(controllerData)) { + Audio.pushingToTalk = true; + returnMakeRunningValues(true, [], []); + } + + return makeRunningValues(false, [], []); + }; + + this.run = function (controllerData, deltaTime) { + if (this.shouldStopTalking(controllerData) || !Audio.pushToTalk) { + Audio.pushingToTalk = false; + return makeRunningValues(false, [], []); + } + + return makeRunningValues(true, [], []); + }; + + this.cleanup = function () { + //pttMapping.disable(); + }; + + this.parameters = makeDispatcherModuleParameters( + 950, + ["head"], + [], + 100); + } + + var pushToTalk = new PushToTalkHandler(); + enableDispatcherModule("PushToTalk", pushToTalk); + + function cleanup () { + pushToTalk.cleanup(); + disableDispatcherModule("PushToTalk"); + }; + + Script.scriptEnding.connect(cleanup); +}()); // END LOCAL_SCOPE \ No newline at end of file From 9d59e68d45d53e53d6471993dd34bca6695ddfab Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Thu, 7 Mar 2019 11:31:48 -0800 Subject: [PATCH 391/474] Enable pushToTalk.js controller module. --- scripts/system/controllers/controllerDispatcher.js | 1 + scripts/system/controllers/controllerScripts.js | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 28c3e2a299..f4c0cbb0d6 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -58,6 +58,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); // not set to false (not in use), a module cannot start. When a module is using a slot, that module's name // is stored as the value, rather than false. this.activitySlots = { + head: false, leftHand: false, rightHand: false, rightHandTrigger: false, diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index 86ff7701c3..d236d6b12a 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -34,7 +34,8 @@ var CONTOLLER_SCRIPTS = [ "controllerModules/nearGrabHyperLinkEntity.js", "controllerModules/nearTabletHighlight.js", "controllerModules/nearGrabEntity.js", - "controllerModules/farGrabEntity.js" + "controllerModules/farGrabEntity.js", + "controllerModules/pushToTalk.js" ]; var DEBUG_MENU_ITEM = "Debug defaultScripts.js"; From 55efc315f10604c663b8df2fa6f65c7dce7d4f3f Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Thu, 7 Mar 2019 11:45:59 -0800 Subject: [PATCH 392/474] Fix activation / deactivation criteria for PTT controller module. --- scripts/system/controllers/controllerModules/pushToTalk.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/system/controllers/controllerModules/pushToTalk.js b/scripts/system/controllers/controllerModules/pushToTalk.js index e764b228c9..557476ccd7 100644 --- a/scripts/system/controllers/controllerModules/pushToTalk.js +++ b/scripts/system/controllers/controllerModules/pushToTalk.js @@ -25,12 +25,12 @@ Script.include("/~/system/libraries/controllers.js"); this.shouldTalk = function (controllerData) { // Set up test against controllerData here... - var gripVal = controllerData.secondaryValues[this.hand]; + var gripVal = controllerData.secondaryValues[LEFT_HAND] && controllerData.secondaryValues[RIGHT_HAND]; return (gripVal) ? true : false; }; this.shouldStopTalking = function (controllerData) { - var gripVal = controllerData.secondaryValues[this.hand]; + var gripVal = controllerData.secondaryValues[LEFT_HAND] && controllerData.secondaryValues[RIGHT_HAND]; return (gripVal) ? false : true; }; From 8acec5c9e94264ea2e13936b0bfe99c954e0e333 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Thu, 7 Mar 2019 12:21:40 -0800 Subject: [PATCH 393/474] Increased threshold to 0.9999 --- tools/nitpick/src/TestCreator.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/nitpick/src/TestCreator.h b/tools/nitpick/src/TestCreator.h index e17bc1dd03..7cd38b42d4 100644 --- a/tools/nitpick/src/TestCreator.h +++ b/tools/nitpick/src/TestCreator.h @@ -121,7 +121,7 @@ private: const QString TEST_RESULTS_FOLDER { "TestResults" }; const QString TEST_RESULTS_FILENAME { "TestResults.txt" }; - const double THRESHOLD{ 0.995 }; + const double THRESHOLD{ 0.9999 }; QDir _imageDirectory; From 684f9929a9e6552a89128570c0eb59df9b617eb5 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Thu, 7 Mar 2019 12:22:07 -0800 Subject: [PATCH 394/474] Improved error message. --- tools/nitpick/src/TestCreator.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/nitpick/src/TestCreator.cpp b/tools/nitpick/src/TestCreator.cpp index c548a63a83..089e84904a 100644 --- a/tools/nitpick/src/TestCreator.cpp +++ b/tools/nitpick/src/TestCreator.cpp @@ -340,8 +340,10 @@ void TestCreator::finishTestsEvaluation() { if (!_isRunningFromCommandLine && !_isRunningInAutomaticTestRun) { if (numberOfFailures == 0) { QMessageBox::information(0, "Success", "All images are as expected"); + } else if (numberOfFailures == 1) { + QMessageBox::information(0, "Failure", "One image is not as expected"); } else { - QMessageBox::information(0, "Failure", "One or more images are not as expected"); + QMessageBox::information(0, "Failure", QString::number(numberOfFailures) + " images are not as expected"); } } From a62cadc54145dcbc54bf17d6f13d1277a1832247 Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Thu, 7 Mar 2019 12:35:04 -0800 Subject: [PATCH 395/474] Fix typo. --- scripts/system/controllers/controllerModules/pushToTalk.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/controllers/controllerModules/pushToTalk.js b/scripts/system/controllers/controllerModules/pushToTalk.js index 557476ccd7..dd959ae6fb 100644 --- a/scripts/system/controllers/controllerModules/pushToTalk.js +++ b/scripts/system/controllers/controllerModules/pushToTalk.js @@ -35,7 +35,7 @@ Script.include("/~/system/libraries/controllers.js"); }; this.isReady = function (controllerData, deltaTime) { - if (HMD.active() && Audio.pushToTalk && this.shouldTalk(controllerData)) { + if (HMD.active && Audio.pushToTalk && this.shouldTalk(controllerData)) { Audio.pushingToTalk = true; returnMakeRunningValues(true, [], []); } From b83e9f70e64ff7717f1fda7a5ed7d7401cdf304b Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Thu, 7 Mar 2019 12:36:56 -0800 Subject: [PATCH 396/474] adding PushToTalk action --- interface/src/Application.cpp | 11 +++++++++++ libraries/controllers/src/controllers/Actions.cpp | 4 ++++ libraries/controllers/src/controllers/Actions.h | 1 + .../controllers/controllerModules/pushToTalk.js | 4 ++-- 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index fa63757560..1582c69bc9 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1601,12 +1601,23 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(userInputMapper.data(), &UserInputMapper::actionEvent, [this](int action, float state) { using namespace controller; auto tabletScriptingInterface = DependencyManager::get(); + auto audioScriptingInterface = reinterpret_cast(DependencyManager::get().data()); { auto actionEnum = static_cast(action); int key = Qt::Key_unknown; static int lastKey = Qt::Key_unknown; bool navAxis = false; switch (actionEnum) { + case Action::TOGGLE_PUSHTOTALK: + if (audioScriptingInterface->getPTT()) { + qDebug() << "State is " << state; + if (state > 0.0f) { + audioScriptingInterface->setPushingToTalk(false); + } else if (state < 0.0f) { + audioScriptingInterface->setPushingToTalk(true); + } + } + case Action::UI_NAV_VERTICAL: navAxis = true; if (state > 0.0f) { diff --git a/libraries/controllers/src/controllers/Actions.cpp b/libraries/controllers/src/controllers/Actions.cpp index 5a396231b6..57be2f788b 100644 --- a/libraries/controllers/src/controllers/Actions.cpp +++ b/libraries/controllers/src/controllers/Actions.cpp @@ -180,6 +180,7 @@ namespace controller { * third person, to full screen mirror, then back to first person and repeat. * ContextMenunumbernumberShow / hide the tablet. * ToggleMutenumbernumberToggle the microphone mute. + * TogglePushToTalknumbernumberToggle push to talk. * ToggleOverlaynumbernumberToggle the display of overlays. * SprintnumbernumberSet avatar sprint mode. * ReticleClicknumbernumberSet mouse-pressed. @@ -245,6 +246,8 @@ namespace controller { * ContextMenu instead. * TOGGLE_MUTEnumbernumberDeprecated: Use * ToggleMute instead. + * TOGGLE_PUSHTOTALKnumbernumberDeprecated: Use + * TogglePushToTalk instead. * SPRINTnumbernumberDeprecated: Use * Sprint instead. * LONGITUDINAL_BACKWARDnumbernumberDeprecated: Use @@ -411,6 +414,7 @@ namespace controller { makeButtonPair(Action::ACTION2, "SecondaryAction"), makeButtonPair(Action::CONTEXT_MENU, "ContextMenu"), makeButtonPair(Action::TOGGLE_MUTE, "ToggleMute"), + makeButtonPair(Action::TOGGLE_PUSHTOTALK, "TogglePushToTalk"), makeButtonPair(Action::CYCLE_CAMERA, "CycleCamera"), makeButtonPair(Action::TOGGLE_OVERLAY, "ToggleOverlay"), makeButtonPair(Action::SPRINT, "Sprint"), diff --git a/libraries/controllers/src/controllers/Actions.h b/libraries/controllers/src/controllers/Actions.h index a12a3d60a9..3e99d8d147 100644 --- a/libraries/controllers/src/controllers/Actions.h +++ b/libraries/controllers/src/controllers/Actions.h @@ -60,6 +60,7 @@ enum class Action { CONTEXT_MENU, TOGGLE_MUTE, + TOGGLE_PUSHTOTALK, CYCLE_CAMERA, TOGGLE_OVERLAY, diff --git a/scripts/system/controllers/controllerModules/pushToTalk.js b/scripts/system/controllers/controllerModules/pushToTalk.js index e764b228c9..916769934d 100644 --- a/scripts/system/controllers/controllerModules/pushToTalk.js +++ b/scripts/system/controllers/controllerModules/pushToTalk.js @@ -35,7 +35,7 @@ Script.include("/~/system/libraries/controllers.js"); }; this.isReady = function (controllerData, deltaTime) { - if (HMD.active() && Audio.pushToTalk && this.shouldTalk(controllerData)) { + if (HMD.active && Audio.pushToTalk && this.shouldTalk(controllerData)) { Audio.pushingToTalk = true; returnMakeRunningValues(true, [], []); } @@ -72,4 +72,4 @@ Script.include("/~/system/libraries/controllers.js"); }; Script.scriptEnding.connect(cleanup); -}()); // END LOCAL_SCOPE \ No newline at end of file +}()); // END LOCAL_SCOPE From 472c7ffab443afbeaa31b090a07f1ddfd475e5ad Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Thu, 7 Mar 2019 12:54:32 -0800 Subject: [PATCH 397/474] removing debug statement --- interface/src/Application.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 1582c69bc9..16f4a9094f 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1610,7 +1610,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo switch (actionEnum) { case Action::TOGGLE_PUSHTOTALK: if (audioScriptingInterface->getPTT()) { - qDebug() << "State is " << state; if (state > 0.0f) { audioScriptingInterface->setPushingToTalk(false); } else if (state < 0.0f) { From 87cfbe48d18d4fe5ca34f3c444a4e91984e7b3ef Mon Sep 17 00:00:00 2001 From: danteruiz Date: Thu, 7 Mar 2019 13:11:18 -0800 Subject: [PATCH 398/474] fix webSurfaceIntersection --- .../controllers/controllerModules/webSurfaceLaserInput.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js index 4f21b44533..cf700a8ad9 100644 --- a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js +++ b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js @@ -247,8 +247,8 @@ Script.include("/~/system/libraries/controllers.js"); this.run = function(controllerData, deltaTime) { this.addObjectToIgnoreList(controllerData); - var type = this.getInteractableType(controllerData, isTriggerPressed, false); var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE; + var type = this.getInteractableType(controllerData, isTriggerPressed, false); var laserOn = isTriggerPressed || this.parameters.handLaser.alwaysOn; this.addObjectToIgnoreList(controllerData); From cce43357d01cf271646a691ed5a4a94fae95256d Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Fri, 22 Feb 2019 10:44:56 -0800 Subject: [PATCH 399/474] Case 21315, Case 21326, Case 21370 Case 21315 - Clicking on item picture from Inventor Items list does not always display the Marketplace item page. Case 21326 - See All Markets page is missing its footer and the two Explore buttons no longer work Case 21370 - QML Marketplace - For unavailable items (no more editions for sale), marketplace item list 'buy' button is not disabled Also, a few minor font sizing and other graphics tweaks --- .../qml/hifi/commerce/marketplace/Marketplace.qml | 7 ++++++- .../hifi/commerce/marketplace/MarketplaceItem.qml | 12 ++++-------- .../commerce/marketplace/MarketplaceListItem.qml | 11 +++++++---- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml index 3004af6c15..4ff935921f 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml @@ -41,6 +41,7 @@ Rectangle { property string searchScopeString: "Featured" property bool isLoggedIn: false property bool supports3DHTML: true + property bool pendingGetMarketplaceItemCall: false anchors.fill: (typeof parent === undefined) ? undefined : parent @@ -100,7 +101,9 @@ Rectangle { getMarketplaceItems(); } onGetMarketplaceItemsResult: { - marketBrowseModel.handlePage(result.status !== "success" && result.message, result); + if (!pendingGetMarketplaceItemCall) { + marketBrowseModel.handlePage(result.status !== "success" && result.message, result); + } } onGetMarketplaceItemResult: { @@ -127,6 +130,7 @@ Rectangle { marketplaceItemScrollView.contentHeight = marketplaceItemContent.height; itemsList.visible = false; marketplaceItemView.visible = true; + pendingGetMarketplaceItemCall = false; } } } @@ -1224,6 +1228,7 @@ Rectangle { console.log("A message with method 'updateMarketplaceQMLItem' was sent without an itemId!"); return; } + pendingGetMarketplaceItemCall = true; marketplaceItem.edition = message.params.edition ? message.params.edition : -1; MarketplaceScriptingInterface.getMarketplaceItem(message.params.itemId); break; diff --git a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml index 8c9d3c31c8..fa7e311026 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml @@ -52,13 +52,7 @@ Rectangle { } onDescriptionChanged: { - - if(root.supports3DHTML) { - descriptionTextModel.clear(); - descriptionTextModel.append({text: description}); - } else { - descriptionText.text = description; - } + descriptionText.text = description; } onAttributionsChanged: { @@ -250,7 +244,6 @@ Rectangle { function evalHeight() { height = categoriesList.y - buyButton.y + categoriesList.height; - console.log("HEIGHT: " + height); } HifiControlsUit.Button { @@ -272,6 +265,9 @@ Rectangle { text: isUpdate ? "UPDATE FOR FREE" : (isStocking ? "FREE STOCK" : (enabled ? (price || "FREE") : availability)) color: hifi.buttons.blue + buttonGlyphSize: 24 + fontSize: 24 + onClicked: root.buy(); } diff --git a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml index 587d71da28..439247e410 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml @@ -226,6 +226,7 @@ Rectangle { top: parent.top left: parent.left leftMargin: 15 + topMargin: 10 } width: paintedWidth @@ -241,7 +242,7 @@ Rectangle { anchors { top: creatorLabel.top; left: creatorLabel.right; - leftMargin: 15; + leftMargin: 10; } width: paintedWidth; @@ -273,7 +274,7 @@ Rectangle { anchors { top: categoryLabel.top left: categoryLabel.right - leftMargin: 15 + leftMargin: 10 } width: paintedWidth @@ -298,19 +299,21 @@ Rectangle { topMargin:10 bottomMargin: 10 } + width: 180 property bool isNFS: availability === "not for sale" // Note: server will say "sold out" or "invalidated" before it says NFS property bool isMine: creator === Account.username property bool isUpgrade: root.edition >= 0 property int costToMe: ((isMine && isNFS) || isUpgrade) ? 0 : price - property bool isAvailable: costToMe >= 0 + property bool isAvailable: availability === "available" text: isUpgrade ? "UPGRADE FOR FREE" : (isAvailable ? (costToMe || "FREE") : availability) enabled: isAvailable buttonGlyph: isAvailable ? (costToMe ? hifi.glyphs.hfc : "") : "" color: hifi.buttons.blue; - + buttonGlyphSize: 24 + fontSize: 24 onClicked: root.buy(); } } From 0cf8f3e5c3d306545355ad3013e923cc9dca868f Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Thu, 7 Mar 2019 14:11:01 -0800 Subject: [PATCH 400/474] Code review feed back remove NEW_VERSION ifdef --- libraries/animation/src/AnimPose.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/libraries/animation/src/AnimPose.cpp b/libraries/animation/src/AnimPose.cpp index 56692e763e..8649db8233 100644 --- a/libraries/animation/src/AnimPose.cpp +++ b/libraries/animation/src/AnimPose.cpp @@ -17,8 +17,6 @@ const AnimPose AnimPose::identity = AnimPose(glm::vec3(1.0f), glm::quat(), glm::vec3(0.0f)); -#define NEW_VERSION - AnimPose::AnimPose(const glm::mat4& mat) { glm::mat3 m(mat); _scale = glm::vec3(glm::length(m[0]), glm::length(m[1]), glm::length(m[2])); From d1e376de0e15e4d4dda0aa6d0c81c43367352115 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Thu, 7 Mar 2019 14:26:47 -0800 Subject: [PATCH 401/474] destroy modal dialogs that prevent interface from getting input --- .../resources/qml/dialogs/CustomQueryDialog.qml | 12 ++---------- interface/resources/qml/dialogs/FileDialog.qml | 2 +- interface/resources/qml/dialogs/QueryDialog.qml | 12 ++---------- 3 files changed, 5 insertions(+), 21 deletions(-) diff --git a/interface/resources/qml/dialogs/CustomQueryDialog.qml b/interface/resources/qml/dialogs/CustomQueryDialog.qml index 026068eee1..2497781db0 100644 --- a/interface/resources/qml/dialogs/CustomQueryDialog.qml +++ b/interface/resources/qml/dialogs/CustomQueryDialog.qml @@ -273,11 +273,7 @@ ModalWindow { onTriggered: { root.result = null; root.canceled(); - // FIXME we are leaking memory to avoid a crash - // root.destroy(); - - root.disableFade = true - visible = false; + root.destroy(); } } @@ -299,11 +295,7 @@ ModalWindow { } root.result = JSON.stringify(result); root.selected(root.result); - // FIXME we are leaking memory to avoid a crash - // root.destroy(); - - root.disableFade = true - visible = false; + root.destroy(); } } } diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index c1509e0fc1..ba5e162391 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -815,7 +815,7 @@ ModalWindow { Action { id: cancelAction text: "Cancel" - onTriggered: { canceled(); root.shown = false; } + onTriggered: { canceled(); root.destroy(); } } } diff --git a/interface/resources/qml/dialogs/QueryDialog.qml b/interface/resources/qml/dialogs/QueryDialog.qml index 9cfb3011bd..41ded7e934 100644 --- a/interface/resources/qml/dialogs/QueryDialog.qml +++ b/interface/resources/qml/dialogs/QueryDialog.qml @@ -168,11 +168,7 @@ ModalWindow { shortcut: "Esc" onTriggered: { root.canceled(); - // FIXME we are leaking memory to avoid a crash - // root.destroy(); - - root.disableFade = true - visible = false; + root.destroy(); } } @@ -183,11 +179,7 @@ ModalWindow { onTriggered: { root.result = items ? comboBox.currentText : textResult.text root.selected(root.result); - // FIXME we are leaking memory to avoid a crash - // root.destroy(); - - root.disableFade = true - visible = false; + root.destroy(); } } } From d309995aef07894d081eca61fee1235027cfa5b6 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Thu, 7 Mar 2019 14:48:57 -0800 Subject: [PATCH 402/474] spell domainUnlimited right, and track challenges so that we don't reap --- libraries/entities/src/EntityTree.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 619cd9781f..ee6ba3469b 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -42,7 +42,7 @@ static const quint64 DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER = USECS_PER_MSEC * 50; const float EntityTree::DEFAULT_MAX_TMP_ENTITY_LIFETIME = 60 * 60; // 1 hour -static const QString DOMAIN_UNLIMITED = "domainUnlimitied"; +static const QString DOMAIN_UNLIMITED = "domainUnlimited"; EntityTree::EntityTree(bool shouldReaverage) : Octree(shouldReaverage) @@ -293,7 +293,7 @@ void EntityTree::postAddEntity(EntityItemPointer entity) { { QWriteLocker locker(&_entityCertificateIDMapLock); existingEntityItemID = _entityCertificateIDMap.value(certID); - if (!certID.isEmpty() && !entity->getCertificateType().contains(DOMAIN_UNLIMITED)) { + if (!certID.isEmpty()) { _entityCertificateIDMap.insert(certID, entityItemID); qCDebug(entities) << "Certificate ID" << certID << "belongs to" << entityItemID; } @@ -301,7 +301,7 @@ void EntityTree::postAddEntity(EntityItemPointer entity) { // Delete an already-existing entity from the tree if it has the same // CertificateID as the entity we're trying to add. - if (!existingEntityItemID.isNull()) { + if (!existingEntityItemID.isNull() && !entity->getCertificateType().contains(DOMAIN_UNLIMITED)) { qCDebug(entities) << "Certificate ID" << certID << "already exists on entity with ID" << existingEntityItemID << ". Deleting existing entity."; deleteEntity(existingEntityItemID, true); From 1d6364773da9f193573697a47c1a523318871ace Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 7 Mar 2019 15:26:41 -0800 Subject: [PATCH 403/474] fix crash on startup --- libraries/model-baker/src/model-baker/ParseFlowDataTask.cpp | 3 ++- libraries/model-baker/src/model-baker/ParseFlowDataTask.h | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/libraries/model-baker/src/model-baker/ParseFlowDataTask.cpp b/libraries/model-baker/src/model-baker/ParseFlowDataTask.cpp index 6dff4f8c55..10991ecbe6 100644 --- a/libraries/model-baker/src/model-baker/ParseFlowDataTask.cpp +++ b/libraries/model-baker/src/model-baker/ParseFlowDataTask.cpp @@ -8,10 +8,11 @@ #include "ParseFlowDataTask.h" -void ParseFlowDataTask::run(const baker::BakeContextPointer& context, const Input& mapping, Output& output) { +void ParseFlowDataTask::run(const baker::BakeContextPointer& context, const Input& mappingPair, Output& output) { FlowData flowData; static const QString FLOW_PHYSICS_FIELD = "flowPhysicsData"; static const QString FLOW_COLLISIONS_FIELD = "flowCollisionsData"; + auto mapping = mappingPair.second; for (auto mappingIter = mapping.begin(); mappingIter != mapping.end(); mappingIter++) { if (mappingIter.key() == FLOW_PHYSICS_FIELD || mappingIter.key() == FLOW_COLLISIONS_FIELD) { QByteArray data = mappingIter.value().toByteArray(); diff --git a/libraries/model-baker/src/model-baker/ParseFlowDataTask.h b/libraries/model-baker/src/model-baker/ParseFlowDataTask.h index 7e1bc9fba1..65b8f7654b 100644 --- a/libraries/model-baker/src/model-baker/ParseFlowDataTask.h +++ b/libraries/model-baker/src/model-baker/ParseFlowDataTask.h @@ -12,9 +12,11 @@ #include #include "Engine.h" +#include "BakerTypes.h" + class ParseFlowDataTask { public: - using Input = QVariantHash; + using Input = baker::GeometryMappingPair; using Output = FlowData; using JobModel = baker::Job::ModelIO; From 0e1dbded229c5202806de742506c9b772d2d0046 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 7 Mar 2019 16:19:50 -0800 Subject: [PATCH 404/474] update avatar exporter package --- .../avatarExporter.unitypackage | Bin 15720 -> 15944 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tools/unity-avatar-exporter/avatarExporter.unitypackage b/tools/unity-avatar-exporter/avatarExporter.unitypackage index aada1d3515d3ee1a165a97e0eee41c92a058e6b0..281cc80ddb4680277c4a84f68a44788bae88ba0d 100644 GIT binary patch literal 15944 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 literal 15720 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 From af03fb5f85dd376326d4a1d8e62ad822546b758e Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 7 Mar 2019 16:23:12 -0800 Subject: [PATCH 405/474] try to fix text rendering on amd with transparency --- interface/src/Application.cpp | 9 ++--- .../src/RenderablePolyLineEntityItem.cpp | 9 ++--- .../src/RenderableShapeEntityItem.cpp | 9 ++--- .../src/RenderableTextEntityItem.cpp | 8 +++-- libraries/render-utils/src/GeometryCache.cpp | 11 ++---- libraries/render-utils/src/TextRenderer3D.cpp | 4 +-- libraries/render-utils/src/TextRenderer3D.h | 2 +- libraries/render-utils/src/sdf_text3D.slv | 3 +- .../src/sdf_text3D_transparent.slf | 34 ++++++++++++++++--- libraries/render-utils/src/text/Font.cpp | 4 +-- libraries/render-utils/src/text/Font.h | 2 +- libraries/shared/src/DisableDeferred.h | 24 +++++++++++++ 12 files changed, 75 insertions(+), 44 deletions(-) create mode 100644 libraries/shared/src/DisableDeferred.h diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index b8ab4d10db..b854387fd8 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -246,6 +246,8 @@ #include "AboutUtil.h" +#include + #if defined(Q_OS_WIN) #include @@ -289,13 +291,6 @@ static const QString DISABLE_WATCHDOG_FLAG{ "HIFI_DISABLE_WATCHDOG" }; static bool DISABLE_WATCHDOG = nsightActive() || QProcessEnvironment::systemEnvironment().contains(DISABLE_WATCHDOG_FLAG); #endif -#if defined(USE_GLES) -static bool DISABLE_DEFERRED = true; -#else -static const QString RENDER_FORWARD{ "HIFI_RENDER_FORWARD" }; -static bool DISABLE_DEFERRED = QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD); -#endif - #if !defined(Q_OS_ANDROID) static const uint32_t MAX_CONCURRENT_RESOURCE_DOWNLOADS = 16; #else diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp index 454e8b136a..7050393221 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp @@ -19,6 +19,8 @@ #include #include +#include + #include "paintStroke_Shared.slh" using namespace render; @@ -29,13 +31,6 @@ gpu::PipelinePointer PolyLineEntityRenderer::_glowPipeline = nullptr; static const QUrl DEFAULT_POLYLINE_TEXTURE = PathUtils::resourcesUrl("images/paintStroke.png"); -#if defined(USE_GLES) -static bool DISABLE_DEFERRED = true; -#else -static const QString RENDER_FORWARD{ "HIFI_RENDER_FORWARD" }; -static bool DISABLE_DEFERRED = QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD); -#endif - PolyLineEntityRenderer::PolyLineEntityRenderer(const EntityItemPointer& entity) : Parent(entity) { _texture = DependencyManager::get()->getTexture(DEFAULT_POLYLINE_TEXTURE); diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index b61bb2cbda..20837070d8 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -19,6 +19,8 @@ #include "RenderPipelines.h" +#include + //#define SHAPE_ENTITY_USE_FADE_EFFECT #ifdef SHAPE_ENTITY_USE_FADE_EFFECT #include @@ -30,13 +32,6 @@ using namespace render::entities; // is a half unit sphere. However, the geometry cache renders a UNIT sphere, so we need to scale down. static const float SPHERE_ENTITY_SCALE = 0.5f; -#if defined(USE_GLES) -static bool DISABLE_DEFERRED = true; -#else -static const QString RENDER_FORWARD{ "HIFI_RENDER_FORWARD" }; -static bool DISABLE_DEFERRED = QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD); -#endif - static_assert(shader::render_utils::program::simple != 0, "Validate simple program exists"); static_assert(shader::render_utils::program::simple_transparent != 0, "Validate simple transparent program exists"); diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp index dfc9277bf0..107847826c 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp @@ -19,6 +19,8 @@ #include "GLMHelpers.h" +#include + using namespace render; using namespace render::entities; @@ -160,6 +162,7 @@ void TextEntityRenderer::doRender(RenderArgs* args) { glm::vec4 backgroundColor; Transform modelTransform; glm::vec3 dimensions; + bool forwardRendered; withReadLock([&] { modelTransform = _renderTransform; dimensions = _dimensions; @@ -169,6 +172,7 @@ void TextEntityRenderer::doRender(RenderArgs* args) { textColor = EntityRenderer::calculatePulseColor(textColor, _pulseProperties, _created); backgroundColor = glm::vec4(_backgroundColor, fadeRatio * _backgroundAlpha); backgroundColor = EntityRenderer::calculatePulseColor(backgroundColor, _pulseProperties, _created); + forwardRendered = _renderLayer != RenderLayer::WORLD || DISABLE_DEFERRED; }); // Render background @@ -188,7 +192,7 @@ void TextEntityRenderer::doRender(RenderArgs* args) { if (backgroundColor.a > 0.0f) { batch.setModelTransform(transformToTopLeft); auto geometryCache = DependencyManager::get(); - geometryCache->bindSimpleProgram(batch, false, backgroundColor.a < 1.0f, false, false, false); + geometryCache->bindSimpleProgram(batch, false, backgroundColor.a < 1.0f, false, false, false, true, forwardRendered); geometryCache->renderQuad(batch, minCorner, maxCorner, backgroundColor, _geometryID); } @@ -199,7 +203,7 @@ void TextEntityRenderer::doRender(RenderArgs* args) { batch.setModelTransform(transformToTopLeft); glm::vec2 bounds = glm::vec2(dimensions.x - (_leftMargin + _rightMargin), dimensions.y - (_topMargin + _bottomMargin)); - _textRenderer->draw(batch, _leftMargin / scale, -_topMargin / scale, _text, textColor, bounds / scale); + _textRenderer->draw(batch, _leftMargin / scale, -_topMargin / scale, _text, textColor, bounds / scale, forwardRendered); } } diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 7d29b9e792..e322dc9d2b 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -35,10 +35,10 @@ #include "StencilMaskPass.h" #include "FadeEffect.h" - - #include "DeferredLightingEffect.h" +#include + namespace gr { using graphics::slot::texture::Texture; using graphics::slot::buffer::Buffer; @@ -49,13 +49,6 @@ namespace ru { using render_utils::slot::buffer::Buffer; } -#if defined(USE_GLES) -static bool DISABLE_DEFERRED = true; -#else -static const QString RENDER_FORWARD{ "HIFI_RENDER_FORWARD" }; -static bool DISABLE_DEFERRED = QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD); -#endif - //#define WANT_DEBUG // @note: Originally size entity::NUM_SHAPES diff --git a/libraries/render-utils/src/TextRenderer3D.cpp b/libraries/render-utils/src/TextRenderer3D.cpp index 2d507ba21b..93edc4217d 100644 --- a/libraries/render-utils/src/TextRenderer3D.cpp +++ b/libraries/render-utils/src/TextRenderer3D.cpp @@ -67,11 +67,11 @@ float TextRenderer3D::getFontSize() const { } void TextRenderer3D::draw(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color, - const glm::vec2& bounds) { + const glm::vec2& bounds, bool forwardRendered) { // The font does all the OpenGL work if (_font) { _color = color; - _font->drawString(batch, _drawInfo, str, _color, _effectType, { x, y }, bounds); + _font->drawString(batch, _drawInfo, str, _color, _effectType, { x, y }, bounds, forwardRendered); } } diff --git a/libraries/render-utils/src/TextRenderer3D.h b/libraries/render-utils/src/TextRenderer3D.h index 97b74fcabd..b6475ab0ed 100644 --- a/libraries/render-utils/src/TextRenderer3D.h +++ b/libraries/render-utils/src/TextRenderer3D.h @@ -39,7 +39,7 @@ public: float getFontSize() const; // Pixel size void draw(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color = glm::vec4(1.0f), - const glm::vec2& bounds = glm::vec2(-1.0f)); + const glm::vec2& bounds = glm::vec2(-1.0f), bool forwardRendered = false); private: TextRenderer3D(const char* family, float pointSize, int weight = -1, bool italic = false, diff --git a/libraries/render-utils/src/sdf_text3D.slv b/libraries/render-utils/src/sdf_text3D.slv index 04ee44510a..5f4df86d56 100644 --- a/libraries/render-utils/src/sdf_text3D.slv +++ b/libraries/render-utils/src/sdf_text3D.slv @@ -19,6 +19,7 @@ // the interpolated normal layout(location=RENDER_UTILS_ATTR_NORMAL_WS) out vec3 _normalWS; layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01; +layout(location=RENDER_UTILS_ATTR_POSITION_ES) out vec4 _positionES; void main() { _texCoord01.xy = inTexCoord0.xy; @@ -26,7 +27,7 @@ void main() { // standard transform TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); - <$transformModelToClipPos(cam, obj, inPosition, gl_Position)$> + <$transformModelToEyeAndClipPos(cam, obj, inPosition, _positionES, gl_Position)$> const vec3 normal = vec3(0, 0, 1); <$transformModelToWorldDir(cam, obj, normal, _normalWS)$> } \ No newline at end of file diff --git a/libraries/render-utils/src/sdf_text3D_transparent.slf b/libraries/render-utils/src/sdf_text3D_transparent.slf index 2fbfb2ca43..311c849915 100644 --- a/libraries/render-utils/src/sdf_text3D_transparent.slf +++ b/libraries/render-utils/src/sdf_text3D_transparent.slf @@ -10,7 +10,14 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -<@include DeferredBufferWrite.slh@> +<@include DefaultMaterials.slh@> + +<@include ForwardGlobalLight.slh@> +<$declareEvalGlobalLightingAlphaBlended()$> + +<@include gpu/Transform.slh@> +<$declareStandardCameraTransform()$> + <@include render-utils/ShaderConstants.h@> LAYOUT(binding=0) uniform sampler2D Font; @@ -24,12 +31,14 @@ LAYOUT(binding=0) uniform textParamsBuffer { TextParams params; }; -// the interpolated normal +layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES; layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; #define _texCoord0 _texCoord01.xy #define _texCoord1 _texCoord01.zw +layout(location=0) out vec4 _fragColor0; + #define TAA_TEXTURE_LOD_BIAS -3.0 const float interiorCutoff = 0.8; @@ -57,9 +66,24 @@ void main() { a += evalSDF(_texCoord0 + dxTexCoord + dyTexCoord); a *= 0.25; - packDeferredFragmentTranslucent( + float alpha = a * params.color.a; + if (alpha <= 0.0) { + discard; + } + + TransformCamera cam = getTransformCamera(); + vec3 fragPosition = _positionES.xyz; + + _fragColor0 = vec4(evalGlobalLightingAlphaBlendedWithHaze( + cam._viewInverse, + 1.0, + DEFAULT_OCCLUSION, + fragPosition, normalize(_normalWS), - a * params.color.a, params.color.rgb, - DEFAULT_ROUGHNESS); + DEFAULT_FRESNEL, + DEFAULT_METALLIC, + DEFAULT_EMISSIVE, + DEFAULT_ROUGHNESS, alpha), + alpha); } \ No newline at end of file diff --git a/libraries/render-utils/src/text/Font.cpp b/libraries/render-utils/src/text/Font.cpp index cc451eeedc..e0e99da020 100644 --- a/libraries/render-utils/src/text/Font.cpp +++ b/libraries/render-utils/src/text/Font.cpp @@ -343,7 +343,7 @@ void Font::buildVertices(Font::DrawInfo& drawInfo, const QString& str, const glm } void Font::drawString(gpu::Batch& batch, Font::DrawInfo& drawInfo, const QString& str, const glm::vec4& color, - EffectType effectType, const glm::vec2& origin, const glm::vec2& bounds) { + EffectType effectType, const glm::vec2& origin, const glm::vec2& bounds, bool forwardRendered) { if (str == "") { return; } @@ -370,7 +370,7 @@ void Font::drawString(gpu::Batch& batch, Font::DrawInfo& drawInfo, const QString } // need the gamma corrected color here - batch.setPipeline((color.a < 1.0f) ? _transparentPipeline : _pipeline); + batch.setPipeline(forwardRendered || (color.a < 1.0f) ? _transparentPipeline : _pipeline); batch.setInputFormat(_format); batch.setInputBuffer(0, drawInfo.verticesBuffer, 0, _format->getChannels().at(0)._stride); batch.setResourceTexture(render_utils::slot::texture::TextFont, _texture); diff --git a/libraries/render-utils/src/text/Font.h b/libraries/render-utils/src/text/Font.h index 85f692f5d8..26cc4e46c3 100644 --- a/libraries/render-utils/src/text/Font.h +++ b/libraries/render-utils/src/text/Font.h @@ -46,7 +46,7 @@ public: // Render string to batch void drawString(gpu::Batch& batch, DrawInfo& drawInfo, const QString& str, const glm::vec4& color, EffectType effectType, - const glm::vec2& origin, const glm::vec2& bound); + const glm::vec2& origin, const glm::vec2& bound, bool forwardRendered); static Pointer load(const QString& family); diff --git a/libraries/shared/src/DisableDeferred.h b/libraries/shared/src/DisableDeferred.h new file mode 100644 index 0000000000..9a1f9be117 --- /dev/null +++ b/libraries/shared/src/DisableDeferred.h @@ -0,0 +1,24 @@ +// +// Created by Sam Gondelman on 3/7/19. +// Copyright 2019 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_DisableDeferred_h +#define hifi_DisableDeferred_h + +#include +#include + +#if defined(USE_GLES) +static bool DISABLE_DEFERRED = true; +#else +static const QString RENDER_FORWARD{ "HIFI_RENDER_FORWARD" }; +static bool DISABLE_DEFERRED = QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD); +#endif + + +#endif // hifi_DisableDeferred_h + From 236c61dae53dedb39a7be9dd87d672c88b788bfd Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Thu, 7 Mar 2019 17:39:41 -0800 Subject: [PATCH 406/474] Add ModelCache singleton & other requirements for resource handling Cherry-pick of 6b5598bdc5e562119c55 from 79-HERO. --- assignment-client/src/avatars/AvatarMixer.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index e4077d5d46..33e1034128 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -35,6 +35,8 @@ #include #include "../AssignmentDynamicFactory.h" #include "../entities/AssignmentParentFinder.h" +#include +#include const QString AVATAR_MIXER_LOGGING_NAME = "avatar-mixer"; @@ -60,7 +62,10 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) : { DependencyManager::registerInheritance(); DependencyManager::set(); - + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); // make sure we hear about node kills so we can tell the other nodes connect(DependencyManager::get().data(), &NodeList::nodeKilled, this, &AvatarMixer::handleAvatarKilled); @@ -1060,6 +1065,10 @@ void AvatarMixer::handleOctreePacket(QSharedPointer message, Sh } void AvatarMixer::aboutToFinish() { + DependencyManager::destroy(); + DependencyManager::destroy(); + DependencyManager::destroy(); + DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); From bcd00f98d08253e03fce26baed669b11bcb41afa Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Tue, 26 Feb 2019 21:18:32 +0100 Subject: [PATCH 407/474] - added more warnings to the avatar doctor - read embedded fst texture mappings --- interface/src/avatar/AvatarDoctor.cpp | 448 ++++++++++++++++++++------ interface/src/avatar/AvatarDoctor.h | 27 +- 2 files changed, 371 insertions(+), 104 deletions(-) diff --git a/interface/src/avatar/AvatarDoctor.cpp b/interface/src/avatar/AvatarDoctor.cpp index b528441be7..66433f005b 100644 --- a/interface/src/avatar/AvatarDoctor.cpp +++ b/interface/src/avatar/AvatarDoctor.cpp @@ -14,8 +14,61 @@ #include #include +#include -AvatarDoctor::AvatarDoctor(QUrl avatarFSTFileUrl) : +const int NETWORKED_JOINTS_LIMIT = 256; +const float HIPS_GROUND_MIN_Y = 0.01f; +const float HIPS_SPINE_CHEST_MIN_SEPARATION = 0.001f; +const QString LEFT_JOINT_PREFIX = "Left"; +const QString RIGHT_JOINT_PREFIX = "Right"; + +const QStringList LEG_MAPPING_SUFFIXES = { + "UpLeg" + "Leg", + "Foot", + "ToeBase", +}; + +static QStringList ARM_MAPPING_SUFFIXES = { + "Shoulder", + "Arm", + "ForeArm", + "Hand", +}; + +static QStringList HAND_MAPPING_SUFFIXES = { + "HandIndex3", + "HandIndex2", + "HandIndex1", + "HandPinky3", + "HandPinky2", + "HandPinky1", + "HandMiddle3", + "HandMiddle2", + "HandMiddle1", + "HandRing3", + "HandRing2", + "HandRing1", + "HandThumb3", + "HandThumb2", + "HandThumb1", +}; + +const QUrl DEFAULT_URL = QUrl("https://docs.highfidelity.com/create/avatars/create-avatars.html#create-your-own-avatar"); + +DiagnosableAvatar::DiagnosableAvatar(QThread* thread) : Avatar(thread) { + // give the pointer to our head to inherited _headData variable from AvatarData + _headData = new Head(this); + _skeletonModel = std::make_shared(this, nullptr); + _skeletonModel->setLoadingPriority(MYAVATAR_LOADING_PRIORITY); + connect(_skeletonModel.get(), &Model::setURLFinished, this, &Avatar::setModelURLFinished); + connect(_skeletonModel.get(), &Model::rigReady, this, &Avatar::rigReady); + connect(_skeletonModel.get(), &Model::rigReset, this, &Avatar::rigReset); +} + +DiagnosableAvatar::~DiagnosableAvatar() = default; + +AvatarDoctor::AvatarDoctor(const QUrl& avatarFSTFileUrl) : _avatarFSTFileUrl(avatarFSTFileUrl) { connect(this, &AvatarDoctor::complete, this, [this](QVariantList errors) { @@ -39,136 +92,206 @@ void AvatarDoctor::startDiagnosing() { const auto resource = DependencyManager::get()->getGeometryResource(_avatarFSTFileUrl); resource->refresh(); - const QUrl DEFAULT_URL = QUrl("https://docs.highfidelity.com/create/avatars/create-avatars.html#create-your-own-avatar"); - const auto resourceLoaded = [this, resource, DEFAULT_URL](bool success) { + + const auto resourceLoaded = [this, resource](bool success) { // MODEL if (!success) { - _errors.push_back({ "Model file cannot be opened", DEFAULT_URL }); + _errors.push_back({ "Model file cannot be opened.", DEFAULT_URL }); emit complete(getErrors()); return; } + _model = resource; const auto model = resource.data(); const auto avatarModel = resource.data()->getHFMModel(); if (!avatarModel.originalURL.endsWith(".fbx")) { - _errors.push_back({ "Unsupported avatar model format", DEFAULT_URL }); + _errors.push_back({ "Unsupported avatar model format.", DEFAULT_URL }); emit complete(getErrors()); return; } // RIG if (avatarModel.joints.isEmpty()) { - _errors.push_back({ "Avatar has no rig", DEFAULT_URL }); + _errors.push_back({ "Avatar has no rig.", DEFAULT_URL }); } else { - if (avatarModel.joints.length() > 256) { - _errors.push_back({ "Avatar has over 256 bones", DEFAULT_URL }); + auto jointNames = avatarModel.getJointNames(); + + if (avatarModel.joints.length() > NETWORKED_JOINTS_LIMIT) { + _errors.push_back({tr( "Avatar has over %n bones.", "", NETWORKED_JOINTS_LIMIT), DEFAULT_URL }); } // Avatar does not have Hips bone mapped - if (!avatarModel.getJointNames().contains("Hips")) { - _errors.push_back({ "Hips are not mapped", DEFAULT_URL }); + if (!jointNames.contains("Hips")) { + _errors.push_back({ "Hips are not mapped.", DEFAULT_URL }); } - if (!avatarModel.getJointNames().contains("Spine")) { - _errors.push_back({ "Spine is not mapped", DEFAULT_URL }); + if (!jointNames.contains("Spine")) { + _errors.push_back({ "Spine is not mapped.", DEFAULT_URL }); } - if (!avatarModel.getJointNames().contains("Head")) { - _errors.push_back({ "Head is not mapped", DEFAULT_URL }); + if (!jointNames.contains("Spine1")) { + _errors.push_back({ "Chest (Spine1) is not mapped.", DEFAULT_URL }); + } + if (!jointNames.contains("Neck")) { + _errors.push_back({ "Neck is not mapped.", DEFAULT_URL }); + } + if (!jointNames.contains("Head")) { + _errors.push_back({ "Head is not mapped.", DEFAULT_URL }); + } + + if (!jointNames.contains("LeftEye")) { + if (jointNames.contains("RightEye")) { + _errors.push_back({ "LeftEye is not mapped.", DEFAULT_URL }); + } else { + _errors.push_back({ "Eyes are not mapped.", DEFAULT_URL }); + } + } else if (!jointNames.contains("RightEye")) { + _errors.push_back({ "RightEye is not mapped.", DEFAULT_URL }); + } + + const auto checkJointAsymmetry = [jointNames] (const QStringList& jointMappingSuffixes) { + foreach (const QString& jointSuffix, jointMappingSuffixes) { + if (jointNames.contains(LEFT_JOINT_PREFIX + jointSuffix) != + jointNames.contains(RIGHT_JOINT_PREFIX + jointSuffix)) { + return true; + } + } + return false; + }; + + const auto isDescendantOfJointWhenJointsExist = [avatarModel, jointNames] (const QString& jointName, const QString& ancestorName) { + if (!jointNames.contains(jointName) || !jointNames.contains(ancestorName)) { + return true; + } + auto currentJoint = avatarModel.joints[avatarModel.jointIndices[jointName] - 1]; + while (currentJoint.parentIndex != -1) { + currentJoint = avatarModel.joints[currentJoint.parentIndex]; + if (currentJoint.name == ancestorName) { + return true; + } + } + return false; + }; + + if (checkJointAsymmetry(ARM_MAPPING_SUFFIXES)) { + _errors.push_back({ "Asymmetrical arm bones.", DEFAULT_URL }); + } + if (checkJointAsymmetry(HAND_MAPPING_SUFFIXES)) { + _errors.push_back({ "Asymmetrical hand bones.", DEFAULT_URL }); + } + if (checkJointAsymmetry(LEG_MAPPING_SUFFIXES)) { + _errors.push_back({ "Asymmetrical leg bones.", DEFAULT_URL }); + } + _avatar = new DiagnosableAvatar(QThread::currentThread()); + + _avatar->setSkeletonModelURL(_avatarFSTFileUrl); + if (_avatar->getSkeletonModel()->updateGeometry()) { + // Rig has been fully loaded + + // SCALE + const float RECOMMENDED_MIN_HEIGHT = DEFAULT_AVATAR_HEIGHT * 0.25f; + const float RECOMMENDED_MAX_HEIGHT = DEFAULT_AVATAR_HEIGHT * 1.5f; + + const float avatarHeight = _avatar->getHeight(); + if (avatarHeight < RECOMMENDED_MIN_HEIGHT) { + _errors.push_back({ "Avatar is possibly too short.", DEFAULT_URL }); + } else if (avatarHeight > RECOMMENDED_MAX_HEIGHT) { + _errors.push_back({ "Avatar is possibly too tall.", DEFAULT_URL }); + } + + auto rig = &_avatar->getSkeletonModel()->getRig(); + + // HipsNotOnGround + auto hipsIndex = rig->indexOfJoint("Hips"); + if (hipsIndex >= 0) { + glm::vec3 hipsPosition; + if (rig->getJointPosition(hipsIndex, hipsPosition)) { + const auto hipJoint = avatarModel.joints.at(avatarModel.getJointIndex("Hips")); + + if (hipsPosition.y < HIPS_GROUND_MIN_Y) { + _errors.push_back({ "Hips are on ground.", DEFAULT_URL }); + } + } + } + + // HipsSpineChestNotCoincident + auto spineIndex = rig->indexOfJoint("Spine"); + auto chestIndex = rig->indexOfJoint("Spine1"); + if (hipsIndex >= 0 && spineIndex >= 0 && chestIndex >= 0) { + glm::vec3 hipsPosition; + glm::vec3 spinePosition; + glm::vec3 chestPosition; + if (rig->getJointPosition(hipsIndex, hipsPosition) && + rig->getJointPosition(spineIndex, spinePosition) && + rig->getJointPosition(chestIndex, chestPosition)) { + + const auto hipsToSpine = glm::length(hipsPosition - spinePosition); + const auto spineToChest = glm::length(spinePosition - chestPosition); + if (hipsToSpine < HIPS_SPINE_CHEST_MIN_SEPARATION && spineToChest < HIPS_SPINE_CHEST_MIN_SEPARATION) { + _errors.push_back({ "Hips/Spine/Chest overlap.", DEFAULT_URL }); + } + } + } + } + _avatar->deleteLater(); + _avatar = nullptr; + + auto mapping = resource->getMapping(); + + if (mapping.contains(JOINT_NAME_MAPPING_FIELD) && mapping[JOINT_NAME_MAPPING_FIELD].type() == QVariant::Hash) { + const auto& jointNameMappings = mapping[JOINT_NAME_MAPPING_FIELD].toHash(); + QStringList jointValues; + foreach(const auto& jointVariant, jointNameMappings.values()) { + jointValues << jointVariant.toString(); + } + + const auto& uniqueJointValues = jointValues.toSet(); + foreach (const auto& jointName, uniqueJointValues) { + if (jointValues.count(jointName) > 1) { + _errors.push_back({ tr("%1 is mapped multiple times.").arg(jointName), DEFAULT_URL }); + } + } + } + + if (!isDescendantOfJointWhenJointsExist("Spine", "Hips")) { + _errors.push_back({ "Spine is no child of Hips.", DEFAULT_URL }); + } + + if (!isDescendantOfJointWhenJointsExist("Spine1", "Spine")) { + _errors.push_back({ "Spine1 is no child of Spine.", DEFAULT_URL }); + } + + if (!isDescendantOfJointWhenJointsExist("Head", "Spine1")) { + _errors.push_back({ "Head is no child of Spine1.", DEFAULT_URL }); } } - // SCALE - const float RECOMMENDED_MIN_HEIGHT = DEFAULT_AVATAR_HEIGHT * 0.25f; - const float RECOMMENDED_MAX_HEIGHT = DEFAULT_AVATAR_HEIGHT * 1.5f; - - const float avatarHeight = avatarModel.bindExtents.largestDimension(); - if (avatarHeight < RECOMMENDED_MIN_HEIGHT) { - _errors.push_back({ "Avatar is possibly too small.", DEFAULT_URL }); - } else if (avatarHeight > RECOMMENDED_MAX_HEIGHT) { - _errors.push_back({ "Avatar is possibly too large.", DEFAULT_URL }); - } - - // TEXTURES - QStringList externalTextures{}; - QSet textureNames{}; - auto addTextureToList = [&externalTextures](hfm::Texture texture) mutable { - if (!texture.filename.isEmpty() && texture.content.isEmpty() && !externalTextures.contains(texture.name)) { - externalTextures << texture.name; + auto materialMappingHandled = [this]() mutable { + _materialMappingLoadedCount++; + // Continue diagnosing the textures as soon as the material mappings have tried to load. + if (_materialMappingLoadedCount == _materialMappingCount) { + // TEXTURES + diagnoseTextures(); } }; - - foreach(const HFMMaterial material, avatarModel.materials) { - addTextureToList(material.normalTexture); - addTextureToList(material.albedoTexture); - addTextureToList(material.opacityTexture); - addTextureToList(material.glossTexture); - addTextureToList(material.roughnessTexture); - addTextureToList(material.specularTexture); - addTextureToList(material.metallicTexture); - addTextureToList(material.emissiveTexture); - addTextureToList(material.occlusionTexture); - addTextureToList(material.scatteringTexture); - addTextureToList(material.lightmapTexture); - } - if (!externalTextures.empty()) { - // Check External Textures: - auto modelTexturesURLs = model->getTextures(); - _externalTextureCount = externalTextures.length(); - foreach(const QString textureKey, externalTextures) { - if (!modelTexturesURLs.contains(textureKey)) { - _missingTextureCount++; - _checkedTextureCount++; - continue; - } - - const QUrl textureURL = modelTexturesURLs[textureKey].toUrl(); - - auto textureResource = DependencyManager::get()->getTexture(textureURL); - auto checkTextureLoadingComplete = [this, DEFAULT_URL] () mutable { - qDebug() << "checkTextureLoadingComplete" << _checkedTextureCount << "/" << _externalTextureCount; - - if (_checkedTextureCount == _externalTextureCount) { - if (_missingTextureCount > 0) { - _errors.push_back({ tr("Missing %n texture(s).","", _missingTextureCount), DEFAULT_URL }); - } - if (_unsupportedTextureCount > 0) { - _errors.push_back({ tr("%n unsupported texture(s) found.", "", _unsupportedTextureCount), DEFAULT_URL }); - } - emit complete(getErrors()); - } - }; - - auto textureLoaded = [this, textureResource, checkTextureLoadingComplete] (bool success) mutable { - if (!success) { - auto normalizedURL = DependencyManager::get()->normalizeURL(textureResource->getURL()); - if (normalizedURL.isLocalFile()) { - QFile textureFile(normalizedURL.toLocalFile()); - if (textureFile.exists()) { - _unsupportedTextureCount++; - } else { - _missingTextureCount++; - } - } else { - _missingTextureCount++; - } - } - _checkedTextureCount++; - checkTextureLoadingComplete(); - }; - - if (textureResource) { - textureResource->refresh(); - if (textureResource->isLoaded()) { - textureLoaded(!textureResource->isFailed()); - } else { - connect(textureResource.data(), &NetworkTexture::finished, this, textureLoaded); - } + _materialMappingCount = (int)model->getMaterialMapping().size(); + _materialMappingLoadedCount = 0; + foreach(const auto& materialMapping, model->getMaterialMapping()) { + // refresh the texture mappings + auto materialMappingResource = materialMapping.second; + if (materialMappingResource) { + materialMappingResource->refresh(); + if (materialMappingResource->isLoaded()) { + materialMappingHandled(); } else { - _missingTextureCount++; - _checkedTextureCount++; - checkTextureLoadingComplete(); + connect(materialMappingResource.data(), &NetworkTexture::finished, this, [materialMappingHandled](bool success) mutable { + materialMappingHandled(); + }); } + } else { + materialMappingHandled(); } - } else { - emit complete(getErrors()); + } + if (_materialMappingCount == 0) { + // TEXTURES + diagnoseTextures(); } }; @@ -184,6 +307,125 @@ void AvatarDoctor::startDiagnosing() { } } +void AvatarDoctor::diagnoseTextures() { + const auto model = _model.data(); + const auto avatarModel = _model.data()->getHFMModel(); + QStringList externalTextures{}; + QSet textureNames{}; + int texturesFound = 0; + auto addTextureToList = [&externalTextures, &texturesFound](hfm::Texture texture) mutable { + if (texture.filename.isEmpty()) { + return; + } + if (texture.content.isEmpty() && !externalTextures.contains(texture.name)) { + externalTextures << texture.name; + } + texturesFound++; + }; + + foreach(const HFMMaterial material, avatarModel.materials) { + addTextureToList(material.normalTexture); + addTextureToList(material.albedoTexture); + addTextureToList(material.opacityTexture); + addTextureToList(material.glossTexture); + addTextureToList(material.roughnessTexture); + addTextureToList(material.specularTexture); + addTextureToList(material.metallicTexture); + addTextureToList(material.emissiveTexture); + addTextureToList(material.occlusionTexture); + addTextureToList(material.scatteringTexture); + addTextureToList(material.lightmapTexture); + } + + foreach(const auto& materialMapping, model->getMaterialMapping()) { + foreach(const auto& networkMaterial, materialMapping.second.data()->parsedMaterials.networkMaterials) { + foreach(const auto& textureMap, networkMaterial.second->getTextureMaps()) { + texturesFound++; + } + } + } + + auto normalizedURL = DependencyManager::get()->normalizeURL( + QUrl(avatarModel.originalURL)).resolved(QUrl("textures")); + + bool isTextureFolderEmpty = true; + if (normalizedURL.isLocalFile()) { + QDir texturesFolder(normalizedURL.toLocalFile()); + if (texturesFolder.exists()) { + isTextureFolderEmpty = texturesFolder.isEmpty(); + } + } + + if (texturesFound == 0 && !isTextureFolderEmpty) { + _errors.push_back({ tr("No textures assigned."), DEFAULT_URL }); + } + + if (!externalTextures.empty()) { + // Check External Textures: + auto modelTexturesURLs = model->getTextures(); + _externalTextureCount = externalTextures.length(); + foreach(const QString textureKey, externalTextures) { + if (!modelTexturesURLs.contains(textureKey)) { + _missingTextureCount++; + _checkedTextureCount++; + continue; + } + + const QUrl textureURL = modelTexturesURLs[textureKey].toUrl(); + + auto textureResource = DependencyManager::get()->getTexture(textureURL); + auto checkTextureLoadingComplete = [this]() mutable { + qDebug() << "checkTextureLoadingComplete" << _checkedTextureCount << "/" << _externalTextureCount; + + if (_checkedTextureCount == _externalTextureCount) { + if (_missingTextureCount > 0) { + _errors.push_back({ tr("Missing %n texture(s).","", _missingTextureCount), DEFAULT_URL }); + } + if (_unsupportedTextureCount > 0) { + _errors.push_back({ tr("%n unsupported texture(s) found.", "", _unsupportedTextureCount), + DEFAULT_URL }); + } + + emit complete(getErrors()); + } + }; + + auto textureLoaded = [this, textureResource, checkTextureLoadingComplete](bool success) mutable { + if (!success) { + auto normalizedURL = DependencyManager::get()->normalizeURL(textureResource->getURL()); + if (normalizedURL.isLocalFile()) { + QFile textureFile(normalizedURL.toLocalFile()); + if (textureFile.exists()) { + _unsupportedTextureCount++; + } else { + _missingTextureCount++; + } + } else { + _missingTextureCount++; + } + } + _checkedTextureCount++; + checkTextureLoadingComplete(); + }; + + if (textureResource) { + textureResource->refresh(); + if (textureResource->isLoaded()) { + textureLoaded(!textureResource->isFailed()); + } else { + connect(textureResource.data(), &NetworkTexture::finished, this, textureLoaded); + } + } else { + _missingTextureCount++; + _checkedTextureCount++; + checkTextureLoadingComplete(); + } + } + } else { + emit complete(getErrors()); + } +} + QVariantList AvatarDoctor::getErrors() const { QVariantList result; for (const auto& error : _errors) { diff --git a/interface/src/avatar/AvatarDoctor.h b/interface/src/avatar/AvatarDoctor.h index bebec32542..17397d99df 100644 --- a/interface/src/avatar/AvatarDoctor.h +++ b/interface/src/avatar/AvatarDoctor.h @@ -16,6 +16,7 @@ #include #include #include +#include struct AvatarDiagnosticResult { QString message; @@ -24,10 +25,25 @@ struct AvatarDiagnosticResult { Q_DECLARE_METATYPE(AvatarDiagnosticResult) Q_DECLARE_METATYPE(QVector) + +class DiagnosableAvatar: public Avatar { +public: + explicit DiagnosableAvatar(QThread* thread); + virtual ~DiagnosableAvatar(); + + void simulate(float deltaTime, bool inView) override { + + } + void rebuildCollisionShape() override { + + } + virtual void instantiableAvatar() override { }; +}; + class AvatarDoctor : public QObject { Q_OBJECT public: - AvatarDoctor(QUrl avatarFSTFileUrl); + AvatarDoctor(const QUrl& avatarFSTFileUrl); Q_INVOKABLE void startDiagnosing(); @@ -37,6 +53,8 @@ signals: void complete(QVariantList errors); private: + void diagnoseTextures(); + QUrl _avatarFSTFileUrl; QVector _errors; @@ -45,6 +63,13 @@ private: int _missingTextureCount = 0; int _unsupportedTextureCount = 0; + int _materialMappingCount = 0; + int _materialMappingLoadedCount = 0; + + DiagnosableAvatar* _avatar { nullptr }; + + GeometryResource::Pointer _model; + bool _isDiagnosing = false; }; From d985d1bff0ee5221986d0d4526c7e8cb586e71c8 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Mon, 4 Mar 2019 19:59:29 +0100 Subject: [PATCH 408/474] use Rig instead of Avatar for height calculations --- interface/src/avatar/AvatarDoctor.cpp | 99 +++++++++---------- interface/src/avatar/AvatarDoctor.h | 19 +--- libraries/animation/src/Rig.cpp | 49 +++++++++ libraries/animation/src/Rig.h | 3 + .../src/avatars-renderer/Avatar.cpp | 49 +-------- 5 files changed, 99 insertions(+), 120 deletions(-) diff --git a/interface/src/avatar/AvatarDoctor.cpp b/interface/src/avatar/AvatarDoctor.cpp index 66433f005b..f15c8bbc6c 100644 --- a/interface/src/avatar/AvatarDoctor.cpp +++ b/interface/src/avatar/AvatarDoctor.cpp @@ -12,8 +12,9 @@ #include "AvatarDoctor.h" #include #include +#include #include - +#include #include const int NETWORKED_JOINTS_LIMIT = 256; @@ -56,18 +57,6 @@ static QStringList HAND_MAPPING_SUFFIXES = { const QUrl DEFAULT_URL = QUrl("https://docs.highfidelity.com/create/avatars/create-avatars.html#create-your-own-avatar"); -DiagnosableAvatar::DiagnosableAvatar(QThread* thread) : Avatar(thread) { - // give the pointer to our head to inherited _headData variable from AvatarData - _headData = new Head(this); - _skeletonModel = std::make_shared(this, nullptr); - _skeletonModel->setLoadingPriority(MYAVATAR_LOADING_PRIORITY); - connect(_skeletonModel.get(), &Model::setURLFinished, this, &Avatar::setModelURLFinished); - connect(_skeletonModel.get(), &Model::rigReady, this, &Avatar::rigReady); - connect(_skeletonModel.get(), &Model::rigReset, this, &Avatar::rigReset); -} - -DiagnosableAvatar::~DiagnosableAvatar() = default; - AvatarDoctor::AvatarDoctor(const QUrl& avatarFSTFileUrl) : _avatarFSTFileUrl(avatarFSTFileUrl) { @@ -178,59 +167,61 @@ void AvatarDoctor::startDiagnosing() { if (checkJointAsymmetry(LEG_MAPPING_SUFFIXES)) { _errors.push_back({ "Asymmetrical leg bones.", DEFAULT_URL }); } - _avatar = new DiagnosableAvatar(QThread::currentThread()); - _avatar->setSkeletonModelURL(_avatarFSTFileUrl); - if (_avatar->getSkeletonModel()->updateGeometry()) { - // Rig has been fully loaded - // SCALE - const float RECOMMENDED_MIN_HEIGHT = DEFAULT_AVATAR_HEIGHT * 0.25f; - const float RECOMMENDED_MAX_HEIGHT = DEFAULT_AVATAR_HEIGHT * 1.5f; - const float avatarHeight = _avatar->getHeight(); - if (avatarHeight < RECOMMENDED_MIN_HEIGHT) { - _errors.push_back({ "Avatar is possibly too short.", DEFAULT_URL }); - } else if (avatarHeight > RECOMMENDED_MAX_HEIGHT) { - _errors.push_back({ "Avatar is possibly too tall.", DEFAULT_URL }); } - auto rig = &_avatar->getSkeletonModel()->getRig(); - // HipsNotOnGround - auto hipsIndex = rig->indexOfJoint("Hips"); - if (hipsIndex >= 0) { - glm::vec3 hipsPosition; - if (rig->getJointPosition(hipsIndex, hipsPosition)) { - const auto hipJoint = avatarModel.joints.at(avatarModel.getJointIndex("Hips")); + const auto rig = new Rig(); + rig->reset(avatarModel); + const float eyeHeight = rig->getUnscaledEyeHeight(); + const float ratio = eyeHeight / DEFAULT_AVATAR_HEIGHT; + const float avatarHeight = eyeHeight + ratio * DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD; + qDebug() << "avatarHeight = " << avatarHeight; - if (hipsPosition.y < HIPS_GROUND_MIN_Y) { - _errors.push_back({ "Hips are on ground.", DEFAULT_URL }); - } - } - } + // SCALE + const float RECOMMENDED_MIN_HEIGHT = DEFAULT_AVATAR_HEIGHT * 0.25f; + const float RECOMMENDED_MAX_HEIGHT = DEFAULT_AVATAR_HEIGHT * 1.5f; - // HipsSpineChestNotCoincident - auto spineIndex = rig->indexOfJoint("Spine"); - auto chestIndex = rig->indexOfJoint("Spine1"); - if (hipsIndex >= 0 && spineIndex >= 0 && chestIndex >= 0) { - glm::vec3 hipsPosition; - glm::vec3 spinePosition; - glm::vec3 chestPosition; - if (rig->getJointPosition(hipsIndex, hipsPosition) && - rig->getJointPosition(spineIndex, spinePosition) && - rig->getJointPosition(chestIndex, chestPosition)) { + if (avatarHeight < RECOMMENDED_MIN_HEIGHT) { + _errors.push_back({ "Avatar is possibly too short.", DEFAULT_URL }); + } else if (avatarHeight > RECOMMENDED_MAX_HEIGHT) { + _errors.push_back({ "Avatar is possibly too tall.", DEFAULT_URL }); + } - const auto hipsToSpine = glm::length(hipsPosition - spinePosition); - const auto spineToChest = glm::length(spinePosition - chestPosition); - if (hipsToSpine < HIPS_SPINE_CHEST_MIN_SEPARATION && spineToChest < HIPS_SPINE_CHEST_MIN_SEPARATION) { - _errors.push_back({ "Hips/Spine/Chest overlap.", DEFAULT_URL }); - } + // HipsNotOnGround + auto hipsIndex = rig->indexOfJoint("Hips"); + if (hipsIndex >= 0) { + glm::vec3 hipsPosition; + if (rig->getJointPosition(hipsIndex, hipsPosition)) { + const auto hipJoint = avatarModel.joints.at(avatarModel.getJointIndex("Hips")); + + if (hipsPosition.y < HIPS_GROUND_MIN_Y) { + _errors.push_back({ "Hips are on ground.", DEFAULT_URL }); } } } - _avatar->deleteLater(); - _avatar = nullptr; + + // HipsSpineChestNotCoincident + auto spineIndex = rig->indexOfJoint("Spine"); + auto chestIndex = rig->indexOfJoint("Spine1"); + if (hipsIndex >= 0 && spineIndex >= 0 && chestIndex >= 0) { + glm::vec3 hipsPosition; + glm::vec3 spinePosition; + glm::vec3 chestPosition; + if (rig->getJointPosition(hipsIndex, hipsPosition) && + rig->getJointPosition(spineIndex, spinePosition) && + rig->getJointPosition(chestIndex, chestPosition)) { + + const auto hipsToSpine = glm::length(hipsPosition - spinePosition); + const auto spineToChest = glm::length(spinePosition - chestPosition); + if (hipsToSpine < HIPS_SPINE_CHEST_MIN_SEPARATION && spineToChest < HIPS_SPINE_CHEST_MIN_SEPARATION) { + _errors.push_back({ "Hips/Spine/Chest overlap.", DEFAULT_URL }); + } + } + } + rig->deleteLater(); auto mapping = resource->getMapping(); diff --git a/interface/src/avatar/AvatarDoctor.h b/interface/src/avatar/AvatarDoctor.h index 17397d99df..780f600bed 100644 --- a/interface/src/avatar/AvatarDoctor.h +++ b/interface/src/avatar/AvatarDoctor.h @@ -16,7 +16,7 @@ #include #include #include -#include +#include "GeometryCache.h" struct AvatarDiagnosticResult { QString message; @@ -25,21 +25,6 @@ struct AvatarDiagnosticResult { Q_DECLARE_METATYPE(AvatarDiagnosticResult) Q_DECLARE_METATYPE(QVector) - -class DiagnosableAvatar: public Avatar { -public: - explicit DiagnosableAvatar(QThread* thread); - virtual ~DiagnosableAvatar(); - - void simulate(float deltaTime, bool inView) override { - - } - void rebuildCollisionShape() override { - - } - virtual void instantiableAvatar() override { }; -}; - class AvatarDoctor : public QObject { Q_OBJECT public: @@ -66,8 +51,6 @@ private: int _materialMappingCount = 0; int _materialMappingLoadedCount = 0; - DiagnosableAvatar* _avatar { nullptr }; - GeometryResource::Pointer _model; bool _isDiagnosing = false; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 0fe03c7074..07bdfde189 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -2179,3 +2179,52 @@ void Rig::initFlow(bool isActive) { _networkFlow.cleanUp(); } } + +float Rig::getUnscaledEyeHeight() const { + // Normally the model offset transform will contain the avatar scale factor, we explicitly remove it here. + AnimPose modelOffsetWithoutAvatarScale(glm::vec3(1.0f), getModelOffsetPose().rot(), getModelOffsetPose().trans()); + AnimPose geomToRigWithoutAvatarScale = modelOffsetWithoutAvatarScale * getGeometryOffsetPose(); + + // This factor can be used to scale distances in the geometry frame into the unscaled rig frame. + // Typically it will be the unit conversion from cm to m. + float scaleFactor = geomToRigWithoutAvatarScale.scale().x; // in practice this always a uniform scale factor. + + int headTopJoint = indexOfJoint("HeadTop_End"); + int headJoint = indexOfJoint("Head"); + int eyeJoint = indexOfJoint("LeftEye") != -1 ? indexOfJoint("LeftEye") : indexOfJoint("RightEye"); + int toeJoint = indexOfJoint("LeftToeBase") != -1 ? indexOfJoint("LeftToeBase") : indexOfJoint("RightToeBase"); + + // Makes assumption that the y = 0 plane in geometry is the ground plane. + // We also make that assumption in Rig::computeAvatarBoundingCapsule() + const float GROUND_Y = 0.0f; + + // Values from the skeleton are in the geometry coordinate frame. + auto skeleton = getAnimSkeleton(); + if (eyeJoint >= 0 && toeJoint >= 0) { + // Measure from eyes to toes. + float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - skeleton->getAbsoluteDefaultPose(toeJoint).trans().y; + return scaleFactor * eyeHeight; + } else if (eyeJoint >= 0) { + // Measure Eye joint to y = 0 plane. + float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - GROUND_Y; + return scaleFactor * eyeHeight; + } else if (headTopJoint >= 0 && toeJoint >= 0) { + // Measure from ToeBase joint to HeadTop_End joint, then remove forehead distance. + const float ratio = DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD / DEFAULT_AVATAR_HEIGHT; + float height = skeleton->getAbsoluteDefaultPose(headTopJoint).trans().y - skeleton->getAbsoluteDefaultPose(toeJoint).trans().y; + return scaleFactor * (height - height * ratio); + } else if (headTopJoint >= 0) { + // Measure from HeadTop_End joint to the ground, then remove forehead distance. + const float ratio = DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD / DEFAULT_AVATAR_HEIGHT; + float headHeight = skeleton->getAbsoluteDefaultPose(headTopJoint).trans().y - GROUND_Y; + return scaleFactor * (headHeight - headHeight * ratio); + } else if (headJoint >= 0) { + // Measure Head joint to the ground, then add in distance from neck to eye. + const float DEFAULT_AVATAR_NECK_TO_EYE = DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD - DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD; + const float ratio = DEFAULT_AVATAR_NECK_TO_EYE / DEFAULT_AVATAR_NECK_HEIGHT; + float neckHeight = skeleton->getAbsoluteDefaultPose(headJoint).trans().y - GROUND_Y; + return scaleFactor * (neckHeight + neckHeight * ratio); + } else { + return DEFAULT_AVATAR_EYE_HEIGHT; + } +} diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 2f0e2ad65b..df13ff5c2b 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -26,6 +26,7 @@ #include "SimpleMovingAverage.h" #include "AnimUtil.h" #include "Flow.h" +#include "AvatarConstants.h" class Rig; class AnimInverseKinematics; @@ -237,6 +238,8 @@ public: void initFlow(bool isActive); Flow& getFlow() { return _internalFlow; } + float getUnscaledEyeHeight() const; + signals: void onLoadComplete(); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index d3ae030296..f3e671143b 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -2040,54 +2040,7 @@ float Avatar::getUnscaledEyeHeightFromSkeleton() const { // TODO: if performance becomes a concern we can cache this value rather then computing it everytime. if (_skeletonModel) { - auto& rig = _skeletonModel->getRig(); - - // Normally the model offset transform will contain the avatar scale factor, we explicitly remove it here. - AnimPose modelOffsetWithoutAvatarScale(glm::vec3(1.0f), rig.getModelOffsetPose().rot(), rig.getModelOffsetPose().trans()); - AnimPose geomToRigWithoutAvatarScale = modelOffsetWithoutAvatarScale * rig.getGeometryOffsetPose(); - - // This factor can be used to scale distances in the geometry frame into the unscaled rig frame. - // Typically it will be the unit conversion from cm to m. - float scaleFactor = geomToRigWithoutAvatarScale.scale().x; // in practice this always a uniform scale factor. - - int headTopJoint = rig.indexOfJoint("HeadTop_End"); - int headJoint = rig.indexOfJoint("Head"); - int eyeJoint = rig.indexOfJoint("LeftEye") != -1 ? rig.indexOfJoint("LeftEye") : rig.indexOfJoint("RightEye"); - int toeJoint = rig.indexOfJoint("LeftToeBase") != -1 ? rig.indexOfJoint("LeftToeBase") : rig.indexOfJoint("RightToeBase"); - - // Makes assumption that the y = 0 plane in geometry is the ground plane. - // We also make that assumption in Rig::computeAvatarBoundingCapsule() - const float GROUND_Y = 0.0f; - - // Values from the skeleton are in the geometry coordinate frame. - auto skeleton = rig.getAnimSkeleton(); - if (eyeJoint >= 0 && toeJoint >= 0) { - // Measure from eyes to toes. - float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - skeleton->getAbsoluteDefaultPose(toeJoint).trans().y; - return scaleFactor * eyeHeight; - } else if (eyeJoint >= 0) { - // Measure Eye joint to y = 0 plane. - float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - GROUND_Y; - return scaleFactor * eyeHeight; - } else if (headTopJoint >= 0 && toeJoint >= 0) { - // Measure from ToeBase joint to HeadTop_End joint, then remove forehead distance. - const float ratio = DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD / DEFAULT_AVATAR_HEIGHT; - float height = skeleton->getAbsoluteDefaultPose(headTopJoint).trans().y - skeleton->getAbsoluteDefaultPose(toeJoint).trans().y; - return scaleFactor * (height - height * ratio); - } else if (headTopJoint >= 0) { - // Measure from HeadTop_End joint to the ground, then remove forehead distance. - const float ratio = DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD / DEFAULT_AVATAR_HEIGHT; - float headHeight = skeleton->getAbsoluteDefaultPose(headTopJoint).trans().y - GROUND_Y; - return scaleFactor * (headHeight - headHeight * ratio); - } else if (headJoint >= 0) { - // Measure Head joint to the ground, then add in distance from neck to eye. - const float DEFAULT_AVATAR_NECK_TO_EYE = DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD - DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD; - const float ratio = DEFAULT_AVATAR_NECK_TO_EYE / DEFAULT_AVATAR_NECK_HEIGHT; - float neckHeight = skeleton->getAbsoluteDefaultPose(headJoint).trans().y - GROUND_Y; - return scaleFactor * (neckHeight + neckHeight * ratio); - } else { - return DEFAULT_AVATAR_EYE_HEIGHT; - } + return _skeletonModel->getRig().getUnscaledEyeHeight(); } else { return DEFAULT_AVATAR_EYE_HEIGHT; } From c35e4d15ab8252f0ff46100bbe34b6f3f78065e9 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Wed, 6 Mar 2019 07:20:12 +0100 Subject: [PATCH 409/474] - warning grammar / no more texture folder content requirement for No textures assigned warning - resolved partial commit issue / Fix UNIX builds by removal of unused warning --- interface/src/avatar/AvatarDoctor.cpp | 29 ++++++++------------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/interface/src/avatar/AvatarDoctor.cpp b/interface/src/avatar/AvatarDoctor.cpp index f15c8bbc6c..45d05ad608 100644 --- a/interface/src/avatar/AvatarDoctor.cpp +++ b/interface/src/avatar/AvatarDoctor.cpp @@ -168,11 +168,6 @@ void AvatarDoctor::startDiagnosing() { _errors.push_back({ "Asymmetrical leg bones.", DEFAULT_URL }); } - - - } - - const auto rig = new Rig(); rig->reset(avatarModel); const float eyeHeight = rig->getUnscaledEyeHeight(); @@ -241,15 +236,15 @@ void AvatarDoctor::startDiagnosing() { } if (!isDescendantOfJointWhenJointsExist("Spine", "Hips")) { - _errors.push_back({ "Spine is no child of Hips.", DEFAULT_URL }); + _errors.push_back({ "Spine is not a child of Hips.", DEFAULT_URL }); } if (!isDescendantOfJointWhenJointsExist("Spine1", "Spine")) { - _errors.push_back({ "Spine1 is no child of Spine.", DEFAULT_URL }); + _errors.push_back({ "Spine1 is not a child of Spine.", DEFAULT_URL }); } if (!isDescendantOfJointWhenJointsExist("Head", "Spine1")) { - _errors.push_back({ "Head is no child of Spine1.", DEFAULT_URL }); + _errors.push_back({ "Head is not a child of Spine1.", DEFAULT_URL }); } } @@ -272,7 +267,9 @@ void AvatarDoctor::startDiagnosing() { if (materialMappingResource->isLoaded()) { materialMappingHandled(); } else { - connect(materialMappingResource.data(), &NetworkTexture::finished, this, [materialMappingHandled](bool success) mutable { + connect(materialMappingResource.data(), &NetworkTexture::finished, this, + [materialMappingHandled](bool success) mutable { + materialMappingHandled(); }); } @@ -330,24 +327,14 @@ void AvatarDoctor::diagnoseTextures() { foreach(const auto& materialMapping, model->getMaterialMapping()) { foreach(const auto& networkMaterial, materialMapping.second.data()->parsedMaterials.networkMaterials) { - foreach(const auto& textureMap, networkMaterial.second->getTextureMaps()) { - texturesFound++; - } + texturesFound += (int)networkMaterial.second->getTextureMaps().size(); } } auto normalizedURL = DependencyManager::get()->normalizeURL( QUrl(avatarModel.originalURL)).resolved(QUrl("textures")); - bool isTextureFolderEmpty = true; - if (normalizedURL.isLocalFile()) { - QDir texturesFolder(normalizedURL.toLocalFile()); - if (texturesFolder.exists()) { - isTextureFolderEmpty = texturesFolder.isEmpty(); - } - } - - if (texturesFound == 0 && !isTextureFolderEmpty) { + if (texturesFound == 0) { _errors.push_back({ tr("No textures assigned."), DEFAULT_URL }); } From f2bca5d6c6b94d3c749844a8f375ed15a81b419a Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Fri, 8 Mar 2019 07:32:41 +0100 Subject: [PATCH 410/474] added the multiple root joints warning --- interface/src/avatar/AvatarDoctor.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/interface/src/avatar/AvatarDoctor.cpp b/interface/src/avatar/AvatarDoctor.cpp index 45d05ad608..8c22780b52 100644 --- a/interface/src/avatar/AvatarDoctor.cpp +++ b/interface/src/avatar/AvatarDoctor.cpp @@ -168,12 +168,23 @@ void AvatarDoctor::startDiagnosing() { _errors.push_back({ "Asymmetrical leg bones.", DEFAULT_URL }); } + // Multiple skeleton root joints checkup + int skeletonRootJoints = 0; + foreach(const HFMJoint& joint, avatarModel.joints) { + if (joint.parentIndex == -1 && joint.isSkeletonJoint) { + skeletonRootJoints++; + } + } + + if (skeletonRootJoints > 1) { + _errors.push_back({ "Multiple root joints found.", DEFAULT_URL }); + } + const auto rig = new Rig(); rig->reset(avatarModel); const float eyeHeight = rig->getUnscaledEyeHeight(); const float ratio = eyeHeight / DEFAULT_AVATAR_HEIGHT; const float avatarHeight = eyeHeight + ratio * DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD; - qDebug() << "avatarHeight = " << avatarHeight; // SCALE const float RECOMMENDED_MIN_HEIGHT = DEFAULT_AVATAR_HEIGHT * 0.25f; From 571e7c1ecc1af5c7a0475ec7ee33c709a69bb5ce Mon Sep 17 00:00:00 2001 From: David Back Date: Fri, 8 Mar 2019 09:20:19 -0800 Subject: [PATCH 411/474] update avatar exporter package --- .../avatarExporter.unitypackage | Bin 15813 -> 15811 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tools/unity-avatar-exporter/avatarExporter.unitypackage b/tools/unity-avatar-exporter/avatarExporter.unitypackage index fd9c06f21233a8974171617d2a393e5ac8f4c6f1..c60a156711329e88a09234efca428eb3ceb6e89d 100644 GIT binary patch delta 15003 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 l;8mQ>b{4%|h>$8IK@_rTG+@_S|y@Nl! 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 From e8da6b5a0cf9c388f70ae9c04fabe26601fff944 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Fri, 8 Mar 2019 10:36:58 -0700 Subject: [PATCH 412/474] add getFlowData --- interface/src/avatar/MyAvatar.cpp | 57 ++++++++++++++++++++++++++++++- interface/src/avatar/MyAvatar.h | 2 ++ libraries/animation/src/Flow.cpp | 10 ++++++ libraries/animation/src/Flow.h | 4 +++ 4 files changed, 72 insertions(+), 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index faa9f88ae9..8d499cd8db 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -5331,9 +5331,13 @@ void MyAvatar::addAvatarHandsToFlow(const std::shared_ptr& otherAvatar) void MyAvatar::useFlow(bool isActive, bool isCollidable, const QVariantMap& physicsConfig, const QVariantMap& collisionsConfig) { if (_skeletonModel->isLoaded()) { - _skeletonModel->getRig().initFlow(isActive); auto &flow = _skeletonModel->getRig().getFlow(); auto &collisionSystem = flow.getCollisionSystem(); + if (!flow.isInitialized() && isActive) { + _skeletonModel->getRig().initFlow(true); + } else { + flow.setActive(isActive); + } collisionSystem.setActive(isCollidable); auto physicsGroups = physicsConfig.keys(); if (physicsGroups.size() > 0) { @@ -5384,6 +5388,57 @@ void MyAvatar::useFlow(bool isActive, bool isCollidable, const QVariantMap& phys } } +QVariantMap MyAvatar::getFlowData() { + QVariantMap flowData; + if (_skeletonModel->isLoaded()) { + auto jointNames = getJointNames(); + auto &flow = _skeletonModel->getRig().getFlow(); + auto &collisionSystem = flow.getCollisionSystem(); + bool initialized = flow.isInitialized(); + flowData.insert("initialized", initialized); + flowData.insert("active", flow.getActive()); + flowData.insert("colliding", collisionSystem.getActive()); + QVariantMap physicsData; + QVariantMap collisionsData; + QVariantMap threadData; + auto &groups = flow.getGroupSettings(); + for (auto &group : groups) { + QVariantMap settingsObject; + QString groupName = group.first; + FlowPhysicsSettings groupSettings = group.second; + settingsObject.insert("active", groupSettings._active); + settingsObject.insert("damping", groupSettings._damping); + settingsObject.insert("delta", groupSettings._delta); + settingsObject.insert("gravity", groupSettings._gravity); + settingsObject.insert("inertia", groupSettings._inertia); + settingsObject.insert("radius", groupSettings._radius); + settingsObject.insert("stiffness", groupSettings._stiffness); + physicsData.insert(groupName, settingsObject); + } + auto &collisions = collisionSystem.getCollisions(); + for (auto &collision : collisions) { + QVariantMap collisionObject; + collisionObject.insert("offset", vec3toVariant(collision._offset)); + collisionObject.insert("radius", collision._radius); + collisionObject.insert("jointIndex", collision._jointIndex); + QString jointName = jointNames.size() > collision._jointIndex ? jointNames[collision._jointIndex] : "unknown"; + collisionsData.insert(jointName, collisionObject); + } + int count = 0; + for (auto &thread : flow.getThreads()) { + QVariantList indices; + for (int index : thread._joints) { + indices.append(index); + } + threadData.insert(thread._jointsPointer->at(thread._joints[0]).getName(), indices); + } + flowData.insert("physics", physicsData); + flowData.insert("collisions", collisionsData); + flowData.insert("threads", threadData); + } + return flowData; +} + void MyAvatar::sendPacket(const QUuid& entityID, const EntityItemProperties& properties) const { auto treeRenderer = DependencyManager::get(); EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 2c8dedd430..26aea8bf6e 100755 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1198,6 +1198,8 @@ public: */ Q_INVOKABLE void useFlow(bool isActive, bool isCollidable, const QVariantMap& physicsConfig = QVariantMap(), const QVariantMap& collisionsConfig = QVariantMap()); + Q_INVOKABLE QVariantMap getFlowData(); + public slots: /**jsdoc diff --git a/libraries/animation/src/Flow.cpp b/libraries/animation/src/Flow.cpp index 3bb80b9375..ddc3354a2f 100644 --- a/libraries/animation/src/Flow.cpp +++ b/libraries/animation/src/Flow.cpp @@ -509,6 +509,7 @@ void Flow::calculateConstraints(const std::shared_ptr& skeleton, auto flowJoint = FlowJoint(i, parentIndex, -1, name, group, jointSettings); _flowJointData.insert(std::pair(i, flowJoint)); } + updateGroupSettings(group, jointSettings); } } else { if (PRESET_COLLISION_DATA.find(name) != PRESET_COLLISION_DATA.end()) { @@ -727,6 +728,7 @@ void Flow::setPhysicsSettingsForGroup(const QString& group, const FlowPhysicsSet joint.second.setSettings(settings); } } + updateGroupSettings(group, settings); } bool Flow::getJointPositionInWorldFrame(const AnimPoseVec& absolutePoses, int jointIndex, glm::vec3& position, glm::vec3 translation, glm::quat rotation) const { @@ -780,4 +782,12 @@ Flow& Flow::operator=(const Flow& otherFlow) { } } return *this; +} + +void Flow::updateGroupSettings(const QString& group, const FlowPhysicsSettings& settings) { + if (_groupSettings.find(group) != _groupSettings.end()) { + _groupSettings.insert(std::pair(group, settings)); + } else { + _groupSettings[group] = settings; + } } \ No newline at end of file diff --git a/libraries/animation/src/Flow.h b/libraries/animation/src/Flow.h index 35464e9420..d2eaaa22d9 100644 --- a/libraries/animation/src/Flow.h +++ b/libraries/animation/src/Flow.h @@ -149,6 +149,7 @@ public: void setCollisionSettingsByJoint(int jointIndex, const FlowCollisionSettings& settings); void setActive(bool active) { _active = active; } bool getActive() const { return _active; } + const std::vector& getCollisions() const { return _selfCollisions; } protected: std::vector _selfCollisions; std::vector _othersCollisions; @@ -293,6 +294,7 @@ public: void setOthersCollision(const QUuid& otherId, int jointIndex, const glm::vec3& position); FlowCollisionSystem& getCollisionSystem() { return _collisionSystem; } void setPhysicsSettingsForGroup(const QString& group, const FlowPhysicsSettings& settings); + const std::map& getGroupSettings() const { return _groupSettings; } void cleanUp(); signals: @@ -309,6 +311,7 @@ private: void setJoints(AnimPoseVec& relativePoses, const std::vector& overrideFlags); void updateJoints(AnimPoseVec& relativePoses, AnimPoseVec& absolutePoses); bool updateRootFramePositions(const AnimPoseVec& absolutePoses, size_t threadIndex); + void updateGroupSettings(const QString& group, const FlowPhysicsSettings& settings); void setScale(float scale); float _scale { 1.0f }; @@ -316,6 +319,7 @@ private: glm::vec3 _entityPosition; glm::quat _entityRotation; std::map _flowJointData; + std::map _groupSettings; std::vector _jointThreads; std::vector _flowJointKeywords; FlowCollisionSystem _collisionSystem; From 5fd2b0699ab61023ce9c494c627bdadfe0fb4a4c Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 7 Mar 2019 22:44:29 -0800 Subject: [PATCH 413/474] Add more complete support for .tga files --- libraries/image/src/image/Image.cpp | 12 ++ libraries/image/src/image/TGAReader.cpp | 186 ++++++++++++++++++++++++ libraries/image/src/image/TGAReader.h | 23 +++ 3 files changed, 221 insertions(+) create mode 100644 libraries/image/src/image/TGAReader.cpp create mode 100644 libraries/image/src/image/TGAReader.h diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp index a2161caec9..6aa09c4d0f 100644 --- a/libraries/image/src/image/Image.cpp +++ b/libraries/image/src/image/Image.cpp @@ -25,6 +25,8 @@ #include #include +#include "TGAReader.h" + #include "ImageLogging.h" using namespace gpu; @@ -203,6 +205,16 @@ QImage processRawImageData(QIODevice& content, const std::string& filename) { // Help the QImage loader by extracting the image file format from the url filename ext. // Some tga are not created properly without it. auto filenameExtension = filename.substr(filename.find_last_of('.') + 1); + content.open(QIODevice::ReadOnly); + + if (filenameExtension == "tga") { + QImage image = image::readTGA(content); + if (!image.isNull()) { + return image; + } + content.reset(); + } + QImageReader imageReader(&content, filenameExtension.c_str()); if (imageReader.canRead()) { diff --git a/libraries/image/src/image/TGAReader.cpp b/libraries/image/src/image/TGAReader.cpp new file mode 100644 index 0000000000..a496faf9e4 --- /dev/null +++ b/libraries/image/src/image/TGAReader.cpp @@ -0,0 +1,186 @@ +// +// TGAReader.cpp +// image/src/image +// +// Created by Ryan Huffman +// Copyright 2019 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 "TGAReader.h" + +#include +#include + +QImage image::readTGA(QIODevice& content) { + enum class TGAImageType : uint8_t { + NoImageData = 0, + UncompressedColorMapped = 1, + UncompressedTrueColor = 2, + UncompressedBlackWhite = 3, + RunLengthEncodedColorMapped = 9, + RunLengthEncodedTrueColor = 10, + RunLengthEncodedBlackWhite = 11, + }; + + struct TGAHeader { + uint8_t idLength; + uint8_t colorMapType; + TGAImageType imageType; + struct { + uint64_t firstEntryIndex : 16; + uint64_t length : 16; + uint64_t entrySize : 8; + } colorMap; + uint16_t xOrigin; + uint16_t yOrigin; + uint16_t width; + uint16_t height; + uint8_t pixelDepth; + struct { + uint8_t attributeBitsPerPixel : 4; + uint8_t orientation : 2; + uint8_t padding : 2; + } imageDescriptor; + }; + + constexpr bool WANT_DEBUG { false }; + constexpr size_t TGA_HEADER_SIZE_BYTES { 18 }; + + TGAHeader header; + + if (content.isSequential()) { + qWarning() << "TGA - Sequential devices are not supported for reading"; + return QImage(); + } + + if (content.bytesAvailable() < TGA_HEADER_SIZE_BYTES) { + qWarning() << "TGA - Unexpectedly reached end of file"; + return QImage(); + } + + content.read((char*)&header.idLength, 1); + content.read((char*)&header.colorMapType, 1); + content.read((char*)&header.imageType, 1); + content.read((char*)&header.colorMap, 5); + content.read((char*)&header.xOrigin, 2); + content.read((char*)&header.yOrigin, 2); + content.read((char*)&header.width, 2); + content.read((char*)&header.height, 2); + content.read((char*)&header.pixelDepth, 1); + content.read((char*)&header.imageDescriptor, 1); + + if (WANT_DEBUG) { + qDebug() << "Id Length: " << (int)header.idLength; + qDebug() << "Color map: " << (int)header.colorMap.firstEntryIndex << header.colorMap.length << header.colorMap.entrySize; + qDebug() << "Color map type: " << (int)header.colorMapType; + qDebug() << "Image type: " << (int)header.imageType; + qDebug() << "Origin: " << header.xOrigin << header.yOrigin; + qDebug() << "Size: " << header.width << header.height; + qDebug() << "Depth: " << header.pixelDepth; + qDebug() << "Image desc: " << header.imageDescriptor.attributeBitsPerPixel << header.imageDescriptor.orientation; + } + + if (header.xOrigin != 0 || header.yOrigin != 0) { + qWarning() << "TGA - origin not supporter"; + return QImage(); + } + + if (!(header.pixelDepth == 24 && header.imageDescriptor.attributeBitsPerPixel == 0) && header.pixelDepth != 32) { + qWarning() << "TGA - Only pixel depths of 24 (with no alpha) and 32 bits are supported"; + return QImage(); + } + + if (header.imageDescriptor.attributeBitsPerPixel != 0 && header.imageDescriptor.attributeBitsPerPixel != 8) { + qWarning() << "TGA - Only 0 or 8 bits for the alpha channel is supported"; + return QImage(); + } + + char alphaMask = header.imageDescriptor.attributeBitsPerPixel == 8 ? 0x00 : 0xFF; + int bytesPerPixel = header.pixelDepth / 8; + + content.skip(header.idLength); + if (header.imageType == TGAImageType::UncompressedTrueColor) { + qint64 minimumSize = header.width * header.height * bytesPerPixel; + if (content.bytesAvailable() < minimumSize) { + qWarning() << "TGA - Unexpectedly reached end of file"; + return QImage(); + } + + QImage image{ header.width, header.height, QImage::Format_ARGB32 }; + for (int y = 0; y < header.height; ++y) { + char* line = (char*)image.scanLine(y); + for (int x = 0; x < header.width; ++x) { + content.read(line, bytesPerPixel); + *(line + 3) |= alphaMask; + + line += 4; + } + } + return image; + } else if (header.imageType == TGAImageType::RunLengthEncodedTrueColor) { + QImage image{ header.width, header.height, QImage::Format_ARGB32 }; + + for (int y = 0; y < header.height; ++y) { + char* line = (char*)image.scanLine(y); + int col = 0; + while (col < header.width) { + constexpr char IS_REPETITION_MASK{ (char)0x80 }; + constexpr char LENGTH_MASK{ (char)0x7f }; + char repetition; + if (content.read(&repetition, 1) != 1) { + qWarning() << "TGA - Unexpectedly reached end of file"; + return QImage(); + } + bool isRepetition = repetition & IS_REPETITION_MASK; + + // The length in `repetition` is always 1 less than the number of following pixels, + // so we need to increment it by 1. Because of this, the length is never 0. + int length = (repetition & LENGTH_MASK) + 1; + + if (isRepetition) { + // Read into temporary buffer + char color[4]; + if (content.read(color, bytesPerPixel) != bytesPerPixel) { + qWarning() << "TGA - Unexpectedly reached end of file"; + return QImage(); + } + color[3] |= alphaMask; + + // Copy `length` number of times + col += length; + while (length-- > 0) { + *line = color[0]; + *(line + 1) = color[1]; + *(line + 2) = color[2]; + *(line + 3) = color[3]; + + line += 4; + } + } else { + qint64 minimumSize = length * bytesPerPixel; + if (content.bytesAvailable() < minimumSize) { + qWarning() << "TGA - Unexpectedly reached end of file"; + return QImage(); + } + + // Read in `length` number of pixels + col += length; + while (length-- > 0) { + content.read(line, bytesPerPixel); + *(line + 3) |= alphaMask; + + line += 4; + } + } + } + } + return image; + } else { + qWarning() << "TGA - Unsupported image type: " << (int)header.imageType; + } + + return QImage(); +} diff --git a/libraries/image/src/image/TGAReader.h b/libraries/image/src/image/TGAReader.h new file mode 100644 index 0000000000..11e7678264 --- /dev/null +++ b/libraries/image/src/image/TGAReader.h @@ -0,0 +1,23 @@ +// +// TGAReader.h +// image/src/image +// +// Created by Ryan Huffman +// Copyright 2019 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_image_TGAReader_h +#define hifi_image_TGAReader_h + +#include + +namespace image { + +QImage readTGA(QIODevice& contents); + +} + +#endif // hifi_image_TGAReader_h From c55811ced521d3c0582ccde5155842d0339374ff Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 8 Mar 2019 09:36:20 -0800 Subject: [PATCH 414/474] Add support for TGA orientation --- libraries/image/src/image/TGAReader.cpp | 23 +++++++++++++++++++---- libraries/image/src/image/TGAReader.h | 1 + 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/libraries/image/src/image/TGAReader.cpp b/libraries/image/src/image/TGAReader.cpp index a496faf9e4..a230185b49 100644 --- a/libraries/image/src/image/TGAReader.cpp +++ b/libraries/image/src/image/TGAReader.cpp @@ -24,6 +24,10 @@ QImage image::readTGA(QIODevice& content) { RunLengthEncodedTrueColor = 10, RunLengthEncodedBlackWhite = 11, }; + enum class TGAOrientation : uint8_t { + BottomLeft = 0, + TopLeft = 1, + }; struct TGAHeader { uint8_t idLength; @@ -41,7 +45,8 @@ QImage image::readTGA(QIODevice& content) { uint8_t pixelDepth; struct { uint8_t attributeBitsPerPixel : 4; - uint8_t orientation : 2; + uint8_t reserved : 1; + TGAOrientation orientation : 1; uint8_t padding : 2; } imageDescriptor; }; @@ -80,7 +85,7 @@ QImage image::readTGA(QIODevice& content) { qDebug() << "Origin: " << header.xOrigin << header.yOrigin; qDebug() << "Size: " << header.width << header.height; qDebug() << "Depth: " << header.pixelDepth; - qDebug() << "Image desc: " << header.imageDescriptor.attributeBitsPerPixel << header.imageDescriptor.orientation; + qDebug() << "Image desc: " << header.imageDescriptor.attributeBitsPerPixel << (int)header.imageDescriptor.orientation; } if (header.xOrigin != 0 || header.yOrigin != 0) { @@ -110,8 +115,13 @@ QImage image::readTGA(QIODevice& content) { } QImage image{ header.width, header.height, QImage::Format_ARGB32 }; + char* line; for (int y = 0; y < header.height; ++y) { - char* line = (char*)image.scanLine(y); + if (header.imageDescriptor.orientation == TGAOrientation::BottomLeft) { + line = (char*)image.scanLine(header.height - y - 1); + } else { + line = (char*)image.scanLine(y); + } for (int x = 0; x < header.width; ++x) { content.read(line, bytesPerPixel); *(line + 3) |= alphaMask; @@ -124,7 +134,12 @@ QImage image::readTGA(QIODevice& content) { QImage image{ header.width, header.height, QImage::Format_ARGB32 }; for (int y = 0; y < header.height; ++y) { - char* line = (char*)image.scanLine(y); + char* line; + if (header.imageDescriptor.orientation == TGAOrientation::BottomLeft) { + line = (char*)image.scanLine(header.height - y - 1); + } else { + line = (char*)image.scanLine(y); + } int col = 0; while (col < header.width) { constexpr char IS_REPETITION_MASK{ (char)0x80 }; diff --git a/libraries/image/src/image/TGAReader.h b/libraries/image/src/image/TGAReader.h index 11e7678264..8019be7f0b 100644 --- a/libraries/image/src/image/TGAReader.h +++ b/libraries/image/src/image/TGAReader.h @@ -16,6 +16,7 @@ namespace image { +// TODO Move this into a plugin that QImageReader can use QImage readTGA(QIODevice& contents); } From 750dbfae21785da6c0a9757b44cd117f0bd9fca2 Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Fri, 8 Mar 2019 10:48:42 -0800 Subject: [PATCH 415/474] Stand-alone Tags: Throw up popup for first unoptimized domain Throw up a popup in stand-alone builds when user attempts to go to an unoptimized domain. --- interface/resources/qml/hifi/Card.qml | 2 +- .../hifi/commerce/marketplace/Marketplace.qml | 4 +- .../resources/qml/hifi/tablet/TADLightbox.qml | 144 ++++++++++++++++++ .../qml/hifi/tablet/TabletAddressDialog.qml | 47 ++++-- interface/src/Application.cpp | 1 + 5 files changed, 185 insertions(+), 13 deletions(-) create mode 100644 interface/resources/qml/hifi/tablet/TADLightbox.qml diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml index 1c0424a691..fc49bcf048 100644 --- a/interface/resources/qml/hifi/Card.qml +++ b/interface/resources/qml/hifi/Card.qml @@ -269,7 +269,7 @@ Item { hoverEnabled: false onClicked: { Tablet.playSound(TabletEnums.ButtonClick); - goFunction("hifi://" + hifiUrl); + goFunction("hifi://" + hifiUrl, standaloneOptimized); } } diff --git a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml index 2206dfcb99..9c5e1aa898 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml @@ -92,11 +92,11 @@ Rectangle { }); categoriesModel.append({ id: -1, - name: "Standalone Optimized" + name: "Stand-alone Optimized" }); categoriesModel.append({ id: -1, - name: "Standalone Compatible" + name: "Stand-alone Compatible" }); result.data.items.forEach(function(category) { categoriesModel.append({ diff --git a/interface/resources/qml/hifi/tablet/TADLightbox.qml b/interface/resources/qml/hifi/tablet/TADLightbox.qml new file mode 100644 index 0000000000..35a01aeec3 --- /dev/null +++ b/interface/resources/qml/hifi/tablet/TADLightbox.qml @@ -0,0 +1,144 @@ +// +// TADLightbox.qml +// qml/hifi/tablet +// +// TADLightbox +// +// Created by Roxanne Skelly on 2019-03-07 +// Copyright 2019 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 +// + +import Hifi 1.0 as Hifi +import QtQuick 2.5 +import QtGraphicalEffects 1.0 +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit +import "qrc:////qml//controls" as HifiControls + +// references XXX from root context + +Rectangle { + property string titleText; + property string bodyImageSource; + property string bodyText; + property string button1color: hifi.buttons.noneBorderlessGray; + property string button1text; + property var button1method; + property string button2color: hifi.buttons.blue; + property string button2text; + property var button2method; + property string buttonLayout: "leftright"; + + id: root; + visible: false; + anchors.fill: parent; + color: Qt.rgba(0, 0, 0, 0.5); + z: 999; + + HifiConstants { id: hifi; } + + // This object is always used in a popup. + // This MouseArea is used to prevent a user from being + // able to click on a button/mouseArea underneath the popup. + MouseArea { + anchors.fill: parent; + propagateComposedEvents: false; + hoverEnabled: true; + } + + Rectangle { + anchors.centerIn: parent; + width: 376; + height: childrenRect.height + 30; + color: "white"; + + RalewaySemiBold { + id: titleText; + text: root.titleText; + anchors.top: parent.top; + anchors.topMargin: 30; + anchors.left: parent.left; + anchors.leftMargin: 30; + anchors.right: parent.right; + anchors.rightMargin: 30; + height: paintedHeight; + color: hifi.colors.black; + size: 24; + verticalAlignment: Text.AlignTop; + wrapMode: Text.WordWrap; + } + + RalewayRegular { + id: bodyText; + text: root.bodyText; + anchors.top: root.bodyImageSource ? bodyImage.bottom : (root.titleText ? titleText.bottom : parent.top); + anchors.topMargin: root.bodyImageSource ? 20 : (root.titleText ? 20 : 30); + anchors.left: parent.left; + anchors.leftMargin: 30; + anchors.right: parent.right; + anchors.rightMargin: 30; + height: paintedHeight; + color: hifi.colors.black; + size: 20; + verticalAlignment: Text.AlignTop; + wrapMode: Text.WordWrap; + + } + + Item { + id: buttons; + anchors.top: bodyText.bottom; + anchors.topMargin: 30; + anchors.left: parent.left; + anchors.right: parent.right; + height: root.buttonLayout === "leftright" ? 70 : 150; + + // Button 1 + HifiControlsUit.Button { + id: button1; + color: root.button1color; + colorScheme: hifi.colorSchemes.light; + anchors.top: root.buttonLayout === "leftright" ? parent.top : parent.top; + anchors.left: parent.left; + anchors.leftMargin: root.buttonLayout === "leftright" ? 30 : 10; + anchors.right: root.buttonLayout === "leftright" ? undefined : parent.right; + anchors.rightMargin: root.buttonLayout === "leftright" ? undefined : 10; + width: root.buttonLayout === "leftright" ? (root.button2text ? parent.width/2 - anchors.leftMargin*2 : parent.width - anchors.leftMargin * 2) : + (undefined); + height: 40; + text: root.button1text; + onClicked: { + button1method(); + } + visible: (root.button1text !== ""); + } + + // Button 2 + HifiControlsUit.Button { + id: button2; + visible: root.button2text; + color: root.button2color; + colorScheme: hifi.colorSchemes.light; + anchors.top: root.buttonLayout === "leftright" ? parent.top : button1.bottom; + anchors.topMargin: root.buttonLayout === "leftright" ? undefined : 20; + anchors.left: root.buttonLayout === "leftright" ? undefined : parent.left; + anchors.leftMargin: root.buttonLayout === "leftright" ? undefined : 10; + anchors.right: parent.right; + anchors.rightMargin: root.buttonLayout === "leftright" ? 30 : 10; + width: root.buttonLayout === "leftright" ? parent.width/2 - anchors.rightMargin*2 : undefined; + height: 40; + text: root.button2text; + onClicked: { + button2method(); + } + } + } + } + + // + // FUNCTION DEFINITIONS END + // +} diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index ab0a98a8c5..311d20955b 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -73,7 +73,7 @@ StackView { function resetAfterTeleport() { //storyCardFrame.shown = root.shown = false; } - function goCard(targetString) { + function goCard(targetString, standaloneOptimized) { if (0 !== targetString.indexOf('hifi://')) { var card = tabletWebView.createObject(); card.url = addressBarDialog.metaverseServerUrl + targetString; @@ -82,7 +82,7 @@ StackView { return; } location.text = targetString; - toggleOrGo(targetString, true); + toggleOrGo(targetString, true, standaloneOptimized); clearAddressLineTimer.start(); } @@ -392,7 +392,18 @@ StackView { right: parent.right } } + } + TADLightbox { + id: unoptimizedDomain + titleText: "Unoptimized Domain" + bodyText: "You're trying to access a place that hasn't been optimized for your device. Are you sure you want to continue." + button1text: "CANCEL" + button2text: "YES CONTINUE" + visible: false + button1method: function() { + visible = false; + } } function updateLocationText(enteringAddress) { @@ -407,14 +418,30 @@ StackView { } } - function toggleOrGo(address, fromSuggestions) { - if (address !== undefined && address !== "") { - addressBarDialog.loadAddress(address, fromSuggestions); - clearAddressLineTimer.start(); - } else if (addressLine.text !== "") { - addressBarDialog.loadAddress(addressLine.text, fromSuggestions); - clearAddressLineTimer.start(); + function toggleOrGo(address, fromSuggestions, standaloneOptimized) { + + var goTarget = function () { + if (address !== undefined && address !== "") { + addressBarDialog.loadAddress(address, fromSuggestions); + clearAddressLineTimer.start(); + } else if (addressLine.text !== "") { + addressBarDialog.loadAddress(addressLine.text, fromSuggestions); + clearAddressLineTimer.start(); + } + DialogsManager.hideAddressBar(); + } + + unoptimizedDomain.button2method = function() { + Settings.setValue("ShowUnoptimizedDomainWarning", false); + goTarget(); + } + + var showPopup = PlatformInfo.isStandalone() && !standaloneOptimized && Settings.getValue("ShowUnoptimizedDomainWarning", true); + + if(!showPopup) { + goTarget(); + } else { + unoptimizedDomain.visible = true; } - DialogsManager.hideAddressBar(); } } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c115e4c4d3..5fd90503e3 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3036,6 +3036,7 @@ void Application::initializeUi() { QUrl{ "hifi/commerce/purchases/Purchases.qml" }, QUrl{ "hifi/commerce/wallet/Wallet.qml" }, QUrl{ "hifi/commerce/wallet/WalletHome.qml" }, + QUrl{ "hifi/tablet/TabletAddressDialog.qml" }, }, platformInfoCallback); QmlContextCallback ttsCallback = [](QQmlContext* context) { From a977bb6dc8f5708b12d7fec393f5fdf14a5287a5 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Fri, 8 Mar 2019 12:39:57 -0700 Subject: [PATCH 416/474] remove unuse variable --- interface/src/avatar/MyAvatar.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 8d499cd8db..b596a363f4 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -5424,7 +5424,6 @@ QVariantMap MyAvatar::getFlowData() { QString jointName = jointNames.size() > collision._jointIndex ? jointNames[collision._jointIndex] : "unknown"; collisionsData.insert(jointName, collisionObject); } - int count = 0; for (auto &thread : flow.getThreads()) { QVariantList indices; for (int index : thread._joints) { From cbe920774f90fc917369c2c69d9fc692e3b31f4e Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Fri, 8 Mar 2019 12:45:29 -0700 Subject: [PATCH 417/474] add getFlowData jsdoc --- interface/src/avatar/MyAvatar.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 042a5c2a3c..c67cabe4f5 100755 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1198,6 +1198,10 @@ public: */ Q_INVOKABLE void useFlow(bool isActive, bool isCollidable, const QVariantMap& physicsConfig = QVariantMap(), const QVariantMap& collisionsConfig = QVariantMap()); + /**jsdoc + * @function MyAvatar.getFlowData + * @returns {object} + */ Q_INVOKABLE QVariantMap getFlowData(); public slots: From 6b18b224339dae7393af260e96ee51e390c00b85 Mon Sep 17 00:00:00 2001 From: amantley Date: Wed, 6 Mar 2019 17:26:46 -0800 Subject: [PATCH 418/474] loading jointRotationOffset2 from FST and deal with the shadow joints --- .../src/model-baker/PrepareJointsTask.cpp | 53 ++++++++++++++++--- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp b/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp index a896766058..b8bcdb386e 100644 --- a/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp +++ b/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp @@ -29,8 +29,14 @@ QMap getJointNameMapping(const QVariantHash& mapping) { QMap getJointRotationOffsets(const QVariantHash& mapping) { QMap jointRotationOffsets; static const QString JOINT_ROTATION_OFFSET_FIELD = "jointRotationOffset"; - if (!mapping.isEmpty() && mapping.contains(JOINT_ROTATION_OFFSET_FIELD) && mapping[JOINT_ROTATION_OFFSET_FIELD].type() == QVariant::Hash) { - auto offsets = mapping[JOINT_ROTATION_OFFSET_FIELD].toHash(); + static const QString JOINT_ROTATION_OFFSET2_FIELD = "jointRotationOffset2"; + if (!mapping.isEmpty() && ((mapping.contains(JOINT_ROTATION_OFFSET_FIELD) && mapping[JOINT_ROTATION_OFFSET_FIELD].type() == QVariant::Hash) || (mapping.contains(JOINT_ROTATION_OFFSET2_FIELD) && mapping[JOINT_ROTATION_OFFSET2_FIELD].type() == QVariant::Hash))) { + QHash offsets; + if (mapping.contains(JOINT_ROTATION_OFFSET_FIELD)) { + offsets = mapping[JOINT_ROTATION_OFFSET_FIELD].toHash(); + } else { + offsets = mapping[JOINT_ROTATION_OFFSET2_FIELD].toHash(); + } for (auto itr = offsets.begin(); itr != offsets.end(); itr++) { QString jointName = itr.key(); QString line = itr.value().toString(); @@ -57,6 +63,15 @@ void PrepareJointsTask::run(const baker::BakeContextPointer& context, const Inpu auto& jointRotationOffsets = output.edit1(); auto& jointIndices = output.edit2(); + bool newJointRot = false; + static const QString JOINT_ROTATION_OFFSET2_FIELD = "jointRotationOffset2"; + QVariantHash fstHashMap = mapping.second; + if (fstHashMap.contains(JOINT_ROTATION_OFFSET2_FIELD)) { + newJointRot = true; + } else { + newJointRot = false; + } + // Get joint renames auto jointNameMapping = getJointNameMapping(mapping.second); // Apply joint metadata from FST file mappings @@ -64,11 +79,12 @@ void PrepareJointsTask::run(const baker::BakeContextPointer& context, const Inpu jointsOut.push_back(jointIn); auto& jointOut = jointsOut.back(); - auto jointNameMapKey = jointNameMapping.key(jointIn.name); - if (jointNameMapping.contains(jointNameMapKey)) { - jointOut.name = jointNameMapKey; + if (!newJointRot) { + auto jointNameMapKey = jointNameMapping.key(jointIn.name); + if (jointNameMapping.contains(jointNameMapKey)) { + jointOut.name = jointNameMapKey; + } } - jointIndices.insert(jointOut.name, (int)jointsOut.size()); } @@ -77,10 +93,33 @@ void PrepareJointsTask::run(const baker::BakeContextPointer& context, const Inpu for (auto itr = offsets.begin(); itr != offsets.end(); itr++) { QString jointName = itr.key(); int jointIndex = jointIndices.value(jointName) - 1; - if (jointIndex != -1) { + if (jointIndex >= 0) { glm::quat rotationOffset = itr.value(); jointRotationOffsets.insert(jointIndex, rotationOffset); qCDebug(model_baker) << "Joint Rotation Offset added to Rig._jointRotationOffsets : " << " jointName: " << jointName << " jointIndex: " << jointIndex << " rotation offset: " << rotationOffset; } } + + if (newJointRot) { + for (const auto& jointIn : jointsIn) { + + auto jointNameMapKey = jointNameMapping.key(jointIn.name); + int mappedIndex = jointIndices.value(jointIn.name); + if (jointNameMapping.contains(jointNameMapKey)) { + + // delete and replace with hifi name + jointIndices.remove(jointIn.name); + jointIndices.insert(jointNameMapKey, mappedIndex); + } else { + + // nothing mapped to this fbx joint name + if (jointNameMapping.contains(jointIn.name)) { + // but the name is in the list of hifi names is mapped to a different joint + int extraIndex = jointIndices.value(jointIn.name); + jointIndices.remove(jointIn.name); + jointIndices.insert("", extraIndex); + } + } + } + } } From 2af9dc886a9bc0b6ecdb91d68a0abea121cc684a Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 8 Mar 2019 11:56:04 -0800 Subject: [PATCH 419/474] Add logging category to TGAReader --- libraries/image/src/image/TGAReader.cpp | 38 +++++++++++++------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/libraries/image/src/image/TGAReader.cpp b/libraries/image/src/image/TGAReader.cpp index a230185b49..a6dd7e9c71 100644 --- a/libraries/image/src/image/TGAReader.cpp +++ b/libraries/image/src/image/TGAReader.cpp @@ -11,6 +11,8 @@ #include "TGAReader.h" +#include "ImageLogging.h" + #include #include @@ -57,12 +59,12 @@ QImage image::readTGA(QIODevice& content) { TGAHeader header; if (content.isSequential()) { - qWarning() << "TGA - Sequential devices are not supported for reading"; + qWarning(imagelogging) << "TGA - Sequential devices are not supported for reading"; return QImage(); } if (content.bytesAvailable() < TGA_HEADER_SIZE_BYTES) { - qWarning() << "TGA - Unexpectedly reached end of file"; + qWarning(imagelogging) << "TGA - Unexpectedly reached end of file"; return QImage(); } @@ -78,28 +80,28 @@ QImage image::readTGA(QIODevice& content) { content.read((char*)&header.imageDescriptor, 1); if (WANT_DEBUG) { - qDebug() << "Id Length: " << (int)header.idLength; - qDebug() << "Color map: " << (int)header.colorMap.firstEntryIndex << header.colorMap.length << header.colorMap.entrySize; - qDebug() << "Color map type: " << (int)header.colorMapType; - qDebug() << "Image type: " << (int)header.imageType; - qDebug() << "Origin: " << header.xOrigin << header.yOrigin; - qDebug() << "Size: " << header.width << header.height; - qDebug() << "Depth: " << header.pixelDepth; - qDebug() << "Image desc: " << header.imageDescriptor.attributeBitsPerPixel << (int)header.imageDescriptor.orientation; + qDebug(imagelogging) << "Id Length: " << (int)header.idLength; + qDebug(imagelogging) << "Color map: " << (int)header.colorMap.firstEntryIndex << header.colorMap.length << header.colorMap.entrySize; + qDebug(imagelogging) << "Color map type: " << (int)header.colorMapType; + qDebug(imagelogging) << "Image type: " << (int)header.imageType; + qDebug(imagelogging) << "Origin: " << header.xOrigin << header.yOrigin; + qDebug(imagelogging) << "Size: " << header.width << header.height; + qDebug(imagelogging) << "Depth: " << header.pixelDepth; + qDebug(imagelogging) << "Image desc: " << header.imageDescriptor.attributeBitsPerPixel << (int)header.imageDescriptor.orientation; } if (header.xOrigin != 0 || header.yOrigin != 0) { - qWarning() << "TGA - origin not supporter"; + qWarning(imagelogging) << "TGA - origin not supporter"; return QImage(); } if (!(header.pixelDepth == 24 && header.imageDescriptor.attributeBitsPerPixel == 0) && header.pixelDepth != 32) { - qWarning() << "TGA - Only pixel depths of 24 (with no alpha) and 32 bits are supported"; + qWarning(imagelogging) << "TGA - Only pixel depths of 24 (with no alpha) and 32 bits are supported"; return QImage(); } if (header.imageDescriptor.attributeBitsPerPixel != 0 && header.imageDescriptor.attributeBitsPerPixel != 8) { - qWarning() << "TGA - Only 0 or 8 bits for the alpha channel is supported"; + qWarning(imagelogging) << "TGA - Only 0 or 8 bits for the alpha channel is supported"; return QImage(); } @@ -110,7 +112,7 @@ QImage image::readTGA(QIODevice& content) { if (header.imageType == TGAImageType::UncompressedTrueColor) { qint64 minimumSize = header.width * header.height * bytesPerPixel; if (content.bytesAvailable() < minimumSize) { - qWarning() << "TGA - Unexpectedly reached end of file"; + qWarning(imagelogging) << "TGA - Unexpectedly reached end of file"; return QImage(); } @@ -146,7 +148,7 @@ QImage image::readTGA(QIODevice& content) { constexpr char LENGTH_MASK{ (char)0x7f }; char repetition; if (content.read(&repetition, 1) != 1) { - qWarning() << "TGA - Unexpectedly reached end of file"; + qWarning(imagelogging) << "TGA - Unexpectedly reached end of file"; return QImage(); } bool isRepetition = repetition & IS_REPETITION_MASK; @@ -159,7 +161,7 @@ QImage image::readTGA(QIODevice& content) { // Read into temporary buffer char color[4]; if (content.read(color, bytesPerPixel) != bytesPerPixel) { - qWarning() << "TGA - Unexpectedly reached end of file"; + qWarning(imagelogging) << "TGA - Unexpectedly reached end of file"; return QImage(); } color[3] |= alphaMask; @@ -177,7 +179,7 @@ QImage image::readTGA(QIODevice& content) { } else { qint64 minimumSize = length * bytesPerPixel; if (content.bytesAvailable() < minimumSize) { - qWarning() << "TGA - Unexpectedly reached end of file"; + qWarning(imagelogging) << "TGA - Unexpectedly reached end of file"; return QImage(); } @@ -194,7 +196,7 @@ QImage image::readTGA(QIODevice& content) { } return image; } else { - qWarning() << "TGA - Unsupported image type: " << (int)header.imageType; + qWarning(imagelogging) << "TGA - Unsupported image type: " << (int)header.imageType; } return QImage(); From f363d95ca2b9d66806e50cf74b1e8b8fa668f9bb Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Fri, 8 Mar 2019 13:02:47 -0700 Subject: [PATCH 420/474] clear group settings on init --- libraries/animation/src/Flow.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/animation/src/Flow.cpp b/libraries/animation/src/Flow.cpp index ddc3354a2f..6e210fe71c 100644 --- a/libraries/animation/src/Flow.cpp +++ b/libraries/animation/src/Flow.cpp @@ -463,6 +463,7 @@ void Flow::calculateConstraints(const std::shared_ptr& skeleton, auto flowPrefix = FLOW_JOINT_PREFIX.toUpper(); auto simPrefix = SIM_JOINT_PREFIX.toUpper(); std::vector handsIndices; + _groupSettings.clear(); for (int i = 0; i < skeleton->getNumJoints(); i++) { auto name = skeleton->getJointName(i); From 70cc6594c7094ab13c5d2d0f3de4c832ecc5104a Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Fri, 8 Mar 2019 12:43:43 -0800 Subject: [PATCH 421/474] CR fixes --- .../resources/qml/hifi/commerce/purchases/PurchasedItem.qml | 2 +- interface/resources/qml/hifi/commerce/purchases/Purchases.qml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index 8a20a55141..a7b36eae36 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -422,7 +422,7 @@ Item { verticalAlignment: Text.AlignVCenter; onLinkActivated: { - if(link == "#standaloneIncompatible") { + if (link === "#standaloneIncompatible") { sendToPurchases({method: 'showStandaloneIncompatibleExplanation'}); } else { sendToPurchases({method: 'showPermissionsExplanation', itemType: root.itemType}); diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 4b285e5402..311c20d120 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -114,7 +114,6 @@ Rectangle { Component.onCompleted: { isStandalone = PlatformInfo.isStandalone(); - console.log(isStandalone ? "IS STANDALONE" : "ISN'T STANDALONE"); } HifiCommerceCommon.CommerceLightbox { From 1f0a8b18c8217620437f124a20938706d5751276 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 8 Mar 2019 13:01:56 -0800 Subject: [PATCH 422/474] Fix warning about TGAOrientation --- libraries/image/src/image/TGAReader.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/libraries/image/src/image/TGAReader.cpp b/libraries/image/src/image/TGAReader.cpp index a6dd7e9c71..231155320a 100644 --- a/libraries/image/src/image/TGAReader.cpp +++ b/libraries/image/src/image/TGAReader.cpp @@ -26,10 +26,6 @@ QImage image::readTGA(QIODevice& content) { RunLengthEncodedTrueColor = 10, RunLengthEncodedBlackWhite = 11, }; - enum class TGAOrientation : uint8_t { - BottomLeft = 0, - TopLeft = 1, - }; struct TGAHeader { uint8_t idLength; @@ -48,13 +44,15 @@ QImage image::readTGA(QIODevice& content) { struct { uint8_t attributeBitsPerPixel : 4; uint8_t reserved : 1; - TGAOrientation orientation : 1; + uint8_t orientation : 1; uint8_t padding : 2; } imageDescriptor; }; constexpr bool WANT_DEBUG { false }; constexpr size_t TGA_HEADER_SIZE_BYTES { 18 }; + constexpr uint8_t ORIENTATION_BOTTOM_LEFT { 0 }; + constexpr uint8_t ORIENTATION_TOP_LEFT { 1 }; TGAHeader header; @@ -119,7 +117,7 @@ QImage image::readTGA(QIODevice& content) { QImage image{ header.width, header.height, QImage::Format_ARGB32 }; char* line; for (int y = 0; y < header.height; ++y) { - if (header.imageDescriptor.orientation == TGAOrientation::BottomLeft) { + if (header.imageDescriptor.orientation == ORIENTATION_BOTTOM_LEFT) { line = (char*)image.scanLine(header.height - y - 1); } else { line = (char*)image.scanLine(y); @@ -137,7 +135,7 @@ QImage image::readTGA(QIODevice& content) { for (int y = 0; y < header.height; ++y) { char* line; - if (header.imageDescriptor.orientation == TGAOrientation::BottomLeft) { + if (header.imageDescriptor.orientation == ORIENTATION_BOTTOM_LEFT) { line = (char*)image.scanLine(header.height - y - 1); } else { line = (char*)image.scanLine(y); From 4858f648101d905d41a38c9a6195ba693d70de65 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Fri, 8 Mar 2019 14:36:47 -0700 Subject: [PATCH 423/474] get the colliding joints --- interface/src/avatar/MyAvatar.cpp | 26 ++++++++++++++++++++++++++ interface/src/avatar/MyAvatar.h | 7 +++++++ libraries/animation/src/Flow.h | 1 + 3 files changed, 34 insertions(+) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 7e0ee5c2fd..389263656d 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -5404,7 +5404,18 @@ QVariantMap MyAvatar::getFlowData() { QVariantMap physicsData; QVariantMap collisionsData; QVariantMap threadData; + std::map groupJointsMap; + QVariantList jointCollisionData; auto &groups = flow.getGroupSettings(); + for (auto &joint : flow.getJoints()) { + auto &groupName = joint.second.getGroup(); + if (groups.find(groupName) != groups.end()) { + if (groupJointsMap.find(groupName) == groupJointsMap.end()) { + groupJointsMap.insert(std::pair(groupName, QVariantList())); + } + groupJointsMap[groupName].push_back(joint.second.getIndex()); + } + } for (auto &group : groups) { QVariantMap settingsObject; QString groupName = group.first; @@ -5416,8 +5427,10 @@ QVariantMap MyAvatar::getFlowData() { settingsObject.insert("inertia", groupSettings._inertia); settingsObject.insert("radius", groupSettings._radius); settingsObject.insert("stiffness", groupSettings._stiffness); + settingsObject.insert("jointIndices", groupJointsMap[groupName]); physicsData.insert(groupName, settingsObject); } + auto &collisions = collisionSystem.getCollisions(); for (auto &collision : collisions) { QVariantMap collisionObject; @@ -5441,6 +5454,19 @@ QVariantMap MyAvatar::getFlowData() { return flowData; } +QVariantList MyAvatar::getCollidingFlowJoints() { + QVariantList collidingFlowJoints; + if (_skeletonModel->isLoaded()) { + auto& flow = _skeletonModel->getRig().getFlow(); + for (auto &joint : flow.getJoints()) { + if (joint.second.isColliding()) { + collidingFlowJoints.append(joint.second.getIndex()); + } + } + } + return collidingFlowJoints; +} + void MyAvatar::initFlowFromFST() { if (_skeletonModel->isLoaded()) { auto &flowData = _skeletonModel->getHFMModel().flowData; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index c67cabe4f5..e516364f61 100755 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1204,6 +1204,13 @@ public: */ Q_INVOKABLE QVariantMap getFlowData(); + /**jsdoc + * returns the indices of every colliding flow joint + * @function MyAvatar.getCollidingFlowJoints + * @returns {int[]} + */ + Q_INVOKABLE QVariantList getCollidingFlowJoints(); + public slots: /**jsdoc diff --git a/libraries/animation/src/Flow.h b/libraries/animation/src/Flow.h index d2eaaa22d9..ad81c2be77 100644 --- a/libraries/animation/src/Flow.h +++ b/libraries/animation/src/Flow.h @@ -222,6 +222,7 @@ public: const glm::quat& getCurrentRotation() const { return _currentRotation; } const glm::vec3& getCurrentTranslation() const { return _initialTranslation; } const glm::vec3& getInitialPosition() const { return _initialPosition; } + bool isColliding() const { return _colliding; } protected: From 24b08bd25df8a823bcfb685bc0d305da6c1eede5 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Fri, 8 Mar 2019 13:59:16 -0800 Subject: [PATCH 424/474] Change "about" blockchain from Elements to EOS --- interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml b/interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml index d26bf81e57..213540b334 100644 --- a/interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml +++ b/interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml @@ -97,10 +97,11 @@ Rectangle { textFormat: Text.StyledText linkColor: "#00B4EF" color: "white" - text: "Blockchain technology from Elements." + property string link: "https://eos.io/" + text: "Blockchain technology from EOS." size: 14 onLinkActivated: { - HiFiAbout.openUrl("https://elementsproject.org/elements/"); + HiFiAbout.openUrl(link); } } RalewayRegular { From 62f17acaf78cea83dbb1764ed6c36a3b9db8d74c Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 8 Mar 2019 14:01:29 -0800 Subject: [PATCH 425/474] Fix warnings in TGAReader --- libraries/image/src/image/TGAReader.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libraries/image/src/image/TGAReader.cpp b/libraries/image/src/image/TGAReader.cpp index 231155320a..897d565eba 100644 --- a/libraries/image/src/image/TGAReader.cpp +++ b/libraries/image/src/image/TGAReader.cpp @@ -50,9 +50,10 @@ QImage image::readTGA(QIODevice& content) { }; constexpr bool WANT_DEBUG { false }; - constexpr size_t TGA_HEADER_SIZE_BYTES { 18 }; + constexpr qint64 TGA_HEADER_SIZE_BYTES { 18 }; + + // BottomLeft: 0, TopLeft: 1 constexpr uint8_t ORIENTATION_BOTTOM_LEFT { 0 }; - constexpr uint8_t ORIENTATION_TOP_LEFT { 1 }; TGAHeader header; From 88a4b5c983409fd5d8a14c1e0e6aea0d3279d7dd Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Fri, 8 Mar 2019 14:01:47 -0800 Subject: [PATCH 426/474] Case 21642 - verification dialog when user hits return in goto search text entry+return should throw up verification dialog for first time to non-optimized domain --- interface/resources/qml/hifi/Feed.qml | 9 +++++++++ .../resources/qml/hifi/tablet/TabletAddressDialog.qml | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index 68aab2fdd2..7f48376dc7 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -189,4 +189,13 @@ Column { } } } + function isStandalone(address) { + + for (var i=0; i < suggestions.count; i++) { + if (suggestions.get(i).place_name.toLowerCase() === address.toLowerCase()) { + return suggestions.get(i).standalone_optimized; + } + } + return false; + } } diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 311d20955b..4edae017d1 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -230,7 +230,7 @@ StackView { updateLocationText(text.length > 0); } onAccepted: { - toggleOrGo(); + toggleOrGo(addressLine.text, false, places.isStandalone(addressLine.text)); } // unfortunately TextField from Quick Controls 2 disallow customization of placeHolderText color without creation of new style From 06e5927ee130640f477921807c3b3074f3694842 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Fri, 8 Mar 2019 23:07:01 +0100 Subject: [PATCH 427/474] CR fixes --- interface/src/avatar/AvatarDoctor.cpp | 132 +++++++++++++------------- 1 file changed, 64 insertions(+), 68 deletions(-) diff --git a/interface/src/avatar/AvatarDoctor.cpp b/interface/src/avatar/AvatarDoctor.cpp index 8c22780b52..04a426c3db 100644 --- a/interface/src/avatar/AvatarDoctor.cpp +++ b/interface/src/avatar/AvatarDoctor.cpp @@ -55,7 +55,7 @@ static QStringList HAND_MAPPING_SUFFIXES = { "HandThumb1", }; -const QUrl DEFAULT_URL = QUrl("https://docs.highfidelity.com/create/avatars/create-avatars.html#create-your-own-avatar"); +const QUrl DEFAULT_DOCS_URL = QUrl("https://docs.highfidelity.com/create/avatars/create-avatars.html#create-your-own-avatar"); AvatarDoctor::AvatarDoctor(const QUrl& avatarFSTFileUrl) : _avatarFSTFileUrl(avatarFSTFileUrl) { @@ -85,7 +85,7 @@ void AvatarDoctor::startDiagnosing() { const auto resourceLoaded = [this, resource](bool success) { // MODEL if (!success) { - _errors.push_back({ "Model file cannot be opened.", DEFAULT_URL }); + _errors.push_back({ "Model file cannot be opened.", DEFAULT_DOCS_URL }); emit complete(getErrors()); return; } @@ -93,49 +93,49 @@ void AvatarDoctor::startDiagnosing() { const auto model = resource.data(); const auto avatarModel = resource.data()->getHFMModel(); if (!avatarModel.originalURL.endsWith(".fbx")) { - _errors.push_back({ "Unsupported avatar model format.", DEFAULT_URL }); + _errors.push_back({ "Unsupported avatar model format.", DEFAULT_DOCS_URL }); emit complete(getErrors()); return; } // RIG if (avatarModel.joints.isEmpty()) { - _errors.push_back({ "Avatar has no rig.", DEFAULT_URL }); + _errors.push_back({ "Avatar has no rig.", DEFAULT_DOCS_URL }); } else { auto jointNames = avatarModel.getJointNames(); if (avatarModel.joints.length() > NETWORKED_JOINTS_LIMIT) { - _errors.push_back({tr( "Avatar has over %n bones.", "", NETWORKED_JOINTS_LIMIT), DEFAULT_URL }); + _errors.push_back({tr( "Avatar has over %n bones.", "", NETWORKED_JOINTS_LIMIT), DEFAULT_DOCS_URL }); } // Avatar does not have Hips bone mapped if (!jointNames.contains("Hips")) { - _errors.push_back({ "Hips are not mapped.", DEFAULT_URL }); + _errors.push_back({ "Hips are not mapped.", DEFAULT_DOCS_URL }); } if (!jointNames.contains("Spine")) { - _errors.push_back({ "Spine is not mapped.", DEFAULT_URL }); + _errors.push_back({ "Spine is not mapped.", DEFAULT_DOCS_URL }); } if (!jointNames.contains("Spine1")) { - _errors.push_back({ "Chest (Spine1) is not mapped.", DEFAULT_URL }); + _errors.push_back({ "Chest (Spine1) is not mapped.", DEFAULT_DOCS_URL }); } if (!jointNames.contains("Neck")) { - _errors.push_back({ "Neck is not mapped.", DEFAULT_URL }); + _errors.push_back({ "Neck is not mapped.", DEFAULT_DOCS_URL }); } if (!jointNames.contains("Head")) { - _errors.push_back({ "Head is not mapped.", DEFAULT_URL }); + _errors.push_back({ "Head is not mapped.", DEFAULT_DOCS_URL }); } if (!jointNames.contains("LeftEye")) { if (jointNames.contains("RightEye")) { - _errors.push_back({ "LeftEye is not mapped.", DEFAULT_URL }); + _errors.push_back({ "LeftEye is not mapped.", DEFAULT_DOCS_URL }); } else { - _errors.push_back({ "Eyes are not mapped.", DEFAULT_URL }); + _errors.push_back({ "Eyes are not mapped.", DEFAULT_DOCS_URL }); } } else if (!jointNames.contains("RightEye")) { - _errors.push_back({ "RightEye is not mapped.", DEFAULT_URL }); + _errors.push_back({ "RightEye is not mapped.", DEFAULT_DOCS_URL }); } const auto checkJointAsymmetry = [jointNames] (const QStringList& jointMappingSuffixes) { - foreach (const QString& jointSuffix, jointMappingSuffixes) { + for (const QString& jointSuffix : jointMappingSuffixes) { if (jointNames.contains(LEFT_JOINT_PREFIX + jointSuffix) != jointNames.contains(RIGHT_JOINT_PREFIX + jointSuffix)) { return true; @@ -159,30 +159,30 @@ void AvatarDoctor::startDiagnosing() { }; if (checkJointAsymmetry(ARM_MAPPING_SUFFIXES)) { - _errors.push_back({ "Asymmetrical arm bones.", DEFAULT_URL }); + _errors.push_back({ "Asymmetrical arm bones.", DEFAULT_DOCS_URL }); } if (checkJointAsymmetry(HAND_MAPPING_SUFFIXES)) { - _errors.push_back({ "Asymmetrical hand bones.", DEFAULT_URL }); + _errors.push_back({ "Asymmetrical hand bones.", DEFAULT_DOCS_URL }); } if (checkJointAsymmetry(LEG_MAPPING_SUFFIXES)) { - _errors.push_back({ "Asymmetrical leg bones.", DEFAULT_URL }); + _errors.push_back({ "Asymmetrical leg bones.", DEFAULT_DOCS_URL }); } // Multiple skeleton root joints checkup int skeletonRootJoints = 0; - foreach(const HFMJoint& joint, avatarModel.joints) { + for (const HFMJoint& joint: avatarModel.joints) { if (joint.parentIndex == -1 && joint.isSkeletonJoint) { skeletonRootJoints++; } } if (skeletonRootJoints > 1) { - _errors.push_back({ "Multiple root joints found.", DEFAULT_URL }); + _errors.push_back({ "Multiple top-level joints found.", DEFAULT_DOCS_URL }); } - const auto rig = new Rig(); - rig->reset(avatarModel); - const float eyeHeight = rig->getUnscaledEyeHeight(); + Rig rig; + rig.reset(avatarModel); + const float eyeHeight = rig.getUnscaledEyeHeight(); const float ratio = eyeHeight / DEFAULT_AVATAR_HEIGHT; const float avatarHeight = eyeHeight + ratio * DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD; @@ -191,71 +191,70 @@ void AvatarDoctor::startDiagnosing() { const float RECOMMENDED_MAX_HEIGHT = DEFAULT_AVATAR_HEIGHT * 1.5f; if (avatarHeight < RECOMMENDED_MIN_HEIGHT) { - _errors.push_back({ "Avatar is possibly too short.", DEFAULT_URL }); + _errors.push_back({ "Avatar is possibly too short.", DEFAULT_DOCS_URL }); } else if (avatarHeight > RECOMMENDED_MAX_HEIGHT) { - _errors.push_back({ "Avatar is possibly too tall.", DEFAULT_URL }); + _errors.push_back({ "Avatar is possibly too tall.", DEFAULT_DOCS_URL }); } // HipsNotOnGround - auto hipsIndex = rig->indexOfJoint("Hips"); + auto hipsIndex = rig.indexOfJoint("Hips"); if (hipsIndex >= 0) { glm::vec3 hipsPosition; - if (rig->getJointPosition(hipsIndex, hipsPosition)) { + if (rig.getJointPosition(hipsIndex, hipsPosition)) { const auto hipJoint = avatarModel.joints.at(avatarModel.getJointIndex("Hips")); if (hipsPosition.y < HIPS_GROUND_MIN_Y) { - _errors.push_back({ "Hips are on ground.", DEFAULT_URL }); + _errors.push_back({ "Hips are on ground.", DEFAULT_DOCS_URL }); } } } // HipsSpineChestNotCoincident - auto spineIndex = rig->indexOfJoint("Spine"); - auto chestIndex = rig->indexOfJoint("Spine1"); + auto spineIndex = rig.indexOfJoint("Spine"); + auto chestIndex = rig.indexOfJoint("Spine1"); if (hipsIndex >= 0 && spineIndex >= 0 && chestIndex >= 0) { glm::vec3 hipsPosition; glm::vec3 spinePosition; glm::vec3 chestPosition; - if (rig->getJointPosition(hipsIndex, hipsPosition) && - rig->getJointPosition(spineIndex, spinePosition) && - rig->getJointPosition(chestIndex, chestPosition)) { + if (rig.getJointPosition(hipsIndex, hipsPosition) && + rig.getJointPosition(spineIndex, spinePosition) && + rig.getJointPosition(chestIndex, chestPosition)) { const auto hipsToSpine = glm::length(hipsPosition - spinePosition); const auto spineToChest = glm::length(spinePosition - chestPosition); if (hipsToSpine < HIPS_SPINE_CHEST_MIN_SEPARATION && spineToChest < HIPS_SPINE_CHEST_MIN_SEPARATION) { - _errors.push_back({ "Hips/Spine/Chest overlap.", DEFAULT_URL }); + _errors.push_back({ "Hips/Spine/Chest overlap.", DEFAULT_DOCS_URL }); } } } - rig->deleteLater(); auto mapping = resource->getMapping(); if (mapping.contains(JOINT_NAME_MAPPING_FIELD) && mapping[JOINT_NAME_MAPPING_FIELD].type() == QVariant::Hash) { const auto& jointNameMappings = mapping[JOINT_NAME_MAPPING_FIELD].toHash(); QStringList jointValues; - foreach(const auto& jointVariant, jointNameMappings.values()) { + for (const auto& jointVariant: jointNameMappings.values()) { jointValues << jointVariant.toString(); } const auto& uniqueJointValues = jointValues.toSet(); - foreach (const auto& jointName, uniqueJointValues) { + for (const auto& jointName: uniqueJointValues) { if (jointValues.count(jointName) > 1) { - _errors.push_back({ tr("%1 is mapped multiple times.").arg(jointName), DEFAULT_URL }); + _errors.push_back({ tr("%1 is mapped multiple times.").arg(jointName), DEFAULT_DOCS_URL }); } } } if (!isDescendantOfJointWhenJointsExist("Spine", "Hips")) { - _errors.push_back({ "Spine is not a child of Hips.", DEFAULT_URL }); + _errors.push_back({ "Spine is not a child of Hips.", DEFAULT_DOCS_URL }); } if (!isDescendantOfJointWhenJointsExist("Spine1", "Spine")) { - _errors.push_back({ "Spine1 is not a child of Spine.", DEFAULT_URL }); + _errors.push_back({ "Spine1 is not a child of Spine.", DEFAULT_DOCS_URL }); } if (!isDescendantOfJointWhenJointsExist("Head", "Spine1")) { - _errors.push_back({ "Head is not a child of Spine1.", DEFAULT_URL }); + _errors.push_back({ "Head is not a child of Spine1.", DEFAULT_DOCS_URL }); } } @@ -270,7 +269,7 @@ void AvatarDoctor::startDiagnosing() { _materialMappingCount = (int)model->getMaterialMapping().size(); _materialMappingLoadedCount = 0; - foreach(const auto& materialMapping, model->getMaterialMapping()) { + for (const auto& materialMapping : model->getMaterialMapping()) { // refresh the texture mappings auto materialMappingResource = materialMapping.second; if (materialMappingResource) { @@ -301,7 +300,7 @@ void AvatarDoctor::startDiagnosing() { connect(resource.data(), &GeometryResource::finished, this, resourceLoaded); } } else { - _errors.push_back({ "Model file cannot be opened", DEFAULT_URL }); + _errors.push_back({ "Model file cannot be opened", DEFAULT_DOCS_URL }); emit complete(getErrors()); } } @@ -309,8 +308,8 @@ void AvatarDoctor::startDiagnosing() { void AvatarDoctor::diagnoseTextures() { const auto model = _model.data(); const auto avatarModel = _model.data()->getHFMModel(); - QStringList externalTextures{}; - QSet textureNames{}; + QVector externalTextures{}; + QVector textureNames{}; int texturesFound = 0; auto addTextureToList = [&externalTextures, &texturesFound](hfm::Texture texture) mutable { if (texture.filename.isEmpty()) { @@ -322,7 +321,7 @@ void AvatarDoctor::diagnoseTextures() { texturesFound++; }; - foreach(const HFMMaterial material, avatarModel.materials) { + for (const auto& material : avatarModel.materials) { addTextureToList(material.normalTexture); addTextureToList(material.albedoTexture); addTextureToList(material.opacityTexture); @@ -336,8 +335,8 @@ void AvatarDoctor::diagnoseTextures() { addTextureToList(material.lightmapTexture); } - foreach(const auto& materialMapping, model->getMaterialMapping()) { - foreach(const auto& networkMaterial, materialMapping.second.data()->parsedMaterials.networkMaterials) { + for (const auto& materialMapping : model->getMaterialMapping()) { + for (const auto& networkMaterial : materialMapping.second.data()->parsedMaterials.networkMaterials) { texturesFound += (int)networkMaterial.second->getTextureMaps().size(); } } @@ -346,39 +345,36 @@ void AvatarDoctor::diagnoseTextures() { QUrl(avatarModel.originalURL)).resolved(QUrl("textures")); if (texturesFound == 0) { - _errors.push_back({ tr("No textures assigned."), DEFAULT_URL }); + _errors.push_back({ tr("No textures assigned."), DEFAULT_DOCS_URL }); } if (!externalTextures.empty()) { // Check External Textures: auto modelTexturesURLs = model->getTextures(); _externalTextureCount = externalTextures.length(); - foreach(const QString textureKey, externalTextures) { + + auto checkTextureLoadingComplete = [this]() mutable { + if (_checkedTextureCount == _externalTextureCount) { + if (_missingTextureCount > 0) { + _errors.push_back({ tr("Missing %n texture(s).","", _missingTextureCount), DEFAULT_DOCS_URL }); + } + if (_unsupportedTextureCount > 0) { + _errors.push_back({ tr("%n unsupported texture(s) found.", "", _unsupportedTextureCount), + DEFAULT_DOCS_URL }); + } + + emit complete(getErrors()); + } + }; + + for (const QString& textureKey : externalTextures) { if (!modelTexturesURLs.contains(textureKey)) { _missingTextureCount++; _checkedTextureCount++; continue; } - const QUrl textureURL = modelTexturesURLs[textureKey].toUrl(); - auto textureResource = DependencyManager::get()->getTexture(textureURL); - auto checkTextureLoadingComplete = [this]() mutable { - qDebug() << "checkTextureLoadingComplete" << _checkedTextureCount << "/" << _externalTextureCount; - - if (_checkedTextureCount == _externalTextureCount) { - if (_missingTextureCount > 0) { - _errors.push_back({ tr("Missing %n texture(s).","", _missingTextureCount), DEFAULT_URL }); - } - if (_unsupportedTextureCount > 0) { - _errors.push_back({ tr("%n unsupported texture(s) found.", "", _unsupportedTextureCount), - DEFAULT_URL }); - } - - emit complete(getErrors()); - } - }; - auto textureLoaded = [this, textureResource, checkTextureLoadingComplete](bool success) mutable { if (!success) { auto normalizedURL = DependencyManager::get()->normalizeURL(textureResource->getURL()); @@ -407,9 +403,9 @@ void AvatarDoctor::diagnoseTextures() { } else { _missingTextureCount++; _checkedTextureCount++; - checkTextureLoadingComplete(); } } + checkTextureLoadingComplete(); } else { emit complete(getErrors()); } From 63c0df99458dcc3fc4cb4fd974cd7185fdb97c94 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Fri, 8 Mar 2019 03:13:56 +0100 Subject: [PATCH 428/474] temporary de-optimize game objects during the export process --- .../Assets/Editor/AvatarExporter.cs | 20 ++++++++++++++++-- tools/unity-avatar-exporter/Assets/README.txt | 2 +- .../avatarExporter.unitypackage | Bin 15811 -> 16045 bytes 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs index 2565a537c8..c25a962824 100644 --- a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs +++ b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs @@ -14,7 +14,7 @@ using System.Collections.Generic; class AvatarExporter : MonoBehaviour { // update version number for every PR that changes this file, also set updated version in README file - static readonly string AVATAR_EXPORTER_VERSION = "0.3.2"; + static readonly string AVATAR_EXPORTER_VERSION = "0.3.3"; static readonly float HIPS_GROUND_MIN_Y = 0.01f; static readonly float HIPS_SPINE_CHEST_MIN_SEPARATION = 0.001f; @@ -364,7 +364,14 @@ class AvatarExporter : MonoBehaviour { " the Rig section of it's Inspector window.", "Ok"); return; } - + + // if the rig is optimized we should de-optimize it during the export process + bool shouldDeoptimizeGameObjects = modelImporter.optimizeGameObjects; + if (shouldDeoptimizeGameObjects) { + modelImporter.optimizeGameObjects = false; + modelImporter.SaveAndReimport(); + } + humanDescription = modelImporter.humanDescription; string textureWarnings = SetTextureDependencies(); SetBoneAndMaterialInformation(); @@ -375,6 +382,15 @@ class AvatarExporter : MonoBehaviour { // format resulting avatar rule failure strings // consider export-blocking avatar rules to be errors and show them in an error dialog, // and also include any other avatar rule failures plus texture warnings as warnings in the dialog + if (shouldDeoptimizeGameObjects) { + // switch back to optimized game object in case it was originally optimized + modelImporter.optimizeGameObjects = true; + modelImporter.SaveAndReimport(); + } + + // format resulting bone rule failure strings + // consider export-blocking bone rules to be errors and show them in an error dialog, + // and also include any other bone rule failures plus texture warnings as warnings in the dialog string boneErrors = ""; string warnings = ""; foreach (var failedAvatarRule in failedAvatarRules) { diff --git a/tools/unity-avatar-exporter/Assets/README.txt b/tools/unity-avatar-exporter/Assets/README.txt index 19c31a597c..402719b497 100644 --- a/tools/unity-avatar-exporter/Assets/README.txt +++ b/tools/unity-avatar-exporter/Assets/README.txt @@ -1,6 +1,6 @@ High Fidelity, Inc. Avatar Exporter -Version 0.3.2 +Version 0.3.3 Note: It is recommended to use Unity versions between 2017.4.17f1 and 2018.2.12f1 for this Avatar Exporter. diff --git a/tools/unity-avatar-exporter/avatarExporter.unitypackage b/tools/unity-avatar-exporter/avatarExporter.unitypackage index c60a156711329e88a09234efca428eb3ceb6e89d..f7385e38311e93788a17308f7cb2a174c7f7f138 100644 GIT binary patch literal 16045 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%0DF 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 N Date: Fri, 8 Mar 2019 14:27:56 -0800 Subject: [PATCH 429/474] do not scroll inventory items underneath modal dialog --- interface/resources/qml/hifi/commerce/purchases/Purchases.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index dc892e6640..a315e08398 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -527,6 +527,7 @@ Rectangle { ListView { id: purchasesContentsList; visible: purchasesModel.count !== 0; + interactive: !lightboxPopup.visible; clip: true; model: purchasesModel; snapMode: ListView.NoSnap; From b3f5d76d0f078e49f988faf06f19e305e2c4ecd1 Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Fri, 8 Mar 2019 14:37:15 -0800 Subject: [PATCH 430/474] CR Fix --- interface/resources/qml/hifi/Feed.qml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index 7f48376dc7..718ebc9331 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -190,9 +190,10 @@ Column { } } function isStandalone(address) { - + var lowerAddress = address.toLowerCase(); + for (var i=0; i < suggestions.count; i++) { - if (suggestions.get(i).place_name.toLowerCase() === address.toLowerCase()) { + if (suggestions.get(i).place_name.toLowerCase() === lowerAddress) { return suggestions.get(i).standalone_optimized; } } From bfcb1a8391e18b8c26efb602c6ad84ed354f4690 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Fri, 8 Mar 2019 15:57:25 -0700 Subject: [PATCH 431/474] Make methods thread safe --- interface/src/avatar/MyAvatar.cpp | 42 +++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 389263656d..73751f4dbe 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -5333,6 +5333,15 @@ void MyAvatar::addAvatarHandsToFlow(const std::shared_ptr& otherAvatar) } void MyAvatar::useFlow(bool isActive, bool isCollidable, const QVariantMap& physicsConfig, const QVariantMap& collisionsConfig) { + QVariantList result; + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "useFlow", + Q_ARG(bool, isActive), + Q_ARG(bool, isCollidable), + Q_ARG(const QVariantMap&, physicsConfig), + Q_ARG(const QVariantMap&, collisionsConfig)); + return; + } if (_skeletonModel->isLoaded()) { auto &flow = _skeletonModel->getRig().getFlow(); auto &collisionSystem = flow.getCollisionSystem(); @@ -5392,15 +5401,20 @@ void MyAvatar::useFlow(bool isActive, bool isCollidable, const QVariantMap& phys } QVariantMap MyAvatar::getFlowData() { - QVariantMap flowData; + QVariantMap result; + if (QThread::currentThread() != thread()) { + BLOCKING_INVOKE_METHOD(this, "getFlowData", + Q_RETURN_ARG(QVariantMap, result)); + return result; + } if (_skeletonModel->isLoaded()) { auto jointNames = getJointNames(); auto &flow = _skeletonModel->getRig().getFlow(); auto &collisionSystem = flow.getCollisionSystem(); bool initialized = flow.isInitialized(); - flowData.insert("initialized", initialized); - flowData.insert("active", flow.getActive()); - flowData.insert("colliding", collisionSystem.getActive()); + result.insert("initialized", initialized); + result.insert("active", flow.getActive()); + result.insert("colliding", collisionSystem.getActive()); QVariantMap physicsData; QVariantMap collisionsData; QVariantMap threadData; @@ -5447,24 +5461,30 @@ QVariantMap MyAvatar::getFlowData() { } threadData.insert(thread._jointsPointer->at(thread._joints[0]).getName(), indices); } - flowData.insert("physics", physicsData); - flowData.insert("collisions", collisionsData); - flowData.insert("threads", threadData); + result.insert("physics", physicsData); + result.insert("collisions", collisionsData); + result.insert("threads", threadData); } - return flowData; + return result; } QVariantList MyAvatar::getCollidingFlowJoints() { - QVariantList collidingFlowJoints; + QVariantList result; + if (QThread::currentThread() != thread()) { + BLOCKING_INVOKE_METHOD(this, "getCollidingFlowJoints", + Q_RETURN_ARG(QVariantList, result)); + return result; + } + if (_skeletonModel->isLoaded()) { auto& flow = _skeletonModel->getRig().getFlow(); for (auto &joint : flow.getJoints()) { if (joint.second.isColliding()) { - collidingFlowJoints.append(joint.second.getIndex()); + result.append(joint.second.getIndex()); } } } - return collidingFlowJoints; + return result; } void MyAvatar::initFlowFromFST() { From df32b61eaf08cfc6d99a2813704db014b6caba7f Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Fri, 8 Mar 2019 16:01:18 -0700 Subject: [PATCH 432/474] remove unused variable --- interface/src/avatar/MyAvatar.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 73751f4dbe..9211be3b4f 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -5333,7 +5333,6 @@ void MyAvatar::addAvatarHandsToFlow(const std::shared_ptr& otherAvatar) } void MyAvatar::useFlow(bool isActive, bool isCollidable, const QVariantMap& physicsConfig, const QVariantMap& collisionsConfig) { - QVariantList result; if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "useFlow", Q_ARG(bool, isActive), From 80150565f69472d4b54e2a938145f2f132a8f865 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Fri, 8 Mar 2019 16:30:28 -0700 Subject: [PATCH 433/474] Fix bug on group settings --- libraries/animation/src/Flow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/animation/src/Flow.cpp b/libraries/animation/src/Flow.cpp index 6e210fe71c..5bc2021e5e 100644 --- a/libraries/animation/src/Flow.cpp +++ b/libraries/animation/src/Flow.cpp @@ -786,7 +786,7 @@ Flow& Flow::operator=(const Flow& otherFlow) { } void Flow::updateGroupSettings(const QString& group, const FlowPhysicsSettings& settings) { - if (_groupSettings.find(group) != _groupSettings.end()) { + if (_groupSettings.find(group) == _groupSettings.end()) { _groupSettings.insert(std::pair(group, settings)); } else { _groupSettings[group] = settings; From 15d49fd9a923ff3ddde21b15d2f1b9fe2e2ece65 Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Tue, 5 Mar 2019 17:09:54 -0800 Subject: [PATCH 434/474] Initial implementation (deadlocks still occurring in Audio.cpp) --- interface/resources/qml/hifi/audio/Audio.qml | 19 ++ interface/resources/qml/hifi/audio/MicBar.qml | 9 +- interface/src/Application.cpp | 18 ++ interface/src/Application.h | 2 + interface/src/scripting/Audio.cpp | 174 +++++++++++++++++- interface/src/scripting/Audio.h | 89 ++++++++- scripts/system/audio.js | 3 + 7 files changed, 302 insertions(+), 12 deletions(-) diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index c8dd83cd62..45358f59a2 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -148,6 +148,25 @@ Rectangle { checked = Qt.binding(function() { return AudioScriptingInterface.noiseReduction; }); // restore binding } } + AudioControls.CheckBox { + spacing: muteMic.spacing + text: qsTr("Push To Talk"); + checked: isVR ? AudioScriptingInterface.pushToTalkHMD : AudioScriptingInterface.pushToTalkDesktop; + onClicked: { + if (isVR) { + AudioScriptingInterface.pushToTalkHMD = checked; + } else { + AudioScriptingInterface.pushToTalkDesktop = checked; + } + checked = Qt.binding(function() { + if (isVR) { + return AudioScriptingInterface.pushToTalkHMD; + } else { + return AudioScriptingInterface.pushToTalkDesktop; + } + }); // restore binding + } + } AudioControls.CheckBox { spacing: muteMic.spacing text: qsTr("Show audio level meter"); diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index 39f75a9182..f91058bc3c 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -11,10 +11,13 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 +import stylesUit 1.0 import TabletScriptingInterface 1.0 Rectangle { + HifiConstants { id: hifi; } + readonly property var level: AudioScriptingInterface.inputLevel; property bool gated: false; @@ -131,9 +134,9 @@ Rectangle { Item { id: status; - readonly property string color: AudioScriptingInterface.muted ? colors.muted : colors.unmuted; + readonly property string color: (AudioScriptingInterface.pushingToTalk && AudioScriptingInterface.muted) ? hifi.colors.blueHighlight : AudioScriptingInterface.muted ? colors.muted : colors.unmuted; - visible: AudioScriptingInterface.muted; + visible: AudioScriptingInterface.pushingToTalk || AudioScriptingInterface.muted; anchors { left: parent.left; @@ -152,7 +155,7 @@ Rectangle { color: parent.color; - text: AudioScriptingInterface.muted ? "MUTED" : "MUTE"; + text: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? "SPEAKING" : "PUSH TO TALK") : (AudioScriptingInterface.muted ? "MUTED" : "MUTE"); font.pointSize: 12; } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 6d9a1823a1..dad86e748e 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1435,6 +1435,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo }); connect(this, &Application::activeDisplayPluginChanged, reinterpret_cast(audioScriptingInterface.data()), &scripting::Audio::onContextChanged); + connect(this, &Application::pushedToTalk, + reinterpret_cast(audioScriptingInterface.data()), &scripting::Audio::handlePushedToTalk); } // Create the rendering engine. This can be slow on some machines due to lots of @@ -4205,6 +4207,10 @@ void Application::keyPressEvent(QKeyEvent* event) { } break; + case Qt::Key_T: + emit pushedToTalk(true); + break; + case Qt::Key_P: { if (!isShifted && !isMeta && !isOption && !event->isAutoRepeat()) { AudioInjectorOptions options; @@ -4310,6 +4316,12 @@ void Application::keyReleaseEvent(QKeyEvent* event) { if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->keyReleaseEvent(event); } + + switch (event->key()) { + case Qt::Key_T: + emit pushedToTalk(false); + break; + } } void Application::focusOutEvent(QFocusEvent* event) { @@ -5241,6 +5253,9 @@ void Application::loadSettings() { } } + auto audioScriptingInterface = reinterpret_cast(DependencyManager::get().data()); + audioScriptingInterface->loadData(); + getMyAvatar()->loadData(); _settingsLoaded = true; } @@ -5250,6 +5265,9 @@ void Application::saveSettings() const { DependencyManager::get()->saveSettings(); DependencyManager::get()->saveSettings(); + auto audioScriptingInterface = reinterpret_cast(DependencyManager::get().data()); + audioScriptingInterface->saveData(); + Menu::getInstance()->saveSettings(); getMyAvatar()->saveData(); PluginManager::getInstance()->saveSettings(); diff --git a/interface/src/Application.h b/interface/src/Application.h index a8cc9450c5..1c86326f90 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -358,6 +358,8 @@ signals: void miniTabletEnabledChanged(bool enabled); + void pushedToTalk(bool enabled); + public slots: QVector pasteEntities(float x, float y, float z); bool exportEntities(const QString& filename, const QVector& entityIDs, const glm::vec3* givenOffset = nullptr); diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 2c4c29ff65..fe04ce47ca 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -63,26 +63,163 @@ void Audio::stopRecording() { } bool Audio::isMuted() const { - return resultWithReadLock([&] { - return _isMuted; - }); + bool isHMD = qApp->isHMDMode(); + if (isHMD) { + return getMutedHMD(); + } + else { + return getMutedDesktop(); + } } void Audio::setMuted(bool isMuted) { + withWriteLock([&] { + bool isHMD = qApp->isHMDMode(); + if (isHMD) { + setMutedHMD(isMuted); + } + else { + setMutedDesktop(isMuted); + } + }); +} + +void Audio::setMutedDesktop(bool isMuted) { + bool changed = false; + if (_desktopMuted != isMuted) { + changed = true; + _desktopMuted = isMuted; + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + } + if (changed) { + emit mutedChanged(isMuted); + emit desktopMutedChanged(isMuted); + } +} + +bool Audio::getMutedDesktop() const { + return resultWithReadLock([&] { + return _desktopMuted; + }); +} + +void Audio::setMutedHMD(bool isMuted) { + bool changed = false; + if (_hmdMuted != isMuted) { + changed = true; + _hmdMuted = isMuted; + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + } + if (changed) { + emit mutedChanged(isMuted); + emit hmdMutedChanged(isMuted); + } +} + +bool Audio::getMutedHMD() const { + return resultWithReadLock([&] { + return _hmdMuted; + }); +} + +bool Audio::getPTT() { + bool isHMD = qApp->isHMDMode(); + if (isHMD) { + return getPTTHMD(); + } + else { + return getPTTDesktop(); + } +} + +bool Audio::getPushingToTalk() const { + return resultWithReadLock([&] { + return _pushingToTalk; + }); +} + +void Audio::setPTT(bool enabled) { + bool isHMD = qApp->isHMDMode(); + if (isHMD) { + setPTTHMD(enabled); + } + else { + setPTTDesktop(enabled); + } +} + +void Audio::setPTTDesktop(bool enabled) { bool changed = false; withWriteLock([&] { - if (_isMuted != isMuted) { - _isMuted = isMuted; - auto client = DependencyManager::get().data(); - QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + if (_pttDesktop != enabled) { changed = true; + _pttDesktop = enabled; + if (!enabled) { + // Set to default behavior (unmuted for Desktop) on Push-To-Talk disable. + setMutedDesktop(true); + } + else { + // Should be muted when not pushing to talk while PTT is enabled. + setMutedDesktop(true); + } } }); if (changed) { - emit mutedChanged(isMuted); + emit pushToTalkChanged(enabled); + emit pushToTalkDesktopChanged(enabled); } } +bool Audio::getPTTDesktop() const { + return resultWithReadLock([&] { + return _pttDesktop; + }); +} + +void Audio::setPTTHMD(bool enabled) { + bool changed = false; + withWriteLock([&] { + if (_pttHMD != enabled) { + changed = true; + _pttHMD = enabled; + if (!enabled) { + // Set to default behavior (unmuted for HMD) on Push-To-Talk disable. + setMutedHMD(false); + } + else { + // Should be muted when not pushing to talk while PTT is enabled. + setMutedHMD(true); + } + } + }); + if (changed) { + emit pushToTalkChanged(enabled); + emit pushToTalkHMDChanged(enabled); + } +} + +bool Audio::getPTTHMD() const { + return resultWithReadLock([&] { + return _pttHMD; + }); +} + +void Audio::saveData() { + _desktopMutedSetting.set(getMutedDesktop()); + _hmdMutedSetting.set(getMutedHMD()); + _pttDesktopSetting.set(getPTTDesktop()); + _pttHMDSetting.set(getPTTHMD()); +} + +void Audio::loadData() { + _desktopMuted = _desktopMutedSetting.get(); + _hmdMuted = _hmdMutedSetting.get(); + _pttDesktop = _pttDesktopSetting.get(); + _pttHMD = _pttHMDSetting.get(); +} + bool Audio::noiseReductionEnabled() const { return resultWithReadLock([&] { return _enableNoiseReduction; @@ -179,11 +316,32 @@ void Audio::onContextChanged() { changed = true; } }); + if (isHMD) { + setMuted(getMutedHMD()); + } + else { + setMuted(getMutedDesktop()); + } if (changed) { emit contextChanged(isHMD ? Audio::HMD : Audio::DESKTOP); } } +void Audio::handlePushedToTalk(bool enabled) { + if (getPTT()) { + if (enabled) { + setMuted(false); + } + else { + setMuted(true); + } + if (_pushingToTalk != enabled) { + _pushingToTalk = enabled; + emit pushingToTalkChanged(enabled); + } + } +} + void Audio::setReverb(bool enable) { withWriteLock([&] { DependencyManager::get()->setReverb(enable); diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index e4dcba9130..6aa589e399 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -12,6 +12,7 @@ #ifndef hifi_scripting_Audio_h #define hifi_scripting_Audio_h +#include #include "AudioScriptingInterface.h" #include "AudioDevices.h" #include "AudioEffectOptions.h" @@ -19,6 +20,9 @@ #include "AudioFileWav.h" #include +using MutedGetter = std::function; +using MutedSetter = std::function; + namespace scripting { class Audio : public AudioScriptingInterface, protected ReadWriteLockable { @@ -63,6 +67,12 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { Q_PROPERTY(bool clipping READ isClipping NOTIFY clippingChanged) Q_PROPERTY(QString context READ getContext NOTIFY contextChanged) Q_PROPERTY(AudioDevices* devices READ getDevices NOTIFY nop) + Q_PROPERTY(bool desktopMuted READ getMutedDesktop WRITE setMutedDesktop NOTIFY desktopMutedChanged) + Q_PROPERTY(bool hmdMuted READ getMutedHMD WRITE setMutedHMD NOTIFY hmdMutedChanged) + Q_PROPERTY(bool pushToTalk READ getPTT WRITE setPTT NOTIFY pushToTalkChanged); + Q_PROPERTY(bool pushToTalkDesktop READ getPTTDesktop WRITE setPTTDesktop NOTIFY pushToTalkDesktopChanged) + Q_PROPERTY(bool pushToTalkHMD READ getPTTHMD WRITE setPTTHMD NOTIFY pushToTalkHMDChanged) + Q_PROPERTY(bool pushingToTalk READ getPushingToTalk NOTIFY pushingToTalkChanged) public: static QString AUDIO; @@ -82,6 +92,25 @@ public: void showMicMeter(bool show); + // Mute setting setters and getters + void setMutedDesktop(bool isMuted); + bool getMutedDesktop() const; + void setMutedHMD(bool isMuted); + bool getMutedHMD() const; + void setPTT(bool enabled); + bool getPTT(); + bool getPushingToTalk() const; + + // Push-To-Talk setters and getters + void setPTTDesktop(bool enabled); + bool getPTTDesktop() const; + void setPTTHMD(bool enabled); + bool getPTTHMD() const; + + // Settings handlers + void saveData(); + void loadData(); + /**jsdoc * @function Audio.setInputDevice * @param {object} device @@ -193,6 +222,46 @@ signals: */ void mutedChanged(bool isMuted); + /**jsdoc + * Triggered when desktop audio input is muted or unmuted. + * @function Audio.desktopMutedChanged + * @param {boolean} isMuted - true if the audio input is muted for desktop mode, otherwise false. + * @returns {Signal} + */ + void desktopMutedChanged(bool isMuted); + + /**jsdoc + * Triggered when HMD audio input is muted or unmuted. + * @function Audio.hmdMutedChanged + * @param {boolean} isMuted - true if the audio input is muted for HMD mode, otherwise false. + * @returns {Signal} + */ + void hmdMutedChanged(bool isMuted); + + /** + * Triggered when Push-to-Talk has been enabled or disabled. + * @function Audio.pushToTalkChanged + * @param {boolean} enabled - true if Push-to-Talk is enabled, otherwise false. + * @returns {Signal} + */ + void pushToTalkChanged(bool enabled); + + /** + * Triggered when Push-to-Talk has been enabled or disabled for desktop mode. + * @function Audio.pushToTalkDesktopChanged + * @param {boolean} enabled - true if Push-to-Talk is emabled for Desktop mode, otherwise false. + * @returns {Signal} + */ + void pushToTalkDesktopChanged(bool enabled); + + /** + * Triggered when Push-to-Talk has been enabled or disabled for HMD mode. + * @function Audio.pushToTalkHMDChanged + * @param {boolean} enabled - true if Push-to-Talk is emabled for HMD mode, otherwise false. + * @returns {Signal} + */ + void pushToTalkHMDChanged(bool enabled); + /**jsdoc * Triggered when the audio input noise reduction is enabled or disabled. * @function Audio.noiseReductionChanged @@ -237,6 +306,14 @@ signals: */ void contextChanged(const QString& context); + /**jsdoc + * Triggered when pushing to talk. + * @function Audio.pushingToTalkChanged + * @param {boolean} talking - true if broadcasting with PTT, false otherwise. + * @returns {Signal} + */ + void pushingToTalkChanged(bool talking); + public slots: /**jsdoc @@ -245,6 +322,8 @@ public slots: */ void onContextChanged(); + void handlePushedToTalk(bool enabled); + private slots: void setMuted(bool muted); void enableNoiseReduction(bool enable); @@ -260,11 +339,19 @@ private: float _inputVolume { 1.0f }; float _inputLevel { 0.0f }; bool _isClipping { false }; - bool _isMuted { false }; bool _enableNoiseReduction { true }; // Match default value of AudioClient::_isNoiseGateEnabled. bool _contextIsHMD { false }; AudioDevices* getDevices() { return &_devices; } AudioDevices _devices; + Setting::Handle _desktopMutedSetting{ QStringList { Audio::AUDIO, "desktopMuted" }, true }; + Setting::Handle _hmdMutedSetting{ QStringList { Audio::AUDIO, "hmdMuted" }, true }; + Setting::Handle _pttDesktopSetting{ QStringList { Audio::AUDIO, "pushToTalkDesktop" }, false }; + Setting::Handle _pttHMDSetting{ QStringList { Audio::AUDIO, "pushToTalkHMD" }, false }; + bool _desktopMuted{ true }; + bool _hmdMuted{ false }; + bool _pttDesktop{ false }; + bool _pttHMD{ false }; + bool _pushingToTalk{ false }; }; }; diff --git a/scripts/system/audio.js b/scripts/system/audio.js index ee82c0c6ea..51d070d8cd 100644 --- a/scripts/system/audio.js +++ b/scripts/system/audio.js @@ -28,6 +28,9 @@ var UNMUTE_ICONS = { }; function onMuteToggled() { + if (Audio.pushingToTalk) { + return; + } if (Audio.muted) { button.editProperties(MUTE_ICONS); } else { From 7cb17b2d6ea31da2aefb0f775569ca1abf43e637 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 6 Mar 2019 11:00:09 -0800 Subject: [PATCH 435/474] laying groundwork for audio app + fixing deadlocks --- interface/resources/qml/hifi/audio/MicBar.qml | 6 +- interface/src/scripting/Audio.cpp | 108 +++++++++--------- interface/src/scripting/Audio.h | 1 + scripts/system/audio.js | 11 +- 4 files changed, 69 insertions(+), 57 deletions(-) diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index f91058bc3c..2ab1085408 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -134,7 +134,7 @@ Rectangle { Item { id: status; - readonly property string color: (AudioScriptingInterface.pushingToTalk && AudioScriptingInterface.muted) ? hifi.colors.blueHighlight : AudioScriptingInterface.muted ? colors.muted : colors.unmuted; + readonly property string color: AudioScriptingInterface.pushToTalk ? hifi.colors.blueHighlight : AudioScriptingInterface.muted ? colors.muted : colors.unmuted; visible: AudioScriptingInterface.pushingToTalk || AudioScriptingInterface.muted; @@ -165,7 +165,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - width: 50; + width: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? 45: 30) : 50; height: 4; color: parent.color; } @@ -176,7 +176,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - width: 50; + width: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? 45: 30) : 50; height: 4; color: parent.color; } diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index fe04ce47ca..63ce9d2b2e 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -66,32 +66,30 @@ bool Audio::isMuted() const { bool isHMD = qApp->isHMDMode(); if (isHMD) { return getMutedHMD(); - } - else { + } else { return getMutedDesktop(); } } void Audio::setMuted(bool isMuted) { - withWriteLock([&] { - bool isHMD = qApp->isHMDMode(); - if (isHMD) { - setMutedHMD(isMuted); - } - else { - setMutedDesktop(isMuted); - } - }); + bool isHMD = qApp->isHMDMode(); + if (isHMD) { + setMutedHMD(isMuted); + } else { + setMutedDesktop(isMuted); + } } void Audio::setMutedDesktop(bool isMuted) { bool changed = false; - if (_desktopMuted != isMuted) { - changed = true; - _desktopMuted = isMuted; - auto client = DependencyManager::get().data(); - QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); - } + withWriteLock([&] { + if (_desktopMuted != isMuted) { + changed = true; + _desktopMuted = isMuted; + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + } + }); if (changed) { emit mutedChanged(isMuted); emit desktopMutedChanged(isMuted); @@ -106,12 +104,14 @@ bool Audio::getMutedDesktop() const { void Audio::setMutedHMD(bool isMuted) { bool changed = false; - if (_hmdMuted != isMuted) { - changed = true; - _hmdMuted = isMuted; - auto client = DependencyManager::get().data(); - QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); - } + withWriteLock([&] { + if (_hmdMuted != isMuted) { + changed = true; + _hmdMuted = isMuted; + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + } + }); if (changed) { emit mutedChanged(isMuted); emit hmdMutedChanged(isMuted); @@ -128,12 +128,24 @@ bool Audio::getPTT() { bool isHMD = qApp->isHMDMode(); if (isHMD) { return getPTTHMD(); - } - else { + } else { return getPTTDesktop(); } } +void scripting::Audio::setPushingToTalk(bool pushingToTalk) { + bool changed = false; + withWriteLock([&] { + if (_pushingToTalk != pushingToTalk) { + changed = true; + _pushingToTalk = pushingToTalk; + } + }); + if (changed) { + emit pushingToTalkChanged(pushingToTalk); + } +} + bool Audio::getPushingToTalk() const { return resultWithReadLock([&] { return _pushingToTalk; @@ -144,8 +156,7 @@ void Audio::setPTT(bool enabled) { bool isHMD = qApp->isHMDMode(); if (isHMD) { setPTTHMD(enabled); - } - else { + } else { setPTTDesktop(enabled); } } @@ -156,16 +167,16 @@ void Audio::setPTTDesktop(bool enabled) { if (_pttDesktop != enabled) { changed = true; _pttDesktop = enabled; - if (!enabled) { - // Set to default behavior (unmuted for Desktop) on Push-To-Talk disable. - setMutedDesktop(true); - } - else { - // Should be muted when not pushing to talk while PTT is enabled. - setMutedDesktop(true); - } } }); + if (!enabled) { + // Set to default behavior (unmuted for Desktop) on Push-To-Talk disable. + setMutedDesktop(true); + } else { + // Should be muted when not pushing to talk while PTT is enabled. + setMutedDesktop(true); + } + if (changed) { emit pushToTalkChanged(enabled); emit pushToTalkDesktopChanged(enabled); @@ -184,16 +195,16 @@ void Audio::setPTTHMD(bool enabled) { if (_pttHMD != enabled) { changed = true; _pttHMD = enabled; - if (!enabled) { - // Set to default behavior (unmuted for HMD) on Push-To-Talk disable. - setMutedHMD(false); - } - else { - // Should be muted when not pushing to talk while PTT is enabled. - setMutedHMD(true); - } } }); + if (!enabled) { + // Set to default behavior (unmuted for HMD) on Push-To-Talk disable. + setMutedHMD(false); + } else { + // Should be muted when not pushing to talk while PTT is enabled. + setMutedHMD(true); + } + if (changed) { emit pushToTalkChanged(enabled); emit pushToTalkHMDChanged(enabled); @@ -318,8 +329,7 @@ void Audio::onContextChanged() { }); if (isHMD) { setMuted(getMutedHMD()); - } - else { + } else { setMuted(getMutedDesktop()); } if (changed) { @@ -331,14 +341,10 @@ void Audio::handlePushedToTalk(bool enabled) { if (getPTT()) { if (enabled) { setMuted(false); - } - else { + } else { setMuted(true); } - if (_pushingToTalk != enabled) { - _pushingToTalk = enabled; - emit pushingToTalkChanged(enabled); - } + setPushingToTalk(enabled); } } diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index 6aa589e399..94f8a7bf54 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -99,6 +99,7 @@ public: bool getMutedHMD() const; void setPTT(bool enabled); bool getPTT(); + void setPushingToTalk(bool pushingToTalk); bool getPushingToTalk() const; // Push-To-Talk setters and getters diff --git a/scripts/system/audio.js b/scripts/system/audio.js index 51d070d8cd..bf44cfa7cc 100644 --- a/scripts/system/audio.js +++ b/scripts/system/audio.js @@ -26,12 +26,15 @@ var UNMUTE_ICONS = { icon: "icons/tablet-icons/mic-unmute-i.svg", activeIcon: "icons/tablet-icons/mic-unmute-a.svg" }; +var PTT_ICONS = { + icon: "icons/tablet-icons/mic-unmute-i.svg", + activeIcon: "icons/tablet-icons/mic-unmute-a.svg" +}; function onMuteToggled() { if (Audio.pushingToTalk) { - return; - } - if (Audio.muted) { + button.editProperties(PTT_ICONS); + } else if (Audio.muted) { button.editProperties(MUTE_ICONS); } else { button.editProperties(UNMUTE_ICONS); @@ -71,6 +74,7 @@ onMuteToggled(); button.clicked.connect(onClicked); tablet.screenChanged.connect(onScreenChanged); Audio.mutedChanged.connect(onMuteToggled); +Audio.pushingToTalkChanged.connect(onMuteToggled); Script.scriptEnding.connect(function () { if (onAudioScreen) { @@ -79,6 +83,7 @@ Script.scriptEnding.connect(function () { button.clicked.disconnect(onClicked); tablet.screenChanged.disconnect(onScreenChanged); Audio.mutedChanged.disconnect(onMuteToggled); + Audio.pushingToTalkChanged.disconnect(onMuteToggled); tablet.removeButton(button); }); From 60b98d2c70b2b9366d9d1d6abd18e9136d566a69 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 6 Mar 2019 11:11:47 -0800 Subject: [PATCH 436/474] Push to talk changes + rebased with master (#7) Push to talk changes + rebased with master --- interface/src/scripting/Audio.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 63ce9d2b2e..45bb15f1a3 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -231,6 +231,26 @@ void Audio::loadData() { _pttHMD = _pttHMDSetting.get(); } +bool Audio::getPTTHMD() const { + return resultWithReadLock([&] { + return _pttHMD; + }); +} + +void Audio::saveData() { + _desktopMutedSetting.set(getMutedDesktop()); + _hmdMutedSetting.set(getMutedHMD()); + _pttDesktopSetting.set(getPTTDesktop()); + _pttHMDSetting.set(getPTTHMD()); +} + +void Audio::loadData() { + _desktopMuted = _desktopMutedSetting.get(); + _hmdMuted = _hmdMutedSetting.get(); + _pttDesktop = _pttDesktopSetting.get(); + _pttHMD = _pttHMDSetting.get(); +} + bool Audio::noiseReductionEnabled() const { return resultWithReadLock([&] { return _enableNoiseReduction; From 8a5924077a24d0892ab402dceb0c1a258ce07860 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 6 Mar 2019 11:18:57 -0800 Subject: [PATCH 437/474] changing text display --- interface/resources/qml/hifi/audio/MicBar.qml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index 2ab1085408..9d1cbfbc6c 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -134,9 +134,9 @@ Rectangle { Item { id: status; - readonly property string color: AudioScriptingInterface.pushToTalk ? hifi.colors.blueHighlight : AudioScriptingInterface.muted ? colors.muted : colors.unmuted; + readonly property string color: AudioScriptingInterface.muted ? colors.muted : colors.unmuted; - visible: AudioScriptingInterface.pushingToTalk || AudioScriptingInterface.muted; + visible: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) || AudioScriptingInterface.muted; anchors { left: parent.left; @@ -155,7 +155,7 @@ Rectangle { color: parent.color; - text: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? "SPEAKING" : "PUSH TO TALK") : (AudioScriptingInterface.muted ? "MUTED" : "MUTE"); + text: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? "MUTED-PTT (T)" : (AudioScriptingInterface.muted ? "MUTED" : "MUTE"); font.pointSize: 12; } @@ -165,7 +165,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - width: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? 45: 30) : 50; + width: AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk ? 25 : 50; height: 4; color: parent.color; } @@ -176,7 +176,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - width: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? 45: 30) : 50; + width: AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk ? 25 : 50; height: 4; color: parent.color; } From a688c0a92b56062b46442f9e25d6d8b57f77294d Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 6 Mar 2019 11:45:43 -0800 Subject: [PATCH 438/474] exposing setting pushingToTalk --- interface/src/scripting/Audio.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index 94f8a7bf54..9ad4aac9c1 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -72,7 +72,7 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { Q_PROPERTY(bool pushToTalk READ getPTT WRITE setPTT NOTIFY pushToTalkChanged); Q_PROPERTY(bool pushToTalkDesktop READ getPTTDesktop WRITE setPTTDesktop NOTIFY pushToTalkDesktopChanged) Q_PROPERTY(bool pushToTalkHMD READ getPTTHMD WRITE setPTTHMD NOTIFY pushToTalkHMDChanged) - Q_PROPERTY(bool pushingToTalk READ getPushingToTalk NOTIFY pushingToTalkChanged) + Q_PROPERTY(bool pushingToTalk READ getPushingToTalk WRITE setPushingToTalk NOTIFY pushingToTalkChanged) public: static QString AUDIO; From b7d44403e16e76e8d7b1c672c457946e1933a378 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 6 Mar 2019 17:29:32 -0800 Subject: [PATCH 439/474] updating compile failure + icons/settings update --- .../icons/tablet-icons/mic-ptt-a.svg | 1 + .../icons/tablet-icons/mic-ptt-i.svg | 24 +++++++ interface/resources/qml/hifi/audio/Audio.qml | 64 ++++++++++++++++--- interface/resources/qml/hifi/audio/MicBar.qml | 8 ++- interface/src/scripting/Audio.cpp | 20 ------ scripts/system/audio.js | 14 ++-- 6 files changed, 93 insertions(+), 38 deletions(-) create mode 100644 interface/resources/icons/tablet-icons/mic-ptt-a.svg create mode 100644 interface/resources/icons/tablet-icons/mic-ptt-i.svg diff --git a/interface/resources/icons/tablet-icons/mic-ptt-a.svg b/interface/resources/icons/tablet-icons/mic-ptt-a.svg new file mode 100644 index 0000000000..e6df3c69d7 --- /dev/null +++ b/interface/resources/icons/tablet-icons/mic-ptt-a.svg @@ -0,0 +1 @@ +mic-ptt-a \ No newline at end of file diff --git a/interface/resources/icons/tablet-icons/mic-ptt-i.svg b/interface/resources/icons/tablet-icons/mic-ptt-i.svg new file mode 100644 index 0000000000..2141ea5229 --- /dev/null +++ b/interface/resources/icons/tablet-icons/mic-ptt-i.svg @@ -0,0 +1,24 @@ + + + + + + diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index 45358f59a2..d44a9c862e 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -120,6 +120,10 @@ Rectangle { isRedCheck: true; checked: AudioScriptingInterface.muted; onClicked: { + if (AudioScriptingInterface.pushToTalk && !checked) { + // disable push to talk if unmuting + AudioScriptingInterface.pushToTalk = false; + } AudioScriptingInterface.muted = checked; checked = Qt.binding(function() { return AudioScriptingInterface.muted; }); // restore binding } @@ -150,7 +154,23 @@ Rectangle { } AudioControls.CheckBox { spacing: muteMic.spacing - text: qsTr("Push To Talk"); + text: qsTr("Show audio level meter"); + checked: AvatarInputs.showAudioTools; + onClicked: { + AvatarInputs.showAudioTools = checked; + checked = Qt.binding(function() { return AvatarInputs.showAudioTools; }); // restore binding + } + onXChanged: rightMostInputLevelPos = x + width + } + } + + Separator {} + + ColumnLayout { + spacing: muteMic.spacing; + AudioControls.CheckBox { + spacing: muteMic.spacing + text: qsTr("Push To Talk (T)"); checked: isVR ? AudioScriptingInterface.pushToTalkHMD : AudioScriptingInterface.pushToTalkDesktop; onClicked: { if (isVR) { @@ -167,15 +187,41 @@ Rectangle { }); // restore binding } } - AudioControls.CheckBox { - spacing: muteMic.spacing - text: qsTr("Show audio level meter"); - checked: AvatarInputs.showAudioTools; - onClicked: { - AvatarInputs.showAudioTools = checked; - checked = Qt.binding(function() { return AvatarInputs.showAudioTools; }); // restore binding + Item { + id: pttTextContainer + x: margins.paddings; + width: rightMostInputLevelPos + height: pttTextMetrics.height + visible: true + TextMetrics { + id: pttTextMetrics + text: pttText.text + font: pttText.font + } + RalewayRegular { + id: pttText + wrapMode: Text.WordWrap + color: hifi.colors.white; + width: parent.width; + font.italic: true + size: 16; + text: isVR ? qsTr("Press and hold grip triggers on both of your controllers to unmute.") : + qsTr("Press and hold the button \"T\" to unmute."); + onTextChanged: { + if (pttTextMetrics.width > rightMostInputLevelPos) { + pttTextContainer.height = Math.ceil(pttTextMetrics.width / rightMostInputLevelPos) * pttTextMetrics.height; + } else { + pttTextContainer.height = pttTextMetrics.height; + } + } + } + Component.onCompleted: { + if (pttTextMetrics.width > rightMostInputLevelPos) { + pttTextContainer.height = Math.ceil(pttTextMetrics.width / rightMostInputLevelPos) * pttTextMetrics.height; + } else { + pttTextContainer.height = pttTextMetrics.height; + } } - onXChanged: rightMostInputLevelPos = x + width } } } diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index 9d1cbfbc6c..50477b82f8 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -67,6 +67,9 @@ Rectangle { hoverEnabled: true; scrollGestureEnabled: false; onClicked: { + if (AudioScriptingInterface.pushToTalk) { + return; + } AudioScriptingInterface.muted = !AudioScriptingInterface.muted; Tablet.playSound(TabletEnums.ButtonClick); } @@ -109,9 +112,10 @@ Rectangle { Image { readonly property string unmutedIcon: "../../../icons/tablet-icons/mic-unmute-i.svg"; readonly property string mutedIcon: "../../../icons/tablet-icons/mic-mute-i.svg"; + readonly property string pushToTalkIcon: "../../../icons/tablet-icons/mic-ptt-i.svg"; id: image; - source: AudioScriptingInterface.muted ? mutedIcon : unmutedIcon; + source: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? pushToTalkIcon : AudioScriptingInterface.muted ? mutedIcon : unmutedIcon; width: 30; height: 30; @@ -155,7 +159,7 @@ Rectangle { color: parent.color; - text: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? "MUTED-PTT (T)" : (AudioScriptingInterface.muted ? "MUTED" : "MUTE"); + text: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? "MUTED PTT-(T)" : (AudioScriptingInterface.muted ? "MUTED" : "MUTE"); font.pointSize: 12; } diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 45bb15f1a3..63ce9d2b2e 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -231,26 +231,6 @@ void Audio::loadData() { _pttHMD = _pttHMDSetting.get(); } -bool Audio::getPTTHMD() const { - return resultWithReadLock([&] { - return _pttHMD; - }); -} - -void Audio::saveData() { - _desktopMutedSetting.set(getMutedDesktop()); - _hmdMutedSetting.set(getMutedHMD()); - _pttDesktopSetting.set(getPTTDesktop()); - _pttHMDSetting.set(getPTTHMD()); -} - -void Audio::loadData() { - _desktopMuted = _desktopMutedSetting.get(); - _hmdMuted = _hmdMutedSetting.get(); - _pttDesktop = _pttDesktopSetting.get(); - _pttHMD = _pttHMDSetting.get(); -} - bool Audio::noiseReductionEnabled() const { return resultWithReadLock([&] { return _enableNoiseReduction; diff --git a/scripts/system/audio.js b/scripts/system/audio.js index bf44cfa7cc..19ed3faef2 100644 --- a/scripts/system/audio.js +++ b/scripts/system/audio.js @@ -27,12 +27,12 @@ var UNMUTE_ICONS = { activeIcon: "icons/tablet-icons/mic-unmute-a.svg" }; var PTT_ICONS = { - icon: "icons/tablet-icons/mic-unmute-i.svg", - activeIcon: "icons/tablet-icons/mic-unmute-a.svg" + icon: "icons/tablet-icons/mic-ptt-i.svg", + activeIcon: "icons/tablet-icons/mic-ptt-a.svg" }; function onMuteToggled() { - if (Audio.pushingToTalk) { + if (Audio.pushToTalk) { button.editProperties(PTT_ICONS); } else if (Audio.muted) { button.editProperties(MUTE_ICONS); @@ -63,8 +63,8 @@ function onScreenChanged(type, url) { var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var button = tablet.addButton({ - icon: Audio.muted ? MUTE_ICONS.icon : UNMUTE_ICONS.icon, - activeIcon: Audio.muted ? MUTE_ICONS.activeIcon : UNMUTE_ICONS.activeIcon, + icon: Audio.pushToTalk ? PTT_ICONS.icon : Audio.muted ? MUTE_ICONS.icon : UNMUTE_ICONS.icon, + activeIcon: Audio.pushToTalk ? PTT_ICONS.activeIcon : Audio.muted ? MUTE_ICONS.activeIcon : UNMUTE_ICONS.activeIcon, text: TABLET_BUTTON_NAME, sortOrder: 1 }); @@ -74,7 +74,7 @@ onMuteToggled(); button.clicked.connect(onClicked); tablet.screenChanged.connect(onScreenChanged); Audio.mutedChanged.connect(onMuteToggled); -Audio.pushingToTalkChanged.connect(onMuteToggled); +Audio.pushToTalkChanged.connect(onMuteToggled); Script.scriptEnding.connect(function () { if (onAudioScreen) { @@ -83,7 +83,7 @@ Script.scriptEnding.connect(function () { button.clicked.disconnect(onClicked); tablet.screenChanged.disconnect(onScreenChanged); Audio.mutedChanged.disconnect(onMuteToggled); - Audio.pushingToTalkChanged.disconnect(onMuteToggled); + Audio.pushToTalkChanged.disconnect(onMuteToggled); tablet.removeButton(button); }); From f6add9cafdd87c6a253c344f905c295cbaa617fc Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Thu, 7 Mar 2019 11:31:28 -0800 Subject: [PATCH 440/474] Add pushToTalk.js controllerModule. --- .../controllerModules/pushToTalk.js | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 scripts/system/controllers/controllerModules/pushToTalk.js diff --git a/scripts/system/controllers/controllerModules/pushToTalk.js b/scripts/system/controllers/controllerModules/pushToTalk.js new file mode 100644 index 0000000000..e764b228c9 --- /dev/null +++ b/scripts/system/controllers/controllerModules/pushToTalk.js @@ -0,0 +1,75 @@ +"use strict"; + +// Created by Jason C. Najera on 3/7/2019 +// Copyright 2019 High Fidelity, Inc. +// +// Handles Push-to-Talk functionality for HMD mode. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +Script.include("/~/system/libraries/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/controllers.js"); + +(function() { // BEGIN LOCAL_SCOPE + function PushToTalkHandler() { + var _this = this; + this.active = false; + //var pttMapping, mappingName; + + this.setup = function() { + //mappingName = 'Hifi-PTT-Dev-' + Math.random(); + //pttMapping = Controller.newMapping(mappingName); + //pttMapping.enable(); + }; + + this.shouldTalk = function (controllerData) { + // Set up test against controllerData here... + var gripVal = controllerData.secondaryValues[this.hand]; + return (gripVal) ? true : false; + }; + + this.shouldStopTalking = function (controllerData) { + var gripVal = controllerData.secondaryValues[this.hand]; + return (gripVal) ? false : true; + }; + + this.isReady = function (controllerData, deltaTime) { + if (HMD.active() && Audio.pushToTalk && this.shouldTalk(controllerData)) { + Audio.pushingToTalk = true; + returnMakeRunningValues(true, [], []); + } + + return makeRunningValues(false, [], []); + }; + + this.run = function (controllerData, deltaTime) { + if (this.shouldStopTalking(controllerData) || !Audio.pushToTalk) { + Audio.pushingToTalk = false; + return makeRunningValues(false, [], []); + } + + return makeRunningValues(true, [], []); + }; + + this.cleanup = function () { + //pttMapping.disable(); + }; + + this.parameters = makeDispatcherModuleParameters( + 950, + ["head"], + [], + 100); + } + + var pushToTalk = new PushToTalkHandler(); + enableDispatcherModule("PushToTalk", pushToTalk); + + function cleanup () { + pushToTalk.cleanup(); + disableDispatcherModule("PushToTalk"); + }; + + Script.scriptEnding.connect(cleanup); +}()); // END LOCAL_SCOPE \ No newline at end of file From 3b274c2b6e7dbe77a9e89b255ae197c36e9184b8 Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Thu, 7 Mar 2019 11:31:48 -0800 Subject: [PATCH 441/474] Enable pushToTalk.js controller module. --- scripts/system/controllers/controllerDispatcher.js | 1 + scripts/system/controllers/controllerScripts.js | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 28c3e2a299..f4c0cbb0d6 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -58,6 +58,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); // not set to false (not in use), a module cannot start. When a module is using a slot, that module's name // is stored as the value, rather than false. this.activitySlots = { + head: false, leftHand: false, rightHand: false, rightHandTrigger: false, diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index 726e075fcc..ca7d041792 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -33,7 +33,8 @@ var CONTOLLER_SCRIPTS = [ "controllerModules/nearGrabHyperLinkEntity.js", "controllerModules/nearTabletHighlight.js", "controllerModules/nearGrabEntity.js", - "controllerModules/farGrabEntity.js" + "controllerModules/farGrabEntity.js", + "controllerModules/pushToTalk.js" ]; var DEBUG_MENU_ITEM = "Debug defaultScripts.js"; From 88a125aff0a5dd3de6d523e5325ddedc24dff30e Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Tue, 5 Mar 2019 17:09:54 -0800 Subject: [PATCH 442/474] Initial implementation (deadlocks still occurring in Audio.cpp) --- interface/resources/qml/hifi/audio/Audio.qml | 19 ++++++++ interface/resources/qml/hifi/audio/MicBar.qml | 18 ++++---- interface/src/scripting/Audio.cpp | 20 ++++++++ interface/src/scripting/Audio.h | 46 +++++++++---------- 4 files changed, 71 insertions(+), 32 deletions(-) diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index d44a9c862e..9e9a8c0022 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -152,6 +152,25 @@ Rectangle { checked = Qt.binding(function() { return AudioScriptingInterface.noiseReduction; }); // restore binding } } + AudioControls.CheckBox { + spacing: muteMic.spacing + text: qsTr("Push To Talk"); + checked: isVR ? AudioScriptingInterface.pushToTalkHMD : AudioScriptingInterface.pushToTalkDesktop; + onClicked: { + if (isVR) { + AudioScriptingInterface.pushToTalkHMD = checked; + } else { + AudioScriptingInterface.pushToTalkDesktop = checked; + } + checked = Qt.binding(function() { + if (isVR) { + return AudioScriptingInterface.pushToTalkHMD; + } else { + return AudioScriptingInterface.pushToTalkDesktop; + } + }); // restore binding + } + } AudioControls.CheckBox { spacing: muteMic.spacing text: qsTr("Show audio level meter"); diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index 50477b82f8..6cb45eaecb 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -19,13 +19,13 @@ Rectangle { HifiConstants { id: hifi; } readonly property var level: AudioScriptingInterface.inputLevel; - + property bool gated: false; Component.onCompleted: { AudioScriptingInterface.noiseGateOpened.connect(function() { gated = false; }); AudioScriptingInterface.noiseGateClosed.connect(function() { gated = true; }); } - + property bool standalone: false; property var dragTarget: null; @@ -138,7 +138,7 @@ Rectangle { Item { id: status; - readonly property string color: AudioScriptingInterface.muted ? colors.muted : colors.unmuted; + readonly property string color: (AudioScriptingInterface.pushingToTalk && AudioScriptingInterface.muted) ? hifi.colors.blueHighlight : AudioScriptingInterface.muted ? colors.muted : colors.unmuted; visible: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) || AudioScriptingInterface.muted; @@ -235,12 +235,12 @@ Rectangle { } } } - + Rectangle { id: gatedIndicator; visible: gated && !AudioScriptingInterface.clipping - - radius: 4; + + radius: 4; width: 2 * radius; height: 2 * radius; color: "#0080FF"; @@ -249,12 +249,12 @@ Rectangle { verticalCenter: parent.verticalCenter; } } - + Rectangle { id: clippingIndicator; visible: AudioScriptingInterface.clipping - - radius: 4; + + radius: 4; width: 2 * radius; height: 2 * radius; color: colors.red; diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 63ce9d2b2e..45bb15f1a3 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -231,6 +231,26 @@ void Audio::loadData() { _pttHMD = _pttHMDSetting.get(); } +bool Audio::getPTTHMD() const { + return resultWithReadLock([&] { + return _pttHMD; + }); +} + +void Audio::saveData() { + _desktopMutedSetting.set(getMutedDesktop()); + _hmdMutedSetting.set(getMutedHMD()); + _pttDesktopSetting.set(getPTTDesktop()); + _pttHMDSetting.set(getPTTHMD()); +} + +void Audio::loadData() { + _desktopMuted = _desktopMutedSetting.get(); + _hmdMuted = _hmdMutedSetting.get(); + _pttDesktop = _pttDesktopSetting.get(); + _pttHMD = _pttHMDSetting.get(); +} + bool Audio::noiseReductionEnabled() const { return resultWithReadLock([&] { return _enableNoiseReduction; diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index 9ad4aac9c1..10aceb02fb 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -41,16 +41,16 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { * @hifi-assignment-client * * @property {boolean} muted - true if the audio input is muted, otherwise false. - * @property {boolean} noiseReduction - true if noise reduction is enabled, otherwise false. When - * enabled, the input audio signal is blocked (fully attenuated) when it falls below an adaptive threshold set just + * @property {boolean} noiseReduction - true if noise reduction is enabled, otherwise false. When + * enabled, the input audio signal is blocked (fully attenuated) when it falls below an adaptive threshold set just * above the noise floor. - * @property {number} inputLevel - The loudness of the audio input, range 0.0 (no sound) – + * @property {number} inputLevel - The loudness of the audio input, range 0.0 (no sound) – * 1.0 (the onset of clipping). Read-only. * @property {boolean} clipping - true if the audio input is clipping, otherwise false. - * @property {number} inputVolume - Adjusts the volume of the input audio; range 0.01.0. - * If set to a value, the resulting value depends on the input device: for example, the volume can't be changed on some + * @property {number} inputVolume - Adjusts the volume of the input audio; range 0.01.0. + * If set to a value, the resulting value depends on the input device: for example, the volume can't be changed on some * devices, and others might only support values of 0.0 and 1.0. - * @property {boolean} isStereoInput - true if the input audio is being used in stereo, otherwise + * @property {boolean} isStereoInput - true if the input audio is being used in stereo, otherwise * false. Some devices do not support stereo, in which case the value is always false. * @property {string} context - The current context of the audio: either "Desktop" or "HMD". * Read-only. @@ -115,7 +115,7 @@ public: /**jsdoc * @function Audio.setInputDevice * @param {object} device - * @param {boolean} isHMD + * @param {boolean} isHMD * @deprecated This function is deprecated and will be removed. */ Q_INVOKABLE void setInputDevice(const QAudioDeviceInfo& device, bool isHMD); @@ -129,8 +129,8 @@ public: Q_INVOKABLE void setOutputDevice(const QAudioDeviceInfo& device, bool isHMD); /**jsdoc - * Enable or disable reverberation. Reverberation is done by the client, on the post-mix audio. The reverberation options - * come from either the domain's audio zone if used — configured on the server — or as scripted by + * Enable or disable reverberation. Reverberation is done by the client, on the post-mix audio. The reverberation options + * come from either the domain's audio zone if used — configured on the server — or as scripted by * {@link Audio.setReverbOptions|setReverbOptions}. * @function Audio.setReverb * @param {boolean} enable - true to enable reverberation, false to disable. @@ -140,13 +140,13 @@ public: * var injectorOptions = { * position: MyAvatar.position * }; - * + * * Script.setTimeout(function () { * print("Reverb OFF"); * Audio.setReverb(false); * injector = Audio.playSound(sound, injectorOptions); * }, 1000); - * + * * Script.setTimeout(function () { * var reverbOptions = new AudioEffectOptions(); * reverbOptions.roomSize = 100; @@ -154,26 +154,26 @@ public: * print("Reverb ON"); * Audio.setReverb(true); * }, 4000); - * + * * Script.setTimeout(function () { * print("Reverb OFF"); * Audio.setReverb(false); * }, 8000); */ Q_INVOKABLE void setReverb(bool enable); - + /**jsdoc * Configure reverberation options. Use {@link Audio.setReverb|setReverb} to enable or disable reverberation. * @function Audio.setReverbOptions * @param {AudioEffectOptions} options - The reverberation options. */ Q_INVOKABLE void setReverbOptions(const AudioEffectOptions* options); - + /**jsdoc * Starts making an audio recording of the audio being played in-world (i.e., not local-only audio) to a file in WAV format. * @function Audio.startRecording - * @param {string} filename - The path and name of the file to make the recording in. Should have a .wav + * @param {string} filename - The path and name of the file to make the recording in. Should have a .wav * extension. The file is overwritten if it already exists. - * @returns {boolean} true if the specified file could be opened and audio recording has started, otherwise + * @returns {boolean} true if the specified file could be opened and audio recording has started, otherwise * false. * @example Make a 10 second audio recording. * var filename = File.getTempDir() + "/audio.wav"; @@ -182,13 +182,13 @@ public: * Audio.stopRecording(); * print("Audio recording made in: " + filename); * }, 10000); - * + * * } else { * print("Could not make an audio recording in: " + filename); * } */ Q_INVOKABLE bool startRecording(const QString& filename); - + /**jsdoc * Finish making an audio recording started with {@link Audio.startRecording|startRecording}. * @function Audio.stopRecording @@ -222,7 +222,7 @@ signals: * }); */ void mutedChanged(bool isMuted); - + /**jsdoc * Triggered when desktop audio input is muted or unmuted. * @function Audio.desktopMutedChanged @@ -274,9 +274,9 @@ signals: /**jsdoc * Triggered when the input audio volume changes. * @function Audio.inputVolumeChanged - * @param {number} volume - The requested volume to be applied to the audio input, range 0.0 – - * 1.0. The resulting value of Audio.inputVolume depends on the capabilities of the device: - * for example, the volume can't be changed on some devices, and others might only support values of 0.0 + * @param {number} volume - The requested volume to be applied to the audio input, range 0.0 – + * 1.0. The resulting value of Audio.inputVolume depends on the capabilities of the device: + * for example, the volume can't be changed on some devices, and others might only support values of 0.0 * and 1.0. * @returns {Signal} */ @@ -285,7 +285,7 @@ signals: /**jsdoc * Triggered when the input audio level changes. * @function Audio.inputLevelChanged - * @param {number} level - The loudness of the input audio, range 0.0 (no sound) – 1.0 (the + * @param {number} level - The loudness of the input audio, range 0.0 (no sound) – 1.0 (the * onset of clipping). * @returns {Signal} */ From cb0fdd2ef2ff91854b0c4962bb2bd12bac4aa551 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 6 Mar 2019 11:00:09 -0800 Subject: [PATCH 443/474] laying groundwork for audio app + fixing deadlocks --- interface/resources/qml/hifi/audio/MicBar.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index 6cb45eaecb..41ab0b6e91 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -138,7 +138,7 @@ Rectangle { Item { id: status; - readonly property string color: (AudioScriptingInterface.pushingToTalk && AudioScriptingInterface.muted) ? hifi.colors.blueHighlight : AudioScriptingInterface.muted ? colors.muted : colors.unmuted; + readonly property string color: AudioScriptingInterface.pushToTalk ? hifi.colors.blueHighlight : AudioScriptingInterface.muted ? colors.muted : colors.unmuted; visible: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) || AudioScriptingInterface.muted; From f4db9fefd0d81e539491492981b32a8a75a51964 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 6 Mar 2019 11:11:47 -0800 Subject: [PATCH 444/474] Push to talk changes + rebased with master (#7) Push to talk changes + rebased with master --- interface/src/scripting/Audio.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 45bb15f1a3..0a859c4dcc 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -251,6 +251,26 @@ void Audio::loadData() { _pttHMD = _pttHMDSetting.get(); } +bool Audio::getPTTHMD() const { + return resultWithReadLock([&] { + return _pttHMD; + }); +} + +void Audio::saveData() { + _desktopMutedSetting.set(getMutedDesktop()); + _hmdMutedSetting.set(getMutedHMD()); + _pttDesktopSetting.set(getPTTDesktop()); + _pttHMDSetting.set(getPTTHMD()); +} + +void Audio::loadData() { + _desktopMuted = _desktopMutedSetting.get(); + _hmdMuted = _hmdMutedSetting.get(); + _pttDesktop = _pttDesktopSetting.get(); + _pttHMD = _pttHMDSetting.get(); +} + bool Audio::noiseReductionEnabled() const { return resultWithReadLock([&] { return _enableNoiseReduction; From 39db3979b9eb8e46b71623752ff4785fcb6f93dc Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 6 Mar 2019 11:18:57 -0800 Subject: [PATCH 445/474] changing text display --- interface/resources/qml/hifi/audio/MicBar.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index 41ab0b6e91..491b9f9554 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -138,7 +138,7 @@ Rectangle { Item { id: status; - readonly property string color: AudioScriptingInterface.pushToTalk ? hifi.colors.blueHighlight : AudioScriptingInterface.muted ? colors.muted : colors.unmuted; + readonly property string color: AudioScriptingInterface.muted ? colors.muted : colors.unmuted; visible: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) || AudioScriptingInterface.muted; From 88d8163b04e8b740c1433530a8821f94dae7ae3b Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 6 Mar 2019 17:29:32 -0800 Subject: [PATCH 446/474] updating compile failure + icons/settings update --- interface/resources/qml/hifi/audio/Audio.qml | 60 +++++++++++++++++--- 1 file changed, 51 insertions(+), 9 deletions(-) diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index 9e9a8c0022..569cd23176 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -154,7 +154,23 @@ Rectangle { } AudioControls.CheckBox { spacing: muteMic.spacing - text: qsTr("Push To Talk"); + text: qsTr("Show audio level meter"); + checked: AvatarInputs.showAudioTools; + onClicked: { + AvatarInputs.showAudioTools = checked; + checked = Qt.binding(function() { return AvatarInputs.showAudioTools; }); // restore binding + } + onXChanged: rightMostInputLevelPos = x + width + } + } + + Separator {} + + ColumnLayout { + spacing: muteMic.spacing; + AudioControls.CheckBox { + spacing: muteMic.spacing + text: qsTr("Push To Talk (T)"); checked: isVR ? AudioScriptingInterface.pushToTalkHMD : AudioScriptingInterface.pushToTalkDesktop; onClicked: { if (isVR) { @@ -171,15 +187,41 @@ Rectangle { }); // restore binding } } - AudioControls.CheckBox { - spacing: muteMic.spacing - text: qsTr("Show audio level meter"); - checked: AvatarInputs.showAudioTools; - onClicked: { - AvatarInputs.showAudioTools = checked; - checked = Qt.binding(function() { return AvatarInputs.showAudioTools; }); // restore binding + Item { + id: pttTextContainer + x: margins.paddings; + width: rightMostInputLevelPos + height: pttTextMetrics.height + visible: true + TextMetrics { + id: pttTextMetrics + text: pttText.text + font: pttText.font + } + RalewayRegular { + id: pttText + wrapMode: Text.WordWrap + color: hifi.colors.white; + width: parent.width; + font.italic: true + size: 16; + text: isVR ? qsTr("Press and hold grip triggers on both of your controllers to unmute.") : + qsTr("Press and hold the button \"T\" to unmute."); + onTextChanged: { + if (pttTextMetrics.width > rightMostInputLevelPos) { + pttTextContainer.height = Math.ceil(pttTextMetrics.width / rightMostInputLevelPos) * pttTextMetrics.height; + } else { + pttTextContainer.height = pttTextMetrics.height; + } + } + } + Component.onCompleted: { + if (pttTextMetrics.width > rightMostInputLevelPos) { + pttTextContainer.height = Math.ceil(pttTextMetrics.width / rightMostInputLevelPos) * pttTextMetrics.height; + } else { + pttTextContainer.height = pttTextMetrics.height; + } } - onXChanged: rightMostInputLevelPos = x + width } } From 24d6646e8d23f6aab6b441a7b097e17bd41ddbb4 Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Thu, 7 Mar 2019 11:45:59 -0800 Subject: [PATCH 447/474] Fix activation / deactivation criteria for PTT controller module. --- scripts/system/controllers/controllerModules/pushToTalk.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/system/controllers/controllerModules/pushToTalk.js b/scripts/system/controllers/controllerModules/pushToTalk.js index e764b228c9..557476ccd7 100644 --- a/scripts/system/controllers/controllerModules/pushToTalk.js +++ b/scripts/system/controllers/controllerModules/pushToTalk.js @@ -25,12 +25,12 @@ Script.include("/~/system/libraries/controllers.js"); this.shouldTalk = function (controllerData) { // Set up test against controllerData here... - var gripVal = controllerData.secondaryValues[this.hand]; + var gripVal = controllerData.secondaryValues[LEFT_HAND] && controllerData.secondaryValues[RIGHT_HAND]; return (gripVal) ? true : false; }; this.shouldStopTalking = function (controllerData) { - var gripVal = controllerData.secondaryValues[this.hand]; + var gripVal = controllerData.secondaryValues[LEFT_HAND] && controllerData.secondaryValues[RIGHT_HAND]; return (gripVal) ? false : true; }; From 5f48a6d1044fd11cab6110db39eca367448ab0e4 Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Thu, 7 Mar 2019 12:35:04 -0800 Subject: [PATCH 448/474] Fix typo. --- scripts/system/controllers/controllerModules/pushToTalk.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/controllers/controllerModules/pushToTalk.js b/scripts/system/controllers/controllerModules/pushToTalk.js index 557476ccd7..dd959ae6fb 100644 --- a/scripts/system/controllers/controllerModules/pushToTalk.js +++ b/scripts/system/controllers/controllerModules/pushToTalk.js @@ -35,7 +35,7 @@ Script.include("/~/system/libraries/controllers.js"); }; this.isReady = function (controllerData, deltaTime) { - if (HMD.active() && Audio.pushToTalk && this.shouldTalk(controllerData)) { + if (HMD.active && Audio.pushToTalk && this.shouldTalk(controllerData)) { Audio.pushingToTalk = true; returnMakeRunningValues(true, [], []); } From 18b86d550de3f71290b354e1e4ff602ba4dd03ff Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Thu, 7 Mar 2019 12:36:56 -0800 Subject: [PATCH 449/474] adding PushToTalk action --- interface/src/Application.cpp | 11 +++++++++++ libraries/controllers/src/controllers/Actions.cpp | 4 ++++ libraries/controllers/src/controllers/Actions.h | 1 + .../controllers/controllerModules/pushToTalk.js | 2 +- 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index dad86e748e..c0cacd4e40 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1601,12 +1601,23 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(userInputMapper.data(), &UserInputMapper::actionEvent, [this](int action, float state) { using namespace controller; auto tabletScriptingInterface = DependencyManager::get(); + auto audioScriptingInterface = reinterpret_cast(DependencyManager::get().data()); { auto actionEnum = static_cast(action); int key = Qt::Key_unknown; static int lastKey = Qt::Key_unknown; bool navAxis = false; switch (actionEnum) { + case Action::TOGGLE_PUSHTOTALK: + if (audioScriptingInterface->getPTT()) { + qDebug() << "State is " << state; + if (state > 0.0f) { + audioScriptingInterface->setPushingToTalk(false); + } else if (state < 0.0f) { + audioScriptingInterface->setPushingToTalk(true); + } + } + case Action::UI_NAV_VERTICAL: navAxis = true; if (state > 0.0f) { diff --git a/libraries/controllers/src/controllers/Actions.cpp b/libraries/controllers/src/controllers/Actions.cpp index 5a396231b6..57be2f788b 100644 --- a/libraries/controllers/src/controllers/Actions.cpp +++ b/libraries/controllers/src/controllers/Actions.cpp @@ -180,6 +180,7 @@ namespace controller { * third person, to full screen mirror, then back to first person and repeat. * ContextMenunumbernumberShow / hide the tablet. * ToggleMutenumbernumberToggle the microphone mute. + * TogglePushToTalknumbernumberToggle push to talk. * ToggleOverlaynumbernumberToggle the display of overlays. * SprintnumbernumberSet avatar sprint mode. * ReticleClicknumbernumberSet mouse-pressed. @@ -245,6 +246,8 @@ namespace controller { * ContextMenu instead. * TOGGLE_MUTEnumbernumberDeprecated: Use * ToggleMute instead. + * TOGGLE_PUSHTOTALKnumbernumberDeprecated: Use + * TogglePushToTalk instead. * SPRINTnumbernumberDeprecated: Use * Sprint instead. * LONGITUDINAL_BACKWARDnumbernumberDeprecated: Use @@ -411,6 +414,7 @@ namespace controller { makeButtonPair(Action::ACTION2, "SecondaryAction"), makeButtonPair(Action::CONTEXT_MENU, "ContextMenu"), makeButtonPair(Action::TOGGLE_MUTE, "ToggleMute"), + makeButtonPair(Action::TOGGLE_PUSHTOTALK, "TogglePushToTalk"), makeButtonPair(Action::CYCLE_CAMERA, "CycleCamera"), makeButtonPair(Action::TOGGLE_OVERLAY, "ToggleOverlay"), makeButtonPair(Action::SPRINT, "Sprint"), diff --git a/libraries/controllers/src/controllers/Actions.h b/libraries/controllers/src/controllers/Actions.h index a12a3d60a9..3e99d8d147 100644 --- a/libraries/controllers/src/controllers/Actions.h +++ b/libraries/controllers/src/controllers/Actions.h @@ -60,6 +60,7 @@ enum class Action { CONTEXT_MENU, TOGGLE_MUTE, + TOGGLE_PUSHTOTALK, CYCLE_CAMERA, TOGGLE_OVERLAY, diff --git a/scripts/system/controllers/controllerModules/pushToTalk.js b/scripts/system/controllers/controllerModules/pushToTalk.js index dd959ae6fb..9d6435f497 100644 --- a/scripts/system/controllers/controllerModules/pushToTalk.js +++ b/scripts/system/controllers/controllerModules/pushToTalk.js @@ -72,4 +72,4 @@ Script.include("/~/system/libraries/controllers.js"); }; Script.scriptEnding.connect(cleanup); -}()); // END LOCAL_SCOPE \ No newline at end of file +}()); // END LOCAL_SCOPE From 48d1ec850c64e16822ce495cf55d0f1c8a9b60cf Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Thu, 7 Mar 2019 12:54:32 -0800 Subject: [PATCH 450/474] removing debug statement --- interface/src/Application.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c0cacd4e40..fc9fcd1bbb 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1610,7 +1610,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo switch (actionEnum) { case Action::TOGGLE_PUSHTOTALK: if (audioScriptingInterface->getPTT()) { - qDebug() << "State is " << state; if (state > 0.0f) { audioScriptingInterface->setPushingToTalk(false); } else if (state < 0.0f) { From e0fe11056e8e49e529eddc12930b50f737d55966 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Fri, 8 Mar 2019 16:37:41 -0800 Subject: [PATCH 451/474] fixing compile error --- interface/src/scripting/Audio.h | 40 --------------------------------- 1 file changed, 40 deletions(-) diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index ce857211e0..10aceb02fb 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -263,46 +263,6 @@ signals: */ void pushToTalkHMDChanged(bool enabled); - /**jsdoc - * Triggered when desktop audio input is muted or unmuted. - * @function Audio.desktopMutedChanged - * @param {boolean} isMuted - true if the audio input is muted for desktop mode, otherwise false. - * @returns {Signal} - */ - void desktopMutedChanged(bool isMuted); - - /**jsdoc - * Triggered when HMD audio input is muted or unmuted. - * @function Audio.hmdMutedChanged - * @param {boolean} isMuted - true if the audio input is muted for HMD mode, otherwise false. - * @returns {Signal} - */ - void hmdMutedChanged(bool isMuted); - - /** - * Triggered when Push-to-Talk has been enabled or disabled. - * @function Audio.pushToTalkChanged - * @param {boolean} enabled - true if Push-to-Talk is enabled, otherwise false. - * @returns {Signal} - */ - void pushToTalkChanged(bool enabled); - - /** - * Triggered when Push-to-Talk has been enabled or disabled for desktop mode. - * @function Audio.pushToTalkDesktopChanged - * @param {boolean} enabled - true if Push-to-Talk is emabled for Desktop mode, otherwise false. - * @returns {Signal} - */ - void pushToTalkDesktopChanged(bool enabled); - - /** - * Triggered when Push-to-Talk has been enabled or disabled for HMD mode. - * @function Audio.pushToTalkHMDChanged - * @param {boolean} enabled - true if Push-to-Talk is emabled for HMD mode, otherwise false. - * @returns {Signal} - */ - void pushToTalkHMDChanged(bool enabled); - /**jsdoc * Triggered when the audio input noise reduction is enabled or disabled. * @function Audio.noiseReductionChanged From 3464fe09c1b0ea2b3671941e09bbe91dbe6f36bf Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Fri, 8 Mar 2019 17:39:11 -0800 Subject: [PATCH 452/474] Applying the hero changes to master soon to be rc81 --- .../src/avatars/AvatarMixerClientData.cpp | 4 + assignment-client/src/avatars/MixerAvatar.h | 3 - .../qml/+android_interface/Stats.qml | 4 + interface/resources/qml/Stats.qml | 4 + interface/src/avatar/AvatarManager.cpp | 183 +++++++++++------- interface/src/avatar/AvatarManager.h | 4 + interface/src/avatar/OtherAvatar.cpp | 12 -- interface/src/ui/Stats.cpp | 2 + interface/src/ui/Stats.h | 18 ++ libraries/avatars/src/AvatarData.cpp | 13 +- libraries/avatars/src/AvatarData.h | 18 +- 11 files changed, 179 insertions(+), 86 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index cef4383aee..557c5c9fe3 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -130,12 +130,16 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message, const SlaveShared } _lastReceivedSequenceNumber = sequenceNumber; glm::vec3 oldPosition = getPosition(); + bool oldHasPriority = _avatar->getHasPriority(); // compute the offset to the data payload if (!_avatar->parseDataFromBuffer(message.readWithoutCopy(message.getBytesLeftToRead()))) { return false; } + // Regardless of what the client says, restore the priority as we know it without triggering any update. + _avatar->setHasPriorityWithoutTimestampReset(oldHasPriority); + auto newPosition = getPosition(); if (newPosition != oldPosition) { //#define AVATAR_HERO_TEST_HACK diff --git a/assignment-client/src/avatars/MixerAvatar.h b/assignment-client/src/avatars/MixerAvatar.h index 4c3ded4582..3e80704495 100644 --- a/assignment-client/src/avatars/MixerAvatar.h +++ b/assignment-client/src/avatars/MixerAvatar.h @@ -19,11 +19,8 @@ class MixerAvatar : public AvatarData { public: - bool getHasPriority() const { return _hasPriority; } - void setHasPriority(bool hasPriority) { _hasPriority = hasPriority; } private: - bool _hasPriority { false }; }; using MixerAvatarSharedPointer = std::shared_ptr; diff --git a/interface/resources/qml/+android_interface/Stats.qml b/interface/resources/qml/+android_interface/Stats.qml index fe56f3797b..54f6086a86 100644 --- a/interface/resources/qml/+android_interface/Stats.qml +++ b/interface/resources/qml/+android_interface/Stats.qml @@ -113,6 +113,10 @@ Item { visible: root.expanded text: "Avatars Updated: " + root.updatedAvatarCount } + StatText { + visible: root.expanded + text: "Heroes Count/Updated: " + root.heroAvatarCount + "/" + root.updatedHeroAvatarCount + } StatText { visible: root.expanded text: "Avatars NOT Updated: " + root.notUpdatedAvatarCount diff --git a/interface/resources/qml/Stats.qml b/interface/resources/qml/Stats.qml index 3b703d72e6..6748418d19 100644 --- a/interface/resources/qml/Stats.qml +++ b/interface/resources/qml/Stats.qml @@ -115,6 +115,10 @@ Item { visible: root.expanded text: "Avatars Updated: " + root.updatedAvatarCount } + StatText { + visible: root.expanded + text: "Heroes Count/Updated: " + root.heroAvatarCount + "/" + root.updatedHeroAvatarCount + } StatText { visible: root.expanded text: "Avatars NOT Updated: " + root.notUpdatedAvatarCount diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 55025b3b23..c66c0a30cb 100755 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -232,96 +232,142 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { auto avatarMap = getHashCopy(); const auto& views = qApp->getConicalViews(); - PrioritySortUtil::PriorityQueue sortedAvatars(views, - AvatarData::_avatarSortCoefficientSize, - AvatarData::_avatarSortCoefficientCenter, - AvatarData::_avatarSortCoefficientAge); - sortedAvatars.reserve(avatarMap.size() - 1); // don't include MyAvatar + // Prepare 2 queues for heros and for crowd avatars + using AvatarPriorityQueue = PrioritySortUtil::PriorityQueue; + // Keep two independent queues, one for heroes and one for the riff-raff. + enum PriorityVariants + { + kHero = 0, + kNonHero, + NumVariants + }; + AvatarPriorityQueue avatarPriorityQueues[NumVariants] = { + { views, + AvatarData::_avatarSortCoefficientSize, + AvatarData::_avatarSortCoefficientCenter, + AvatarData::_avatarSortCoefficientAge }, + { views, + AvatarData::_avatarSortCoefficientSize, + AvatarData::_avatarSortCoefficientCenter, + AvatarData::_avatarSortCoefficientAge } }; + // Reserve space + //avatarPriorityQueues[kHero].reserve(10); // just few + avatarPriorityQueues[kNonHero].reserve(avatarMap.size() - 1); // don't include MyAvatar // Build vector and compute priorities auto nodeList = DependencyManager::get(); AvatarHash::iterator itr = avatarMap.begin(); while (itr != avatarMap.end()) { - const auto& avatar = std::static_pointer_cast(*itr); + auto avatar = std::static_pointer_cast(*itr); // DO NOT update _myAvatar! Its update has already been done earlier in the main loop. // DO NOT update or fade out uninitialized Avatars if (avatar != _myAvatar && avatar->isInitialized() && !nodeList->isPersonalMutingNode(avatar->getID())) { - sortedAvatars.push(SortableAvatar(avatar)); + if (avatar->getHasPriority()) { + avatarPriorityQueues[kHero].push(SortableAvatar(avatar)); + } else { + avatarPriorityQueues[kNonHero].push(SortableAvatar(avatar)); + } } ++itr; } - // Sort - const auto& sortedAvatarVector = sortedAvatars.getSortedVector(); + + _numHeroAvatars = (int)avatarPriorityQueues[kHero].size(); // process in sorted order uint64_t startTime = usecTimestampNow(); - uint64_t updateExpiry = startTime + MAX_UPDATE_AVATARS_TIME_BUDGET; + + const uint64_t MAX_UPDATE_HEROS_TIME_BUDGET = uint64_t(0.8 * MAX_UPDATE_AVATARS_TIME_BUDGET); + + uint64_t updatePriorityExpiries[NumVariants] = { startTime + MAX_UPDATE_HEROS_TIME_BUDGET, startTime + MAX_UPDATE_AVATARS_TIME_BUDGET }; + int numHerosUpdated = 0; int numAvatarsUpdated = 0; - int numAVatarsNotUpdated = 0; + int numAvatarsNotUpdated = 0; render::Transaction renderTransaction; workload::Transaction workloadTransaction; - for (auto it = sortedAvatarVector.begin(); it != sortedAvatarVector.end(); ++it) { - const SortableAvatar& sortData = *it; - const auto avatar = std::static_pointer_cast(sortData.getAvatar()); - if (!avatar->_isClientAvatar) { - avatar->setIsClientAvatar(true); - } - // TODO: to help us scale to more avatars it would be nice to not have to poll this stuff every update - if (avatar->getSkeletonModel()->isLoaded()) { - // remove the orb if it is there - avatar->removeOrb(); - if (avatar->needsPhysicsUpdate()) { - _avatarsToChangeInPhysics.insert(avatar); - } - } else { - avatar->updateOrbPosition(); - } + + for (int p = kHero; p < NumVariants; p++) { + auto& priorityQueue = avatarPriorityQueues[p]; + // Sorting the current queue HERE as part of the measured timing. + const auto& sortedAvatarVector = priorityQueue.getSortedVector(); - // for ALL avatars... - if (_shouldRender) { - avatar->ensureInScene(avatar, qApp->getMain3DScene()); - } - avatar->animateScaleChanges(deltaTime); + auto passExpiry = updatePriorityExpiries[p]; - uint64_t now = usecTimestampNow(); - if (now < updateExpiry) { - // we're within budget - bool inView = sortData.getPriority() > OUT_OF_VIEW_THRESHOLD; - if (inView && avatar->hasNewJointData()) { - numAvatarsUpdated++; + for (auto it = sortedAvatarVector.begin(); it != sortedAvatarVector.end(); ++it) { + const SortableAvatar& sortData = *it; + const auto avatar = std::static_pointer_cast(sortData.getAvatar()); + if (!avatar->_isClientAvatar) { + avatar->setIsClientAvatar(true); } - auto transitStatus = avatar->_transit.update(deltaTime, avatar->_serverPosition, _transitConfig); - if (avatar->getIsNewAvatar() && (transitStatus == AvatarTransit::Status::START_TRANSIT || transitStatus == AvatarTransit::Status::ABORT_TRANSIT)) { - avatar->_transit.reset(); - avatar->setIsNewAvatar(false); - } - avatar->simulate(deltaTime, inView); - if (avatar->getSkeletonModel()->isLoaded() && avatar->getWorkloadRegion() == workload::Region::R1) { - _myAvatar->addAvatarHandsToFlow(avatar); - } - avatar->updateRenderItem(renderTransaction); - avatar->updateSpaceProxy(workloadTransaction); - avatar->setLastRenderUpdateTime(startTime); - } else { - // we've spent our full time budget --> bail on the rest of the avatar updates - // --> more avatars may freeze until their priority trickles up - // --> some scale animations may glitch - // --> some avatar velocity measurements may be a little off - - // no time to simulate, but we take the time to count how many were tragically missed - while (it != sortedAvatarVector.end()) { - const SortableAvatar& newSortData = *it; - const auto& newAvatar = newSortData.getAvatar(); - bool inView = newSortData.getPriority() > OUT_OF_VIEW_THRESHOLD; - // Once we reach an avatar that's not in view, all avatars after it will also be out of view - if (!inView) { - break; + // TODO: to help us scale to more avatars it would be nice to not have to poll this stuff every update + if (avatar->getSkeletonModel()->isLoaded()) { + // remove the orb if it is there + avatar->removeOrb(); + if (avatar->needsPhysicsUpdate()) { + _avatarsToChangeInPhysics.insert(avatar); } - numAVatarsNotUpdated += (int)(newAvatar->hasNewJointData()); - ++it; + } else { + avatar->updateOrbPosition(); } - break; + + // for ALL avatars... + if (_shouldRender) { + avatar->ensureInScene(avatar, qApp->getMain3DScene()); + } + + avatar->animateScaleChanges(deltaTime); + + uint64_t now = usecTimestampNow(); + if (now < passExpiry) { + // we're within budget + bool inView = sortData.getPriority() > OUT_OF_VIEW_THRESHOLD; + if (inView && avatar->hasNewJointData()) { + numAvatarsUpdated++; + } + auto transitStatus = avatar->_transit.update(deltaTime, avatar->_serverPosition, _transitConfig); + if (avatar->getIsNewAvatar() && (transitStatus == AvatarTransit::Status::START_TRANSIT || + transitStatus == AvatarTransit::Status::ABORT_TRANSIT)) { + avatar->_transit.reset(); + avatar->setIsNewAvatar(false); + } + avatar->simulate(deltaTime, inView); + if (avatar->getSkeletonModel()->isLoaded() && avatar->getWorkloadRegion() == workload::Region::R1) { + _myAvatar->addAvatarHandsToFlow(avatar); + } + avatar->updateRenderItem(renderTransaction); + avatar->updateSpaceProxy(workloadTransaction); + avatar->setLastRenderUpdateTime(startTime); + } else { + // we've spent our time budget for this priority bucket + // let's deal with the reminding avatars if this pass and BREAK from the for loop + + if (p == kHero) { + // Hero, + // --> put them back in the non hero queue + + auto& crowdQueue = avatarPriorityQueues[kNonHero]; + while (it != sortedAvatarVector.end()) { + crowdQueue.push(SortableAvatar((*it).getAvatar())); + ++it; + } + } else { + // Non Hero + // --> bail on the rest of the avatar updates + // --> more avatars may freeze until their priority trickles up + // --> some scale animations may glitch + // --> some avatar velocity measurements may be a little off + + // no time to simulate, but we take the time to count how many were tragically missed + numAvatarsNotUpdated = sortedAvatarVector.end() - it; + } + + // We had to cut short this pass, we must break out of the for loop here + break; + } + } + + if (p == kHero) { + numHerosUpdated = numAvatarsUpdated; } } @@ -337,7 +383,8 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { _space->enqueueTransaction(workloadTransaction); _numAvatarsUpdated = numAvatarsUpdated; - _numAvatarsNotUpdated = numAVatarsNotUpdated; + _numAvatarsNotUpdated = numAvatarsNotUpdated; + _numHeroAvatarsUpdated = numHerosUpdated; simulateAvatarFades(deltaTime); diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 51352ec861..2b58b14d11 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -90,6 +90,8 @@ public: int getNumAvatarsUpdated() const { return _numAvatarsUpdated; } int getNumAvatarsNotUpdated() const { return _numAvatarsNotUpdated; } + int getNumHeroAvatars() const { return _numHeroAvatars; } + int getNumHeroAvatarsUpdated() const { return _numHeroAvatarsUpdated; } float getAvatarSimulationTime() const { return _avatarSimulationTime; } void updateMyAvatar(float deltaTime); @@ -242,6 +244,8 @@ private: RateCounter<> _myAvatarSendRate; int _numAvatarsUpdated { 0 }; int _numAvatarsNotUpdated { 0 }; + int _numHeroAvatars{ 0 }; + int _numHeroAvatarsUpdated{ 0 }; float _avatarSimulationTime { 0.0f }; bool _shouldRender { true }; bool _myAvatarDataPacketsPaused { false }; diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp index 7848c46eee..11eb6542c4 100755 --- a/interface/src/avatar/OtherAvatar.cpp +++ b/interface/src/avatar/OtherAvatar.cpp @@ -200,17 +200,6 @@ void OtherAvatar::resetDetailedMotionStates() { void OtherAvatar::setWorkloadRegion(uint8_t region) { _workloadRegion = region; - QString printRegion = ""; - if (region == workload::Region::R1) { - printRegion = "R1"; - } else if (region == workload::Region::R2) { - printRegion = "R2"; - } else if (region == workload::Region::R3) { - printRegion = "R3"; - } else { - printRegion = "invalid"; - } - qCDebug(avatars) << "Setting workload region to " << printRegion; computeShapeLOD(); } @@ -235,7 +224,6 @@ void OtherAvatar::computeShapeLOD() { if (newLOD != _bodyLOD) { _bodyLOD = newLOD; if (isInPhysicsSimulation()) { - qCDebug(avatars) << "Changing to body LOD " << newLOD; _needsReinsertion = true; } } diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index e3697ee8ec..ecdae0b375 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -125,8 +125,10 @@ void Stats::updateStats(bool force) { auto avatarManager = DependencyManager::get(); // we need to take one avatar out so we don't include ourselves STAT_UPDATE(avatarCount, avatarManager->size() - 1); + STAT_UPDATE(heroAvatarCount, avatarManager->getNumHeroAvatars()); STAT_UPDATE(physicsObjectCount, qApp->getNumCollisionObjects()); STAT_UPDATE(updatedAvatarCount, avatarManager->getNumAvatarsUpdated()); + STAT_UPDATE(updatedHeroAvatarCount, avatarManager->getNumHeroAvatarsUpdated()); STAT_UPDATE(notUpdatedAvatarCount, avatarManager->getNumAvatarsNotUpdated()); STAT_UPDATE(serverCount, (int)nodeList->size()); STAT_UPDATE_FLOAT(renderrate, qApp->getRenderLoopRate(), 0.1f); diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index 36e92b00af..0f563a6935 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -49,8 +49,10 @@ private: \ * @property {number} presentdroprate - Read-only. * @property {number} gameLoopRate - Read-only. * @property {number} avatarCount - Read-only. + * @property {number} heroAvatarCount - Read-only. * @property {number} physicsObjectCount - Read-only. * @property {number} updatedAvatarCount - Read-only. + * @property {number} updatedHeroAvatarCount - Read-only. * @property {number} notUpdatedAvatarCount - Read-only. * @property {number} packetInCount - Read-only. * @property {number} packetOutCount - Read-only. @@ -203,8 +205,10 @@ class Stats : public QQuickItem { STATS_PROPERTY(float, presentdroprate, 0) STATS_PROPERTY(int, gameLoopRate, 0) STATS_PROPERTY(int, avatarCount, 0) + STATS_PROPERTY(int, heroAvatarCount, 0) STATS_PROPERTY(int, physicsObjectCount, 0) STATS_PROPERTY(int, updatedAvatarCount, 0) + STATS_PROPERTY(int, updatedHeroAvatarCount, 0) STATS_PROPERTY(int, notUpdatedAvatarCount, 0) STATS_PROPERTY(int, packetInCount, 0) STATS_PROPERTY(int, packetOutCount, 0) @@ -436,6 +440,13 @@ signals: */ void avatarCountChanged(); + /**jsdoc + * Triggered when the value of the heroAvatarCount property changes. + * @function Stats.heroAvatarCountChanged + * @returns {Signal} + */ + void heroAvatarCountChanged(); + /**jsdoc * Triggered when the value of the updatedAvatarCount property changes. * @function Stats.updatedAvatarCountChanged @@ -443,6 +454,13 @@ signals: */ void updatedAvatarCountChanged(); + /**jsdoc + * Triggered when the value of the updatedHeroAvatarCount property changes. + * @function Stats.updatedHeroAvatarCountChanged + * @returns {Signal} + */ + void updatedHeroAvatarCountChanged(); + /**jsdoc * Triggered when the value of the notUpdatedAvatarCount property changes. * @function Stats.notUpdatedAvatarCountChanged diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index c733cfa291..26407c3564 100755 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -564,6 +564,11 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent setAtBit16(flags, COLLIDE_WITH_OTHER_AVATARS); } + // Avatar has hero priority + if (getHasPriority()) { + setAtBit16(flags, HAS_HERO_PRIORITY); + } + data->flags = flags; destinationBuffer += sizeof(AvatarDataPacket::AdditionalFlags); @@ -1152,7 +1157,8 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { auto newHasProceduralEyeFaceMovement = oneAtBit16(bitItems, PROCEDURAL_EYE_FACE_MOVEMENT); auto newHasProceduralBlinkFaceMovement = oneAtBit16(bitItems, PROCEDURAL_BLINK_FACE_MOVEMENT); auto newCollideWithOtherAvatars = oneAtBit16(bitItems, COLLIDE_WITH_OTHER_AVATARS); - + auto newHasPriority = oneAtBit16(bitItems, HAS_HERO_PRIORITY); + bool keyStateChanged = (_keyState != newKeyState); bool handStateChanged = (_handState != newHandState); bool faceStateChanged = (_headData->_isFaceTrackerConnected != newFaceTrackerConnected); @@ -1161,8 +1167,10 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { bool proceduralEyeFaceMovementChanged = (_headData->getHasProceduralEyeFaceMovement() != newHasProceduralEyeFaceMovement); bool proceduralBlinkFaceMovementChanged = (_headData->getHasProceduralBlinkFaceMovement() != newHasProceduralBlinkFaceMovement); bool collideWithOtherAvatarsChanged = (_collideWithOtherAvatars != newCollideWithOtherAvatars); + bool hasPriorityChanged = (getHasPriority() != newHasPriority); bool somethingChanged = keyStateChanged || handStateChanged || faceStateChanged || eyeStateChanged || audioEnableFaceMovementChanged || - proceduralEyeFaceMovementChanged || proceduralBlinkFaceMovementChanged || collideWithOtherAvatarsChanged; + proceduralEyeFaceMovementChanged || + proceduralBlinkFaceMovementChanged || collideWithOtherAvatarsChanged || hasPriorityChanged; _keyState = newKeyState; _handState = newHandState; @@ -1172,6 +1180,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { _headData->setHasProceduralEyeFaceMovement(newHasProceduralEyeFaceMovement); _headData->setHasProceduralBlinkFaceMovement(newHasProceduralBlinkFaceMovement); _collideWithOtherAvatars = newCollideWithOtherAvatars; + setHasPriorityWithoutTimestampReset(newHasPriority); sourceBuffer += sizeof(AvatarDataPacket::AdditionalFlags); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 63396a59ac..95bbcbeb16 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -100,6 +100,9 @@ const quint32 AVATAR_MOTION_SCRIPTABLE_BITS = // Procedural audio to mouth movement is enabled 8th bit // Procedural Blink is enabled 9th bit // Procedural Eyelid is enabled 10th bit +// Procedural PROCEDURAL_BLINK_FACE_MOVEMENT is enabled 11th bit +// Procedural Collide with other avatars is enabled 12th bit +// Procedural Has Hero Priority is enabled 13th bit const int KEY_STATE_START_BIT = 0; // 1st and 2nd bits const int HAND_STATE_START_BIT = 2; // 3rd and 4th bits @@ -111,7 +114,7 @@ const int AUDIO_ENABLED_FACE_MOVEMENT = 8; // 9th bit const int PROCEDURAL_EYE_FACE_MOVEMENT = 9; // 10th bit const int PROCEDURAL_BLINK_FACE_MOVEMENT = 10; // 11th bit const int COLLIDE_WITH_OTHER_AVATARS = 11; // 12th bit - +const int HAS_HERO_PRIORITY = 12; // 13th bit (be scared) const char HAND_STATE_NULL = 0; const char LEFT_HAND_POINTING_FLAG = 1; @@ -1121,6 +1124,18 @@ public: int getAverageBytesReceivedPerSecond() const; int getReceiveRate() const; + // An Avatar can be set Priority from the AvatarMixer side. + bool getHasPriority() const { return _hasPriority; } + // regular setHasPriority does a check of state changed and if true reset 'additionalFlagsChanged' timestamp + void setHasPriority(bool hasPriority) { + if (_hasPriority != hasPriority) { + _additionalFlagsChanged = usecTimestampNow(); + _hasPriority = hasPriority; + } + } + // In some cases, we want to assign the hasPRiority flag without reseting timestamp + void setHasPriorityWithoutTimestampReset(bool hasPriority) { _hasPriority = hasPriority; } + const glm::vec3& getTargetVelocity() const { return _targetVelocity; } void clearRecordingBasis(); @@ -1498,6 +1513,7 @@ protected: bool _isNewAvatar { true }; bool _isClientAvatar { false }; bool _collideWithOtherAvatars { true }; + bool _hasPriority{ false }; // null unless MyAvatar or ScriptableAvatar sending traits data to mixer std::unique_ptr _clientTraitsHandler; From 04c6c425125d4da61863662ac83f1242c1496705 Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Sun, 10 Mar 2019 14:28:31 -0700 Subject: [PATCH 453/474] Fix build error from merge. --- interface/src/scripting/Audio.cpp | 40 ------------------------------- 1 file changed, 40 deletions(-) diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 0a859c4dcc..669856198d 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -211,12 +211,6 @@ void Audio::setPTTHMD(bool enabled) { } } -bool Audio::getPTTHMD() const { - return resultWithReadLock([&] { - return _pttHMD; - }); -} - void Audio::saveData() { _desktopMutedSetting.set(getMutedDesktop()); _hmdMutedSetting.set(getMutedHMD()); @@ -237,40 +231,6 @@ bool Audio::getPTTHMD() const { }); } -void Audio::saveData() { - _desktopMutedSetting.set(getMutedDesktop()); - _hmdMutedSetting.set(getMutedHMD()); - _pttDesktopSetting.set(getPTTDesktop()); - _pttHMDSetting.set(getPTTHMD()); -} - -void Audio::loadData() { - _desktopMuted = _desktopMutedSetting.get(); - _hmdMuted = _hmdMutedSetting.get(); - _pttDesktop = _pttDesktopSetting.get(); - _pttHMD = _pttHMDSetting.get(); -} - -bool Audio::getPTTHMD() const { - return resultWithReadLock([&] { - return _pttHMD; - }); -} - -void Audio::saveData() { - _desktopMutedSetting.set(getMutedDesktop()); - _hmdMutedSetting.set(getMutedHMD()); - _pttDesktopSetting.set(getPTTDesktop()); - _pttHMDSetting.set(getPTTHMD()); -} - -void Audio::loadData() { - _desktopMuted = _desktopMutedSetting.get(); - _hmdMuted = _hmdMutedSetting.get(); - _pttDesktop = _pttDesktopSetting.get(); - _pttHMD = _pttHMDSetting.get(); -} - bool Audio::noiseReductionEnabled() const { return resultWithReadLock([&] { return _enableNoiseReduction; From 2eaa1e63d333a2872683001d04cb24e724ac1f40 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Sun, 10 Mar 2019 17:07:02 -0700 Subject: [PATCH 454/474] switch from column layout to item --- interface/resources/qml/hifi/audio/Audio.qml | 74 +++----------------- 1 file changed, 8 insertions(+), 66 deletions(-) diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index 569cd23176..ce968090b4 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -166,10 +166,12 @@ Rectangle { Separator {} - ColumnLayout { - spacing: muteMic.spacing; + Item { + width: rightMostInputLevelPos + height: pttTextContainer.height + pttCheckBox.height + margins.paddings + 10 AudioControls.CheckBox { - spacing: muteMic.spacing + id: pttCheckBox + anchors.top: parent.top text: qsTr("Push To Talk (T)"); checked: isVR ? AudioScriptingInterface.pushToTalkHMD : AudioScriptingInterface.pushToTalkDesktop; onClicked: { @@ -189,69 +191,9 @@ Rectangle { } Item { id: pttTextContainer - x: margins.paddings; - width: rightMostInputLevelPos - height: pttTextMetrics.height - visible: true - TextMetrics { - id: pttTextMetrics - text: pttText.text - font: pttText.font - } - RalewayRegular { - id: pttText - wrapMode: Text.WordWrap - color: hifi.colors.white; - width: parent.width; - font.italic: true - size: 16; - text: isVR ? qsTr("Press and hold grip triggers on both of your controllers to unmute.") : - qsTr("Press and hold the button \"T\" to unmute."); - onTextChanged: { - if (pttTextMetrics.width > rightMostInputLevelPos) { - pttTextContainer.height = Math.ceil(pttTextMetrics.width / rightMostInputLevelPos) * pttTextMetrics.height; - } else { - pttTextContainer.height = pttTextMetrics.height; - } - } - } - Component.onCompleted: { - if (pttTextMetrics.width > rightMostInputLevelPos) { - pttTextContainer.height = Math.ceil(pttTextMetrics.width / rightMostInputLevelPos) * pttTextMetrics.height; - } else { - pttTextContainer.height = pttTextMetrics.height; - } - } - } - } - - Separator {} - - ColumnLayout { - spacing: muteMic.spacing; - AudioControls.CheckBox { - spacing: muteMic.spacing - text: qsTr("Push To Talk (T)"); - checked: isVR ? AudioScriptingInterface.pushToTalkHMD : AudioScriptingInterface.pushToTalkDesktop; - onClicked: { - if (isVR) { - AudioScriptingInterface.pushToTalkHMD = checked; - } else { - AudioScriptingInterface.pushToTalkDesktop = checked; - } - checked = Qt.binding(function() { - if (isVR) { - return AudioScriptingInterface.pushToTalkHMD; - } else { - return AudioScriptingInterface.pushToTalkDesktop; - } - }); // restore binding - } - } - Item { - id: pttTextContainer - x: margins.paddings; - width: rightMostInputLevelPos + anchors.top: pttCheckBox.bottom + anchors.topMargin: 10 + width: parent.width height: pttTextMetrics.height visible: true TextMetrics { From 54973375a4f543557a7547828e3c8896db5bdb20 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Sun, 10 Mar 2019 17:18:18 -0700 Subject: [PATCH 455/474] fixng ptt checkbox --- interface/resources/qml/hifi/audio/Audio.qml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index ce968090b4..46ae937614 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -171,6 +171,8 @@ Rectangle { height: pttTextContainer.height + pttCheckBox.height + margins.paddings + 10 AudioControls.CheckBox { id: pttCheckBox + spacing: muteMic.spacing; + width: rightMostInputLevelPos anchors.top: parent.top text: qsTr("Push To Talk (T)"); checked: isVR ? AudioScriptingInterface.pushToTalkHMD : AudioScriptingInterface.pushToTalkDesktop; From cf3694e8e343f0dd39e8875c689214ea4611ed33 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Sun, 10 Mar 2019 19:00:41 -0700 Subject: [PATCH 456/474] fixing error in master + getting ptt to show up --- interface/resources/qml/hifi/audio/Audio.qml | 114 +++++++++---------- 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index a07fb1cb95..376aa39269 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -104,7 +104,6 @@ Rectangle { RowLayout { x: 2 * margins.paddings; - spacing: columnOne.width; width: parent.width; // mute is in its own row @@ -170,66 +169,66 @@ Rectangle { } } } + } - Separator {} + Separator {} - Item { - width: rightMostInputLevelPos - height: pttTextContainer.height + pttCheckBox.height + margins.paddings + 10 - AudioControls.CheckBox { - id: pttCheckBox - spacing: muteMic.spacing; - width: rightMostInputLevelPos - anchors.top: parent.top - text: qsTr("Push To Talk (T)"); - checked: isVR ? AudioScriptingInterface.pushToTalkHMD : AudioScriptingInterface.pushToTalkDesktop; - onClicked: { - if (isVR) { - AudioScriptingInterface.pushToTalkHMD = checked; + + ColumnLayout { + id: pttColumn + spacing: 24; + x: 2 * margins.paddings; + HifiControlsUit.Switch { + id: pttSwitch + height: root.switchHeight; + switchWidth: root.switchWidth; + labelTextOn: qsTr("Push To Talk (T)"); + backgroundOnColor: "#E3E3E3"; + checked: (bar.currentIndex === 1 && isVR) || + (bar.currentIndex === 0 && !isVR) ? AudioScriptingInterface.pushToTalkDesktop : AudioScriptingInterface.pushToTalkHMD; + onCheckedChanged: { + if ((bar.currentIndex === 1 && isVR) || + (bar.currentIndex === 0 && !isVR)) { + AudioScriptingInterface.pushToTalkDesktop = checked; + } else { + AudioScriptingInterface.pushToTalkHMD = checked; + } + checked = Qt.binding(function() { + if ((bar.currentIndex === 1 && isVR) || + (bar.currentIndex === 0 && !isVR)) { + return AudioScriptingInterface.pushToTalkDesktop; } else { - AudioScriptingInterface.pushToTalkDesktop = checked; + return AudioScriptingInterface.pushToTalkHMD; } - checked = Qt.binding(function() { - if (isVR) { - return AudioScriptingInterface.pushToTalkHMD; - } else { - return AudioScriptingInterface.pushToTalkDesktop; - } - }); // restore binding - } + }); // restore binding } - Item { - id: pttTextContainer - anchors.top: pttCheckBox.bottom - anchors.topMargin: 10 - width: parent.width - height: pttTextMetrics.height - visible: true - TextMetrics { - id: pttTextMetrics - text: pttText.text - font: pttText.font - } - RalewayRegular { - id: pttText - wrapMode: Text.WordWrap - color: hifi.colors.white; - width: parent.width; - font.italic: true - size: 16; - text: isVR ? qsTr("Press and hold grip triggers on both of your controllers to unmute.") : - qsTr("Press and hold the button \"T\" to unmute."); - onTextChanged: { - if (pttTextMetrics.width > rightMostInputLevelPos) { - pttTextContainer.height = Math.ceil(pttTextMetrics.width / rightMostInputLevelPos) * pttTextMetrics.height; - } else { - pttTextContainer.height = pttTextMetrics.height; - } - } - } - Component.onCompleted: { - if (pttTextMetrics.width > rightMostInputLevelPos) { - pttTextContainer.height = Math.ceil(pttTextMetrics.width / rightMostInputLevelPos) * pttTextMetrics.height; + } + Item { + id: pttTextContainer + width: rightMostInputLevelPos + height: pttTextMetrics.height + anchors.left: parent.left + anchors.leftMargin: -margins.padding + TextMetrics { + id: pttTextMetrics + text: pttText.text + font: pttText.font + } + RalewayRegular { + id: pttText + color: hifi.colors.white; + width: parent.width; + wrapMode: (bar.currentIndex === 1 && isVR) || + (bar.currentIndex === 0 && !isVR) ? Text.NoWrap : Text.WordWrap; + font.italic: true + size: 16; + + text: (bar.currentIndex === 1 && isVR) || + (bar.currentIndex === 0 && !isVR) ? qsTr("Press and hold the button \"T\" to unmute.") : + qsTr("Press and hold grip triggers on both of your controllers to unmute."); + onTextChanged: { + if (pttTextMetrics.width > pttTextContainer.width) { + pttTextContainer.height = Math.ceil(pttTextMetrics.width / pttTextContainer.width) * pttTextMetrics.height; } else { pttTextContainer.height = pttTextMetrics.height; } @@ -240,6 +239,7 @@ Rectangle { Separator {} + Item { x: margins.paddings; width: parent.width - margins.paddings*2 @@ -293,7 +293,7 @@ Rectangle { text: devicename onPressed: { if (!checked) { - stereoMic.checked = false; + stereoInput.checked = false; AudioScriptingInterface.setStereoInput(false); // the next selected audio device might not support stereo AudioScriptingInterface.setInputDevice(info, bar.currentIndex === 1); } From 20487b2ad1adb1a10b16bac8cb48e3bf12e93e44 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Sun, 10 Mar 2019 19:39:45 -0700 Subject: [PATCH 457/474] connecting pushingToTalkChanged with handlePushedToTalk --- interface/src/Application.cpp | 19 +++++++++---------- interface/src/Application.h | 2 -- interface/src/scripting/Audio.cpp | 3 ++- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index fc9fcd1bbb..215736001c 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1435,8 +1435,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo }); connect(this, &Application::activeDisplayPluginChanged, reinterpret_cast(audioScriptingInterface.data()), &scripting::Audio::onContextChanged); - connect(this, &Application::pushedToTalk, - reinterpret_cast(audioScriptingInterface.data()), &scripting::Audio::handlePushedToTalk); } // Create the rendering engine. This can be slow on some machines due to lots of @@ -1609,13 +1607,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo bool navAxis = false; switch (actionEnum) { case Action::TOGGLE_PUSHTOTALK: - if (audioScriptingInterface->getPTT()) { - if (state > 0.0f) { - audioScriptingInterface->setPushingToTalk(false); - } else if (state < 0.0f) { - audioScriptingInterface->setPushingToTalk(true); - } + if (state > 0.0f) { + audioScriptingInterface->setPushingToTalk(false); + } else if (state < 0.0f) { + audioScriptingInterface->setPushingToTalk(true); } + break; case Action::UI_NAV_VERTICAL: navAxis = true; @@ -4218,7 +4215,8 @@ void Application::keyPressEvent(QKeyEvent* event) { break; case Qt::Key_T: - emit pushedToTalk(true); + auto audioScriptingInterface = reinterpret_cast(DependencyManager::get().data()); + audioScriptingInterface->setPushingToTalk(true); break; case Qt::Key_P: { @@ -4329,7 +4327,8 @@ void Application::keyReleaseEvent(QKeyEvent* event) { switch (event->key()) { case Qt::Key_T: - emit pushedToTalk(false); + auto audioScriptingInterface = reinterpret_cast(DependencyManager::get().data()); + audioScriptingInterface->setPushingToTalk(false); break; } } diff --git a/interface/src/Application.h b/interface/src/Application.h index 1c86326f90..a8cc9450c5 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -358,8 +358,6 @@ signals: void miniTabletEnabledChanged(bool enabled); - void pushedToTalk(bool enabled); - public slots: QVector pasteEntities(float x, float y, float z); bool exportEntities(const QString& filename, const QVector& entityIDs, const glm::vec3* givenOffset = nullptr); diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 669856198d..c4dfcffb61 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -40,6 +40,8 @@ Audio::Audio() : _devices(_contextIsHMD) { connect(client, &AudioClient::inputLoudnessChanged, this, &Audio::onInputLoudnessChanged); connect(client, &AudioClient::inputVolumeChanged, this, &Audio::setInputVolume); connect(this, &Audio::contextChanged, &_devices, &AudioDevices::onContextChanged); + // when pushing to talk changed, handle it. + connect(this, &Audio::pushingToTalkChanged, this, &Audio::handlePushedToTalk); enableNoiseReduction(enableNoiseReductionSetting.get()); onContextChanged(); } @@ -344,7 +346,6 @@ void Audio::handlePushedToTalk(bool enabled) { } else { setMuted(true); } - setPushingToTalk(enabled); } } From 7b51061b5f3eee889b6d5f7ea4cec5e502518650 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Sun, 10 Mar 2019 19:46:01 -0700 Subject: [PATCH 458/474] fixing initialization in switch statement --- interface/src/Application.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 215736001c..3230419816 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4047,6 +4047,7 @@ void Application::keyPressEvent(QKeyEvent* event) { _keysPressed.insert(event->key(), *event); } + auto audioScriptingInterface = reinterpret_cast(DependencyManager::get().data()); _controllerScriptingInterface->emitKeyPressEvent(event); // send events to any registered scripts // if one of our scripts have asked to capture this event, then stop processing it if (_controllerScriptingInterface->isKeyCaptured(event) || isInterstitialMode()) { @@ -4215,7 +4216,6 @@ void Application::keyPressEvent(QKeyEvent* event) { break; case Qt::Key_T: - auto audioScriptingInterface = reinterpret_cast(DependencyManager::get().data()); audioScriptingInterface->setPushingToTalk(true); break; @@ -4325,9 +4325,9 @@ void Application::keyReleaseEvent(QKeyEvent* event) { _keyboardMouseDevice->keyReleaseEvent(event); } + auto audioScriptingInterface = reinterpret_cast(DependencyManager::get().data()); switch (event->key()) { case Qt::Key_T: - auto audioScriptingInterface = reinterpret_cast(DependencyManager::get().data()); audioScriptingInterface->setPushingToTalk(false); break; } From ec0cf3ee3ace28f9c2bc39fa350190238f15a46c Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Mon, 11 Mar 2019 09:58:20 -0700 Subject: [PATCH 459/474] Fix typo. --- .../controllerModules/pushToTalk.js | 123 +++++++++--------- 1 file changed, 62 insertions(+), 61 deletions(-) diff --git a/scripts/system/controllers/controllerModules/pushToTalk.js b/scripts/system/controllers/controllerModules/pushToTalk.js index 9d6435f497..6b1bacc367 100644 --- a/scripts/system/controllers/controllerModules/pushToTalk.js +++ b/scripts/system/controllers/controllerModules/pushToTalk.js @@ -11,65 +11,66 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Script.include("/~/system/libraries/controllers.js"); -(function() { // BEGIN LOCAL_SCOPE - function PushToTalkHandler() { - var _this = this; - this.active = false; - //var pttMapping, mappingName; - - this.setup = function() { - //mappingName = 'Hifi-PTT-Dev-' + Math.random(); - //pttMapping = Controller.newMapping(mappingName); - //pttMapping.enable(); - }; - - this.shouldTalk = function (controllerData) { - // Set up test against controllerData here... - var gripVal = controllerData.secondaryValues[LEFT_HAND] && controllerData.secondaryValues[RIGHT_HAND]; - return (gripVal) ? true : false; - }; - - this.shouldStopTalking = function (controllerData) { - var gripVal = controllerData.secondaryValues[LEFT_HAND] && controllerData.secondaryValues[RIGHT_HAND]; - return (gripVal) ? false : true; - }; - - this.isReady = function (controllerData, deltaTime) { - if (HMD.active && Audio.pushToTalk && this.shouldTalk(controllerData)) { - Audio.pushingToTalk = true; - returnMakeRunningValues(true, [], []); - } - - return makeRunningValues(false, [], []); - }; - - this.run = function (controllerData, deltaTime) { - if (this.shouldStopTalking(controllerData) || !Audio.pushToTalk) { - Audio.pushingToTalk = false; - return makeRunningValues(false, [], []); - } - - return makeRunningValues(true, [], []); - }; - - this.cleanup = function () { - //pttMapping.disable(); - }; - - this.parameters = makeDispatcherModuleParameters( - 950, - ["head"], - [], - 100); - } - - var pushToTalk = new PushToTalkHandler(); - enableDispatcherModule("PushToTalk", pushToTalk); - - function cleanup () { - pushToTalk.cleanup(); - disableDispatcherModule("PushToTalk"); - }; - - Script.scriptEnding.connect(cleanup); +(function () { // BEGIN LOCAL_SCOPE + function PushToTalkHandler() { + var _this = this; + this.active = false; + //var pttMapping, mappingName; + + this.setup = function () { + //mappingName = 'Hifi-PTT-Dev-' + Math.random(); + //pttMapping = Controller.newMapping(mappingName); + //pttMapping.enable(); + }; + + this.shouldTalk = function (controllerData) { + // Set up test against controllerData here... + var gripVal = controllerData.secondaryValues[LEFT_HAND] && controllerData.secondaryValues[RIGHT_HAND]; + return (gripVal) ? true : false; + }; + + this.shouldStopTalking = function (controllerData) { + var gripVal = controllerData.secondaryValues[LEFT_HAND] && controllerData.secondaryValues[RIGHT_HAND]; + return (gripVal) ? false : true; + }; + + this.isReady = function (controllerData, deltaTime) { + if (HMD.active && Audio.pushToTalk && this.shouldTalk(controllerData)) { + Audio.pushingToTalk = true; + return makeRunningValues(true, [], []); + } + + return makeRunningValues(false, [], []); + }; + + this.run = function (controllerData, deltaTime) { + if (this.shouldStopTalking(controllerData) || !Audio.pushToTalk) { + Audio.pushingToTalk = false; + print("Stop pushing to talk."); + return makeRunningValues(false, [], []); + } + + return makeRunningValues(true, [], []); + }; + + this.cleanup = function () { + //pttMapping.disable(); + }; + + this.parameters = makeDispatcherModuleParameters( + 950, + ["head"], + [], + 100); + } + + var pushToTalk = new PushToTalkHandler(); + enableDispatcherModule("PushToTalk", pushToTalk); + + function cleanup() { + pushToTalk.cleanup(); + disableDispatcherModule("PushToTalk"); + }; + + Script.scriptEnding.connect(cleanup); }()); // END LOCAL_SCOPE From c1ed01115de29730e9d2132d5f63e13d143f04a3 Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Mon, 11 Mar 2019 09:58:35 -0700 Subject: [PATCH 460/474] Fix logic for HMD vs Desktop. --- interface/resources/qml/hifi/audio/Audio.qml | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index 376aa39269..ecc3297d9f 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -184,18 +184,15 @@ Rectangle { switchWidth: root.switchWidth; labelTextOn: qsTr("Push To Talk (T)"); backgroundOnColor: "#E3E3E3"; - checked: (bar.currentIndex === 1 && isVR) || - (bar.currentIndex === 0 && !isVR) ? AudioScriptingInterface.pushToTalkDesktop : AudioScriptingInterface.pushToTalkHMD; + checked: (bar.currentIndex === 0) ? AudioScriptingInterface.pushToTalkDesktop : AudioScriptingInterface.pushToTalkHMD; onCheckedChanged: { - if ((bar.currentIndex === 1 && isVR) || - (bar.currentIndex === 0 && !isVR)) { + if (bar.currentIndex === 0) { AudioScriptingInterface.pushToTalkDesktop = checked; } else { AudioScriptingInterface.pushToTalkHMD = checked; } checked = Qt.binding(function() { - if ((bar.currentIndex === 1 && isVR) || - (bar.currentIndex === 0 && !isVR)) { + if (bar.currentIndex === 0) { return AudioScriptingInterface.pushToTalkDesktop; } else { return AudioScriptingInterface.pushToTalkHMD; @@ -218,13 +215,11 @@ Rectangle { id: pttText color: hifi.colors.white; width: parent.width; - wrapMode: (bar.currentIndex === 1 && isVR) || - (bar.currentIndex === 0 && !isVR) ? Text.NoWrap : Text.WordWrap; + wrapMode: (bar.currentIndex === 0) ? Text.NoWrap : Text.WordWrap; font.italic: true size: 16; - text: (bar.currentIndex === 1 && isVR) || - (bar.currentIndex === 0 && !isVR) ? qsTr("Press and hold the button \"T\" to unmute.") : + text: (bar.currentIndex === 0) ? qsTr("Press and hold the button \"T\" to unmute.") : qsTr("Press and hold grip triggers on both of your controllers to unmute."); onTextChanged: { if (pttTextMetrics.width > pttTextContainer.width) { From e4e8a61328d1a2ec9c70608e216b8e4dbf1625b2 Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Mon, 11 Mar 2019 10:46:51 -0700 Subject: [PATCH 461/474] Case 21326 - missing marketplaceInject.js The re-addition of marketplaceInject.js didn't merge from 80 for some reason. --- scripts/system/html/js/marketplacesInject.js | 744 +++++++++++++++++++ 1 file changed, 744 insertions(+) create mode 100644 scripts/system/html/js/marketplacesInject.js diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js new file mode 100644 index 0000000000..8d408169ba --- /dev/null +++ b/scripts/system/html/js/marketplacesInject.js @@ -0,0 +1,744 @@ +/* global $, window, MutationObserver */ + +// +// marketplacesInject.js +// +// Created by David Rowe on 12 Nov 2016. +// Copyright 2016 High Fidelity, Inc. +// +// Injected into marketplace Web pages. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function () { + // Event bridge messages. + var CLARA_IO_DOWNLOAD = "CLARA.IO DOWNLOAD"; + var CLARA_IO_STATUS = "CLARA.IO STATUS"; + var CLARA_IO_CANCEL_DOWNLOAD = "CLARA.IO CANCEL DOWNLOAD"; + var CLARA_IO_CANCELLED_DOWNLOAD = "CLARA.IO CANCELLED DOWNLOAD"; + var GOTO_DIRECTORY = "GOTO_DIRECTORY"; + var GOTO_MARKETPLACE = "GOTO_MARKETPLACE"; + var QUERY_CAN_WRITE_ASSETS = "QUERY_CAN_WRITE_ASSETS"; + var CAN_WRITE_ASSETS = "CAN_WRITE_ASSETS"; + var WARN_USER_NO_PERMISSIONS = "WARN_USER_NO_PERMISSIONS"; + + var canWriteAssets = false; + var xmlHttpRequest = null; + var isPreparing = false; // Explicitly track download request status. + + var limitedCommerce = false; + var commerceMode = false; + var userIsLoggedIn = false; + var walletNeedsSetup = false; + var marketplaceBaseURL = "https://highfidelity.com"; + var messagesWaiting = false; + + function injectCommonCode(isDirectoryPage) { + // Supporting styles from marketplaces.css. + // Glyph font family, size, and spacing adjusted because HiFi-Glyphs cannot be used cross-domain. + $("head").append( + '' + ); + + // Supporting styles from edit-style.css. + // Font family, size, and position adjusted because Raleway-Bold cannot be used cross-domain. + $("head").append( + '' + ); + + // Footer. + var isInitialHiFiPage = location.href === (marketplaceBaseURL + "/marketplace?"); + $("body").append( + '

      ' + + (!isInitialHiFiPage ? '' : '') + + (isInitialHiFiPage ? '🛈 Get items from Clara.io!' : '') + + (!isDirectoryPage ? '' : '') + + (isDirectoryPage ? '🛈 Select a marketplace to explore.' : '') + + '
      ' + ); + + // Footer actions. + $("#back-button").on("click", function () { + if (document.referrer !== "") { + window.history.back(); + } else { + var params = { type: GOTO_MARKETPLACE }; + var itemIdMatch = location.search.match(/itemId=([^&]*)/); + if (itemIdMatch && itemIdMatch.length === 2) { + params.itemId = itemIdMatch[1]; + } + EventBridge.emitWebEvent(JSON.stringify(params)); + } + }); + $("#all-markets").on("click", function () { + EventBridge.emitWebEvent(JSON.stringify({ + type: GOTO_DIRECTORY + })); + }); + } + + function injectDirectoryCode() { + + // Remove e-mail hyperlink. + var letUsKnow = $("#letUsKnow"); + letUsKnow.replaceWith(letUsKnow.html()); + + // Add button links. + + $('#exploreClaraMarketplace').on('click', function () { + window.location = "https://clara.io/library?gameCheck=true&public=true"; + }); + $('#exploreHifiMarketplace').on('click', function () { + EventBridge.emitWebEvent(JSON.stringify({ + type: GOTO_MARKETPLACE + })); + }); + } + + emitWalletSetupEvent = function () { + EventBridge.emitWebEvent(JSON.stringify({ + type: "WALLET_SETUP" + })); + }; + + function maybeAddSetupWalletButton() { + if (!$('body').hasClass("walletsetup-injected") && userIsLoggedIn && walletNeedsSetup) { + $('body').addClass("walletsetup-injected"); + + var resultsElement = document.getElementById('results'); + var setupWalletElement = document.createElement('div'); + setupWalletElement.classList.add("row"); + setupWalletElement.id = "setupWalletDiv"; + setupWalletElement.style = "height:60px;margin:20px 10px 10px 10px;padding:12px 5px;" + + "background-color:#D6F4D8;border-color:#aee9b2;border-width:2px;border-style:solid;border-radius:5px;"; + + var span = document.createElement('span'); + span.style = "margin:10px 5px;color:#1b6420;font-size:15px;"; + span.innerHTML = "Activate your Wallet to get money and shop in Marketplace."; + + var xButton = document.createElement('a'); + xButton.id = "xButton"; + xButton.setAttribute('href', "#"); + xButton.style = "width:50px;height:100%;margin:0;color:#ccc;font-size:20px;"; + xButton.innerHTML = "X"; + xButton.onclick = function () { + setupWalletElement.remove(); + dummyRow.remove(); + }; + + setupWalletElement.appendChild(span); + setupWalletElement.appendChild(xButton); + + resultsElement.insertBefore(setupWalletElement, resultsElement.firstChild); + + // Dummy row for padding + var dummyRow = document.createElement('div'); + dummyRow.classList.add("row"); + dummyRow.style = "height:15px;"; + resultsElement.insertBefore(dummyRow, resultsElement.firstChild); + } + } + + function maybeAddLogInButton() { + if (!$('body').hasClass("login-injected") && !userIsLoggedIn) { + $('body').addClass("login-injected"); + var resultsElement = document.getElementById('results'); + if (!resultsElement) { // If we're on the main page, this will evaluate to `true` + resultsElement = document.getElementById('item-show'); + resultsElement.style = 'margin-top:0;'; + } + var logInElement = document.createElement('div'); + logInElement.classList.add("row"); + logInElement.id = "logInDiv"; + logInElement.style = "height:60px;margin:20px 10px 10px 10px;padding:5px;" + + "background-color:#D6F4D8;border-color:#aee9b2;border-width:2px;border-style:solid;border-radius:5px;"; + + var button = document.createElement('a'); + button.classList.add("btn"); + button.classList.add("btn-default"); + button.id = "logInButton"; + button.setAttribute('href', "#"); + button.innerHTML = "LOG IN"; + button.style = "width:80px;height:100%;margin-top:0;margin-left:10px;padding:13px;font-weight:bold;background:linear-gradient(white, #ccc);"; + button.onclick = function () { + EventBridge.emitWebEvent(JSON.stringify({ + type: "LOGIN" + })); + }; + + var span = document.createElement('span'); + span.style = "margin:10px;color:#1b6420;font-size:15px;"; + span.innerHTML = "to get items from the Marketplace."; + + var xButton = document.createElement('a'); + xButton.id = "xButton"; + xButton.setAttribute('href', "#"); + xButton.style = "width:50px;height:100%;margin:0;color:#ccc;font-size:20px;"; + xButton.innerHTML = "X"; + xButton.onclick = function () { + logInElement.remove(); + dummyRow.remove(); + }; + + logInElement.appendChild(button); + logInElement.appendChild(span); + logInElement.appendChild(xButton); + + resultsElement.insertBefore(logInElement, resultsElement.firstChild); + + // Dummy row for padding + var dummyRow = document.createElement('div'); + dummyRow.classList.add("row"); + dummyRow.style = "height:15px;"; + resultsElement.insertBefore(dummyRow, resultsElement.firstChild); + } + } + + function changeDropdownMenu() { + var logInOrOutButton = document.createElement('a'); + logInOrOutButton.id = "logInOrOutButton"; + logInOrOutButton.setAttribute('href', "#"); + logInOrOutButton.innerHTML = userIsLoggedIn ? "Log Out" : "Log In"; + logInOrOutButton.onclick = function () { + EventBridge.emitWebEvent(JSON.stringify({ + type: "LOGIN" + })); + }; + + $($('.dropdown-menu').find('li')[0]).append(logInOrOutButton); + + $('a[href="/marketplace?view=mine"]').each(function () { + $(this).attr('href', '#'); + $(this).on('click', function () { + EventBridge.emitWebEvent(JSON.stringify({ + type: "MY_ITEMS" + })); + }); + }); + } + + function buyButtonClicked(id, referrer, edition) { + EventBridge.emitWebEvent(JSON.stringify({ + type: "CHECKOUT", + itemId: id, + referrer: referrer, + itemEdition: edition + })); + } + + function injectBuyButtonOnMainPage() { + var cost; + + // Unbind original mouseenter and mouseleave behavior + $('body').off('mouseenter', '#price-or-edit .price'); + $('body').off('mouseleave', '#price-or-edit .price'); + + $('.grid-item').find('#price-or-edit').each(function () { + $(this).css({ "margin-top": "0" }); + }); + + $('.grid-item').find('#price-or-edit').find('a').each(function() { + if ($(this).attr('href') !== '#') { // Guard necessary because of the AJAX nature of Marketplace site + $(this).attr('data-href', $(this).attr('href')); + $(this).attr('href', '#'); + } + cost = $(this).closest('.col-xs-3').find('.item-cost').text(); + var costInt = parseInt(cost, 10); + + $(this).closest('.col-xs-3').prev().attr("class", 'col-xs-6'); + $(this).closest('.col-xs-3').attr("class", 'col-xs-6'); + + var priceElement = $(this).find('.price'); + var available = true; + + if (priceElement.text() === 'invalidated' || + priceElement.text() === 'sold out' || + priceElement.text() === 'not for sale') { + available = false; + priceElement.css({ + "padding": "3px 5px 10px 5px", + "height": "40px", + "background": "linear-gradient(#a2a2a2, #fefefe)", + "color": "#000", + "font-weight": "600", + "line-height": "34px" + }); + } else { + priceElement.css({ + "padding": "3px 5px", + "height": "40px", + "background": "linear-gradient(#00b4ef, #0093C5)", + "color": "#FFF", + "font-weight": "600", + "line-height": "34px" + }); + } + + if (parseInt(cost) > 0) { + priceElement.css({ "width": "auto" }); + + if (available) { + priceElement.html(' ' + cost); + } + + priceElement.css({ "min-width": priceElement.width() + 30 }); + } + }); + + // change pricing to GET/BUY on button hover + $('body').on('mouseenter', '#price-or-edit .price', function () { + var $this = $(this); + var buyString = "BUY"; + var getString = "GET"; + // Protection against the button getting stuck in the "BUY"/"GET" state. + // That happens when the browser gets two MOUSEENTER events before getting a + // MOUSELEAVE event. Also, if not available for sale, just return. + if ($this.text() === buyString || + $this.text() === getString || + $this.text() === 'invalidated' || + $this.text() === 'sold out' || + $this.text() === 'not for sale' ) { + return; + } + $this.data('initialHtml', $this.html()); + + var cost = $(this).parent().siblings().text(); + if (parseInt(cost) > 0) { + $this.text(buyString); + } + if (parseInt(cost) == 0) { + $this.text(getString); + } + }); + + $('body').on('mouseleave', '#price-or-edit .price', function () { + var $this = $(this); + $this.html($this.data('initialHtml')); + }); + + + $('.grid-item').find('#price-or-edit').find('a').on('click', function () { + var price = $(this).closest('.grid-item').find('.price').text(); + if (price === 'invalidated' || + price === 'sold out' || + price === 'not for sale') { + return false; + } + buyButtonClicked($(this).closest('.grid-item').attr('data-item-id'), + "mainPage", + -1); + }); + } + + function injectUnfocusOnSearch() { + // unfocus input field on search, thus hiding virtual keyboard + $('#search-box').on('submit', function () { + if (document.activeElement) { + document.activeElement.blur(); + } + }); + } + + // fix for 10108 - marketplace category cannot scroll + function injectAddScrollbarToCategories() { + $('#categories-dropdown').on('show.bs.dropdown', function () { + $('body > div.container').css('display', 'none') + $('#categories-dropdown > ul.dropdown-menu').css({ 'overflow': 'auto', 'height': 'calc(100vh - 110px)' }); + }); + + $('#categories-dropdown').on('hide.bs.dropdown', function () { + $('body > div.container').css('display', ''); + $('#categories-dropdown > ul.dropdown-menu').css({ 'overflow': '', 'height': '' }); + }); + } + + function injectHiFiCode() { + if (commerceMode) { + maybeAddLogInButton(); + maybeAddSetupWalletButton(); + + if (!$('body').hasClass("code-injected")) { + + $('body').addClass("code-injected"); + changeDropdownMenu(); + + var target = document.getElementById('templated-items'); + // MutationObserver is necessary because the DOM is populated after the page is loaded. + // We're searching for changes to the element whose ID is '#templated-items' - this is + // the element that gets filled in by the AJAX. + var observer = new MutationObserver(function (mutations) { + mutations.forEach(function (mutation) { + injectBuyButtonOnMainPage(); + }); + }); + var config = { attributes: true, childList: true, characterData: true }; + observer.observe(target, config); + + // Try this here in case it works (it will if the user just pressed the "back" button, + // since that doesn't trigger another AJAX request. + injectBuyButtonOnMainPage(); + } + } + + injectUnfocusOnSearch(); + injectAddScrollbarToCategories(); + } + + function injectHiFiItemPageCode() { + if (commerceMode) { + maybeAddLogInButton(); + + if (!$('body').hasClass("code-injected")) { + + $('body').addClass("code-injected"); + changeDropdownMenu(); + + var purchaseButton = $('#side-info').find('.btn').first(); + + var href = purchaseButton.attr('href'); + purchaseButton.attr('href', '#'); + var cost = $('.item-cost').text(); + var costInt = parseInt(cost, 10); + var availability = $.trim($('.item-availability').text()); + if (limitedCommerce && (costInt > 0)) { + availability = ''; + } + if (availability === 'available') { + purchaseButton.css({ + "background": "linear-gradient(#00b4ef, #0093C5)", + "color": "#FFF", + "font-weight": "600", + "padding-bottom": "10px" + }); + } else { + purchaseButton.css({ + "background": "linear-gradient(#a2a2a2, #fefefe)", + "color": "#000", + "font-weight": "600", + "padding-bottom": "10px" + }); + } + + var type = $('.item-type').text(); + var isUpdating = window.location.href.indexOf('edition=') > -1; + var urlParams = new URLSearchParams(window.location.search); + if (isUpdating) { + purchaseButton.html('UPDATE FOR FREE'); + } else if (availability !== 'available') { + purchaseButton.html('UNAVAILABLE ' + (availability ? ('(' + availability + ')') : '')); + } else if (parseInt(cost) > 0 && $('#side-info').find('#buyItemButton').size() === 0) { + purchaseButton.html('PURCHASE ' + cost); + } + + purchaseButton.on('click', function () { + if ('available' === availability || isUpdating) { + buyButtonClicked(window.location.pathname.split("/")[3], + "itemPage", + urlParams.get('edition')); + } + }); + } + } + + injectUnfocusOnSearch(); + } + + function updateClaraCode() { + // Have to repeatedly update Clara page because its content can change dynamically without location.href changing. + + // Clara library page. + if (location.href.indexOf("clara.io/library") !== -1) { + // Make entries navigate to "Image" view instead of default "Real Time" view. + var elements = $("a.thumbnail"); + for (var i = 0, length = elements.length; i < length; i++) { + var value = elements[i].getAttribute("href"); + if (value.slice(-6) !== "/image") { + elements[i].setAttribute("href", value + "/image"); + } + } + } + + // Clara item page. + if (location.href.indexOf("clara.io/view/") !== -1) { + // Make site navigation links retain gameCheck etc. parameters. + var element = $("a[href^=\'/library\']")[0]; + var parameters = "?gameCheck=true&public=true"; + var href = element.getAttribute("href"); + if (href.slice(-parameters.length) !== parameters) { + element.setAttribute("href", href + parameters); + } + + // Remove unwanted buttons and replace download options with a single "Download to High Fidelity" button. + var buttons = $("a.embed-button").parent("div"); + var downloadFBX; + if (buttons.find("div.btn-group").length > 0) { + buttons.children(".btn-primary, .btn-group , .embed-button").each(function () { this.remove(); }); + if ($("#hifi-download-container").length === 0) { // Button hasn't been moved already. + downloadFBX = $(' Download to High Fidelity'); + buttons.prepend(downloadFBX); + downloadFBX[0].addEventListener("click", startAutoDownload); + } + } + + // Move the "Download to High Fidelity" button to be more visible on tablet. + if ($("#hifi-download-container").length === 0 && window.innerWidth < 700) { + var downloadContainer = $('
      '); + $(".top-title .col-sm-4").append(downloadContainer); + downloadContainer.append(downloadFBX); + } + } + } + + // Automatic download to High Fidelity. + function startAutoDownload() { + // One file request at a time. + if (isPreparing) { + console.log("WARNING: Clara.io FBX: Prepare only one download at a time"); + return; + } + + // User must be able to write to Asset Server. + if (!canWriteAssets) { + console.log("ERROR: Clara.io FBX: File download cancelled because no permissions to write to Asset Server"); + EventBridge.emitWebEvent(JSON.stringify({ + type: WARN_USER_NO_PERMISSIONS + })); + return; + } + + // User must be logged in. + var loginButton = $("#topnav a[href='/signup']"); + if (loginButton.length > 0) { + loginButton[0].click(); + return; + } + + // Obtain zip file to download for requested asset. + // Reference: https://clara.io/learn/sdk/api/export + + //var XMLHTTPREQUEST_URL = "https://clara.io/api/scenes/{uuid}/export/fbx?zip=true¢erScene=true&alignSceneGround=true&fbxUnit=Meter&fbxVersion=7&fbxEmbedTextures=true&imageFormat=WebGL"; + // 13 Jan 2017: Specify FBX version 5 and remove some options in order to make Clara.io site more likely to + // be successful in generating zip files. + var XMLHTTPREQUEST_URL = "https://clara.io/api/scenes/{uuid}/export/fbx?fbxUnit=Meter&fbxVersion=5&fbxEmbedTextures=true&imageFormat=WebGL"; + + var uuid = location.href.match(/\/view\/([a-z0-9\-]*)/)[1]; + var url = XMLHTTPREQUEST_URL.replace("{uuid}", uuid); + + xmlHttpRequest = new XMLHttpRequest(); + var responseTextIndex = 0; + var zipFileURL = ""; + + xmlHttpRequest.onreadystatechange = function () { + // Messages are appended to responseText; process the new ones. + var message = this.responseText.slice(responseTextIndex); + var statusMessage = ""; + + if (isPreparing) { // Ignore messages in flight after finished/cancelled. + var lines = message.split(/[\n\r]+/); + + for (var i = 0, length = lines.length; i < length; i++) { + if (lines[i].slice(0, 5) === "data:") { + // Parse line. + var data; + try { + data = JSON.parse(lines[i].slice(5)); + } + catch (e) { + data = {}; + } + + // Extract zip file URL. + if (data.hasOwnProperty("files") && data.files.length > 0) { + zipFileURL = data.files[0].url; + } + } + } + + if (statusMessage !== "") { + // Update the UI with the most recent status message. + EventBridge.emitWebEvent(JSON.stringify({ + type: CLARA_IO_STATUS, + status: statusMessage + })); + } + } + + responseTextIndex = this.responseText.length; + }; + + // Note: onprogress doesn't have computable total length so can't use it to determine % complete. + + xmlHttpRequest.onload = function () { + var statusMessage = ""; + + if (!isPreparing) { + return; + } + + isPreparing = false; + + var HTTP_OK = 200; + if (this.status !== HTTP_OK) { + EventBridge.emitWebEvent(JSON.stringify({ + type: CLARA_IO_STATUS, + status: statusMessage + })); + } else if (zipFileURL.slice(-4) !== ".zip") { + EventBridge.emitWebEvent(JSON.stringify({ + type: CLARA_IO_STATUS, + status: (statusMessage + ": " + zipFileURL) + })); + } else { + EventBridge.emitWebEvent(JSON.stringify({ + type: CLARA_IO_DOWNLOAD + })); + } + + xmlHttpRequest = null; + } + + isPreparing = true; + EventBridge.emitWebEvent(JSON.stringify({ + type: CLARA_IO_STATUS, + status: "Initiating download" + })); + + xmlHttpRequest.open("POST", url, true); + xmlHttpRequest.setRequestHeader("Accept", "text/event-stream"); + xmlHttpRequest.send(); + } + + function injectClaraCode() { + + // Make space for marketplaces footer in Clara pages. + $("head").append( + '' + ); + + // Condense space. + $("head").append( + '' + ); + + // Move "Download to High Fidelity" button. + $("head").append( + '' + ); + + // Update code injected per page displayed. + var updateClaraCodeInterval = undefined; + updateClaraCode(); + updateClaraCodeInterval = setInterval(function () { + updateClaraCode(); + }, 1000); + + window.addEventListener("unload", function () { + clearInterval(updateClaraCodeInterval); + updateClaraCodeInterval = undefined; + }); + + EventBridge.emitWebEvent(JSON.stringify({ + type: QUERY_CAN_WRITE_ASSETS + })); + } + + function cancelClaraDownload() { + isPreparing = false; + + if (xmlHttpRequest) { + xmlHttpRequest.abort(); + xmlHttpRequest = null; + console.log("Clara.io FBX: File download cancelled"); + EventBridge.emitWebEvent(JSON.stringify({ + type: CLARA_IO_CANCELLED_DOWNLOAD + })); + } + } + + function injectCode() { + var DIRECTORY = 0; + var HIFI = 1; + var CLARA = 2; + var HIFI_ITEM_PAGE = 3; + var pageType = DIRECTORY; + + if (location.href.indexOf(marketplaceBaseURL + "/") !== -1) { pageType = HIFI; } + if (location.href.indexOf("clara.io/") !== -1) { pageType = CLARA; } + if (location.href.indexOf(marketplaceBaseURL + "/marketplace/items/") !== -1) { pageType = HIFI_ITEM_PAGE; } + + injectCommonCode(pageType === DIRECTORY); + switch (pageType) { + case DIRECTORY: + injectDirectoryCode(); + break; + case HIFI: + injectHiFiCode(); + break; + case CLARA: + injectClaraCode(); + break; + case HIFI_ITEM_PAGE: + injectHiFiItemPageCode(); + break; + + } + } + + function onLoad() { + EventBridge.scriptEventReceived.connect(function (message) { + message = JSON.parse(message); + if (message.type === CAN_WRITE_ASSETS) { + canWriteAssets = message.canWriteAssets; + } else if (message.type === CLARA_IO_CANCEL_DOWNLOAD) { + cancelClaraDownload(); + } else if (message.type === "marketplaces") { + if (message.action === "commerceSetting") { + limitedCommerce = !!message.data.limitedCommerce; + commerceMode = !!message.data.commerceMode; + userIsLoggedIn = !!message.data.userIsLoggedIn; + walletNeedsSetup = !!message.data.walletNeedsSetup; + marketplaceBaseURL = message.data.metaverseServerURL; + if (marketplaceBaseURL.indexOf('metaverse.') !== -1) { + marketplaceBaseURL = marketplaceBaseURL.replace('metaverse.', ''); + } + messagesWaiting = message.data.messagesWaiting; + injectCode(); + } + } + }); + + // Request commerce setting + // Code is injected into the webpage after the setting comes back. + EventBridge.emitWebEvent(JSON.stringify({ + type: "REQUEST_SETTING" + })); + } + + // Load / unload. + window.addEventListener("load", onLoad); // More robust to Web site issues than using $(document).ready(). + window.addEventListener("page:change", onLoad); // Triggered after Marketplace HTML is changed +}()); From 8aedc98a584870156998ba615bd935511621e147 Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Mon, 11 Mar 2019 10:54:48 -0700 Subject: [PATCH 462/474] Fix QML formatting issue. --- interface/resources/qml/hifi/audio/Audio.qml | 57 ++++++++------------ 1 file changed, 21 insertions(+), 36 deletions(-) diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index ecc3297d9f..5e849acedf 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -171,15 +171,14 @@ Rectangle { } } - Separator {} - - - ColumnLayout { - id: pttColumn - spacing: 24; - x: 2 * margins.paddings; + Separator { id: pttStartSeparator; } + Item { + width: rightMostInputLevelPos; + height: pttSwitch.height + pttText.height + 24; HifiControlsUit.Switch { id: pttSwitch + x: 2 * margins.paddings; + anchors.top: parent.top; height: root.switchHeight; switchWidth: root.switchWidth; labelTextOn: qsTr("Push To Talk (T)"); @@ -200,39 +199,25 @@ Rectangle { }); // restore binding } } - Item { - id: pttTextContainer - width: rightMostInputLevelPos - height: pttTextMetrics.height - anchors.left: parent.left - anchors.leftMargin: -margins.padding - TextMetrics { - id: pttTextMetrics - text: pttText.text - font: pttText.font - } - RalewayRegular { - id: pttText - color: hifi.colors.white; - width: parent.width; - wrapMode: (bar.currentIndex === 0) ? Text.NoWrap : Text.WordWrap; - font.italic: true - size: 16; + RalewayRegular { + id: pttText + x: 2 * margins.paddings; + color: hifi.colors.white; + anchors.bottom: parent.bottom; + width: rightMostInputLevelPos; + height: paintedHeight; + wrapMode: Text.WordWrap; + font.italic: true + size: 16; - text: (bar.currentIndex === 0) ? qsTr("Press and hold the button \"T\" to unmute.") : - qsTr("Press and hold grip triggers on both of your controllers to unmute."); - onTextChanged: { - if (pttTextMetrics.width > pttTextContainer.width) { - pttTextContainer.height = Math.ceil(pttTextMetrics.width / pttTextContainer.width) * pttTextMetrics.height; - } else { - pttTextContainer.height = pttTextMetrics.height; - } - } - } + text: (bar.currentIndex === 0) ? qsTr("Press and hold the button \"T\" to unmute.") : + qsTr("Press and hold grip triggers on both of your controllers to unmute."); } } - Separator {} + Separator { + id: pttEndSeparator; + } Item { From 4371723145a2c30d0e57ee2a3613d88fdb4a706a Mon Sep 17 00:00:00 2001 From: danteruiz Date: Mon, 11 Mar 2019 11:16:53 -0700 Subject: [PATCH 463/474] fix soft entity popping --- .../entities-renderer/src/RenderableModelEntityItem.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 03c50008a0..643e5afb70 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -181,9 +181,11 @@ void RenderableModelEntityItem::updateModelBounds() { updateRenderItems = true; } - if (model->getScaleToFitDimensions() != getScaledDimensions() || - model->getRegistrationPoint() != getRegistrationPoint() || - !model->getIsScaledToFit()) { + bool overridingModelTransform = model->isOverridingModelTransformAndOffset(); + if (!overridingModelTransform && + (model->getScaleToFitDimensions() != getScaledDimensions() || + model->getRegistrationPoint() != getRegistrationPoint() || + !model->getIsScaledToFit())) { // The machinery for updateModelBounds will give existing models the opportunity to fix their // translation/rotation/scale/registration. The first two are straightforward, but the latter two // have guards to make sure they don't happen after they've already been set. Here we reset those guards. From b7e1798d1bf9a50003c095ee70180b23279c5e91 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 11 Mar 2019 11:28:30 -0700 Subject: [PATCH 464/474] better handling of unrigged vertices on skinned mesh --- libraries/fbx/src/FBXSerializer.cpp | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/libraries/fbx/src/FBXSerializer.cpp b/libraries/fbx/src/FBXSerializer.cpp index 5246242a1e..ca3659636f 100644 --- a/libraries/fbx/src/FBXSerializer.cpp +++ b/libraries/fbx/src/FBXSerializer.cpp @@ -1043,7 +1043,11 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr cluster.transformLink = createMat4(values); } } - clusters.insert(getID(object.properties), cluster); + + // skip empty clusters + if (cluster.indices.size() > 0 && cluster.weights.size() > 0) { + clusters.insert(getID(object.properties), cluster); + } } else if (object.properties.last() == "BlendShapeChannel") { QByteArray name = object.properties.at(1).toByteArray(); @@ -1510,19 +1514,6 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr const HFMCluster& hfmCluster = extracted.mesh.clusters.at(i); int jointIndex = hfmCluster.jointIndex; HFMJoint& joint = hfmModel.joints[jointIndex]; - glm::mat4 transformJointToMesh = inverseModelTransform * joint.bindTransform; - glm::vec3 boneEnd = extractTranslation(transformJointToMesh); - glm::vec3 boneBegin = boneEnd; - glm::vec3 boneDirection; - float boneLength = 0.0f; - if (joint.parentIndex != -1) { - boneBegin = extractTranslation(inverseModelTransform * hfmModel.joints[joint.parentIndex].bindTransform); - boneDirection = boneEnd - boneBegin; - boneLength = glm::length(boneDirection); - if (boneLength > EPSILON) { - boneDirection /= boneLength; - } - } glm::mat4 meshToJoint = glm::inverse(joint.bindTransform) * modelTransform; ShapeVertices& points = shapeVertices.at(jointIndex); @@ -1575,16 +1566,19 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr int j = i * WEIGHTS_PER_VERTEX; // normalize weights into uint16_t - float totalWeight = weightAccumulators[j]; - for (int k = j + 1; k < j + WEIGHTS_PER_VERTEX; ++k) { + float totalWeight = 0.0f; + for (int k = j; k < j + WEIGHTS_PER_VERTEX; ++k) { totalWeight += weightAccumulators[k]; } + + const float ALMOST_HALF = 0.499f; if (totalWeight > 0.0f) { - const float ALMOST_HALF = 0.499f; float weightScalingFactor = (float)(UINT16_MAX) / totalWeight; for (int k = j; k < j + WEIGHTS_PER_VERTEX; ++k) { extracted.mesh.clusterWeights[k] = (uint16_t)(weightScalingFactor * weightAccumulators[k] + ALMOST_HALF); } + } else { + extracted.mesh.clusterWeights[j] = (uint16_t)((float)(UINT16_MAX) + ALMOST_HALF); } } } else { From 2fb5e1ebc263efc96dedbe4fbf0ca7eba30f7c2d Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 11 Mar 2019 12:36:29 -0700 Subject: [PATCH 465/474] quiet some logging --- scripts/system/miniTablet.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/system/miniTablet.js b/scripts/system/miniTablet.js index 449921514c..91c8b1edcf 100644 --- a/scripts/system/miniTablet.js +++ b/scripts/system/miniTablet.js @@ -1048,6 +1048,7 @@ // Track grabbed state and item. switch (message.action) { case "grab": + case "equip": grabbingHand = HAND_NAMES.indexOf(message.joint); grabbedItem = message.grabbedEntity; break; @@ -1056,7 +1057,7 @@ grabbedItem = null; break; default: - error("Unexpected grab message!"); + error("Unexpected grab message: " + JSON.stringify(message)); return; } @@ -1144,4 +1145,4 @@ setUp(); Script.scriptEnding.connect(tearDown); -}()); \ No newline at end of file +}()); From e515e9cc66d627c543e3e50d8983ffdb38936703 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 11 Mar 2019 12:38:54 -0700 Subject: [PATCH 466/474] fix cloneEntity function --- scripts/system/libraries/cloneEntityUtils.js | 15 ++++++--------- .../system/libraries/controllerDispatcherUtils.js | 4 +++- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/scripts/system/libraries/cloneEntityUtils.js b/scripts/system/libraries/cloneEntityUtils.js index e0f4aba84a..f789e19cd8 100644 --- a/scripts/system/libraries/cloneEntityUtils.js +++ b/scripts/system/libraries/cloneEntityUtils.js @@ -5,8 +5,8 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -/* global entityIsCloneable:true, getGrabbableData:true, cloneEntity:true, propsAreCloneDynamic:true, Script, - propsAreCloneDynamic:true, Entities*/ +/* global entityIsCloneable:true, cloneEntity:true, propsAreCloneDynamic:true, Script, + propsAreCloneDynamic:true, Entities, Uuid */ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); @@ -47,13 +47,10 @@ propsAreCloneDynamic = function(props) { }; cloneEntity = function(props) { - var entityToClone = props.id; - var props = Entities.getEntityProperties(entityToClone, ['certificateID', 'certificateType']) - var certificateID = props.certificateID; - // ensure entity is cloneable and does not have a certificate ID, whereas cloneable limits - // will now be handled by the server where the entity add will fail if limit reached - if (entityIsCloneable(props) && (!!certificateID || props.certificateType.indexOf('domainUnlimited') >= 0)) { - var cloneID = Entities.cloneEntity(entityToClone); + var entityIDToClone = props.id; + if (entityIsCloneable(props) && + (Uuid.isNull(props.certificateID) || props.certificateType.indexOf('domainUnlimited') >= 0)) { + var cloneID = Entities.cloneEntity(entityIDToClone); return cloneID; } return null; diff --git a/scripts/system/libraries/controllerDispatcherUtils.js b/scripts/system/libraries/controllerDispatcherUtils.js index 385ed954b0..5cb95f625d 100644 --- a/scripts/system/libraries/controllerDispatcherUtils.js +++ b/scripts/system/libraries/controllerDispatcherUtils.js @@ -156,7 +156,9 @@ DISPATCHER_PROPERTIES = [ "grab.equippableIndicatorOffset", "userData", "avatarEntity", - "owningAvatarID" + "owningAvatarID", + "certificateID", + "certificateType" ]; // priority -- a lower priority means the module will be asked sooner than one with a higher priority in a given update step From 07ddd4e1dd1d716d78c9c29db8b6d6ec880d7005 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Mon, 11 Mar 2019 14:27:47 -0700 Subject: [PATCH 467/474] moving key press detection to JSON --- .../resources/controllers/keyboardMouse.json | 1 + interface/src/Application.cpp | 15 ++------------- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/interface/resources/controllers/keyboardMouse.json b/interface/resources/controllers/keyboardMouse.json index 74c11203ef..9b3c711c63 100644 --- a/interface/resources/controllers/keyboardMouse.json +++ b/interface/resources/controllers/keyboardMouse.json @@ -5,6 +5,7 @@ { "from": "Keyboard.D", "when": ["Keyboard.RightMouseButton", "!Keyboard.Control"], "to": "Actions.LATERAL_RIGHT" }, { "from": "Keyboard.E", "when": "!Keyboard.Control", "to": "Actions.LATERAL_RIGHT" }, { "from": "Keyboard.Q", "when": "!Keyboard.Control", "to": "Actions.LATERAL_LEFT" }, + { "from": "Keyboard.T", "when": "!Keyboard.Control", "to": "Actions.TogglePushToTalk" }, { "comment" : "Mouse turn need to be small continuous increments", "from": { "makeAxis" : [ diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 3230419816..de4a6bb167 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1608,9 +1608,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo switch (actionEnum) { case Action::TOGGLE_PUSHTOTALK: if (state > 0.0f) { - audioScriptingInterface->setPushingToTalk(false); - } else if (state < 0.0f) { audioScriptingInterface->setPushingToTalk(true); + } else if (state <= 0.0f) { + audioScriptingInterface->setPushingToTalk(false); } break; @@ -4047,7 +4047,6 @@ void Application::keyPressEvent(QKeyEvent* event) { _keysPressed.insert(event->key(), *event); } - auto audioScriptingInterface = reinterpret_cast(DependencyManager::get().data()); _controllerScriptingInterface->emitKeyPressEvent(event); // send events to any registered scripts // if one of our scripts have asked to capture this event, then stop processing it if (_controllerScriptingInterface->isKeyCaptured(event) || isInterstitialMode()) { @@ -4215,10 +4214,6 @@ void Application::keyPressEvent(QKeyEvent* event) { } break; - case Qt::Key_T: - audioScriptingInterface->setPushingToTalk(true); - break; - case Qt::Key_P: { if (!isShifted && !isMeta && !isOption && !event->isAutoRepeat()) { AudioInjectorOptions options; @@ -4325,12 +4320,6 @@ void Application::keyReleaseEvent(QKeyEvent* event) { _keyboardMouseDevice->keyReleaseEvent(event); } - auto audioScriptingInterface = reinterpret_cast(DependencyManager::get().data()); - switch (event->key()) { - case Qt::Key_T: - audioScriptingInterface->setPushingToTalk(false); - break; - } } void Application::focusOutEvent(QFocusEvent* event) { From b24b7fed3d96038aa5c55b2463534868935e1737 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 11 Mar 2019 15:34:41 -0700 Subject: [PATCH 468/474] the root node isn't the first onegit add ../.git add ../. --- libraries/fbx/src/FBXSerializer.cpp | 12 ++++++------ libraries/render-utils/src/CauterizedModel.cpp | 8 ++++---- libraries/render-utils/src/Model.cpp | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/libraries/fbx/src/FBXSerializer.cpp b/libraries/fbx/src/FBXSerializer.cpp index ca3659636f..52f4189bdb 100644 --- a/libraries/fbx/src/FBXSerializer.cpp +++ b/libraries/fbx/src/FBXSerializer.cpp @@ -1486,8 +1486,8 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr } } - // if we don't have a skinned joint, parent to the model itself - if (extracted.mesh.clusters.isEmpty()) { + // the last cluster is the root cluster + { HFMCluster cluster; cluster.jointIndex = modelIDs.indexOf(modelID); if (cluster.jointIndex == -1) { @@ -1498,13 +1498,11 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr } // whether we're skinned depends on how many clusters are attached - const HFMCluster& firstHFMCluster = extracted.mesh.clusters.at(0); - glm::mat4 inverseModelTransform = glm::inverse(modelTransform); if (clusterIDs.size() > 1) { // this is a multi-mesh joint const int WEIGHTS_PER_VERTEX = 4; int numClusterIndices = extracted.mesh.vertices.size() * WEIGHTS_PER_VERTEX; - extracted.mesh.clusterIndices.fill(0, numClusterIndices); + extracted.mesh.clusterIndices.fill(extracted.mesh.clusters.size() - 1, numClusterIndices); QVector weightAccumulators; weightAccumulators.fill(0.0f, numClusterIndices); @@ -1526,6 +1524,7 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr int newIndex = it.value(); // remember vertices with at least 1/4 weight + // FIXME: vertices with no weightpainting won't get recorded here const float EXPANSION_WEIGHT_THRESHOLD = 0.25f; if (weight >= EXPANSION_WEIGHT_THRESHOLD) { // transform to joint-frame and save for later @@ -1582,7 +1581,8 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr } } } else { - // this is a single-mesh joint + // this is a single-joint mesh + const HFMCluster& firstHFMCluster = extracted.mesh.clusters.at(0); int jointIndex = firstHFMCluster.jointIndex; HFMJoint& joint = hfmModel.joints[jointIndex]; diff --git a/libraries/render-utils/src/CauterizedModel.cpp b/libraries/render-utils/src/CauterizedModel.cpp index cfb78d6bbc..cfdcec6e99 100644 --- a/libraries/render-utils/src/CauterizedModel.cpp +++ b/libraries/render-utils/src/CauterizedModel.cpp @@ -245,7 +245,7 @@ void CauterizedModel::updateRenderItems() { Transform renderTransform = modelTransform; if (useDualQuaternionSkinning) { - if (meshState.clusterDualQuaternions.size() == 1) { + if (meshState.clusterDualQuaternions.size() <= 2) { const auto& dq = meshState.clusterDualQuaternions[0]; Transform transform(dq.getRotation(), dq.getScale(), @@ -253,7 +253,7 @@ void CauterizedModel::updateRenderItems() { renderTransform = modelTransform.worldTransform(transform); } } else { - if (meshState.clusterMatrices.size() == 1) { + if (meshState.clusterMatrices.size() <= 2) { renderTransform = modelTransform.worldTransform(Transform(meshState.clusterMatrices[0])); } } @@ -261,7 +261,7 @@ void CauterizedModel::updateRenderItems() { renderTransform = modelTransform; if (useDualQuaternionSkinning) { - if (cauterizedMeshState.clusterDualQuaternions.size() == 1) { + if (cauterizedMeshState.clusterDualQuaternions.size() <= 2) { const auto& dq = cauterizedMeshState.clusterDualQuaternions[0]; Transform transform(dq.getRotation(), dq.getScale(), @@ -269,7 +269,7 @@ void CauterizedModel::updateRenderItems() { renderTransform = modelTransform.worldTransform(Transform(transform)); } } else { - if (cauterizedMeshState.clusterMatrices.size() == 1) { + if (cauterizedMeshState.clusterMatrices.size() <= 2) { renderTransform = modelTransform.worldTransform(Transform(cauterizedMeshState.clusterMatrices[0])); } } diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index a8d3e504f1..3c6565fca9 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -247,7 +247,7 @@ void Model::updateRenderItems() { Transform renderTransform = modelTransform; if (useDualQuaternionSkinning) { - if (meshState.clusterDualQuaternions.size() == 1) { + if (meshState.clusterDualQuaternions.size() <= 2) { const auto& dq = meshState.clusterDualQuaternions[0]; Transform transform(dq.getRotation(), dq.getScale(), @@ -255,7 +255,7 @@ void Model::updateRenderItems() { renderTransform = modelTransform.worldTransform(Transform(transform)); } } else { - if (meshState.clusterMatrices.size() == 1) { + if (meshState.clusterMatrices.size() <= 2) { renderTransform = modelTransform.worldTransform(Transform(meshState.clusterMatrices[0])); } } From ca5ff3381b154d7466a6010e9b344e80cb94e407 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Mon, 11 Mar 2019 16:02:18 -0700 Subject: [PATCH 469/474] changing position of the mute warning setting --- interface/resources/qml/hifi/audio/Audio.qml | 54 ++++++++++---------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index a5138b3dd9..1a0457fd0a 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -125,22 +125,6 @@ Rectangle { } } - HifiControlsUit.Switch { - id: stereoInput; - height: root.switchHeight; - switchWidth: root.switchWidth; - labelTextOn: qsTr("Stereo input"); - backgroundOnColor: "#E3E3E3"; - checked: AudioScriptingInterface.isStereoInput; - onCheckedChanged: { - AudioScriptingInterface.isStereoInput = checked; - checked = Qt.binding(function() { return AudioScriptingInterface.isStereoInput; }); // restore binding - } - } - } - - ColumnLayout { - spacing: 24; HifiControlsUit.Switch { height: root.switchHeight; switchWidth: root.switchWidth; @@ -152,6 +136,23 @@ Rectangle { checked = Qt.binding(function() { return AudioScriptingInterface.noiseReduction; }); // restore binding } } + } + + ColumnLayout { + spacing: 24; + HifiControlsUit.Switch { + id: warnMutedSwitch + height: root.switchHeight; + switchWidth: root.switchWidth; + labelTextOn: qsTr("Warn when muted"); + backgroundOnColor: "#E3E3E3"; + checked: AudioScriptingInterface.warnWhenMuted; + onClicked: { + AudioScriptingInterface.warnWhenMuted = checked; + checked = Qt.binding(function() { return AudioScriptingInterface.warnWhenMuted; }); // restore binding + } + } + HifiControlsUit.Switch { id: audioLevelSwitch @@ -165,19 +166,20 @@ Rectangle { checked = Qt.binding(function() { return AvatarInputs.showAudioTools; }); // restore binding } } - } - RowLayout { - spacing: muteMic.spacing*2; - AudioControls.CheckBox { - spacing: muteMic.spacing - text: qsTr("Warn when muted"); - checked: AudioScriptingInterface.warnWhenMuted; - onClicked: { - AudioScriptingInterface.warnWhenMuted = checked; - checked = Qt.binding(function() { return AudioScriptingInterface.warnWhenMuted; }); // restore binding + HifiControlsUit.Switch { + id: stereoInput; + height: root.switchHeight; + switchWidth: root.switchWidth; + labelTextOn: qsTr("Stereo input"); + backgroundOnColor: "#E3E3E3"; + checked: AudioScriptingInterface.isStereoInput; + onCheckedChanged: { + AudioScriptingInterface.isStereoInput = checked; + checked = Qt.binding(function() { return AudioScriptingInterface.isStereoInput; }); // restore binding } } + } } From 80821e8b7e354ebd1d77a737b74c1e009e942538 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Mon, 11 Mar 2019 16:15:47 -0700 Subject: [PATCH 470/474] changing mic bar indicator when muted in PTT --- interface/resources/qml/hifi/audio/Audio.qml | 62 +++++++++---------- interface/resources/qml/hifi/audio/MicBar.qml | 6 +- 2 files changed, 32 insertions(+), 36 deletions(-) diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index 5e849acedf..faa4f1de2f 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -140,6 +140,29 @@ Rectangle { checked = Qt.binding(function() { return AudioScriptingInterface.isStereoInput; }); // restore binding } } + + HifiControlsUit.Switch { + id: pttSwitch + height: root.switchHeight; + switchWidth: root.switchWidth; + labelTextOn: qsTr("Push To Talk (T)"); + backgroundOnColor: "#E3E3E3"; + checked: (bar.currentIndex === 0) ? AudioScriptingInterface.pushToTalkDesktop : AudioScriptingInterface.pushToTalkHMD; + onCheckedChanged: { + if (bar.currentIndex === 0) { + AudioScriptingInterface.pushToTalkDesktop = checked; + } else { + AudioScriptingInterface.pushToTalkHMD = checked; + } + checked = Qt.binding(function() { + if (bar.currentIndex === 0) { + return AudioScriptingInterface.pushToTalkDesktop; + } else { + return AudioScriptingInterface.pushToTalkHMD; + } + }); // restore binding + } + } } ColumnLayout { @@ -171,53 +194,26 @@ Rectangle { } } - Separator { id: pttStartSeparator; } Item { + anchors.left: parent.left width: rightMostInputLevelPos; - height: pttSwitch.height + pttText.height + 24; - HifiControlsUit.Switch { - id: pttSwitch - x: 2 * margins.paddings; - anchors.top: parent.top; - height: root.switchHeight; - switchWidth: root.switchWidth; - labelTextOn: qsTr("Push To Talk (T)"); - backgroundOnColor: "#E3E3E3"; - checked: (bar.currentIndex === 0) ? AudioScriptingInterface.pushToTalkDesktop : AudioScriptingInterface.pushToTalkHMD; - onCheckedChanged: { - if (bar.currentIndex === 0) { - AudioScriptingInterface.pushToTalkDesktop = checked; - } else { - AudioScriptingInterface.pushToTalkHMD = checked; - } - checked = Qt.binding(function() { - if (bar.currentIndex === 0) { - return AudioScriptingInterface.pushToTalkDesktop; - } else { - return AudioScriptingInterface.pushToTalkHMD; - } - }); // restore binding - } - } + height: pttText.height; RalewayRegular { id: pttText - x: 2 * margins.paddings; + x: margins.paddings; color: hifi.colors.white; - anchors.bottom: parent.bottom; width: rightMostInputLevelPos; height: paintedHeight; wrapMode: Text.WordWrap; font.italic: true size: 16; - text: (bar.currentIndex === 0) ? qsTr("Press and hold the button \"T\" to unmute.") : - qsTr("Press and hold grip triggers on both of your controllers to unmute."); + text: (bar.currentIndex === 0) ? qsTr("Press and hold the button \"T\" to talk.") : + qsTr("Press and hold grip triggers on both of your controllers to talk."); } } - Separator { - id: pttEndSeparator; - } + Separator { } Item { diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index 491b9f9554..f51da9c381 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -159,7 +159,7 @@ Rectangle { color: parent.color; - text: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? "MUTED PTT-(T)" : (AudioScriptingInterface.muted ? "MUTED" : "MUTE"); + text: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? (HMD.active ? "MUTED PTT" : "MUTED PTT-(T)") : (AudioScriptingInterface.muted ? "MUTED" : "MUTE"); font.pointSize: 12; } @@ -169,7 +169,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - width: AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk ? 25 : 50; + width: AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk ? (HMD.active ? 27 : 25) : 50; height: 4; color: parent.color; } @@ -180,7 +180,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - width: AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk ? 25 : 50; + width: AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk ? (HMD.active ? 27 : 25) : 50; height: 4; color: parent.color; } From 3c5fd069595dec2595dd74fbf4e78dee30f5a0a7 Mon Sep 17 00:00:00 2001 From: Jason Najera <39922250+r3tk0n@users.noreply.github.com> Date: Mon, 11 Mar 2019 16:16:58 -0700 Subject: [PATCH 471/474] Remove useless comment. Comment was not helpful. --- interface/src/scripting/Audio.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index c4dfcffb61..6b4ad80231 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -40,7 +40,6 @@ Audio::Audio() : _devices(_contextIsHMD) { connect(client, &AudioClient::inputLoudnessChanged, this, &Audio::onInputLoudnessChanged); connect(client, &AudioClient::inputVolumeChanged, this, &Audio::setInputVolume); connect(this, &Audio::contextChanged, &_devices, &AudioDevices::onContextChanged); - // when pushing to talk changed, handle it. connect(this, &Audio::pushingToTalkChanged, this, &Audio::handlePushedToTalk); enableNoiseReduction(enableNoiseReductionSetting.get()); onContextChanged(); From 84b177996b346deaf82712c4ff74f0ebfa8c608b Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Mon, 11 Mar 2019 16:43:26 -0700 Subject: [PATCH 472/474] removing dead code --- .../controllers/controllerModules/pushToTalk.js | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/scripts/system/controllers/controllerModules/pushToTalk.js b/scripts/system/controllers/controllerModules/pushToTalk.js index 6b1bacc367..11335ba2f5 100644 --- a/scripts/system/controllers/controllerModules/pushToTalk.js +++ b/scripts/system/controllers/controllerModules/pushToTalk.js @@ -11,17 +11,10 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Script.include("/~/system/libraries/controllers.js"); -(function () { // BEGIN LOCAL_SCOPE +(function() { // BEGIN LOCAL_SCOPE function PushToTalkHandler() { var _this = this; this.active = false; - //var pttMapping, mappingName; - - this.setup = function () { - //mappingName = 'Hifi-PTT-Dev-' + Math.random(); - //pttMapping = Controller.newMapping(mappingName); - //pttMapping.enable(); - }; this.shouldTalk = function (controllerData) { // Set up test against controllerData here... @@ -53,10 +46,6 @@ Script.include("/~/system/libraries/controllers.js"); return makeRunningValues(true, [], []); }; - this.cleanup = function () { - //pttMapping.disable(); - }; - this.parameters = makeDispatcherModuleParameters( 950, ["head"], @@ -68,9 +57,8 @@ Script.include("/~/system/libraries/controllers.js"); enableDispatcherModule("PushToTalk", pushToTalk); function cleanup() { - pushToTalk.cleanup(); disableDispatcherModule("PushToTalk"); }; Script.scriptEnding.connect(cleanup); -}()); // END LOCAL_SCOPE +}()); // END LOCAL_SCOPE From 2b32b77bed0875cb72c98948f82fcc7aa2513b37 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 11 Mar 2019 17:32:40 -0700 Subject: [PATCH 473/474] handle case when clusterMatrices.size() == 0 --- .vs/slnx.sqlite | Bin 0 -> 77824 bytes libraries/render-utils/src/CauterizedModel.cpp | 8 ++++---- libraries/render-utils/src/Model.cpp | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) create mode 100644 .vs/slnx.sqlite diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..4227190a376249e4edb90c9be12165dbacd46945 GIT binary patch literal 77824 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 Date: Mon, 11 Mar 2019 17:59:48 -0700 Subject: [PATCH 474/474] fix joint out not being renamed with jointRotationOffset2 --- .../src/model-baker/PrepareJointsTask.cpp | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp b/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp index b8bcdb386e..a746b76c1f 100644 --- a/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp +++ b/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp @@ -101,23 +101,24 @@ void PrepareJointsTask::run(const baker::BakeContextPointer& context, const Inpu } if (newJointRot) { - for (const auto& jointIn : jointsIn) { + for (auto& jointOut : jointsOut) { - auto jointNameMapKey = jointNameMapping.key(jointIn.name); - int mappedIndex = jointIndices.value(jointIn.name); + auto jointNameMapKey = jointNameMapping.key(jointOut.name); + int mappedIndex = jointIndices.value(jointOut.name); if (jointNameMapping.contains(jointNameMapKey)) { - // delete and replace with hifi name - jointIndices.remove(jointIn.name); - jointIndices.insert(jointNameMapKey, mappedIndex); + jointIndices.remove(jointOut.name); + jointOut.name = jointNameMapKey; + jointIndices.insert(jointOut.name, mappedIndex); } else { // nothing mapped to this fbx joint name - if (jointNameMapping.contains(jointIn.name)) { + if (jointNameMapping.contains(jointOut.name)) { // but the name is in the list of hifi names is mapped to a different joint - int extraIndex = jointIndices.value(jointIn.name); - jointIndices.remove(jointIn.name); - jointIndices.insert("", extraIndex); + int extraIndex = jointIndices.value(jointOut.name); + jointIndices.remove(jointOut.name); + jointOut.name = ""; + jointIndices.insert(jointOut.name, extraIndex); } } }