From c7e4bf931b49e3dd04c8f4595572e8395efe1850 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 27 Jun 2017 19:23:59 -0700 Subject: [PATCH 01/39] WIP: first steps toward smoothing ik chains --- .../animation/src/AnimInverseKinematics.cpp | 109 +++++++++++------- .../animation/src/AnimInverseKinematics.h | 8 +- libraries/animation/src/AnimSkeleton.cpp | 14 +++ libraries/animation/src/AnimSkeleton.h | 1 + 4 files changed, 85 insertions(+), 47 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index d7076a443e..4ec10b9faf 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -23,18 +23,18 @@ #include "CubicHermiteSpline.h" #include "AnimUtil.h" -static void lookupJointChainInfo(AnimInverseKinematics::JointChainInfo* jointChainInfos, size_t numJointChainInfos, +static void lookupJointChainInfo(const std::vector& jointChainInfoVec, int indexA, int indexB, - AnimInverseKinematics::JointChainInfo** jointChainInfoA, - AnimInverseKinematics::JointChainInfo** jointChainInfoB) { + const AnimInverseKinematics::JointChainInfo** jointChainInfoA, + const AnimInverseKinematics::JointChainInfo** jointChainInfoB) { *jointChainInfoA = nullptr; *jointChainInfoB = nullptr; - for (size_t i = 0; i < numJointChainInfos; i++) { - if (jointChainInfos[i].jointIndex == indexA) { - *jointChainInfoA = jointChainInfos + i; + for (size_t i = 0; i < jointChainInfoVec.size(); i++) { + if (jointChainInfoVec[i].jointIndex == indexA) { + *jointChainInfoA = &jointChainInfoVec[i]; } - if (jointChainInfos[i].jointIndex == indexB) { - *jointChainInfoB = jointChainInfos + i; + if (jointChainInfoVec[i].jointIndex == indexB) { + *jointChainInfoB = &jointChainInfoVec[i]; } if (*jointChainInfoA && *jointChainInfoB) { break; @@ -234,6 +234,17 @@ void AnimInverseKinematics::solve(const AnimContext& context, const std::vector< accumulator.clearAndClean(); } + // initialize jointChainInfoVecVec, this holds the results for one iteration of each ik chain. + JointChainInfo defaultJointChainInfo = { glm::quat(), glm::vec3(), 0.0f, -1, false }; + std::vector> jointChainInfoVecVec(targets.size()); + for (size_t i = 0; i < targets.size(); i++) { + int chainDepth = _skeleton->getChainDepth(targets[i].getIndex()); + jointChainInfoVecVec[i].reserve(chainDepth); + for (size_t j = 0; j < chainDepth; j++) { + jointChainInfoVecVec[i].push_back(defaultJointChainInfo); + } + } + float maxError = FLT_MAX; int numLoops = 0; const int MAX_IK_LOOPS = 16; @@ -244,11 +255,25 @@ void AnimInverseKinematics::solve(const AnimContext& context, const std::vector< bool debug = context.getEnableDebugDrawIKChains() && numLoops == MAX_IK_LOOPS; // solve all targets - for (auto& target: targets) { - if (target.getType() == IKTarget::Type::Spline) { - solveTargetWithSpline(context, target, absolutePoses, debug); + for (size_t i = 0; i < targets.size(); i++) { + if (targets[i].getType() == IKTarget::Type::Spline) { + solveTargetWithSpline(context, targets[i], absolutePoses, debug, jointChainInfoVecVec[i]); } else { - solveTargetWithCCD(context, target, absolutePoses, debug); + solveTargetWithCCD(context, targets[i], absolutePoses, debug, jointChainInfoVecVec[i]); + } + } + + // TODO: do smooth interpolation of joint chains here, if necessary. + + // copy jointChainInfoVecs into accumulators + for (size_t i = 0; i < targets.size(); i++) { + const std::vector& jointChainInfoVec = jointChainInfoVecVec[i]; + for (size_t j = 0; j < jointChainInfoVec.size(); j++) { + const JointChainInfo& info = jointChainInfoVec[j]; + if (info.jointIndex >= 0) { + _rotationAccumulators[info.jointIndex].add(info.relRot, info.weight); + _translationAccumulators[info.jointIndex].add(info.relTrans, info.weight); + } } } @@ -310,7 +335,8 @@ void AnimInverseKinematics::solve(const AnimContext& context, const std::vector< } } -void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug) { +void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, + bool debug, std::vector& jointChainInfoVec) const { size_t chainDepth = 0; IKTarget::Type targetType = target.getType(); @@ -338,9 +364,6 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const // the tip's parent-relative as we proceed up the chain glm::quat tipParentOrientation = absolutePoses[pivotIndex].rot(); - const size_t MAX_CHAIN_DEPTH = 30; - JointChainInfo jointChainInfos[MAX_CHAIN_DEPTH]; - // NOTE: if this code is removed, the head will remain rigid, causing the spine/hips to thrust forward backward // as the head is nodded. if (targetType == IKTarget::Type::HmdHead || @@ -368,7 +391,7 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const } glm::vec3 tipRelativeTranslation = _relativePoses[target.getIndex()].trans(); - jointChainInfos[chainDepth] = { tipRelativeRotation, tipRelativeTranslation, target.getWeight(), tipIndex, constrained }; + jointChainInfoVec[chainDepth] = { tipRelativeRotation, tipRelativeTranslation, target.getWeight(), tipIndex, constrained }; } // cache tip absolute position @@ -379,7 +402,7 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const // descend toward root, pivoting each joint to get tip closer to target position while (pivotIndex != _hipsIndex && pivotsParentIndex != -1) { - assert(chainDepth < MAX_CHAIN_DEPTH); + assert(chainDepth < jointChainInfoVec.size()); // compute the two lines that should be aligned glm::vec3 jointPosition = absolutePoses[pivotIndex].trans(); @@ -480,7 +503,7 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const } glm::vec3 newTrans = _relativePoses[pivotIndex].trans(); - jointChainInfos[chainDepth] = { newRot, newTrans, target.getWeight(), pivotIndex, constrained }; + jointChainInfoVec[chainDepth] = { newRot, newTrans, target.getWeight(), pivotIndex, constrained }; // keep track of tip's new transform as we descend towards root tipPosition = jointPosition + deltaRotation * (tipPosition - jointPosition); @@ -502,24 +525,25 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const int baseParentJointIndex = _skeleton->getParentIndex(baseJointIndex); AnimPose topPose, midPose, basePose; int topChainIndex = -1, baseChainIndex = -1; + const size_t MAX_CHAIN_DEPTH = 30; AnimPose postAbsPoses[MAX_CHAIN_DEPTH]; AnimPose accum = absolutePoses[_hipsIndex]; AnimPose baseParentPose = absolutePoses[_hipsIndex]; for (int i = (int)chainDepth - 1; i >= 0; i--) { - accum = accum * AnimPose(glm::vec3(1.0f), jointChainInfos[i].relRot, jointChainInfos[i].relTrans); + accum = accum * AnimPose(glm::vec3(1.0f), jointChainInfoVec[i].relRot, jointChainInfoVec[i].relTrans); postAbsPoses[i] = accum; - if (jointChainInfos[i].jointIndex == topJointIndex) { + if (jointChainInfoVec[i].jointIndex == topJointIndex) { topChainIndex = i; topPose = accum; } - if (jointChainInfos[i].jointIndex == midJointIndex) { + if (jointChainInfoVec[i].jointIndex == midJointIndex) { midPose = accum; } - if (jointChainInfos[i].jointIndex == baseJointIndex) { + if (jointChainInfoVec[i].jointIndex == baseJointIndex) { baseChainIndex = i; basePose = accum; } - if (jointChainInfos[i].jointIndex == baseParentJointIndex) { + if (jointChainInfoVec[i].jointIndex == baseParentJointIndex) { baseParentPose = accum; } } @@ -599,21 +623,16 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const } glm::quat newBaseRelRot = glm::inverse(baseParentPose.rot()) * poleRot * basePose.rot(); - jointChainInfos[baseChainIndex].relRot = newBaseRelRot; + jointChainInfoVec[baseChainIndex].relRot = newBaseRelRot; glm::quat newTopRelRot = glm::inverse(midPose.rot()) * glm::inverse(poleRot) * topPose.rot(); - jointChainInfos[topChainIndex].relRot = newTopRelRot; + jointChainInfoVec[topChainIndex].relRot = newTopRelRot; } } } - for (size_t i = 0; i < chainDepth; i++) { - _rotationAccumulators[jointChainInfos[i].jointIndex].add(jointChainInfos[i].relRot, jointChainInfos[i].weight); - _translationAccumulators[jointChainInfos[i].jointIndex].add(jointChainInfos[i].relTrans, jointChainInfos[i].weight); - } - if (debug) { - debugDrawIKChain(jointChainInfos, chainDepth, context); + debugDrawIKChain(jointChainInfoVec, context); } } @@ -697,10 +716,9 @@ const std::vector* AnimInverseKinematics return nullptr; } -void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug) { - - const size_t MAX_CHAIN_DEPTH = 30; - JointChainInfo jointChainInfos[MAX_CHAIN_DEPTH]; +// AJT: TODO: make this const someday +void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, + bool debug, std::vector& jointChainInfoVec) { const int baseIndex = _hipsIndex; @@ -783,19 +801,22 @@ void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, co } } - jointChainInfos[i] = { relPose.rot(), relPose.trans(), target.getWeight(), splineJointInfo.jointIndex, constrained }; + jointChainInfoVec[i] = { relPose.rot(), relPose.trans(), target.getWeight(), splineJointInfo.jointIndex, constrained }; parentAbsPose = flexedAbsPose; } } + // AJT: REMOVE + /* for (size_t i = 0; i < splineJointInfoVec->size(); i++) { _rotationAccumulators[jointChainInfos[i].jointIndex].add(jointChainInfos[i].relRot, jointChainInfos[i].weight); _translationAccumulators[jointChainInfos[i].jointIndex].add(jointChainInfos[i].relTrans, jointChainInfos[i].weight); } + */ if (debug) { - debugDrawIKChain(jointChainInfos, splineJointInfoVec->size(), context); + debugDrawIKChain(jointChainInfoVec, context); } } @@ -1495,12 +1516,12 @@ void AnimInverseKinematics::debugDrawRelativePoses(const AnimContext& context) c } } -void AnimInverseKinematics::debugDrawIKChain(JointChainInfo* jointChainInfos, size_t numJointChainInfos, const AnimContext& context) const { +void AnimInverseKinematics::debugDrawIKChain(const std::vector& jointChainInfoVec, const AnimContext& context) const { AnimPoseVec poses = _relativePoses; // copy debug joint rotations into the relative poses - for (size_t i = 0; i < numJointChainInfos; i++) { - const JointChainInfo& info = jointChainInfos[i]; + for (size_t i = 0; i < jointChainInfoVec.size(); i++) { + const JointChainInfo& info = jointChainInfoVec[i]; poses[info.jointIndex].rot() = info.relRot; poses[info.jointIndex].trans() = info.relTrans; } @@ -1519,9 +1540,9 @@ void AnimInverseKinematics::debugDrawIKChain(JointChainInfo* jointChainInfos, si // draw each pose for (int i = 0; i < (int)poses.size(); i++) { int parentIndex = _skeleton->getParentIndex(i); - JointChainInfo* jointInfo = nullptr; - JointChainInfo* parentJointInfo = nullptr; - lookupJointChainInfo(jointChainInfos, numJointChainInfos, i, parentIndex, &jointInfo, &parentJointInfo); + const JointChainInfo* jointInfo = nullptr; + const JointChainInfo* parentJointInfo = nullptr; + lookupJointChainInfo(jointChainInfoVec, i, parentIndex, &jointInfo, &parentJointInfo); if (jointInfo && parentJointInfo) { // transform local axes into world space. diff --git a/libraries/animation/src/AnimInverseKinematics.h b/libraries/animation/src/AnimInverseKinematics.h index d473ae3698..152034b596 100644 --- a/libraries/animation/src/AnimInverseKinematics.h +++ b/libraries/animation/src/AnimInverseKinematics.h @@ -73,10 +73,12 @@ public: protected: void computeTargets(const AnimVariantMap& animVars, std::vector& targets, const AnimPoseVec& underPoses); void solve(const AnimContext& context, const std::vector& targets); - void solveTargetWithCCD(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug); - void solveTargetWithSpline(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug); + void solveTargetWithCCD(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, + bool debug, std::vector& jointChainInfoVec) const; + void solveTargetWithSpline(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, + bool debug, std::vector& jointChainInfoVec); virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) override; - void debugDrawIKChain(JointChainInfo* jointChainInfos, size_t numJointChainInfos, const AnimContext& context) const; + void debugDrawIKChain(const std::vector& jointChainInfoVec, const AnimContext& context) const; void debugDrawRelativePoses(const AnimContext& context) const; void debugDrawConstraints(const AnimContext& context) const; void debugDrawSpineSplines(const AnimContext& context, const std::vector& targets) const; diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index 062e016660..804ffb0583 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -42,6 +42,20 @@ int AnimSkeleton::getNumJoints() const { return _jointsSize; } +int AnimSkeleton::getChainDepth(int jointIndex) const { + if (jointIndex >= 0) { + int chainDepth = 0; + int index = jointIndex; + do { + chainDepth++; + index = _joints[index].parentIndex; + } while (index != -1); + return chainDepth; + } else { + return 0; + } +} + const AnimPose& AnimSkeleton::getAbsoluteBindPose(int jointIndex) const { return _absoluteBindPoses[jointIndex]; } diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index 6315f2d62b..99c9a148f7 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -28,6 +28,7 @@ public: int nameToJointIndex(const QString& jointName) const; const QString& getJointName(int jointIndex) const; int getNumJoints() const; + int getChainDepth(int jointIndex) const; // absolute pose, not relative to parent const AnimPose& getAbsoluteBindPose(int jointIndex) const; From 75e1a4a1e6f389da13861b2b2854f6e0e948efb4 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 28 Jun 2017 13:31:15 -0700 Subject: [PATCH 02/39] Refactor of JointChainInfo data structure --- .../animation/src/AnimInverseKinematics.cpp | 103 +++++++++--------- .../animation/src/AnimInverseKinematics.h | 20 ++-- 2 files changed, 62 insertions(+), 61 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 4ec10b9faf..ea87fab4c0 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -23,20 +23,21 @@ #include "CubicHermiteSpline.h" #include "AnimUtil.h" -static void lookupJointChainInfo(const std::vector& jointChainInfoVec, +static void lookupJointInfo(const AnimInverseKinematics::JointChainInfo& jointChainInfo, int indexA, int indexB, - const AnimInverseKinematics::JointChainInfo** jointChainInfoA, - const AnimInverseKinematics::JointChainInfo** jointChainInfoB) { - *jointChainInfoA = nullptr; - *jointChainInfoB = nullptr; - for (size_t i = 0; i < jointChainInfoVec.size(); i++) { - if (jointChainInfoVec[i].jointIndex == indexA) { - *jointChainInfoA = &jointChainInfoVec[i]; + const AnimInverseKinematics::JointInfo** jointInfoA, + const AnimInverseKinematics::JointInfo** jointInfoB) { + *jointInfoA = nullptr; + *jointInfoB = nullptr; + for (size_t i = 0; i < jointChainInfo.jointInfoVec.size(); i++) { + const AnimInverseKinematics::JointInfo* jointInfo = &jointChainInfo.jointInfoVec[i]; + if (jointInfo->jointIndex == indexA) { + *jointInfoA = jointInfo; } - if (jointChainInfoVec[i].jointIndex == indexB) { - *jointChainInfoB = &jointChainInfoVec[i]; + if (jointInfo->jointIndex == indexB) { + *jointInfoB = jointInfo; } - if (*jointChainInfoA && *jointChainInfoB) { + if (*jointInfoA && *jointInfoB) { break; } } @@ -234,14 +235,15 @@ void AnimInverseKinematics::solve(const AnimContext& context, const std::vector< accumulator.clearAndClean(); } - // initialize jointChainInfoVecVec, this holds the results for one iteration of each ik chain. - JointChainInfo defaultJointChainInfo = { glm::quat(), glm::vec3(), 0.0f, -1, false }; - std::vector> jointChainInfoVecVec(targets.size()); + // initialize a new jointChainInfoVec, this will holds the results for solving each ik chain. + JointInfo defaultJointInfo = { glm::quat(), glm::vec3(), -1, false }; + JointChainInfoVec jointChainInfoVec(targets.size()); for (size_t i = 0; i < targets.size(); i++) { int chainDepth = _skeleton->getChainDepth(targets[i].getIndex()); - jointChainInfoVecVec[i].reserve(chainDepth); + jointChainInfoVec[i].jointInfoVec.reserve(chainDepth); + jointChainInfoVec[i].target = targets[i]; for (size_t j = 0; j < chainDepth; j++) { - jointChainInfoVecVec[i].push_back(defaultJointChainInfo); + jointChainInfoVec[i].jointInfoVec.push_back(defaultJointInfo); } } @@ -257,9 +259,9 @@ void AnimInverseKinematics::solve(const AnimContext& context, const std::vector< // solve all targets for (size_t i = 0; i < targets.size(); i++) { if (targets[i].getType() == IKTarget::Type::Spline) { - solveTargetWithSpline(context, targets[i], absolutePoses, debug, jointChainInfoVecVec[i]); + solveTargetWithSpline(context, targets[i], absolutePoses, debug, jointChainInfoVec[i]); } else { - solveTargetWithCCD(context, targets[i], absolutePoses, debug, jointChainInfoVecVec[i]); + solveTargetWithCCD(context, targets[i], absolutePoses, debug, jointChainInfoVec[i]); } } @@ -267,12 +269,13 @@ void AnimInverseKinematics::solve(const AnimContext& context, const std::vector< // copy jointChainInfoVecs into accumulators for (size_t i = 0; i < targets.size(); i++) { - const std::vector& jointChainInfoVec = jointChainInfoVecVec[i]; - for (size_t j = 0; j < jointChainInfoVec.size(); j++) { - const JointChainInfo& info = jointChainInfoVec[j]; + const std::vector& jointInfoVec = jointChainInfoVec[i].jointInfoVec; + float weight = jointChainInfoVec[i].target.getWeight(); + for (size_t j = 0; j < jointInfoVec.size(); j++) { + const JointInfo& info = jointInfoVec[j]; if (info.jointIndex >= 0) { - _rotationAccumulators[info.jointIndex].add(info.relRot, info.weight); - _translationAccumulators[info.jointIndex].add(info.relTrans, info.weight); + _rotationAccumulators[info.jointIndex].add(info.rot, weight); + _translationAccumulators[info.jointIndex].add(info.trans, weight); } } } @@ -336,7 +339,7 @@ void AnimInverseKinematics::solve(const AnimContext& context, const std::vector< } void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, - bool debug, std::vector& jointChainInfoVec) const { + bool debug, JointChainInfo& jointChainInfoOut) const { size_t chainDepth = 0; IKTarget::Type targetType = target.getType(); @@ -391,7 +394,7 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const } glm::vec3 tipRelativeTranslation = _relativePoses[target.getIndex()].trans(); - jointChainInfoVec[chainDepth] = { tipRelativeRotation, tipRelativeTranslation, target.getWeight(), tipIndex, constrained }; + jointChainInfoOut.jointInfoVec[chainDepth] = { tipRelativeRotation, tipRelativeTranslation, tipIndex, constrained }; } // cache tip absolute position @@ -402,7 +405,7 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const // descend toward root, pivoting each joint to get tip closer to target position while (pivotIndex != _hipsIndex && pivotsParentIndex != -1) { - assert(chainDepth < jointChainInfoVec.size()); + assert(chainDepth < jointChainInfoOut.jointInfoVec.size()); // compute the two lines that should be aligned glm::vec3 jointPosition = absolutePoses[pivotIndex].trans(); @@ -503,7 +506,7 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const } glm::vec3 newTrans = _relativePoses[pivotIndex].trans(); - jointChainInfoVec[chainDepth] = { newRot, newTrans, target.getWeight(), pivotIndex, constrained }; + jointChainInfoOut.jointInfoVec[chainDepth] = { newRot, newTrans, pivotIndex, constrained }; // keep track of tip's new transform as we descend towards root tipPosition = jointPosition + deltaRotation * (tipPosition - jointPosition); @@ -530,20 +533,20 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const AnimPose accum = absolutePoses[_hipsIndex]; AnimPose baseParentPose = absolutePoses[_hipsIndex]; for (int i = (int)chainDepth - 1; i >= 0; i--) { - accum = accum * AnimPose(glm::vec3(1.0f), jointChainInfoVec[i].relRot, jointChainInfoVec[i].relTrans); + accum = accum * AnimPose(glm::vec3(1.0f), jointChainInfoOut.jointInfoVec[i].rot, jointChainInfoOut.jointInfoVec[i].trans); postAbsPoses[i] = accum; - if (jointChainInfoVec[i].jointIndex == topJointIndex) { + if (jointChainInfoOut.jointInfoVec[i].jointIndex == topJointIndex) { topChainIndex = i; topPose = accum; } - if (jointChainInfoVec[i].jointIndex == midJointIndex) { + if (jointChainInfoOut.jointInfoVec[i].jointIndex == midJointIndex) { midPose = accum; } - if (jointChainInfoVec[i].jointIndex == baseJointIndex) { + if (jointChainInfoOut.jointInfoVec[i].jointIndex == baseJointIndex) { baseChainIndex = i; basePose = accum; } - if (jointChainInfoVec[i].jointIndex == baseParentJointIndex) { + if (jointChainInfoOut.jointInfoVec[i].jointIndex == baseParentJointIndex) { baseParentPose = accum; } } @@ -623,16 +626,16 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const } glm::quat newBaseRelRot = glm::inverse(baseParentPose.rot()) * poleRot * basePose.rot(); - jointChainInfoVec[baseChainIndex].relRot = newBaseRelRot; + jointChainInfoOut.jointInfoVec[baseChainIndex].rot = newBaseRelRot; glm::quat newTopRelRot = glm::inverse(midPose.rot()) * glm::inverse(poleRot) * topPose.rot(); - jointChainInfoVec[topChainIndex].relRot = newTopRelRot; + jointChainInfoOut.jointInfoVec[topChainIndex].rot = newTopRelRot; } } } if (debug) { - debugDrawIKChain(jointChainInfoVec, context); + debugDrawIKChain(jointChainInfoOut, context); } } @@ -718,7 +721,7 @@ const std::vector* AnimInverseKinematics // AJT: TODO: make this const someday void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, - bool debug, std::vector& jointChainInfoVec) { + bool debug, JointChainInfo& jointChainInfoOut) { const int baseIndex = _hipsIndex; @@ -801,22 +804,14 @@ void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, co } } - jointChainInfoVec[i] = { relPose.rot(), relPose.trans(), target.getWeight(), splineJointInfo.jointIndex, constrained }; + jointChainInfoOut.jointInfoVec[i] = { relPose.rot(), relPose.trans(), splineJointInfo.jointIndex, constrained }; parentAbsPose = flexedAbsPose; } } - // AJT: REMOVE - /* - for (size_t i = 0; i < splineJointInfoVec->size(); i++) { - _rotationAccumulators[jointChainInfos[i].jointIndex].add(jointChainInfos[i].relRot, jointChainInfos[i].weight); - _translationAccumulators[jointChainInfos[i].jointIndex].add(jointChainInfos[i].relTrans, jointChainInfos[i].weight); - } - */ - if (debug) { - debugDrawIKChain(jointChainInfoVec, context); + debugDrawIKChain(jointChainInfoOut, context); } } @@ -1516,14 +1511,14 @@ void AnimInverseKinematics::debugDrawRelativePoses(const AnimContext& context) c } } -void AnimInverseKinematics::debugDrawIKChain(const std::vector& jointChainInfoVec, const AnimContext& context) const { +void AnimInverseKinematics::debugDrawIKChain(const JointChainInfo& jointChainInfo, const AnimContext& context) const { AnimPoseVec poses = _relativePoses; // copy debug joint rotations into the relative poses - for (size_t i = 0; i < jointChainInfoVec.size(); i++) { - const JointChainInfo& info = jointChainInfoVec[i]; - poses[info.jointIndex].rot() = info.relRot; - poses[info.jointIndex].trans() = info.relTrans; + for (size_t i = 0; i < jointChainInfo.jointInfoVec.size(); i++) { + const JointInfo& info = jointChainInfo.jointInfoVec[i]; + poses[info.jointIndex].rot() = info.rot; + poses[info.jointIndex].trans() = info.trans; } // convert relative poses to absolute @@ -1540,9 +1535,9 @@ void AnimInverseKinematics::debugDrawIKChain(const std::vector& // draw each pose for (int i = 0; i < (int)poses.size(); i++) { int parentIndex = _skeleton->getParentIndex(i); - const JointChainInfo* jointInfo = nullptr; - const JointChainInfo* parentJointInfo = nullptr; - lookupJointChainInfo(jointChainInfoVec, i, parentIndex, &jointInfo, &parentJointInfo); + const JointInfo* jointInfo = nullptr; + const JointInfo* parentJointInfo = nullptr; + lookupJointInfo(jointChainInfo, i, parentIndex, &jointInfo, &parentJointInfo); if (jointInfo && parentJointInfo) { // transform local axes into world space. diff --git a/libraries/animation/src/AnimInverseKinematics.h b/libraries/animation/src/AnimInverseKinematics.h index 152034b596..38288aa288 100644 --- a/libraries/animation/src/AnimInverseKinematics.h +++ b/libraries/animation/src/AnimInverseKinematics.h @@ -26,14 +26,20 @@ class RotationConstraint; class AnimInverseKinematics : public AnimNode { public: - struct JointChainInfo { - glm::quat relRot; - glm::vec3 relTrans; - float weight; + struct JointInfo { + glm::quat rot; + glm::vec3 trans; int jointIndex; bool constrained; }; + struct JointChainInfo { + std::vector jointInfoVec; + IKTarget target; + }; + + using JointChainInfoVec = std::vector; + explicit AnimInverseKinematics(const QString& id); virtual ~AnimInverseKinematics() override; @@ -74,11 +80,11 @@ protected: void computeTargets(const AnimVariantMap& animVars, std::vector& targets, const AnimPoseVec& underPoses); void solve(const AnimContext& context, const std::vector& targets); void solveTargetWithCCD(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, - bool debug, std::vector& jointChainInfoVec) const; + bool debug, JointChainInfo& jointChainInfoOut) const; void solveTargetWithSpline(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, - bool debug, std::vector& jointChainInfoVec); + bool debug, JointChainInfo& jointChainInfoOut); virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) override; - void debugDrawIKChain(const std::vector& jointChainInfoVec, const AnimContext& context) const; + void debugDrawIKChain(const JointChainInfo& jointChainInfo, const AnimContext& context) const; void debugDrawRelativePoses(const AnimContext& context) const; void debugDrawConstraints(const AnimContext& context) const; void debugDrawSpineSplines(const AnimContext& context, const std::vector& targets) const; From 237872e4779ad9a6c0e3007e1b3e9dae3ca04c25 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 30 Jun 2017 12:47:01 -0700 Subject: [PATCH 03/39] sizes and order of IKTargetVarVec and IKTargetVec are now the same. Also, A change in how the bone name to bone index lookup occurs exposed a bug in Rig::computeAvatarBoundingCapsule(), basically it was not actually preforming IK, and the ik targets were in the wrong coordinate frame. So when IK was actually performed it would give bad results. This bug is now fixed. --- .../animation/src/AnimInverseKinematics.cpp | 56 +++++++------------ .../animation/src/AnimInverseKinematics.h | 4 -- libraries/animation/src/IKTarget.h | 2 +- libraries/animation/src/Rig.cpp | 18 +++--- 4 files changed, 32 insertions(+), 48 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index ea87fab4c0..5ea628d1f4 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -150,24 +150,26 @@ void AnimInverseKinematics::setTargetVars(const QString& jointName, const QStrin } void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std::vector& targets, const AnimPoseVec& underPoses) { - // build a list of valid targets from _targetVarVec and animVars - _maxTargetIndex = -1; + _hipsTargetIndex = -1; - bool removeUnfoundJoints = false; + + targets.reserve(_targetVarVec.size()); for (auto& targetVar : _targetVarVec) { + + // update targetVar jointIndex cache if (targetVar.jointIndex == -1) { - // this targetVar hasn't been validated yet... int jointIndex = _skeleton->nameToJointIndex(targetVar.jointName); if (jointIndex >= 0) { // this targetVar has a valid joint --> cache the indices targetVar.jointIndex = jointIndex; } else { qCWarning(animation) << "AnimInverseKinematics could not find jointName" << targetVar.jointName << "in skeleton"; - removeUnfoundJoints = true; } - } else { - IKTarget target; + } + + IKTarget target; + if (targetVar.jointIndex != -1) { target.setType(animVars.lookup(targetVar.typeVar, (int)IKTarget::Type::RotationAndPosition)); if (target.getType() != IKTarget::Type::Unknown) { AnimPose absPose = _skeleton->getAbsolutePose(targetVar.jointIndex, underPoses); @@ -189,35 +191,16 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std:: glm::vec3 poleReferenceVector = animVars.lookupRigToGeometryVector(targetVar.poleReferenceVectorVar, Vectors::UNIT_Z); target.setPoleReferenceVector(glm::normalize(poleReferenceVector)); - targets.push_back(target); - - if (targetVar.jointIndex > _maxTargetIndex) { - _maxTargetIndex = targetVar.jointIndex; - } - // record the index of the hips ik target. if (target.getIndex() == _hipsIndex) { - _hipsTargetIndex = (int)targets.size() - 1; + _hipsTargetIndex = (int)targets.size(); } } + } else { + target.setType((int)IKTarget::Type::Unknown); } - } - if (removeUnfoundJoints) { - int numVars = (int)_targetVarVec.size(); - int i = 0; - while (i < numVars) { - if (_targetVarVec[i].jointIndex == -1) { - if (numVars > 1) { - // swap i for last element - _targetVarVec[i] = _targetVarVec[numVars - 1]; - } - _targetVarVec.pop_back(); - --numVars; - } else { - ++i; - } - } + targets.push_back(target); } } @@ -258,10 +241,15 @@ void AnimInverseKinematics::solve(const AnimContext& context, const std::vector< // solve all targets for (size_t i = 0; i < targets.size(); i++) { - if (targets[i].getType() == IKTarget::Type::Spline) { + switch (targets[i].getType()) { + case IKTarget::Type::Unknown: + break; + case IKTarget::Type::Spline: solveTargetWithSpline(context, targets[i], absolutePoses, debug, jointChainInfoVec[i]); - } else { + break; + default: solveTargetWithCCD(context, targets[i], absolutePoses, debug, jointChainInfoVec[i]); + break; } } @@ -317,7 +305,7 @@ void AnimInverseKinematics::solve(const AnimContext& context, const std::vector< // finally set the relative rotation of each tip to agree with absolute target rotation for (auto& target: targets) { int tipIndex = target.getIndex(); - int parentIndex = _skeleton->getParentIndex(tipIndex); + int parentIndex = (tipIndex >= 0) ? _skeleton->getParentIndex(tipIndex) : -1; // update rotationOnly targets that don't lie on the ik chain of other ik targets. if (parentIndex != -1 && !_rotationAccumulators[tipIndex].isDirty() && target.getType() == IKTarget::Type::RotationOnly) { @@ -1430,8 +1418,6 @@ void AnimInverseKinematics::setSkeletonInternal(AnimSkeleton::ConstPointer skele targetVar.jointIndex = -1; } - _maxTargetIndex = -1; - for (auto& accumulator: _rotationAccumulators) { accumulator.clearAndClean(); } diff --git a/libraries/animation/src/AnimInverseKinematics.h b/libraries/animation/src/AnimInverseKinematics.h index 38288aa288..fb462cbf50 100644 --- a/libraries/animation/src/AnimInverseKinematics.h +++ b/libraries/animation/src/AnimInverseKinematics.h @@ -156,10 +156,6 @@ protected: int _leftHandIndex { -1 }; int _rightHandIndex { -1 }; - // _maxTargetIndex is tracked to help optimize the recalculation of absolute poses - // during the the cyclic coordinate descent algorithm - int _maxTargetIndex { 0 }; - float _maxErrorOnLastSolve { FLT_MAX }; bool _previousEnableDebugIKTargets { false }; SolutionSource _solutionSource { SolutionSource::RelaxToUnderPoses }; diff --git a/libraries/animation/src/IKTarget.h b/libraries/animation/src/IKTarget.h index 5567539659..a86ae0ca8b 100644 --- a/libraries/animation/src/IKTarget.h +++ b/libraries/animation/src/IKTarget.h @@ -57,7 +57,7 @@ private: bool _poleVectorEnabled { false }; int _index { -1 }; Type _type { Type::RotationAndPosition }; - float _weight; + float _weight { 0.0f }; float _flexCoefficients[MAX_FLEX_COEFFICIENTS]; size_t _numFlexCoefficients; }; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 3d04b0b26f..7b11465062 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1707,36 +1707,38 @@ void Rig::computeAvatarBoundingCapsule( AnimPose geometryToRig = _modelOffset * _geometryOffset; - AnimPose hips(glm::vec3(1), glm::quat(), glm::vec3()); + glm::vec3 hipsPosition(0.0f); int hipsIndex = indexOfJoint("Hips"); if (hipsIndex >= 0) { - hips = geometryToRig * _animSkeleton->getAbsoluteBindPose(hipsIndex); + hipsPosition = transformPoint(_geometryToRigTransform, _animSkeleton->getAbsoluteDefaultPose(hipsIndex).trans()); } AnimVariantMap animVars; + animVars.setRigToGeometryTransform(_rigToGeometryTransform); glm::quat handRotation = glm::angleAxis(PI, Vectors::UNIT_X); - animVars.set("leftHandPosition", hips.trans()); + animVars.set("leftHandPosition", hipsPosition); animVars.set("leftHandRotation", handRotation); animVars.set("leftHandType", (int)IKTarget::Type::RotationAndPosition); - animVars.set("rightHandPosition", hips.trans()); + animVars.set("rightHandPosition", hipsPosition); animVars.set("rightHandRotation", handRotation); animVars.set("rightHandType", (int)IKTarget::Type::RotationAndPosition); int rightFootIndex = indexOfJoint("RightFoot"); int leftFootIndex = indexOfJoint("LeftFoot"); if (rightFootIndex != -1 && leftFootIndex != -1) { - glm::vec3 foot = Vectors::ZERO; + glm::vec3 geomFootPosition = glm::vec3(0.0f, _animSkeleton->getAbsoluteDefaultPose(rightFootIndex).trans().y, 0.0f); + glm::vec3 footPosition = transformPoint(_geometryToRigTransform, geomFootPosition); glm::quat footRotation = glm::angleAxis(0.5f * PI, Vectors::UNIT_X); - animVars.set("leftFootPosition", foot); + animVars.set("leftFootPosition", footPosition); animVars.set("leftFootRotation", footRotation); animVars.set("leftFootType", (int)IKTarget::Type::RotationAndPosition); - animVars.set("rightFootPosition", foot); + animVars.set("rightFootPosition", footPosition); animVars.set("rightFootRotation", footRotation); animVars.set("rightFootType", (int)IKTarget::Type::RotationAndPosition); } // call overlay twice: once to verify AnimPoseVec joints and again to do the IK AnimNode::Triggers triggersOut; - AnimContext context(false, false, false, glm::mat4(), glm::mat4()); + AnimContext context(false, false, false, _geometryToRigTransform, _rigToGeometryTransform); float dt = 1.0f; // the value of this does not matter ikNode.overlay(animVars, context, dt, triggersOut, _animSkeleton->getRelativeBindPoses()); AnimPoseVec finalPoses = ikNode.overlay(animVars, context, dt, triggersOut, _animSkeleton->getRelativeBindPoses()); From aba164b26e2e08ad6ea1a9e48b55d509c183f27a Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 30 Jun 2017 13:27:53 -0700 Subject: [PATCH 04/39] more clean up of Rig::computeAvatarBoundingCapsule --- libraries/animation/src/Rig.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 7b11465062..0dfeb43f3d 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1704,9 +1704,6 @@ void Rig::computeAvatarBoundingCapsule( ikNode.setTargetVars("RightFoot", "rightFootPosition", "rightFootRotation", "rightFootType", "rightFootWeight", 1.0f, {}, QString(), QString(), QString()); - - AnimPose geometryToRig = _modelOffset * _geometryOffset; - glm::vec3 hipsPosition(0.0f); int hipsIndex = indexOfJoint("Hips"); if (hipsIndex >= 0) { @@ -1771,14 +1768,15 @@ void Rig::computeAvatarBoundingCapsule( // compute bounding shape parameters // NOTE: we assume that the longest side of totalExtents is the yAxis... - glm::vec3 diagonal = (geometryToRig * totalExtents.maximum) - (geometryToRig * totalExtents.minimum); + glm::vec3 diagonal = (transformPoint(_geometryToRigTransform, totalExtents.maximum) - + transformPoint(_geometryToRigTransform, totalExtents.minimum)); // ... and assume the radiusOut is half the RMS of the X and Z sides: radiusOut = 0.5f * sqrtf(0.5f * (diagonal.x * diagonal.x + diagonal.z * diagonal.z)); heightOut = diagonal.y - 2.0f * radiusOut; glm::vec3 rootPosition = finalPoses[geometry.rootJointIndex].trans(); - glm::vec3 rigCenter = (geometryToRig * (0.5f * (totalExtents.maximum + totalExtents.minimum))); - localOffsetOut = rigCenter - (geometryToRig * rootPosition); + glm::vec3 rigCenter = transformPoint(_geometryToRigTransform, (0.5f * (totalExtents.maximum + totalExtents.minimum))); + localOffsetOut = rigCenter - transformPoint(_geometryToRigTransform, rootPosition); } bool Rig::transitionHandPose(float deltaTime, float totalDuration, AnimPose& controlledHandPose, bool isLeftHand, From 2f6a37ee53fc5e1546a1592259d94d7d71d4ce21 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 3 Jul 2017 16:31:05 -0700 Subject: [PATCH 05/39] Removed interpolation of hand controllers --- libraries/animation/src/Rig.cpp | 79 +++------------------------------ libraries/animation/src/Rig.h | 9 ---- 2 files changed, 6 insertions(+), 82 deletions(-) diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 0dfeb43f3d..c505353174 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1093,25 +1093,10 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab const float ELBOW_POLE_VECTOR_BLEND_FACTOR = 0.95f; if (leftHandEnabled) { - if (!_isLeftHandControlled) { - _leftHandControlTimeRemaining = CONTROL_DURATION; - _isLeftHandControlled = true; - } glm::vec3 handPosition = leftHandPose.trans(); glm::quat handRotation = leftHandPose.rot(); - if (_leftHandControlTimeRemaining > 0.0f) { - // Move hand from non-controlled position to controlled position. - _leftHandControlTimeRemaining = std::max(_leftHandControlTimeRemaining - dt, 0.0f); - AnimPose handPose(Vectors::ONE, handRotation, handPosition); - if (transitionHandPose(_leftHandControlTimeRemaining, CONTROL_DURATION, handPose, - LEFT_HAND, TO_CONTROLLED, handPose)) { - handPosition = handPose.trans(); - handRotation = handPose.rot(); - } - } - if (!hipsEnabled) { // prevent the hand IK targets from intersecting the body capsule glm::vec3 displacement; @@ -1124,9 +1109,6 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab _animVars.set("leftHandRotation", handRotation); _animVars.set("leftHandType", (int)IKTarget::Type::RotationAndPosition); - _lastLeftHandControlledPose = AnimPose(Vectors::ONE, handRotation, handPosition); - _isLeftHandControlled = true; - // compute pole vector int handJointIndex = _animSkeleton->nameToJointIndex("LeftHand"); int armJointIndex = _animSkeleton->nameToJointIndex("LeftArm"); @@ -1154,47 +1136,17 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab _prevLeftHandPoleVectorValid = false; _animVars.set("leftHandPoleVectorEnabled", false); - if (_isLeftHandControlled) { - _leftHandRelaxTimeRemaining = RELAX_DURATION; - _isLeftHandControlled = false; - } + _animVars.unset("leftHandPosition"); + _animVars.unset("leftHandRotation"); + _animVars.set("leftHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition); - if (_leftHandRelaxTimeRemaining > 0.0f) { - // Move hand from controlled position to non-controlled position. - _leftHandRelaxTimeRemaining = std::max(_leftHandRelaxTimeRemaining - dt, 0.0f); - AnimPose handPose; - if (transitionHandPose(_leftHandRelaxTimeRemaining, RELAX_DURATION, _lastLeftHandControlledPose, - LEFT_HAND, FROM_CONTROLLED, handPose)) { - _animVars.set("leftHandPosition", handPose.trans()); - _animVars.set("leftHandRotation", handPose.rot()); - _animVars.set("leftHandType", (int)IKTarget::Type::RotationAndPosition); - } - } else { - _animVars.unset("leftHandPosition"); - _animVars.unset("leftHandRotation"); - _animVars.set("leftHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition); - } } if (rightHandEnabled) { - if (!_isRightHandControlled) { - _rightHandControlTimeRemaining = CONTROL_DURATION; - _isRightHandControlled = true; - } glm::vec3 handPosition = rightHandPose.trans(); glm::quat handRotation = rightHandPose.rot(); - if (_rightHandControlTimeRemaining > 0.0f) { - // Move hand from non-controlled position to controlled position. - _rightHandControlTimeRemaining = std::max(_rightHandControlTimeRemaining - dt, 0.0f); - AnimPose handPose(Vectors::ONE, handRotation, handPosition); - if (transitionHandPose(_rightHandControlTimeRemaining, CONTROL_DURATION, handPose, RIGHT_HAND, TO_CONTROLLED, handPose)) { - handPosition = handPose.trans(); - handRotation = handPose.rot(); - } - } - if (!hipsEnabled) { // prevent the hand IK targets from intersecting the body capsule glm::vec3 displacement; @@ -1207,9 +1159,6 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab _animVars.set("rightHandRotation", handRotation); _animVars.set("rightHandType", (int)IKTarget::Type::RotationAndPosition); - _lastRightHandControlledPose = AnimPose(Vectors::ONE, handRotation, handPosition); - _isRightHandControlled = true; - // compute pole vector int handJointIndex = _animSkeleton->nameToJointIndex("RightHand"); int armJointIndex = _animSkeleton->nameToJointIndex("RightArm"); @@ -1237,25 +1186,9 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab _prevRightHandPoleVectorValid = false; _animVars.set("rightHandPoleVectorEnabled", false); - if (_isRightHandControlled) { - _rightHandRelaxTimeRemaining = RELAX_DURATION; - _isRightHandControlled = false; - } - - if (_rightHandRelaxTimeRemaining > 0.0f) { - // Move hand from controlled position to non-controlled position. - _rightHandRelaxTimeRemaining = std::max(_rightHandRelaxTimeRemaining - dt, 0.0f); - AnimPose handPose; - if (transitionHandPose(_rightHandRelaxTimeRemaining, RELAX_DURATION, _lastRightHandControlledPose, RIGHT_HAND, FROM_CONTROLLED, handPose)) { - _animVars.set("rightHandPosition", handPose.trans()); - _animVars.set("rightHandRotation", handPose.rot()); - _animVars.set("rightHandType", (int)IKTarget::Type::RotationAndPosition); - } - } else { - _animVars.unset("rightHandPosition"); - _animVars.unset("rightHandRotation"); - _animVars.set("rightHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition); - } + _animVars.unset("rightHandPosition"); + _animVars.unset("rightHandRotation"); + _animVars.set("rightHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition); } } diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index c17a7b9c8f..ec13d98613 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -343,15 +343,6 @@ protected: bool transitionHandPose(float deltaTime, float totalDuration, AnimPose& controlledHandPose, bool isLeftHand, bool isToControlled, AnimPose& returnHandPose); - bool _isLeftHandControlled { false }; - bool _isRightHandControlled { false }; - float _leftHandControlTimeRemaining { 0.0f }; - float _rightHandControlTimeRemaining { 0.0f }; - float _leftHandRelaxTimeRemaining { 0.0f }; - float _rightHandRelaxTimeRemaining { 0.0f }; - AnimPose _lastLeftHandControlledPose; - AnimPose _lastRightHandControlledPose; - glm::vec3 _prevRightFootPoleVector { Vectors::UNIT_Z }; bool _prevRightFootPoleVectorValid { false }; From 7ed1382ac98b4ff0dc0479713126e3832c9b554a Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 3 Jul 2017 16:32:46 -0700 Subject: [PATCH 06/39] ik level interpolation of incoming targets --- .../animation/src/AnimInverseKinematics.cpp | 140 +++++++++++++----- .../animation/src/AnimInverseKinematics.h | 13 +- 2 files changed, 114 insertions(+), 39 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 5ea628d1f4..dd13279786 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -23,6 +23,8 @@ #include "CubicHermiteSpline.h" #include "AnimUtil.h" +static const float JOINT_CHAIN_INTERP_TIME = 0.25f; + static void lookupJointInfo(const AnimInverseKinematics::JointChainInfo& jointChainInfo, int indexA, int indexB, const AnimInverseKinematics::JointInfo** jointInfoA, @@ -171,6 +173,7 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std:: IKTarget target; if (targetVar.jointIndex != -1) { target.setType(animVars.lookup(targetVar.typeVar, (int)IKTarget::Type::RotationAndPosition)); + target.setIndex(targetVar.jointIndex); if (target.getType() != IKTarget::Type::Unknown) { AnimPose absPose = _skeleton->getAbsolutePose(targetVar.jointIndex, underPoses); glm::quat rotation = animVars.lookupRigToGeometry(targetVar.rotationVar, absPose.rot()); @@ -178,7 +181,6 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std:: float weight = animVars.lookup(targetVar.weightVar, targetVar.weight); target.setPose(rotation, translation); - target.setIndex(targetVar.jointIndex); target.setWeight(weight); target.setFlexCoefficients(targetVar.numFlexCoefficients, targetVar.flexCoefficients); @@ -204,7 +206,7 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std:: } } -void AnimInverseKinematics::solve(const AnimContext& context, const std::vector& targets) { +void AnimInverseKinematics::solve(const AnimContext& context, const std::vector& targets, float dt, JointChainInfoVec& jointChainInfoVec) { // compute absolute poses that correspond to relative target poses AnimPoseVec absolutePoses; absolutePoses.resize(_relativePoses.size()); @@ -218,23 +220,10 @@ void AnimInverseKinematics::solve(const AnimContext& context, const std::vector< accumulator.clearAndClean(); } - // initialize a new jointChainInfoVec, this will holds the results for solving each ik chain. - JointInfo defaultJointInfo = { glm::quat(), glm::vec3(), -1, false }; - JointChainInfoVec jointChainInfoVec(targets.size()); - for (size_t i = 0; i < targets.size(); i++) { - int chainDepth = _skeleton->getChainDepth(targets[i].getIndex()); - jointChainInfoVec[i].jointInfoVec.reserve(chainDepth); - jointChainInfoVec[i].target = targets[i]; - for (size_t j = 0; j < chainDepth; j++) { - jointChainInfoVec[i].jointInfoVec.push_back(defaultJointInfo); - } - } - - float maxError = FLT_MAX; + float maxError = 0.0f; int numLoops = 0; const int MAX_IK_LOOPS = 16; - const float MAX_ERROR_TOLERANCE = 0.1f; // cm - while (maxError > MAX_ERROR_TOLERANCE && numLoops < MAX_IK_LOOPS) { + while (numLoops < MAX_IK_LOOPS) { ++numLoops; bool debug = context.getEnableDebugDrawIKChains() && numLoops == MAX_IK_LOOPS; @@ -253,23 +242,45 @@ void AnimInverseKinematics::solve(const AnimContext& context, const std::vector< } } - // TODO: do smooth interpolation of joint chains here, if necessary. + // on last iteration, interpolate jointChains, if necessary + if (numLoops == MAX_IK_LOOPS) { + for (size_t i = 0; i < _prevJointChainInfoVec.size(); i++) { + if (_prevJointChainInfoVec[i].timer > 0.0f) { + float alpha = (JOINT_CHAIN_INTERP_TIME - _prevJointChainInfoVec[i].timer) / JOINT_CHAIN_INTERP_TIME; + size_t chainSize = std::min(_prevJointChainInfoVec[i].jointInfoVec.size(), jointChainInfoVec[i].jointInfoVec.size()); + for (size_t j = 0; j < chainSize; j++) { + jointChainInfoVec[i].jointInfoVec[j].rot = safeMix(_prevJointChainInfoVec[i].jointInfoVec[j].rot, jointChainInfoVec[i].jointInfoVec[j].rot, alpha); + jointChainInfoVec[i].jointInfoVec[j].trans = lerp(_prevJointChainInfoVec[i].jointInfoVec[j].trans, jointChainInfoVec[i].jointInfoVec[j].trans, alpha); + } + } + } + } // copy jointChainInfoVecs into accumulators for (size_t i = 0; i < targets.size(); i++) { const std::vector& jointInfoVec = jointChainInfoVec[i].jointInfoVec; - float weight = jointChainInfoVec[i].target.getWeight(); - for (size_t j = 0; j < jointInfoVec.size(); j++) { - const JointInfo& info = jointInfoVec[j]; - if (info.jointIndex >= 0) { - _rotationAccumulators[info.jointIndex].add(info.rot, weight); - _translationAccumulators[info.jointIndex].add(info.trans, weight); + + // don't accumulate disabled or rotation only ik targets. + IKTarget::Type type = jointChainInfoVec[i].target.getType(); + if (type != IKTarget::Type::Unknown && type != IKTarget::Type::RotationOnly) { + float weight = jointChainInfoVec[i].target.getWeight(); + if (weight > 0.0f) { + for (size_t j = 0; j < jointInfoVec.size(); j++) { + const JointInfo& info = jointInfoVec[j]; + if (info.jointIndex >= 0) { + _rotationAccumulators[info.jointIndex].add(info.rot, weight); + _translationAccumulators[info.jointIndex].add(info.trans, weight); + } + } } } } // 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 + } if (_rotationAccumulators[i].size() > 0) { _relativePoses[i].rot() = _rotationAccumulators[i].getAverage(); _rotationAccumulators[i].clear(); @@ -324,6 +335,28 @@ void AnimInverseKinematics::solve(const AnimContext& context, const std::vector< absolutePoses[tipIndex].rot() = targetRotation; } } + + for (size_t i = 0; i < jointChainInfoVec.size(); i++) { + _prevJointChainInfoVec[i].timer = _prevJointChainInfoVec[i].timer - dt; + if (_prevJointChainInfoVec[i].timer <= 0.0f) { + _prevJointChainInfoVec[i] = jointChainInfoVec[i]; + // store relative poses into unknown/rotation only joint chains. + // so we have something to interpolate from if this chain is activated. + IKTarget::Type type = _prevJointChainInfoVec[i].target.getType(); + if (type == IKTarget::Type::Unknown || type == IKTarget::Type::RotationOnly) { + for (size_t j = 0; j < _prevJointChainInfoVec[i].jointInfoVec.size(); j++) { + auto& info = _prevJointChainInfoVec[i].jointInfoVec[j]; + if (info.jointIndex >= 0) { + info.rot = _relativePoses[info.jointIndex].rot(); + info.trans = _relativePoses[info.jointIndex].trans(); + } else { + info.rot = Quaternions::IDENTITY; + info.trans = glm::vec3(0.0f); + } + } + } + } + } } void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, @@ -638,7 +671,7 @@ static CubicHermiteSplineFunctorWithArcLength computeSplineFromTipAndBase(const } // pre-compute information about each joint influeced by this spline IK target. -void AnimInverseKinematics::computeSplineJointInfosForIKTarget(const AnimContext& context, const IKTarget& target) { +void AnimInverseKinematics::computeAndCacheSplineJointInfosForIKTarget(const AnimContext& context, const IKTarget& target) const { std::vector splineJointInfoVec; // build spline between the default poses. @@ -691,13 +724,13 @@ void AnimInverseKinematics::computeSplineJointInfosForIKTarget(const AnimContext _splineJointInfoMap[target.getIndex()] = splineJointInfoVec; } -const std::vector* AnimInverseKinematics::findOrCreateSplineJointInfo(const AnimContext& context, const IKTarget& target) { +const std::vector* AnimInverseKinematics::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 { - computeSplineJointInfosForIKTarget(context, target); + computeAndCacheSplineJointInfosForIKTarget(context, target); auto iter = _splineJointInfoMap.find(target.getIndex()); if (iter != _splineJointInfoMap.end()) { return &(iter->second); @@ -707,9 +740,8 @@ const std::vector* AnimInverseKinematics return nullptr; } -// AJT: TODO: make this const someday void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, - bool debug, JointChainInfo& jointChainInfoOut) { + bool debug, JointChainInfo& jointChainInfoOut) const { const int baseIndex = _hipsIndex; @@ -854,18 +886,57 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars _relativePoses = underPoses; } else { + JointChainInfoVec jointChainInfoVec(targets.size()); + { + PROFILE_RANGE_EX(simulation_animation, "ik/jointChainInfo", 0xffff00ff, 0); + + // initialize a new jointChainInfoVec, this will holds the results for solving each ik chain. + JointInfo defaultJointInfo = { glm::quat(), glm::vec3(), -1, false }; + for (size_t i = 0; i < targets.size(); i++) { + int chainDepth = _skeleton->getChainDepth(targets[i].getIndex()); + jointChainInfoVec[i].jointInfoVec.reserve(chainDepth); + jointChainInfoVec[i].target = targets[i]; + int index = targets[i].getIndex(); + for (size_t j = 0; j < chainDepth; j++) { + jointChainInfoVec[i].jointInfoVec.push_back(defaultJointInfo); + jointChainInfoVec[i].jointInfoVec[j].jointIndex = index; + index = _skeleton->getParentIndex(index); + } + } + + _prevJointChainInfoVec.resize(jointChainInfoVec.size()); + for (size_t i = 0; i < _prevJointChainInfoVec.size(); i++) { + if (_prevJointChainInfoVec[i].timer <= 0.0f && + (jointChainInfoVec[i].target.getType() != _prevJointChainInfoVec[i].target.getType() || + jointChainInfoVec[i].target.getPoleVectorEnabled() != _prevJointChainInfoVec[i].target.getPoleVectorEnabled())) { + _prevJointChainInfoVec[i].timer = JOINT_CHAIN_INTERP_TIME; + } + } + } + { PROFILE_RANGE_EX(simulation_animation, "ik/shiftHips", 0xffff00ff, 0); if (_hipsTargetIndex >= 0 && _hipsTargetIndex < (int)targets.size()) { // slam the hips to match the _hipsTarget + AnimPose absPose = targets[_hipsTargetIndex].getPose(); + int parentIndex = _skeleton->getParentIndex(targets[_hipsTargetIndex].getIndex()); - if (parentIndex != -1) { - _relativePoses[_hipsIndex] = _skeleton->getAbsolutePose(parentIndex, _relativePoses).inverse() * absPose; - } else { - _relativePoses[_hipsIndex] = absPose; + AnimPose parentAbsPose = _skeleton->getAbsolutePose(parentIndex, _relativePoses); + + // do smooth interpolation of hips here, if necessary. + if (_prevJointChainInfoVec[_hipsTargetIndex].timer > 0.0f) { + float alpha = (JOINT_CHAIN_INTERP_TIME - _prevJointChainInfoVec[_hipsTargetIndex].timer) / JOINT_CHAIN_INTERP_TIME; + + auto& info = _prevJointChainInfoVec[_hipsTargetIndex].jointInfoVec[0]; + AnimPose prevHipsRelPose(info.rot, info.trans); + AnimPose prevHipsAbsPose = parentAbsPose * prevHipsRelPose; + ::blend(1, &prevHipsAbsPose, &absPose, alpha, &absPose); } + + _relativePoses[_hipsIndex] = parentAbsPose.inverse() * absPose; + } else { // if there is no hips target, shift hips according to the _hipsOffset from the previous frame float offsetLength = glm::length(_hipsOffset); @@ -924,8 +995,9 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars { PROFILE_RANGE_EX(simulation_animation, "ik/ccd", 0xffff00ff, 0); + preconditionRelativePosesToAvoidLimbLock(context, targets); - solve(context, targets); + solve(context, targets, dt, jointChainInfoVec); } if (_hipsTargetIndex < 0) { diff --git a/libraries/animation/src/AnimInverseKinematics.h b/libraries/animation/src/AnimInverseKinematics.h index fb462cbf50..d5fc5a6a8c 100644 --- a/libraries/animation/src/AnimInverseKinematics.h +++ b/libraries/animation/src/AnimInverseKinematics.h @@ -36,6 +36,7 @@ public: struct JointChainInfo { std::vector jointInfoVec; IKTarget target; + float timer { 0.0f }; }; using JointChainInfoVec = std::vector; @@ -78,11 +79,11 @@ public: protected: void computeTargets(const AnimVariantMap& animVars, std::vector& targets, const AnimPoseVec& underPoses); - void solve(const AnimContext& context, const std::vector& targets); + void solve(const AnimContext& context, const std::vector& targets, float dt, JointChainInfoVec& jointChainInfoVec); void solveTargetWithCCD(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug, JointChainInfo& jointChainInfoOut) const; void solveTargetWithSpline(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, - bool debug, JointChainInfo& jointChainInfoOut); + bool debug, JointChainInfo& jointChainInfoOut) const; virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) override; void debugDrawIKChain(const JointChainInfo& jointChainInfo, const AnimContext& context) const; void debugDrawRelativePoses(const AnimContext& context) const; @@ -99,8 +100,8 @@ protected: AnimPose offsetPose; // local offset from the spline to the joint. }; - void computeSplineJointInfosForIKTarget(const AnimContext& context, const IKTarget& target); - const std::vector* findOrCreateSplineJointInfo(const AnimContext& context, const IKTarget& target); + void computeAndCacheSplineJointInfosForIKTarget(const AnimContext& context, const IKTarget& target) const; + const std::vector* findOrCreateSplineJointInfo(const AnimContext& context, const IKTarget& target) const; // for AnimDebugDraw rendering virtual const AnimPoseVec& getPosesInternal() const override { return _relativePoses; } @@ -144,7 +145,7 @@ protected: AnimPoseVec _relativePoses; // current relative poses AnimPoseVec _limitCenterPoses; // relative - std::map> _splineJointInfoMap; + mutable std::map> _splineJointInfoMap; // experimental data for moving hips during IK glm::vec3 _hipsOffset { Vectors::ZERO }; @@ -164,6 +165,8 @@ protected: AnimPose _uncontrolledLeftHandPose { AnimPose() }; AnimPose _uncontrolledRightHandPose { AnimPose() }; AnimPose _uncontrolledHipsPose { AnimPose() }; + + JointChainInfoVec _prevJointChainInfoVec; }; #endif // hifi_AnimInverseKinematics_h From 1a24d4d8ec060f87ac29e8a3bf6d119bdb513ccd Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 5 Jul 2017 09:31:02 -0700 Subject: [PATCH 07/39] added safeLerp, shortest angle quat lerp with post normalize --- .../animation/src/AnimInverseKinematics.cpp | 17 +++++++---------- libraries/animation/src/AnimUtil.cpp | 2 +- libraries/animation/src/AnimUtil.h | 10 ++++++++++ 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index dd13279786..f0cd0dea98 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -491,9 +491,8 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const glm::quat twistPart; glm::vec3 axis = glm::normalize(deltaRotation * leverArm); swingTwistDecomposition(missingRotation, axis, swingPart, twistPart); - float dotSign = copysignf(1.0f, twistPart.w); const float LIMIT_LEAK_FRACTION = 0.1f; - deltaRotation = glm::normalize(glm::lerp(glm::quat(), dotSign * twistPart, LIMIT_LEAK_FRACTION)) * deltaRotation; + deltaRotation = safeLerp(glm::quat(), twistPart, LIMIT_LEAK_FRACTION); } } } @@ -502,9 +501,8 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const // An HmdHead target slaves the orientation of the end-effector by distributing rotation // deltas up the hierarchy. Its target position is enforced later (by shifting the hips). deltaRotation = target.getRotation() * glm::inverse(tipOrientation); - float dotSign = copysignf(1.0f, deltaRotation.w); const float ANGLE_DISTRIBUTION_FACTOR = 0.45f; - deltaRotation = glm::normalize(glm::lerp(glm::quat(), dotSign * deltaRotation, ANGLE_DISTRIBUTION_FACTOR)); + deltaRotation = safeLerp(glm::quat(), deltaRotation, ANGLE_DISTRIBUTION_FACTOR); } // compute joint's new parent-relative rotation after swing @@ -761,7 +759,7 @@ void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, co // 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 = glm::normalize(glm::lerp(basePose.rot(), tipPose.rot(), 0.5f)); + 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(); } @@ -784,7 +782,7 @@ void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, co if (target.getIndex() == _headIndex) { rotT = t * t; } - glm::quat twistRot = glm::normalize(glm::lerp(basePose.rot(), tipPose.rot(), rotT)); + 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)); @@ -1682,7 +1680,7 @@ void AnimInverseKinematics::debugDrawConstraints(const AnimContext& context) con const int NUM_SWING_STEPS = 10; for (int i = 0; i < NUM_SWING_STEPS + 1; i++) { - glm::quat rot = glm::normalize(glm::lerp(minRot, maxRot, i * (1.0f / NUM_SWING_STEPS))); + glm::quat rot = safeLerp(minRot, maxRot, i * (1.0f / NUM_SWING_STEPS)); glm::vec3 axis = transformVectorFast(geomToWorldMatrix, parentAbsRot * rot * refRot * Vectors::UNIT_Y); DebugDraw::getInstance().drawRay(pos, pos + TWIST_LENGTH * axis, CYAN); } @@ -1700,7 +1698,7 @@ void AnimInverseKinematics::debugDrawConstraints(const AnimContext& context) con const int NUM_SWING_STEPS = 10; for (int i = 0; i < NUM_SWING_STEPS + 1; i++) { - glm::quat rot = glm::normalize(glm::lerp(minRot, maxRot, i * (1.0f / NUM_SWING_STEPS))); + glm::quat rot = safeLerp(minRot, maxRot, i * (1.0f / NUM_SWING_STEPS)); glm::vec3 axis = transformVectorFast(geomToWorldMatrix, parentAbsRot * rot * refRot * Vectors::UNIT_X); DebugDraw::getInstance().drawRay(pos, pos + TWIST_LENGTH * axis, CYAN); } @@ -1740,10 +1738,9 @@ void AnimInverseKinematics::blendToPoses(const AnimPoseVec& targetPoses, const A // relax toward poses int numJoints = (int)_relativePoses.size(); for (int i = 0; i < numJoints; ++i) { - float dotSign = copysignf(1.0f, glm::dot(_relativePoses[i].rot(), targetPoses[i].rot())); if (_rotationAccumulators[i].isDirty()) { // this joint is affected by IK --> blend toward the targetPoses rotation - _relativePoses[i].rot() = glm::normalize(glm::lerp(_relativePoses[i].rot(), dotSign * targetPoses[i].rot(), blendFactor)); + _relativePoses[i].rot() = safeLerp(_relativePoses[i].rot(), targetPoses[i].rot(), blendFactor); } else { // this joint is NOT affected by IK --> slam to underPoses rotation _relativePoses[i].rot() = underPoses[i].rot(); diff --git a/libraries/animation/src/AnimUtil.cpp b/libraries/animation/src/AnimUtil.cpp index a4659f1e76..bcf30642e8 100644 --- a/libraries/animation/src/AnimUtil.cpp +++ b/libraries/animation/src/AnimUtil.cpp @@ -28,7 +28,7 @@ void blend(size_t numPoses, const AnimPose* a, const AnimPose* b, float alpha, A } result[i].scale() = lerp(aPose.scale(), bPose.scale(), alpha); - result[i].rot() = glm::normalize(glm::lerp(aPose.rot(), q2, alpha)); + result[i].rot() = safeLerp(aPose.rot(), bPose.rot(), alpha); result[i].trans() = lerp(aPose.trans(), bPose.trans(), alpha); } } diff --git a/libraries/animation/src/AnimUtil.h b/libraries/animation/src/AnimUtil.h index 055fd630eb..d215fdc654 100644 --- a/libraries/animation/src/AnimUtil.h +++ b/libraries/animation/src/AnimUtil.h @@ -21,4 +21,14 @@ glm::quat averageQuats(size_t numQuats, const glm::quat* quats); float accumulateTime(float startFrame, float endFrame, float timeScale, float currentFrame, float dt, bool loopFlag, const QString& id, AnimNode::Triggers& triggersOut); +inline glm::quat safeLerp(const glm::quat& a, const glm::quat& b, float alpha) { + // adjust signs if necessary + glm::quat bTemp = b; + float dot = glm::dot(a, bTemp); + if (dot < 0.0f) { + bTemp = -bTemp; + } + return glm::normalize(glm::lerp(a, bTemp, alpha)); +} + #endif From 06d512dab90f2444bc5d968ea5f6b21d4a665b53 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 5 Jul 2017 10:43:24 -0700 Subject: [PATCH 08/39] Warning fixes --- libraries/animation/src/AnimInverseKinematics.cpp | 2 +- libraries/animation/src/Rig.cpp | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index f0cd0dea98..7e13c13650 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -891,7 +891,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars // initialize a new jointChainInfoVec, this will holds the results for solving each ik chain. JointInfo defaultJointInfo = { glm::quat(), glm::vec3(), -1, false }; for (size_t i = 0; i < targets.size(); i++) { - int chainDepth = _skeleton->getChainDepth(targets[i].getIndex()); + size_t chainDepth = (size_t)_skeleton->getChainDepth(targets[i].getIndex()); jointChainInfoVec[i].jointInfoVec.reserve(chainDepth); jointChainInfoVec[i].target = targets[i]; int index = targets[i].getIndex(); diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index c505353174..9cc09addb3 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1082,14 +1082,6 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab const glm::vec3 bodyCapsuleEnd = bodyCapsuleCenter + glm::vec3(0, bodyCapsuleHalfHeight, 0); const float HAND_RADIUS = 0.05f; - - const float RELAX_DURATION = 0.6f; - const float CONTROL_DURATION = 0.4f; - const bool TO_CONTROLLED = true; - const bool FROM_CONTROLLED = false; - const bool LEFT_HAND = true; - const bool RIGHT_HAND = false; - const float ELBOW_POLE_VECTOR_BLEND_FACTOR = 0.95f; if (leftHandEnabled) { From 9cae8684923e3fa7fbf87da27b2d8511fa0370ae Mon Sep 17 00:00:00 2001 From: LaShonda Hopper Date: Wed, 5 Jul 2017 16:33:05 -0400 Subject: [PATCH 09/39] [Worklist #21420] Implement Create button disabled state (details below). * The create button icon is set to its disabled resource when the user enters a domain where they have _neither_ Rez or TempRez permissions. ** If the user has either of the Rez permission levels then the normal edit-i.svg icon is used and the user is able to create items as before. * When the user clicks the button in this state, the INSUFFICIENT_PERMISSIONS_ERROR_MSG is shown and creation menu is not shown. * The disabled icon, edit-disabled.svg, is based on the edit-i.svg and is set to 33% opacity. Item Ticket Link: https://worklist.net/21420 Changes to be committed: new file: interface/resources/icons/tablet-icons/edit-disabled.svg modified: scripts/system/edit.js --- .../icons/tablet-icons/edit-disabled.svg | 25 +++++++++++++++++++ scripts/system/edit.js | 8 +++++- 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 interface/resources/icons/tablet-icons/edit-disabled.svg diff --git a/interface/resources/icons/tablet-icons/edit-disabled.svg b/interface/resources/icons/tablet-icons/edit-disabled.svg new file mode 100644 index 0000000000..4869b30dd9 --- /dev/null +++ b/interface/resources/icons/tablet-icons/edit-disabled.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + diff --git a/scripts/system/edit.js b/scripts/system/edit.js index a83d2159bb..69aacfd48f 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -397,9 +397,10 @@ var toolBar = (function () { } }); + var hasEditPermissions = (Entities.canRez() || Entities.canRezTmp()); tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); activeButton = tablet.addButton({ - icon: "icons/tablet-icons/edit-i.svg", + icon: (hasEditPermissions ? "icons/tablet-icons/edit-i.svg" : "icons/tablet-icons/edit-disabled.svg"), activeIcon: "icons/tablet-icons/edit-a.svg", text: "CREATE", sortOrder: 10 @@ -412,6 +413,11 @@ var toolBar = (function () { tablet.fromQml.connect(fromQml); activeButton.clicked.connect(function() { + if ( ! hasEditPermissions ){ + Window.notifyEditError(INSUFFICIENT_PERMISSIONS_ERROR_MSG); + return; + } + that.toggle(); }); From 85111131da25995b14f4d636581311433fa227a1 Mon Sep 17 00:00:00 2001 From: LaShonda Hopper Date: Thu, 6 Jul 2017 15:16:16 -0400 Subject: [PATCH 10/39] [Worklist #21420] Resolves issue where domain changes didn't refresh the icon (details below). * Adds a local message/event: "Toolbar-DomainChanged" ** This message is sent by the application via its registered domain handlers: *** hostnameChanged *** connectedToDomain *** disconnectedFromDomain * edit.js subscribes to the "Toolbar-DomainChanged" event and updates the Create button icon as long as there's a valid known valid create button. Item Ticket Link: https://worklist.net/21420 Changes to be committed: modified: interface/src/Application.cpp modified: scripts/system/edit.js --- interface/src/Application.cpp | 4 +++ scripts/system/edit.js | 46 +++++++++++++++++++++++++++++++---- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 377819c0a0..1a0d164d65 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5293,6 +5293,10 @@ void Application::updateWindowTitle() const { qCDebug(interfaceapp, "Application title set to: %s", title.toStdString().c_str()); #endif _window->setWindowTitle(title); + + // updateTitleWindow gets called whenever there's a change regarding the domain, so rather + // than placing this within domainChanged, it's placed here to cover the other potential cases. + DependencyManager::get< MessagesClient >()->sendLocalMessage("Toolbar-DomainChanged", ""); } void Application::clearDomainOctreeDetails() { diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 69aacfd48f..d37a6e4189 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -123,6 +123,8 @@ var SETTING_EASE_ON_FOCUS = "cameraEaseOnFocus"; var SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE = "showLightsAndParticlesInEditMode"; var SETTING_SHOW_ZONES_IN_EDIT_MODE = "showZonesInEditMode"; +var CREATE_ENABLED_ICON = "icons/tablet-icons/edit-i.svg"; +var CREATE_DISABLED_ICON = "icons/tablet-icons/edit-disabled.svg"; // marketplace info, etc. not quite ready yet. var SHOULD_SHOW_PROPERTY_MENU = false; @@ -130,6 +132,7 @@ var INSUFFICIENT_PERMISSIONS_ERROR_MSG = "You do not have the necessary permissi var INSUFFICIENT_PERMISSIONS_IMPORT_ERROR_MSG = "You do not have the necessary permissions to place items on this domain."; var isActive = false; +var createButton = null; var IMPORTING_SVO_OVERLAY_WIDTH = 144; var IMPORTING_SVO_OVERLAY_HEIGHT = 30; @@ -397,14 +400,15 @@ var toolBar = (function () { } }); - var hasEditPermissions = (Entities.canRez() || Entities.canRezTmp()); + var createButtonIconRsrc = ((Entities.canRez() || Entities.canRezTmp()) ? CREATE_ENABLED_ICON : CREATE_DISABLED_ICON); tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); activeButton = tablet.addButton({ - icon: (hasEditPermissions ? "icons/tablet-icons/edit-i.svg" : "icons/tablet-icons/edit-disabled.svg"), + icon: createButtonIconRsrc, activeIcon: "icons/tablet-icons/edit-a.svg", text: "CREATE", sortOrder: 10 }); + createButton = activeButton; tablet.screenChanged.connect(function (type, url) { if (isActive && (type !== "QML" || url !== "Edit.qml")) { that.toggle(); @@ -412,8 +416,8 @@ var toolBar = (function () { }); tablet.fromQml.connect(fromQml); - activeButton.clicked.connect(function() { - if ( ! hasEditPermissions ){ + createButton.clicked.connect(function() { + if ( ! (Entities.canRez() || Entities.canRezTmp()) ) { Window.notifyEditError(INSUFFICIENT_PERMISSIONS_ERROR_MSG); return; } @@ -766,8 +770,38 @@ function handleOverlaySelectionToolUpdates(channel, message, sender) { } } +// Handles any edit mode updates required when domains have switched +function handleDomainChange() { + if ( (createButton === null) || (createButton === undefined) ){ + //--EARLY EXIT--( nothing to safely update ) + return; + } + + var hasRezPermissions = (Entities.canRez() || Entities.canRezTmp()); + createButton.editProperties({ + icon: (hasRezPermissions ? CREATE_ENABLED_ICON : CREATE_DISABLED_ICON), + }); +} + +function handleMessagesReceived(channel, message, sender) { + switch( channel ){ + case 'entityToolUpdates': { + handleOverlaySelectionToolUpdates( channel, message, sender ); + break; + } + case 'Toolbar-DomainChanged': { + handleDomainChange(); + break; + } + default: { + return; + } + } +} + +Messages.subscribe('Toolbar-DomainChanged'); Messages.subscribe("entityToolUpdates"); -Messages.messageReceived.connect(handleOverlaySelectionToolUpdates); +Messages.messageReceived.connect(handleMessagesReceived); var mouseHasMovedSincePress = false; var mousePressStartTime = 0; @@ -1195,6 +1229,8 @@ Script.scriptEnding.connect(function () { Messages.messageReceived.disconnect(handleOverlaySelectionToolUpdates); Messages.unsubscribe("entityToolUpdates"); + Messages.unsubscribe("Toolbar-DomainChanged"); + createButton = null; }); var lastOrientation = null; From bd8d6280a8a4636648b840c2fcfb8d80abe756b8 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 7 Jul 2017 09:29:57 -0700 Subject: [PATCH 11/39] Interpolate out of ik chains when they are disabled --- libraries/animation/src/AnimInverseKinematics.cpp | 14 ++++++++++++-- libraries/animation/src/IKTarget.h | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 7e13c13650..a0cb8432d9 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -252,6 +252,14 @@ void AnimInverseKinematics::solve(const AnimContext& context, const std::vector< jointChainInfoVec[i].jointInfoVec[j].rot = safeMix(_prevJointChainInfoVec[i].jointInfoVec[j].rot, jointChainInfoVec[i].jointInfoVec[j].rot, alpha); jointChainInfoVec[i].jointInfoVec[j].trans = lerp(_prevJointChainInfoVec[i].jointInfoVec[j].trans, jointChainInfoVec[i].jointInfoVec[j].trans, alpha); } + + // if joint chain was just disabled, ramp the weight toward zero. + if (_prevJointChainInfoVec[i].target.getType() != IKTarget::Type::Unknown && + jointChainInfoVec[i].target.getType() == IKTarget::Type::Unknown) { + IKTarget newTarget = _prevJointChainInfoVec[i].target; + newTarget.setWeight(alpha); + jointChainInfoVec[i].target = newTarget; + } } } } @@ -336,6 +344,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; if (_prevJointChainInfoVec[i].timer <= 0.0f) { @@ -888,7 +897,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars { PROFILE_RANGE_EX(simulation_animation, "ik/jointChainInfo", 0xffff00ff, 0); - // initialize a new jointChainInfoVec, this will holds the results for solving each ik chain. + // initialize a new jointChainInfoVec, this will hold the results for solving each ik chain. JointInfo defaultJointInfo = { glm::quat(), glm::vec3(), -1, false }; for (size_t i = 0; i < targets.size(); i++) { size_t chainDepth = (size_t)_skeleton->getChainDepth(targets[i].getIndex()); @@ -902,6 +911,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars } } + // identity joint chains that have changed types this frame. _prevJointChainInfoVec.resize(jointChainInfoVec.size()); for (size_t i = 0; i < _prevJointChainInfoVec.size(); i++) { if (_prevJointChainInfoVec[i].timer <= 0.0f && @@ -923,7 +933,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars int parentIndex = _skeleton->getParentIndex(targets[_hipsTargetIndex].getIndex()); AnimPose parentAbsPose = _skeleton->getAbsolutePose(parentIndex, _relativePoses); - // do smooth interpolation of hips here, if necessary. + // do smooth interpolation of hips, if necessary. if (_prevJointChainInfoVec[_hipsTargetIndex].timer > 0.0f) { float alpha = (JOINT_CHAIN_INTERP_TIME - _prevJointChainInfoVec[_hipsTargetIndex].timer) / JOINT_CHAIN_INTERP_TIME; diff --git a/libraries/animation/src/IKTarget.h b/libraries/animation/src/IKTarget.h index a86ae0ca8b..325a1b40b6 100644 --- a/libraries/animation/src/IKTarget.h +++ b/libraries/animation/src/IKTarget.h @@ -56,7 +56,7 @@ private: glm::vec3 _poleReferenceVector; bool _poleVectorEnabled { false }; int _index { -1 }; - Type _type { Type::RotationAndPosition }; + Type _type { Type::Unknown }; float _weight { 0.0f }; float _flexCoefficients[MAX_FLEX_COEFFICIENTS]; size_t _numFlexCoefficients; From 3ee9d8b76640e8206722bd3d12fed649f2a23b8f Mon Sep 17 00:00:00 2001 From: LaShonda Hopper Date: Fri, 7 Jul 2017 17:56:50 -0400 Subject: [PATCH 12/39] [Worklist #21420] Ran lint on edit script (details below). Prior to and after addressing Worklist Item #21420, it was observed that the tool bar buttons popping in and out during load. Taking a while to become visible. Ran JSHint on the edit.js script to see if something stood out. Testing with the various lint fixes the odd loading behavior wasn't observed locally any longer. Pushing this up for testing. This may be what @CainFoool was seeing regarding the create button not appearing when testing a different PR as at times it took quite some time for the buttons to load properly. JSHint Issues Addressed: * Resolved instance of \"Use the function form of \"use strict\"\" ** Moved use strict declaration to the file function level. ** This fixed various unknown or not defined errors. * Resolved instances of \"\'const\' is available in ES6 (use 'esversion: 6') or Mozilla JS extensions (use moz).\" ** Switched const to var to avoid using keywords respected by only a subset of browsers. * Resolved various multiply defined vars. ** Notable callouts being position var and properties var. ** This should fix issues where the same var was being used though the intent of use may have varied. * Resolved instances of missing semi-colons PreFix: 51 JSHint issues detected PostFix: 0 JSHint issues detected. Changes to be committed: modified: scripts/system/edit.js --- scripts/system/edit.js | 56 +++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index d37a6e4189..791642838d 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -1,5 +1,3 @@ -"use strict"; - // edit.js // // Created by Brad Hefta-Gaub on 10/2/14. @@ -16,6 +14,8 @@ Settings, Entities, Tablet, Toolbars, Messages, Menu, Camera, progressDialog, tooltip, MyAvatar, Quat, Controller, Clipboard, HMD, UndoStack, ParticleExplorerTool */ (function() { // BEGIN LOCAL_SCOPE + +"use strict"; var HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; var EDIT_TOGGLE_BUTTON = "com.highfidelity.interface.system.editButton"; @@ -41,19 +41,19 @@ Script.include([ var selectionDisplay = SelectionDisplay; var selectionManager = SelectionManager; -const PARTICLE_SYSTEM_URL = Script.resolvePath("assets/images/icon-particles.svg"); -const POINT_LIGHT_URL = Script.resolvePath("assets/images/icon-point-light.svg"); -const SPOT_LIGHT_URL = Script.resolvePath("assets/images/icon-spot-light.svg"); +var PARTICLE_SYSTEM_URL = Script.resolvePath("assets/images/icon-particles.svg"); +var POINT_LIGHT_URL = Script.resolvePath("assets/images/icon-point-light.svg"); +var SPOT_LIGHT_URL = Script.resolvePath("assets/images/icon-spot-light.svg"); entityIconOverlayManager = new EntityIconOverlayManager(['Light', 'ParticleEffect'], function(entityID) { var properties = Entities.getEntityProperties(entityID, ['type', 'isSpotlight']); if (properties.type === 'Light') { return { url: properties.isSpotlight ? SPOT_LIGHT_URL : POINT_LIGHT_URL, - } + }; } else { return { url: PARTICLE_SYSTEM_URL, - } + }; } }); @@ -94,7 +94,7 @@ selectionManager.addEventListener(function () { } }); -const KEY_P = 80; //Key code for letter p used for Parenting hotkey. +var KEY_P = 80; //Key code for letter p used for Parenting hotkey. var DEGREES_TO_RADIANS = Math.PI / 180.0; var RADIANS_TO_DEGREES = 180.0 / Math.PI; @@ -652,7 +652,7 @@ var toolBar = (function () { grid.setEnabled(true); propertiesTool.setVisible(true); selectionDisplay.triggerMapping.enable(); - print("starting tablet in landscape mode") + print("starting tablet in landscape mode"); tablet.landscape = true; // Not sure what the following was meant to accomplish, but it currently causes // everybody else to think that Interface has lost focus overall. fogbugzid:558 @@ -1356,7 +1356,7 @@ function unparentSelectedEntities() { if (parentId !== null && parentId.length > 0 && parentId !== "{00000000-0000-0000-0000-000000000000}") { parentCheck = true; } - Entities.editEntity(id, {parentID: null}) + Entities.editEntity(id, {parentID: null}); return true; }); if (parentCheck) { @@ -1391,7 +1391,7 @@ function parentSelectedEntities() { if (parentId !== lastEntityId) { parentCheck = true; } - Entities.editEntity(id, {parentID: lastEntityId}) + Entities.editEntity(id, {parentID: lastEntityId}); } }); @@ -1564,9 +1564,9 @@ function importSVO(importURL) { var entityPositions = []; var entityParentIDs = []; - var properties = Entities.getEntityProperties(pastedEntityIDs[0], ["type"]); + var propType = Entities.getEntityProperties(pastedEntityIDs[0], ["type"]).type; var NO_ADJUST_ENTITY_TYPES = ["Zone", "Light", "ParticleEffect"]; - if (NO_ADJUST_ENTITY_TYPES.indexOf(properties.type) === -1) { + if (NO_ADJUST_ENTITY_TYPES.indexOf(propType) === -1) { var targetDirection; if (Camera.mode === "entity" || Camera.mode === "independent") { targetDirection = Camera.orientation; @@ -1579,36 +1579,36 @@ function importSVO(importURL) { var deltaParallel = HALF_TREE_SCALE; // Distance to move entities parallel to targetDirection. var deltaPerpendicular = Vec3.ZERO; // Distance to move entities perpendicular to targetDirection. for (var i = 0, length = pastedEntityIDs.length; i < length; i++) { - var properties = Entities.getEntityProperties(pastedEntityIDs[i], ["position", "dimensions", + var curLoopEntityProps = Entities.getEntityProperties(pastedEntityIDs[i], ["position", "dimensions", "registrationPoint", "rotation", "parentID"]); var adjustedPosition = adjustPositionPerBoundingBox(targetPosition, targetDirection, - properties.registrationPoint, properties.dimensions, properties.rotation); - var delta = Vec3.subtract(adjustedPosition, properties.position); + curLoopEntityProps.registrationPoint, curLoopEntityProps.dimensions, curLoopEntityProps.rotation); + var delta = Vec3.subtract(adjustedPosition, curLoopEntityProps.position); var distance = Vec3.dot(delta, targetDirection); deltaParallel = Math.min(distance, deltaParallel); deltaPerpendicular = Vec3.sum(Vec3.subtract(delta, Vec3.multiply(distance, targetDirection)), deltaPerpendicular); - entityPositions[i] = properties.position; - entityParentIDs[i] = properties.parentID; + entityPositions[i] = curLoopEntityProps.position; + entityParentIDs[i] = curLoopEntityProps.parentID; } deltaPerpendicular = Vec3.multiply(1 / pastedEntityIDs.length, deltaPerpendicular); deltaPosition = Vec3.sum(Vec3.multiply(deltaParallel, targetDirection), deltaPerpendicular); } if (grid.getSnapToGrid()) { - var properties = Entities.getEntityProperties(pastedEntityIDs[0], ["position", "dimensions", + var firstEntityProps = Entities.getEntityProperties(pastedEntityIDs[0], ["position", "dimensions", "registrationPoint"]); - var position = Vec3.sum(deltaPosition, properties.position); - position = grid.snapToSurface(grid.snapToGrid(position, false, properties.dimensions, - properties.registrationPoint), properties.dimensions, properties.registrationPoint); - deltaPosition = Vec3.subtract(position, properties.position); + var positionPreSnap = Vec3.sum(deltaPosition, firstEntityProps.position); + position = grid.snapToSurface(grid.snapToGrid(positionPreSnap, false, firstEntityProps.dimensions, + firstEntityProps.registrationPoint), firstEntityProps.dimensions, firstEntityProps.registrationPoint); + deltaPosition = Vec3.subtract(position, firstEntityProps.position); } if (!Vec3.equal(deltaPosition, Vec3.ZERO)) { - for (var i = 0, length = pastedEntityIDs.length; i < length; i++) { - if (Uuid.isNull(entityParentIDs[i])) { - Entities.editEntity(pastedEntityIDs[i], { - position: Vec3.sum(deltaPosition, entityPositions[i]) + for (var editEntityIndex = 0, numEntities = pastedEntityIDs.length; editEntityIndex < numEntities; editEntityIndex++) { + if (Uuid.isNull(entityParentIDs[editEntityIndex])) { + Entities.editEntity(pastedEntityIDs[editEntityIndex], { + position: Vec3.sum(deltaPosition, entityPositions[editEntityIndex]) }); } } @@ -2254,7 +2254,7 @@ entityListTool.webView.webEventReceived.connect(function (data) { try { data = JSON.parse(data); } catch(e) { - print("edit.js: Error parsing JSON: " + e.name + " data " + data) + print("edit.js: Error parsing JSON: " + e.name + " data " + data); return; } From 1cdc0071f3cfb489831f90d04171c6418d9d4906 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 10 Jul 2017 16:17:25 -0700 Subject: [PATCH 13/39] Fixed issue with hips and chest not ramping off properly. --- .../animation/src/AnimInverseKinematics.cpp | 99 +++++++++++-------- .../animation/src/AnimInverseKinematics.h | 11 +-- libraries/animation/src/Rig.cpp | 22 ----- libraries/animation/src/Rig.h | 3 - 4 files changed, 62 insertions(+), 73 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index a0cb8432d9..8c86ada43c 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -257,7 +257,7 @@ void AnimInverseKinematics::solve(const AnimContext& context, const std::vector< if (_prevJointChainInfoVec[i].target.getType() != IKTarget::Type::Unknown && jointChainInfoVec[i].target.getType() == IKTarget::Type::Unknown) { IKTarget newTarget = _prevJointChainInfoVec[i].target; - newTarget.setWeight(alpha); + newTarget.setWeight((1.0f - alpha) * _prevJointChainInfoVec[i].target.getWeight()); jointChainInfoVec[i].target = newTarget; } } @@ -349,6 +349,7 @@ void AnimInverseKinematics::solve(const AnimContext& context, const std::vector< _prevJointChainInfoVec[i].timer = _prevJointChainInfoVec[i].timer - dt; if (_prevJointChainInfoVec[i].timer <= 0.0f) { _prevJointChainInfoVec[i] = jointChainInfoVec[i]; + _prevJointChainInfoVec[i].target = targets[i]; // store relative poses into unknown/rotation only joint chains. // so we have something to interpolate from if this chain is activated. IKTarget::Type type = _prevJointChainInfoVec[i].target.getType(); @@ -849,6 +850,24 @@ const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVar return _relativePoses; } +AnimPose AnimInverseKinematics::applyHipsOffset() const { + glm::vec3 hipsOffset = _hipsOffset; + AnimPose relHipsPose = _relativePoses[_hipsIndex]; + float offsetLength = glm::length(hipsOffset); + const float MIN_HIPS_OFFSET_LENGTH = 0.03f; + if (offsetLength > MIN_HIPS_OFFSET_LENGTH) { + float scaleFactor = ((offsetLength - MIN_HIPS_OFFSET_LENGTH) / offsetLength); + glm::vec3 scaledHipsOffset = scaleFactor * hipsOffset; + if (_hipsParentIndex == -1) { + relHipsPose.trans() = _relativePoses[_hipsIndex].trans() + scaledHipsOffset; + } else { + AnimPose absHipsPose = _skeleton->getAbsolutePose(_hipsIndex, _relativePoses); + absHipsPose.trans() += scaledHipsOffset; + relHipsPose = _skeleton->getAbsolutePose(_hipsParentIndex, _relativePoses).inverse() * absHipsPose; + } + } + return relHipsPose; +} //virtual const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) { @@ -925,7 +944,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars { PROFILE_RANGE_EX(simulation_animation, "ik/shiftHips", 0xffff00ff, 0); - if (_hipsTargetIndex >= 0 && _hipsTargetIndex < (int)targets.size()) { + if (_hipsTargetIndex >= 0 && _hipsTargetIndex >= 0 && _hipsTargetIndex < (int)targets.size()) { // slam the hips to match the _hipsTarget AnimPose absPose = targets[_hipsTargetIndex].getPose(); @@ -934,7 +953,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars AnimPose parentAbsPose = _skeleton->getAbsolutePose(parentIndex, _relativePoses); // do smooth interpolation of hips, if necessary. - if (_prevJointChainInfoVec[_hipsTargetIndex].timer > 0.0f) { + if (_prevJointChainInfoVec[_hipsTargetIndex].timer > 0.0f && _prevJointChainInfoVec[_hipsTargetIndex].jointInfoVec.size() > 0) { float alpha = (JOINT_CHAIN_INTERP_TIME - _prevJointChainInfoVec[_hipsTargetIndex].timer) / JOINT_CHAIN_INTERP_TIME; auto& info = _prevJointChainInfoVec[_hipsTargetIndex].jointInfoVec[0]; @@ -944,22 +963,36 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars } _relativePoses[_hipsIndex] = parentAbsPose.inverse() * absPose; + _relativePoses[_hipsIndex].scale() = glm::vec3(1.0f); + _hipsOffset = Vectors::ZERO; + + } else if (_hipsIndex >= 0) { - } else { // if there is no hips target, shift hips according to the _hipsOffset from the previous frame - float offsetLength = glm::length(_hipsOffset); - const float MIN_HIPS_OFFSET_LENGTH = 0.03f; - if (offsetLength > MIN_HIPS_OFFSET_LENGTH && _hipsIndex >= 0) { - float scaleFactor = ((offsetLength - MIN_HIPS_OFFSET_LENGTH) / offsetLength); - glm::vec3 hipsOffset = scaleFactor * _hipsOffset; - if (_hipsParentIndex == -1) { - _relativePoses[_hipsIndex].trans() = _relativePoses[_hipsIndex].trans() + hipsOffset; - } else { - auto absHipsPose = _skeleton->getAbsolutePose(_hipsIndex, _relativePoses); - absHipsPose.trans() += hipsOffset; - _relativePoses[_hipsIndex] = _skeleton->getAbsolutePose(_hipsParentIndex, _relativePoses).inverse() * absHipsPose; + AnimPose relHipsPose = applyHipsOffset(); + + // determine if we should begin interpolating the hips. + for (size_t i = 0; i < targets.size(); i++) { + if (_prevJointChainInfoVec[i].target.getIndex() == _hipsIndex) { + if (_prevJointChainInfoVec[i].timer > 0.0f) { + // smoothly lerp in hipsOffset + float alpha = (JOINT_CHAIN_INTERP_TIME - _prevJointChainInfoVec[i].timer) / JOINT_CHAIN_INTERP_TIME; + AnimPose prevRelHipsPose(_prevJointChainInfoVec[i].jointInfoVec[0].rot, _prevJointChainInfoVec[i].jointInfoVec[0].trans); + ::blend(1, &prevRelHipsPose, &relHipsPose, alpha, &relHipsPose); + } + break; } } + + _relativePoses[_hipsIndex] = relHipsPose; + } + + // if there is an active jointChainInfo for the hips store the post shifted hips into it. + // This is so we have a valid pose to interplate from when the hips target is disabled. + if (_hipsTargetIndex >= 0) { + // AJT: TODO: WILL THIS WORK if hips aren't the root of skeleton? + jointChainInfoVec[_hipsTargetIndex].jointInfoVec[0].rot = _relativePoses[_hipsIndex].rot(); + jointChainInfoVec[_hipsTargetIndex].jointInfoVec[0].trans = _relativePoses[_hipsIndex].trans(); } // update all HipsRelative targets to account for the hips shift/ik target. @@ -1010,9 +1043,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars if (_hipsTargetIndex < 0) { PROFILE_RANGE_EX(simulation_animation, "ik/measureHipsOffset", 0xffff00ff, 0); - computeHipsOffset(targets, underPoses, dt); - } else { - _hipsOffset = Vectors::ZERO; + _hipsOffset = computeHipsOffset(targets, underPoses, dt, _hipsOffset); } } @@ -1021,23 +1052,15 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars } } - if (_leftHandIndex > -1) { - _uncontrolledLeftHandPose = _skeleton->getAbsolutePose(_leftHandIndex, underPoses); - } - if (_rightHandIndex > -1) { - _uncontrolledRightHandPose = _skeleton->getAbsolutePose(_rightHandIndex, underPoses); - } - if (_hipsIndex > -1) { - _uncontrolledHipsPose = _skeleton->getAbsolutePose(_hipsIndex, underPoses); - } - return _relativePoses; } -void AnimInverseKinematics::computeHipsOffset(const std::vector& targets, const AnimPoseVec& underPoses, float dt) { +glm::vec3 AnimInverseKinematics::computeHipsOffset(const std::vector& targets, const AnimPoseVec& underPoses, float dt, glm::vec3 prevHipsOffset) const { + // measure new _hipsOffset for next frame // by looking for discrepancies between where a targeted endEffector is // and where it wants to be (after IK solutions are done) + glm::vec3 hipsOffset = prevHipsOffset; glm::vec3 newHipsOffset = Vectors::ZERO; for (auto& target: targets) { int targetIndex = target.getIndex(); @@ -1053,9 +1076,9 @@ void AnimInverseKinematics::computeHipsOffset(const std::vector& targe } else if (target.getType() == IKTarget::Type::HmdHead) { // we want to shift the hips to bring the head to its designated position glm::vec3 actual = _skeleton->getAbsolutePose(_headIndex, _relativePoses).trans(); - _hipsOffset += target.getTranslation() - actual; + hipsOffset += target.getTranslation() - actual; // and ignore all other targets - newHipsOffset = _hipsOffset; + newHipsOffset = hipsOffset; break; } else if (target.getType() == IKTarget::Type::RotationAndPosition) { glm::vec3 actualPosition = _skeleton->getAbsolutePose(targetIndex, _relativePoses).trans(); @@ -1075,16 +1098,18 @@ void AnimInverseKinematics::computeHipsOffset(const std::vector& targe } } - // smooth transitions by relaxing _hipsOffset toward the new value + // smooth transitions by relaxing hipsOffset toward the new value const float HIPS_OFFSET_SLAVE_TIMESCALE = 0.10f; float tau = dt < HIPS_OFFSET_SLAVE_TIMESCALE ? dt / HIPS_OFFSET_SLAVE_TIMESCALE : 1.0f; - _hipsOffset += (newHipsOffset - _hipsOffset) * tau; + hipsOffset += (newHipsOffset - hipsOffset) * tau; // clamp the hips offset - float hipsOffsetLength = glm::length(_hipsOffset); + float hipsOffsetLength = glm::length(hipsOffset); if (hipsOffsetLength > _maxHipsOffsetLength) { - _hipsOffset *= _maxHipsOffsetLength / hipsOffsetLength; + hipsOffset *= _maxHipsOffsetLength / hipsOffsetLength; } + + return hipsOffset; } void AnimInverseKinematics::setMaxHipsOffsetLength(float maxLength) { @@ -1528,10 +1553,6 @@ void AnimInverseKinematics::setSkeletonInternal(AnimSkeleton::ConstPointer skele _leftHandIndex = -1; _rightHandIndex = -1; } - - _uncontrolledLeftHandPose = AnimPose(); - _uncontrolledRightHandPose = AnimPose(); - _uncontrolledHipsPose = AnimPose(); } static glm::vec3 sphericalToCartesian(float phi, float theta) { diff --git a/libraries/animation/src/AnimInverseKinematics.h b/libraries/animation/src/AnimInverseKinematics.h index d5fc5a6a8c..7f7640aa24 100644 --- a/libraries/animation/src/AnimInverseKinematics.h +++ b/libraries/animation/src/AnimInverseKinematics.h @@ -73,10 +73,6 @@ public: void setSolutionSource(SolutionSource solutionSource) { _solutionSource = solutionSource; } void setSolutionSourceVar(const QString& solutionSourceVar) { _solutionSourceVar = solutionSourceVar; } - const AnimPose& getUncontrolledLeftHandPose() { return _uncontrolledLeftHandPose; } - const AnimPose& getUncontrolledRightHandPose() { return _uncontrolledRightHandPose; } - const AnimPose& getUncontrolledHipPose() { return _uncontrolledHipsPose; } - protected: void computeTargets(const AnimVariantMap& animVars, std::vector& targets, const AnimPoseVec& underPoses); void solve(const AnimContext& context, const std::vector& targets, float dt, JointChainInfoVec& jointChainInfoVec); @@ -92,6 +88,7 @@ protected: void initRelativePosesFromSolutionSource(SolutionSource solutionSource, const AnimPoseVec& underPose); void blendToPoses(const AnimPoseVec& targetPoses, const AnimPoseVec& underPose, float blendFactor); void preconditionRelativePosesToAvoidLimbLock(const AnimContext& context, const std::vector& targets); + AnimPose applyHipsOffset() const; // used to pre-compute information about each joint influeced by a spline IK target. struct SplineJointInfo { @@ -110,7 +107,7 @@ protected: void clearConstraints(); void initConstraints(); void initLimitCenterPoses(); - void computeHipsOffset(const std::vector& targets, const AnimPoseVec& underPoses, float dt); + glm::vec3 computeHipsOffset(const std::vector& targets, const AnimPoseVec& underPoses, float dt, glm::vec3 prevHipsOffset) const; // no copies AnimInverseKinematics(const AnimInverseKinematics&) = delete; @@ -162,10 +159,6 @@ protected: SolutionSource _solutionSource { SolutionSource::RelaxToUnderPoses }; QString _solutionSourceVar; - AnimPose _uncontrolledLeftHandPose { AnimPose() }; - AnimPose _uncontrolledRightHandPose { AnimPose() }; - AnimPose _uncontrolledHipsPose { AnimPose() }; - JointChainInfoVec _prevJointChainInfoVec; }; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 9cc09addb3..aa080dfa86 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1703,25 +1703,3 @@ void Rig::computeAvatarBoundingCapsule( glm::vec3 rigCenter = transformPoint(_geometryToRigTransform, (0.5f * (totalExtents.maximum + totalExtents.minimum))); localOffsetOut = rigCenter - transformPoint(_geometryToRigTransform, rootPosition); } - -bool Rig::transitionHandPose(float deltaTime, float totalDuration, AnimPose& controlledHandPose, bool isLeftHand, - bool isToControlled, AnimPose& returnHandPose) { - auto ikNode = getAnimInverseKinematicsNode(); - if (ikNode) { - float alpha = 1.0f - deltaTime / totalDuration; - const AnimPose geometryToRigTransform(_geometryToRigTransform); - AnimPose uncontrolledHandPose; - if (isLeftHand) { - uncontrolledHandPose = geometryToRigTransform * ikNode->getUncontrolledLeftHandPose(); - } else { - uncontrolledHandPose = geometryToRigTransform * ikNode->getUncontrolledRightHandPose(); - } - if (isToControlled) { - ::blend(1, &uncontrolledHandPose, &controlledHandPose, alpha, &returnHandPose); - } else { - ::blend(1, &controlledHandPose, &uncontrolledHandPose, alpha, &returnHandPose); - } - return true; - } - return false; -} diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index ec13d98613..5293fa1fe7 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -340,9 +340,6 @@ protected: int _nextStateHandlerId { 0 }; QMutex _stateMutex; - bool transitionHandPose(float deltaTime, float totalDuration, AnimPose& controlledHandPose, bool isLeftHand, - bool isToControlled, AnimPose& returnHandPose); - glm::vec3 _prevRightFootPoleVector { Vectors::UNIT_Z }; bool _prevRightFootPoleVectorValid { false }; From b0177c25221d59c065a385b6287ca6a48b94e965 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 10 Jul 2017 16:25:37 -0700 Subject: [PATCH 14/39] remove comment, it does indeed work --- libraries/animation/src/AnimInverseKinematics.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 8c86ada43c..20b62c2724 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -990,7 +990,6 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars // if there is an active jointChainInfo for the hips store the post shifted hips into it. // This is so we have a valid pose to interplate from when the hips target is disabled. if (_hipsTargetIndex >= 0) { - // AJT: TODO: WILL THIS WORK if hips aren't the root of skeleton? jointChainInfoVec[_hipsTargetIndex].jointInfoVec[0].rot = _relativePoses[_hipsIndex].rot(); jointChainInfoVec[_hipsTargetIndex].jointInfoVec[0].trans = _relativePoses[_hipsIndex].trans(); } From 62be0af32d9634d597ff26baa2ccb2e934e18f55 Mon Sep 17 00:00:00 2001 From: Liv Date: Tue, 11 Jul 2017 14:57:11 -0700 Subject: [PATCH 15/39] string pass on Chat.js --- scripts/system/chat.js | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/scripts/system/chat.js b/scripts/system/chat.js index d03c6aae98..58a1849f1f 100644 --- a/scripts/system/chat.js +++ b/scripts/system/chat.js @@ -370,14 +370,14 @@ // Change the avatar size to bigger. function biggerSize() { //print("biggerSize"); - logMessage("Increasing avatar size bigger!", null); + logMessage("Increasing avatar size", null); MyAvatar.increaseSize(); } // Change the avatar size to smaller. function smallerSize() { //print("smallerSize"); - logMessage("Decreasing avatar size smaler!", null); + logMessage("Decreasing avatar size", null); MyAvatar.decreaseSize(); } @@ -470,14 +470,13 @@ case '?': case 'help': - logMessage('Type "/?" or "/help" for help, which is this!', null); - logMessage('Type "/name " to set your chat name, or "/name" to use your display name, or a random name if that is not defined.', null); - logMessage('Type "/shutup" to shut up your overhead chat message.', null); - logMessage('Type "/say " to say something.', null); - logMessage('Type "/clear" to clear your cha, nullt log.', null); - logMessage('Type "/who" to ask who is h, nullere to chat.', null); - logMessage('Type "/bigger", "/smaller" or "/normal" to change, null your avatar size.', null); - logMessage('(Sorry, that\'s all there is so far!)', null); + logMessage('Type "/?" or "/help" for help', null); + logMessage('Type "/name " to set your chat name, or "/name" to use your display name. If your display name is not defined, a random name will be used.', null); + logMessage('Type "/close" to close your overhead chat message.', null); + logMessage('Type "/say " to display a new message.', null); + logMessage('Type "/clear" to clear your chat log.', null); + logMessage('Type "/who" to ask who is in the chat session.', null); + logMessage('Type "/bigger", "/smaller" or "/normal" to change your avatar size.', null); break; case 'name': @@ -498,9 +497,9 @@ } break; - case 'shutup': + case 'close': popDownSpeechBubble(); - logMessage('Overhead chat message shut up.', null); + logMessage('Overhead chat message closed.', null); break; case 'say': From bedd0628b9558c59b2ed425bf1909edbfbd3c51d Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Wed, 12 Jul 2017 00:51:07 +0100 Subject: [PATCH 16/39] allowed tablet to toggle when a dialog is open --- interface/src/Application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ddd1870723..352d9e28e2 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1706,7 +1706,7 @@ void Application::toggleTabletUI(bool shouldOpen) const { auto hmd = DependencyManager::get(); TabletProxy* tablet = dynamic_cast(tabletScriptingInterface->getTablet(SYSTEM_TABLET)); bool messageOpen = tablet->isMessageDialogOpen(); - if ((!messageOpen || (messageOpen && !hmd->getShouldShowTablet())) && !(shouldOpen && hmd->getShouldShowTablet())) { + if (!(shouldOpen && hmd->getShouldShowTablet())) { auto HMD = DependencyManager::get(); HMD->toggleShouldShowTablet(); } From e07b067d7b0be083bb8301005d44cb9c01563f8c Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Wed, 12 Jul 2017 21:09:54 +0100 Subject: [PATCH 17/39] fixed tablet getting in the wrong state --- libraries/ui/src/ui/TabletScriptingInterface.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp index 857cae15cc..76f290f17e 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -227,7 +227,6 @@ void TabletProxy::setToolbarMode(bool toolbarMode) { // forward qml surface events to interface js connect(tabletRootWindow, &QmlWindowClass::fromQml, this, &TabletProxy::fromQml); } else { - _state = State::Home; removeButtonsFromToolbar(); addButtonsToHomeScreen(); emit screenChanged(QVariant("Home"), QVariant(TABLET_SOURCE_URL)); From 43782a29c78d8504e1104dbf853bd73777bcda49 Mon Sep 17 00:00:00 2001 From: Cain Kilgore Date: Wed, 12 Jul 2017 22:04:58 +0100 Subject: [PATCH 18/39] Avatar Scaler - WIP --- .../qml/dialogs/preferences/Section.qml | 6 ++ .../preferences/SpinnerSliderPreference.qml | 64 +++++++++++++++++++ .../tabletWindows/preferences/Section.qml | 5 ++ interface/src/ui/PreferencesDialog.cpp | 36 ++++++----- libraries/shared/src/Preferences.h | 10 +++ 5 files changed, 104 insertions(+), 17 deletions(-) create mode 100644 interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml diff --git a/interface/resources/qml/dialogs/preferences/Section.qml b/interface/resources/qml/dialogs/preferences/Section.qml index a813dc6b5f..61b3c7530b 100644 --- a/interface/resources/qml/dialogs/preferences/Section.qml +++ b/interface/resources/qml/dialogs/preferences/Section.qml @@ -72,6 +72,7 @@ Preference { property var avatarBuilder: Component { AvatarPreference { } } property var buttonBuilder: Component { ButtonPreference { } } property var comboBoxBuilder: Component { ComboBoxPreference { } } + property var spinnerSliderBuilder: Component { SpinnerSliderPreference { } } property var preferences: [] property int checkBoxCount: 0 @@ -128,6 +129,11 @@ Preference { checkBoxCount = 0; builder = comboBoxBuilder; break; + + case Preference.SpinnerSlider: + checkBoxCount = 0; + builder = spinnerSliderBuilder; + break; }; if (builder) { diff --git a/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml new file mode 100644 index 0000000000..e9013bc17a --- /dev/null +++ b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml @@ -0,0 +1,64 @@ +// +// SpinBoxPreference.qml +// +// Created by Bradley Austin Davis on 18 Jan 2016 +// Copyright 2016 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 "../../dialogs" +import "../../controls-uit" + +Preference { + id: root + property alias slider: slider + height: control.height + hifi.dimensions.controlInterlineHeight + + Component.onCompleted: { + slider.value = preference.value; + } + + function save() { + preference.value = slider.value; + preference.save(); + } + + Item { + id: control + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } + height: Math.max(labelText.height, slider.height) + + Label { + id: labelText + text: root.label + ":" + colorScheme: hifi.colorSchemes.dark + anchors { + left: parent.left + right: slider.left + rightMargin: hifi.dimensions.labelPadding + verticalCenter: parent.verticalCenter + } + horizontalAlignment: Text.AlignRight + wrapMode: Text.Wrap + } + + Slider { + id: slider + value: preference.value + width: 130 + anchors { + right: parent.right + verticalCenter: parent.verticalCenter + } + colorScheme: hifi.colorSchemes.dark + } + } +} diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml index 9dd0956000..25279bb6bf 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml @@ -81,6 +81,7 @@ Preference { property var avatarBuilder: Component { AvatarPreference { } } property var buttonBuilder: Component { ButtonPreference { } } property var comboBoxBuilder: Component { ComboBoxPreference { } } + property var spinnerSliderBuilder: Component { SpinnerSliderPreference { } } property var preferences: [] property int checkBoxCount: 0 @@ -143,6 +144,10 @@ Preference { //to be not overlapped when drop down is active zpos = root.z + 1000 - itemNum break; + case Preference.SpinnerSlider: + checkBoxCount = 0; + builder = spinnerSliderBuilder; + break; }; if (builder) { diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 767c122bb6..9c1b3e8c70 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -181,16 +181,18 @@ void setupPreferences() { preference->setStep(1); preferences->addPreference(preference); } - { - auto getter = [=]()->float { return myAvatar->getUniformScale(); }; - auto setter = [=](float value) { myAvatar->setTargetScale(value); }; - auto preference = new SpinnerPreference(AVATAR_TUNING, "Avatar scale (default is 1.0)", getter, setter); - preference->setMin(0.01f); - preference->setMax(99.9f); - preference->setDecimals(2); - preference->setStep(1); - preferences->addPreference(preference); - } + { + auto getter = [=]()->float { return myAvatar->getUniformScale(); }; + auto setter = [=](float value) { myAvatar->setTargetScale(value); }; + + auto scaleSpinner = new SpinnerSliderPreference(AVATAR_TUNING, "Avatar Scale", getter, setter); + scaleSpinner->setMin(0.01f); + scaleSpinner->setMax(99.9f); + scaleSpinner->setDecimals(2); + scaleSpinner->setStep(1); + + preferences->addPreference(scaleSpinner); + } { auto getter = []()->float { return DependencyManager::get()->getEyeClosingThreshold(); }; auto setter = [](float value) { DependencyManager::get()->setEyeClosingThreshold(value); }; @@ -227,17 +229,17 @@ void setupPreferences() { preferences->addPreference(preference); } - static const QString AUDIO("Audio"); + static const QString AUDIO_BUFFERS("Audio Buffers"); { auto getter = []()->bool { return !DependencyManager::get()->getReceivedAudioStream().dynamicJitterBufferEnabled(); }; auto setter = [](bool value) { DependencyManager::get()->getReceivedAudioStream().setDynamicJitterBufferEnabled(!value); }; - auto preference = new CheckPreference(AUDIO, "Disable dynamic jitter buffer", getter, setter); + auto preference = new CheckPreference(AUDIO_BUFFERS, "Disable dynamic jitter buffer", getter, setter); preferences->addPreference(preference); } { auto getter = []()->float { return DependencyManager::get()->getReceivedAudioStream().getStaticJitterBufferFrames(); }; auto setter = [](float value) { DependencyManager::get()->getReceivedAudioStream().setStaticJitterBufferFrames(value); }; - auto preference = new SpinnerPreference(AUDIO, "Static jitter buffer frames", getter, setter); + auto preference = new SpinnerPreference(AUDIO_BUFFERS, "Static jitter buffer frames", getter, setter); preference->setMin(0); preference->setMax(2000); preference->setStep(1); @@ -246,13 +248,13 @@ void setupPreferences() { { auto getter = []()->bool { return !DependencyManager::get()->getOutputStarveDetectionEnabled(); }; auto setter = [](bool value) { DependencyManager::get()->setOutputStarveDetectionEnabled(!value); }; - auto preference = new CheckPreference(AUDIO, "Disable output starve detection", getter, setter); + auto preference = new CheckPreference(AUDIO_BUFFERS, "Disable output starve detection", getter, setter); preferences->addPreference(preference); } { auto getter = []()->float { return DependencyManager::get()->getOutputBufferSize(); }; auto setter = [](float value) { DependencyManager::get()->setOutputBufferSize(value); }; - auto preference = new SpinnerPreference(AUDIO, "Output buffer initial frames", getter, setter); + auto preference = new SpinnerPreference(AUDIO_BUFFERS, "Output buffer initial frames", getter, setter); preference->setMin(AudioClient::MIN_BUFFER_FRAMES); preference->setMax(AudioClient::MAX_BUFFER_FRAMES); preference->setStep(1); @@ -262,13 +264,13 @@ void setupPreferences() { { auto getter = []()->bool { return DependencyManager::get()->isSimulatingJitter(); }; auto setter = [](bool value) { return DependencyManager::get()->setIsSimulatingJitter(value); }; - auto preference = new CheckPreference(AUDIO, "Packet jitter simulator", getter, setter); + auto preference = new CheckPreference(AUDIO_BUFFERS, "Packet jitter simulator", getter, setter); preferences->addPreference(preference); } { auto getter = []()->float { return DependencyManager::get()->getGateThreshold(); }; auto setter = [](float value) { return DependencyManager::get()->setGateThreshold(value); }; - auto preference = new SpinnerPreference(AUDIO, "Packet throttle threshold", getter, setter); + auto preference = new SpinnerPreference(AUDIO_BUFFERS, "Packet throttle threshold", getter, setter); preference->setMin(1); preference->setMax(200); preference->setStep(1); diff --git a/libraries/shared/src/Preferences.h b/libraries/shared/src/Preferences.h index f1915a9d6a..271df58951 100644 --- a/libraries/shared/src/Preferences.h +++ b/libraries/shared/src/Preferences.h @@ -52,6 +52,7 @@ public: Browsable, Slider, Spinner, + SpinnerSlider, Checkbox, Button, ComboBox, @@ -254,6 +255,15 @@ public: Type getType() override { return Spinner; } }; +class SpinnerSliderPreference : public FloatPreference { + Q_OBJECT +public: + SpinnerSliderPreference(const QString& category, const QString& name, Getter getter, Setter setter) + : FloatPreference(category, name, getter, setter) { } + + Type getType() override { return SpinnerSlider; } +}; + class IntSpinnerPreference : public IntPreference { Q_OBJECT public: From d6ac67d2215db06f6d64a05c8a0800342bdd63e7 Mon Sep 17 00:00:00 2001 From: utkarshgautamnyu Date: Wed, 12 Jul 2017 16:09:30 -0700 Subject: [PATCH 19/39] Added option for clean install --- cmake/templates/NSIS.template.in | 80 +++++++++++++++++++++----------- 1 file changed, 54 insertions(+), 26 deletions(-) diff --git a/cmake/templates/NSIS.template.in b/cmake/templates/NSIS.template.in index 710fd81316..f279bd8a71 100644 --- a/cmake/templates/NSIS.template.in +++ b/cmake/templates/NSIS.template.in @@ -49,7 +49,7 @@ Var STR_CONTAINS_VAR_3 Var STR_CONTAINS_VAR_4 Var STR_RETURN_VAR - + Function StrContains Exch $STR_NEEDLE Exch 1 @@ -267,6 +267,7 @@ Var substringResult "${SecName}_unchanged:" !macroend + ;--- End of Add/Remove macros --- ;-------------------------------- @@ -438,6 +439,7 @@ Var DesktopServerCheckbox Var ServerStartupCheckbox Var LaunchServerNowCheckbox Var LaunchClientNowCheckbox +Var CleanInstallCheckbox Var CurrentOffset Var OffsetUnits Var CopyFromProductionCheckbox @@ -479,23 +481,14 @@ Function PostInstallOptionsPage ; set the checkbox state depending on what is present in the registry !insertmacro SetPostInstallOption $DesktopClientCheckbox @CLIENT_DESKTOP_SHORTCUT_REG_KEY@ ${BST_CHECKED} ${EndIf} - + ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Create a desktop shortcut for @CONSOLE_HF_SHORTCUT_NAME@" Pop $DesktopServerCheckbox + IntOp $CurrentOffset $CurrentOffset + 15 ; set the checkbox state depending on what is present in the registry !insertmacro SetPostInstallOption $DesktopServerCheckbox @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ ${BST_UNCHECKED} - - IntOp $CurrentOffset $CurrentOffset + 15 - - ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch @CONSOLE_HF_SHORTCUT_NAME@ on startup" - Pop $ServerStartupCheckbox - - ; set the checkbox state depending on what is present in the registry - !insertmacro SetPostInstallOption $ServerStartupCheckbox @CONSOLE_STARTUP_REG_KEY@ ${BST_CHECKED} - - IntOp $CurrentOffset $CurrentOffset + 15 ${EndIf} ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} @@ -513,15 +506,34 @@ Function PostInstallOptionsPage ${EndIf} ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} - ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch @INTERFACE_HF_SHORTCUT_NAME@ after install" - Pop $LaunchClientNowCheckbox - - ; set the checkbox state depending on what is present in the registry - !insertmacro SetPostInstallOption $LaunchClientNowCheckbox @CLIENT_LAUNCH_NOW_REG_KEY@ ${BST_CHECKED} - ${StrContains} $substringResult "/forceNoLaunchClient" $CMDLINE - ${IfNot} $substringResult == "" - ${NSD_SetState} $LaunchClientNowCheckbox ${BST_UNCHECKED} - ${EndIf} + ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch @INTERFACE_HF_SHORTCUT_NAME@ after install" + Pop $LaunchClientNowCheckbox + IntOp $CurrentOffset $CurrentOffset + 30 + + ; set the checkbox state depending on what is present in the registry + !insertmacro SetPostInstallOption $LaunchClientNowCheckbox @CLIENT_LAUNCH_NOW_REG_KEY@ ${BST_CHECKED} + ${StrContains} $substringResult "/forceNoLaunchClient" $CMDLINE + ${IfNot} $substringResult == "" + ${NSD_SetState} $LaunchClientNowCheckbox ${BST_UNCHECKED} + ${EndIf} + ${EndIf} + + ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} + ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch @CONSOLE_HF_SHORTCUT_NAME@ on startup" + Pop $ServerStartupCheckbox + IntOp $CurrentOffset $CurrentOffset + 15 + + ; set the checkbox state depending on what is present in the registry + !insertmacro SetPostInstallOption $ServerStartupCheckbox @CONSOLE_STARTUP_REG_KEY@ ${BST_CHECKED} + ${EndIf} + + ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} + ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Perform a clean install (Delete older settings and content)" + Pop $CleanInstallCheckbox + IntOp $CurrentOffset $CurrentOffset + 15 + + ; not saving checkbox state in registry + !insertmacro SetPostInstallOption $CleanInstallCheckbox @@ ${BST_UNCHECKED} ${EndIf} ${If} @PR_BUILD@ == 1 @@ -543,7 +555,7 @@ Function PostInstallOptionsPage ${NSD_SetState} $CopyFromProductionCheckbox ${BST_CHECKED} ${EndIf} - + nsDialogs::Show FunctionEnd @@ -558,6 +570,7 @@ Var ServerStartupState Var LaunchServerNowState Var LaunchClientNowState Var CopyFromProductionState +Var CleanInstallState Function ReadPostInstallOptions ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} @@ -579,13 +592,18 @@ Function ReadPostInstallOptions ${EndIf} ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} - ; check if we need to launch the server post-install - ${NSD_GetState} $LaunchServerNowCheckbox $LaunchServerNowState + ; check if we need to launch the server post-install + ${NSD_GetState} $LaunchServerNowCheckbox $LaunchServerNowState ${EndIf} ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} - ; check if we need to launch the client post-install - ${NSD_GetState} $LaunchClientNowCheckbox $LaunchClientNowState + ; check if we need to launch the client post-install + ${NSD_GetState} $LaunchClientNowCheckbox $LaunchClientNowState + ${EndIf} + + ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} + ; check if the user asked for a clean install + ${NSD_GetState} $CleanInstallCheckbox $CleanInstallState ${EndIf} FunctionEnd @@ -628,6 +646,15 @@ Function HandlePostInstallOptions !insertmacro WritePostInstallOption @CONSOLE_STARTUP_REG_KEY@ NO ${EndIf} ${EndIf} + + ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} + ; check if the user asked for a clean install + ${If} $CleanInstallState == ${BST_CHECKED} + SetShellVarContext current + RMDir /r "$APPDATA\@BUILD_ORGANIZATION@" + RMDir /r "$LOCALAPPDATA\@BUILD_ORGANIZATION@" + ${EndIf} + ${EndIf} ${If} @PR_BUILD@ == 1 @@ -683,6 +710,7 @@ Function HandlePostInstallOptions ${EndIf} ${EndIf} + FunctionEnd ;-------------------------------- From 902705297f4493714aec04474efbf4c99d7e1c78 Mon Sep 17 00:00:00 2001 From: utkarshgautamnyu Date: Wed, 12 Jul 2017 16:28:21 -0700 Subject: [PATCH 20/39] indentation fix --- cmake/templates/NSIS.template.in | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/cmake/templates/NSIS.template.in b/cmake/templates/NSIS.template.in index f279bd8a71..3ff4f8986e 100644 --- a/cmake/templates/NSIS.template.in +++ b/cmake/templates/NSIS.template.in @@ -49,7 +49,7 @@ Var STR_CONTAINS_VAR_3 Var STR_CONTAINS_VAR_4 Var STR_RETURN_VAR - + Function StrContains Exch $STR_NEEDLE Exch 1 @@ -267,7 +267,6 @@ Var substringResult "${SecName}_unchanged:" !macroend - ;--- End of Add/Remove macros --- ;-------------------------------- @@ -477,7 +476,7 @@ Function PostInstallOptionsPage ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Create a desktop shortcut for @INTERFACE_HF_SHORTCUT_NAME@" Pop $DesktopClientCheckbox IntOp $CurrentOffset $CurrentOffset + 15 - + ; set the checkbox state depending on what is present in the registry !insertmacro SetPostInstallOption $DesktopClientCheckbox @CLIENT_DESKTOP_SHORTCUT_REG_KEY@ ${BST_CHECKED} ${EndIf} @@ -486,7 +485,7 @@ Function PostInstallOptionsPage ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Create a desktop shortcut for @CONSOLE_HF_SHORTCUT_NAME@" Pop $DesktopServerCheckbox IntOp $CurrentOffset $CurrentOffset + 15 - + ; set the checkbox state depending on what is present in the registry !insertmacro SetPostInstallOption $DesktopServerCheckbox @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ ${BST_UNCHECKED} ${EndIf} @@ -504,7 +503,7 @@ Function PostInstallOptionsPage IntOp $CurrentOffset $CurrentOffset + 15 ${EndIf} - + ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch @INTERFACE_HF_SHORTCUT_NAME@ after install" Pop $LaunchClientNowCheckbox @@ -595,7 +594,7 @@ Function ReadPostInstallOptions ; check if we need to launch the server post-install ${NSD_GetState} $LaunchServerNowCheckbox $LaunchServerNowState ${EndIf} - + ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} ; check if we need to launch the client post-install ${NSD_GetState} $LaunchClientNowCheckbox $LaunchClientNowState @@ -710,7 +709,6 @@ Function HandlePostInstallOptions ${EndIf} ${EndIf} - FunctionEnd ;-------------------------------- From 070db2922d1ccf0741af18a936dfdf61c43d6bbc Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 13 Jul 2017 18:40:38 +0100 Subject: [PATCH 21/39] fixed warning compile error --- interface/src/Application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 352d9e28e2..ef6ad0771d 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1,3 +1,4 @@ + // // Application.cpp // interface/src @@ -1705,7 +1706,6 @@ void Application::toggleTabletUI(bool shouldOpen) const { auto tabletScriptingInterface = DependencyManager::get(); auto hmd = DependencyManager::get(); TabletProxy* tablet = dynamic_cast(tabletScriptingInterface->getTablet(SYSTEM_TABLET)); - bool messageOpen = tablet->isMessageDialogOpen(); if (!(shouldOpen && hmd->getShouldShowTablet())) { auto HMD = DependencyManager::get(); HMD->toggleShouldShowTablet(); From b46dc7d389224d3ee609974df5ef406885502911 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 13 Jul 2017 18:42:26 +0100 Subject: [PATCH 22/39] removed space --- interface/src/Application.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ef6ad0771d..237fc3f6a4 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1,4 +1,3 @@ - // // Application.cpp // interface/src From 35f97837e797a70e63a1779b156e8e0d4f0b2d40 Mon Sep 17 00:00:00 2001 From: utkarshgautamnyu Date: Thu, 13 Jul 2017 10:50:14 -0700 Subject: [PATCH 23/39] ensured clean install state is not saved in the registry --- cmake/templates/NSIS.template.in | 3 --- 1 file changed, 3 deletions(-) diff --git a/cmake/templates/NSIS.template.in b/cmake/templates/NSIS.template.in index 3ff4f8986e..f44c8185d8 100644 --- a/cmake/templates/NSIS.template.in +++ b/cmake/templates/NSIS.template.in @@ -530,9 +530,6 @@ Function PostInstallOptionsPage ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Perform a clean install (Delete older settings and content)" Pop $CleanInstallCheckbox IntOp $CurrentOffset $CurrentOffset + 15 - - ; not saving checkbox state in registry - !insertmacro SetPostInstallOption $CleanInstallCheckbox @@ ${BST_UNCHECKED} ${EndIf} ${If} @PR_BUILD@ == 1 From 03fbd2a1e6a029723ebb063e7392bd38ce593d34 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 13 Jul 2017 19:12:34 +0100 Subject: [PATCH 24/39] fixed another warning --- interface/src/Application.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 237fc3f6a4..6a01b35230 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1704,7 +1704,6 @@ QString Application::getUserAgent() { void Application::toggleTabletUI(bool shouldOpen) const { auto tabletScriptingInterface = DependencyManager::get(); auto hmd = DependencyManager::get(); - TabletProxy* tablet = dynamic_cast(tabletScriptingInterface->getTablet(SYSTEM_TABLET)); if (!(shouldOpen && hmd->getShouldShowTablet())) { auto HMD = DependencyManager::get(); HMD->toggleShouldShowTablet(); From 521babb6bd558641667094c8139568af7ba34ee2 Mon Sep 17 00:00:00 2001 From: Cain Kilgore Date: Thu, 13 Jul 2017 19:18:51 +0100 Subject: [PATCH 25/39] Avatar Scaler - should be PR Ready. --- .../resources/qml/controls-uit/Slider.qml | 2 +- .../qml/dialogs/preferences/Section.qml | 6 +- .../preferences/SpinnerSliderPreference.qml | 54 +- interface/src/avatar/MyAvatar.cpp | 745 ++++++++++++++---- interface/src/avatar/MyAvatar.h | 131 ++- interface/src/ui/PreferencesDialog.cpp | 12 +- libraries/shared/src/Preferences.h | 2 +- 7 files changed, 738 insertions(+), 214 deletions(-) diff --git a/interface/resources/qml/controls-uit/Slider.qml b/interface/resources/qml/controls-uit/Slider.qml index 39831546e1..18d0d33fe2 100644 --- a/interface/resources/qml/controls-uit/Slider.qml +++ b/interface/resources/qml/controls-uit/Slider.qml @@ -36,7 +36,7 @@ Slider { Rectangle { width: parent.height - 2 - height: slider.value * (slider.width/(slider.maximumValue - slider.minimumValue)) - 1 + height: slider.width * (slider.value - slider.minimumValue)/(slider.maximumValue-slider.minimumValue)-1 radius: height / 2 anchors { top: parent.top diff --git a/interface/resources/qml/dialogs/preferences/Section.qml b/interface/resources/qml/dialogs/preferences/Section.qml index 61b3c7530b..a2bfa9ba0e 100644 --- a/interface/resources/qml/dialogs/preferences/Section.qml +++ b/interface/resources/qml/dialogs/preferences/Section.qml @@ -72,7 +72,7 @@ Preference { property var avatarBuilder: Component { AvatarPreference { } } property var buttonBuilder: Component { ButtonPreference { } } property var comboBoxBuilder: Component { ComboBoxPreference { } } - property var spinnerSliderBuilder: Component { SpinnerSliderPreference { } } + property var spinnerSliderBuilder: Component { SpinnerSliderPreference { } } property var preferences: [] property int checkBoxCount: 0 @@ -87,7 +87,7 @@ Preference { } function buildPreference(preference) { - console.log("\tPreference type " + preference.type + " name " + preference.name) + console.log("\tPreference type " + preference.type + " name " + preference.name); var builder; switch (preference.type) { case Preference.Editable: @@ -128,11 +128,13 @@ Preference { case Preference.ComboBox: checkBoxCount = 0; builder = comboBoxBuilder; + console.log("Built COMBOBOX"); break; case Preference.SpinnerSlider: checkBoxCount = 0; builder = spinnerSliderBuilder; + console.log("Built SPINNERSLIDER"); break; }; diff --git a/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml index e9013bc17a..5b9e70a42a 100644 --- a/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml +++ b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml @@ -1,7 +1,7 @@ // -// SpinBoxPreference.qml +// SpinnerSliderPreference.qml // -// Created by Bradley Austin Davis on 18 Jan 2016 +// Created by Cain Kilgore on 11th July 2017 // Copyright 2016 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. @@ -27,6 +27,10 @@ Preference { preference.save(); } + function sliderToAvatarScale(sliderValue) { + return MyAvatar.getDomainMinScale() + (MyAvatar.getDomainMaxScale()-MyAvatar.getDomainMinScale())*value; + } + Item { id: control anchors { @@ -53,12 +57,50 @@ Preference { Slider { id: slider value: preference.value - width: 130 + width: 100 + minimumValue: MyAvatar.getDomainMinScale() + maximumValue: MyAvatar.getDomainMaxScale() + stepSize: preference.step + onValueChanged: { + spinner.value = value + } anchors { - right: parent.right - verticalCenter: parent.verticalCenter + right: spinner.left + rightMargin: 10 + } + } + SpinBox { + id: spinner + decimals: preference.decimals + value: preference.value + minimumValue: MyAvatar.getDomainMinScale() + maximumValue: MyAvatar.getDomainMaxScale() + width: 100 + onValueChanged: { + slider.value = value; + } + anchors { + right: button.left + rightMargin: 10 } colorScheme: hifi.colorSchemes.dark } + Button { + id: button + onClicked: { + if(spinner.maximumValue >= 1) { + spinner.value = 1 + slider.value = 1 + } else { + spinner.value = spinner.maximumValue + slider.value = spinner.maximumValue + } + } + width: 50 + text: "Reset" + anchors { + right: parent.right + } + } } -} +} \ No newline at end of file diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 333c33ba3b..728fba2fc7 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -21,6 +21,7 @@ #include +#include #include #include #include @@ -38,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -86,19 +88,30 @@ const float MyAvatar::ZOOM_MAX = 25.0f; const float MyAvatar::ZOOM_DEFAULT = 1.5f; // default values, used when avatar is missing joints... (avatar space) -// static const glm::quat DEFAULT_AVATAR_MIDDLE_EYE_ROT { Quaternions::Y_180 }; +static const glm::quat DEFAULT_AVATAR_MIDDLE_EYE_ROT { Quaternions::Y_180 }; static const glm::vec3 DEFAULT_AVATAR_MIDDLE_EYE_POS { 0.0f, 0.6f, 0.0f }; static const glm::vec3 DEFAULT_AVATAR_HEAD_POS { 0.0f, 0.53f, 0.0f }; +static const glm::quat DEFAULT_AVATAR_HEAD_ROT { Quaternions::Y_180 }; +static const glm::vec3 DEFAULT_AVATAR_RIGHTARM_POS { -0.134824f, 0.396348f, -0.0515777f }; +static const glm::quat DEFAULT_AVATAR_RIGHTARM_ROT { -0.536241f, 0.536241f, -0.460918f, -0.460918f }; +static const glm::vec3 DEFAULT_AVATAR_LEFTARM_POS { 0.134795f, 0.396349f, -0.0515881f }; +static const glm::quat DEFAULT_AVATAR_LEFTARM_ROT { 0.536257f, 0.536258f, -0.460899f, 0.4609f }; +static const glm::vec3 DEFAULT_AVATAR_RIGHTHAND_POS { -0.72768f, 0.396349f, -0.0515779f }; +static const glm::quat DEFAULT_AVATAR_RIGHTHAND_ROT { 0.479184f, -0.520013f, 0.522537f, 0.476365f}; +static const glm::vec3 DEFAULT_AVATAR_LEFTHAND_POS { 0.727588f, 0.39635f, -0.0515878f }; +static const glm::quat DEFAULT_AVATAR_LEFTHAND_ROT { -0.479181f, -0.52001f, 0.52254f, -0.476369f }; static const glm::vec3 DEFAULT_AVATAR_NECK_POS { 0.0f, 0.445f, 0.025f }; static const glm::vec3 DEFAULT_AVATAR_SPINE2_POS { 0.0f, 0.32f, 0.02f }; +static const glm::quat DEFAULT_AVATAR_SPINE2_ROT { Quaternions::Y_180 }; static const glm::vec3 DEFAULT_AVATAR_HIPS_POS { 0.0f, 0.0f, 0.0f }; +static const glm::quat DEFAULT_AVATAR_HIPS_ROT { Quaternions::Y_180 }; static const glm::vec3 DEFAULT_AVATAR_LEFTFOOT_POS { -0.08f, -0.96f, 0.029f}; static const glm::quat DEFAULT_AVATAR_LEFTFOOT_ROT { -0.40167322754859924f, 0.9154590368270874f, -0.005437685176730156f, -0.023744143545627594f }; static const glm::vec3 DEFAULT_AVATAR_RIGHTFOOT_POS { 0.08f, -0.96f, 0.029f }; static const glm::quat DEFAULT_AVATAR_RIGHTFOOT_ROT { -0.4016716778278351f, 0.9154615998268127f, 0.0053307069465518f, 0.023696165531873703f }; -MyAvatar::MyAvatar(QThread* thread, RigPointer rig) : - Avatar(thread, rig), +MyAvatar::MyAvatar(QThread* thread) : + Avatar(thread), _yawSpeed(YAW_SPEED_DEFAULT), _pitchSpeed(PITCH_SPEED_DEFAULT), _scriptedMotorTimescale(DEFAULT_SCRIPTED_MOTOR_TIMESCALE), @@ -109,6 +122,9 @@ MyAvatar::MyAvatar(QThread* thread, RigPointer rig) : _realWorldFieldOfView("realWorldFieldOfView", DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES), _useAdvancedMovementControls("advancedMovementForHandControllersIsChecked", false), + _smoothOrientationTimer(std::numeric_limits::max()), + _smoothOrientationInitial(), + _smoothOrientationTarget(), _hmdSensorMatrix(), _hmdSensorOrientation(), _hmdSensorPosition(), @@ -116,7 +132,6 @@ MyAvatar::MyAvatar(QThread* thread, RigPointer rig) : _goToPending(false), _goToPosition(), _goToOrientation(), - _rig(rig), _prevShouldDrawHead(true), _audioListenerMode(FROM_HEAD), _hmdAtRestDetector(glm::vec3(0), glm::quat()) @@ -125,7 +140,7 @@ MyAvatar::MyAvatar(QThread* thread, RigPointer rig) : // give the pointer to our head to inherited _headData variable from AvatarData _headData = new MyHead(this); - _skeletonModel = std::make_shared(this, nullptr, rig); + _skeletonModel = std::make_shared(this, nullptr); connect(_skeletonModel.get(), &Model::setURLFinished, this, &Avatar::setModelURLFinished); @@ -176,9 +191,7 @@ MyAvatar::MyAvatar(QThread* thread, RigPointer rig) : auto audioIO = DependencyManager::get(); audioIO->setIsPlayingBackRecording(isPlaying); - if (_rig) { - _rig->setEnableAnimations(!isPlaying); - } + _skeletonModel->getRig().setEnableAnimations(!isPlaying); }); connect(recorder.data(), &Recorder::recordingStateChanged, [=] { @@ -229,12 +242,13 @@ MyAvatar::MyAvatar(QThread* thread, RigPointer rig) : } auto jointData = dummyAvatar.getRawJointData(); - if (jointData.length() > 0 && _rig) { - _rig->copyJointsFromJointData(jointData); + if (jointData.length() > 0) { + _skeletonModel->getRig().copyJointsFromJointData(jointData); } }); - connect(rig.get(), SIGNAL(onLoadComplete()), this, SIGNAL(onLoadComplete())); + connect(&(_skeletonModel->getRig()), SIGNAL(onLoadComplete()), this, SIGNAL(onLoadComplete())); + _characterController.setDensity(_density); } MyAvatar::~MyAvatar() { @@ -264,15 +278,27 @@ QVariant MyAvatar::getOrientationVar() const { return quatToVariant(Avatar::getOrientation()); } +glm::quat MyAvatar::getOrientationOutbound() const { + // Allows MyAvatar to send out smoothed data to remote agents if required. + if (_smoothOrientationTimer > SMOOTH_TIME_ORIENTATION) { + return (getLocalOrientation()); + } + + // Smooth the remote avatar movement. + float t = _smoothOrientationTimer / SMOOTH_TIME_ORIENTATION; + float interp = Interpolate::easeInOutQuad(glm::clamp(t, 0.0f, 1.0f)); + return (slerp(_smoothOrientationInitial, _smoothOrientationTarget, interp)); +} // virtual void MyAvatar::simulateAttachments(float deltaTime) { // don't update attachments here, do it in harvestResultsFromPhysicsSimulation() } -QByteArray MyAvatar::toByteArrayStateful(AvatarDataDetail dataDetail) { +QByteArray MyAvatar::toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking) { CameraMode mode = qApp->getCamera().getMode(); _globalPosition = getPosition(); + // This might not be right! Isn't the capsule local offset in avatar space, and don't we need to add the radius to the y as well? -HRS 5/26/17 _globalBoundingBoxDimensions.x = _characterController.getCapsuleRadius(); _globalBoundingBoxDimensions.y = _characterController.getCapsuleHalfHeight(); _globalBoundingBoxDimensions.z = _characterController.getCapsuleRadius(); @@ -290,6 +316,11 @@ QByteArray MyAvatar::toByteArrayStateful(AvatarDataDetail dataDetail) { } void MyAvatar::resetSensorsAndBody() { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "resetSensorsAndBody"); + return; + } + qApp->getActiveDisplayPlugin()->resetSensors(); reset(true, false, true); } @@ -334,9 +365,7 @@ void MyAvatar::clearIKJointLimitHistory() { return; } - if (_rig) { - _rig->clearIKJointLimitHistory(); - } + _skeletonModel->getRig().clearIKJointLimitHistory(); } void MyAvatar::reset(bool andRecenter, bool andReload, bool andHead) { @@ -385,19 +414,26 @@ void MyAvatar::update(float deltaTime) { // update moving average of HMD facing in xz plane. const float HMD_FACING_TIMESCALE = 4.0f; // very slow average float tau = deltaTime / HMD_FACING_TIMESCALE; - _hmdSensorFacingMovingAverage = lerp(_hmdSensorFacingMovingAverage, _hmdSensorFacing, tau); + _headControllerFacingMovingAverage = lerp(_headControllerFacingMovingAverage, _headControllerFacing, tau); + + if (_smoothOrientationTimer < SMOOTH_TIME_ORIENTATION) { + _rotationChanged = usecTimestampNow(); + _smoothOrientationTimer += deltaTime; + } #ifdef DEBUG_DRAW_HMD_MOVING_AVERAGE - glm::vec3 p = transformPoint(getSensorToWorldMatrix(), _hmdSensorPosition + glm::vec3(_hmdSensorFacingMovingAverage.x, 0.0f, _hmdSensorFacingMovingAverage.y)); + glm::vec3 p = transformPoint(getSensorToWorldMatrix(), getHeadControllerPoseInAvatarFrame() * + glm::vec3(_headControllerFacingMovingAverage.x, 0.0f, _headControllerFacingMovingAverage.y)); DebugDraw::getInstance().addMarker("facing-avg", getOrientation(), p, glm::vec4(1.0f)); - p = transformPoint(getSensorToWorldMatrix(), _hmdSensorPosition + glm::vec3(_hmdSensorFacing.x, 0.0f, _hmdSensorFacing.y)); + p = transformPoint(getSensorToWorldMatrix(), getHMDSensorPosition() + + glm::vec3(_headControllerFacing.x, 0.0f, _headControllerFacing.y)); DebugDraw::getInstance().addMarker("facing", getOrientation(), p, glm::vec4(1.0f)); #endif if (_goToPending) { setPosition(_goToPosition); setOrientation(_goToOrientation); - _hmdSensorFacingMovingAverage = _hmdSensorFacing; // reset moving average + _headControllerFacingMovingAverage = _headControllerFacing; // reset moving average _goToPending = false; // updateFromHMDSensorMatrix (called from paintGL) expects that the sensorToWorldMatrix is updated for any position changes // that happen between render and Application::update (which calls updateSensorToWorldMatrix to do so). @@ -405,6 +441,13 @@ void MyAvatar::update(float deltaTime) { // so we update now. It's ok if it updates again in the normal way. updateSensorToWorldMatrix(); emit positionGoneTo(); + // Run safety tests as soon as we can after goToLocation, or clear if we're not colliding. + _physicsSafetyPending = getCollisionsEnabled(); + } + if (_physicsSafetyPending && qApp->isPhysicsEnabled() && _characterController.isEnabledAndReady()) { + // When needed and ready, arrange to check and fix. + _physicsSafetyPending = false; + safeLanding(_goToPosition); // no-op if already safe } Head* head = getHead(); @@ -419,6 +462,7 @@ void MyAvatar::update(float deltaTime) { setAudioAverageLoudness(audio->getAudioAverageInputLoudness()); glm::vec3 halfBoundingBoxDimensions(_characterController.getCapsuleRadius(), _characterController.getCapsuleHalfHeight(), _characterController.getCapsuleRadius()); + // This might not be right! Isn't the capsule local offset in avatar space? -HRS 5/26/17 halfBoundingBoxDimensions += _characterController.getCapsuleLocalOffset(); QMetaObject::invokeMethod(audio.data(), "setAvatarBoundingBoxParameters", Q_ARG(glm::vec3, (getPosition() - halfBoundingBoxDimensions)), @@ -502,10 +546,9 @@ void MyAvatar::simulate(float deltaTime) { { PerformanceTimer perfTimer("skeleton"); - if (_rig) { - _rig->setEnableDebugDrawIKTargets(_enableDebugDrawIKTargets); - } - + _skeletonModel->getRig().setEnableDebugDrawIKTargets(_enableDebugDrawIKTargets); + _skeletonModel->getRig().setEnableDebugDrawIKConstraints(_enableDebugDrawIKConstraints); + _skeletonModel->getRig().setEnableDebugDrawIKChains(_enableDebugDrawIKChains); _skeletonModel->simulate(deltaTime); } @@ -523,7 +566,7 @@ void MyAvatar::simulate(float deltaTime) { PerformanceTimer perfTimer("joints"); // copy out the skeleton joints from the model if (_rigEnabled) { - _rig->copyJointsIntoJointData(_jointData); + _skeletonModel->getRig().copyJointsIntoJointData(_jointData); } } @@ -551,12 +594,12 @@ void MyAvatar::simulate(float deltaTime) { auto entityTreeRenderer = qApp->getEntities(); EntityTreePointer entityTree = entityTreeRenderer ? entityTreeRenderer->getTree() : nullptr; if (entityTree) { - bool flyingAllowed = true; + bool zoneAllowsFlying = true; bool collisionlessAllowed = true; entityTree->withWriteLock([&] { std::shared_ptr zone = entityTreeRenderer->myAvatarZone(); if (zone) { - flyingAllowed = zone->getFlyingAllowed(); + zoneAllowsFlying = zone->getFlyingAllowed(); collisionlessAllowed = zone->getGhostingAllowed(); } auto now = usecTimestampNow(); @@ -587,7 +630,7 @@ void MyAvatar::simulate(float deltaTime) { entityTree->recurseTreeWithOperator(&moveOperator); } }); - _characterController.setFlyingAllowed(flyingAllowed); + _characterController.setFlyingAllowed(zoneAllowsFlying && _enableFlying); _characterController.setCollisionlessAllowed(collisionlessAllowed); } @@ -605,15 +648,21 @@ void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix) { _hmdSensorMatrix = hmdSensorMatrix; auto newHmdSensorPosition = extractTranslation(hmdSensorMatrix); - if (newHmdSensorPosition != _hmdSensorPosition && + if (newHmdSensorPosition != getHMDSensorPosition() && glm::length(newHmdSensorPosition) > MAX_HMD_ORIGIN_DISTANCE) { qWarning() << "Invalid HMD sensor position " << newHmdSensorPosition; // Ignore unreasonable HMD sensor data return; } + _hmdSensorPosition = newHmdSensorPosition; _hmdSensorOrientation = glm::quat_cast(hmdSensorMatrix); - _hmdSensorFacing = getFacingDir2D(_hmdSensorOrientation); + auto headPose = _headControllerPoseInSensorFrameCache.get(); + if (headPose.isValid()) { + _headControllerFacing = getFacingDir2D(headPose.rotation); + } else { + _headControllerFacing = glm::vec2(1.0f, 0.0f); + } } void MyAvatar::updateJointFromController(controller::Action poseKey, ThreadSafeValueCache& matrixCache) { @@ -651,7 +700,7 @@ void MyAvatar::updateSensorToWorldMatrix() { // Update avatar head rotation with sensor data void MyAvatar::updateFromTrackers(float deltaTime) { - glm::vec3 estimatedPosition, estimatedRotation; + glm::vec3 estimatedRotation; bool inHmd = qApp->isHMDMode(); bool playing = DependencyManager::get()->isPlaying(); @@ -662,11 +711,7 @@ void MyAvatar::updateFromTrackers(float deltaTime) { FaceTracker* tracker = qApp->getActiveFaceTracker(); bool inFacetracker = tracker && !FaceTracker::isMuted(); - if (inHmd) { - estimatedPosition = extractTranslation(getHMDSensorMatrix()); - estimatedPosition.x *= -1.0f; - } else if (inFacetracker) { - estimatedPosition = tracker->getHeadTranslation(); + if (inFacetracker) { estimatedRotation = glm::degrees(safeEulerAngles(tracker->getHeadRotation())); } @@ -753,6 +798,77 @@ controller::Pose MyAvatar::getRightHandTipPose() const { return pose; } +glm::vec3 MyAvatar::worldToJointPoint(const glm::vec3& position, const int jointIndex) const { + glm::vec3 jointPos = getPosition();//default value if no or invalid joint specified + glm::quat jointRot = getRotation();//default value if no or invalid joint specified + if (jointIndex != -1) { + if (_skeletonModel->getJointPositionInWorldFrame(jointIndex, jointPos)) { + _skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot); + } else { + qWarning() << "Invalid joint index specified: " << jointIndex; + } + } + glm::vec3 modelOffset = position - jointPos; + glm::vec3 jointSpacePosition = glm::inverse(jointRot) * modelOffset; + + return jointSpacePosition; +} + +glm::vec3 MyAvatar::worldToJointDirection(const glm::vec3& worldDir, const int jointIndex) const { + glm::quat jointRot = getRotation();//default value if no or invalid joint specified + if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) { + qWarning() << "Invalid joint index specified: " << jointIndex; + } + + glm::vec3 jointSpaceDir = glm::inverse(jointRot) * worldDir; + return jointSpaceDir; +} + +glm::quat MyAvatar::worldToJointRotation(const glm::quat& worldRot, const int jointIndex) const { + glm::quat jointRot = getRotation();//default value if no or invalid joint specified + if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) { + qWarning() << "Invalid joint index specified: " << jointIndex; + } + glm::quat jointSpaceRot = glm::inverse(jointRot) * worldRot; + return jointSpaceRot; +} + +glm::vec3 MyAvatar::jointToWorldPoint(const glm::vec3& jointSpacePos, const int jointIndex) const { + glm::vec3 jointPos = getPosition();//default value if no or invalid joint specified + glm::quat jointRot = getRotation();//default value if no or invalid joint specified + + if (jointIndex != -1) { + if (_skeletonModel->getJointPositionInWorldFrame(jointIndex, jointPos)) { + _skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot); + } else { + qWarning() << "Invalid joint index specified: " << jointIndex; + } + } + + glm::vec3 worldOffset = jointRot * jointSpacePos; + glm::vec3 worldPos = jointPos + worldOffset; + + return worldPos; +} + +glm::vec3 MyAvatar::jointToWorldDirection(const glm::vec3& jointSpaceDir, const int jointIndex) const { + glm::quat jointRot = getRotation();//default value if no or invalid joint specified + if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) { + qWarning() << "Invalid joint index specified: " << jointIndex; + } + glm::vec3 worldDir = jointRot * jointSpaceDir; + return worldDir; +} + +glm::quat MyAvatar::jointToWorldRotation(const glm::quat& jointSpaceRot, const int jointIndex) const { + glm::quat jointRot = getRotation();//default value if no or invalid joint specified + if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) { + qWarning() << "Invalid joint index specified: " << jointIndex; + } + glm::quat worldRot = jointRot * jointSpaceRot; + return worldRot; +} + // virtual void MyAvatar::render(RenderArgs* renderArgs) { // don't render if we've been asked to disable local rendering @@ -768,7 +884,7 @@ void MyAvatar::overrideAnimation(const QString& url, float fps, bool loop, float Q_ARG(bool, loop), Q_ARG(float, firstFrame), Q_ARG(float, lastFrame)); return; } - _rig->overrideAnimation(url, fps, loop, firstFrame, lastFrame); + _skeletonModel->getRig().overrideAnimation(url, fps, loop, firstFrame, lastFrame); } void MyAvatar::restoreAnimation() { @@ -776,16 +892,16 @@ void MyAvatar::restoreAnimation() { QMetaObject::invokeMethod(this, "restoreAnimation"); return; } - _rig->restoreAnimation(); + _skeletonModel->getRig().restoreAnimation(); } QStringList MyAvatar::getAnimationRoles() { if (QThread::currentThread() != thread()) { QStringList result; - QMetaObject::invokeMethod(this, "getAnimationRoles", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QStringList, result)); + BLOCKING_INVOKE_METHOD(this, "getAnimationRoles", Q_RETURN_ARG(QStringList, result)); return result; } - return _rig->getAnimationRoles(); + return _skeletonModel->getRig().getAnimationRoles(); } void MyAvatar::overrideRoleAnimation(const QString& role, const QString& url, float fps, bool loop, @@ -795,7 +911,7 @@ void MyAvatar::overrideRoleAnimation(const QString& role, const QString& url, fl Q_ARG(float, fps), Q_ARG(bool, loop), Q_ARG(float, firstFrame), Q_ARG(float, lastFrame)); return; } - _rig->overrideRoleAnimation(role, url, fps, loop, firstFrame, lastFrame); + _skeletonModel->getRig().overrideRoleAnimation(role, url, fps, loop, firstFrame, lastFrame); } void MyAvatar::restoreRoleAnimation(const QString& role) { @@ -803,7 +919,7 @@ void MyAvatar::restoreRoleAnimation(const QString& role) { QMetaObject::invokeMethod(this, "restoreRoleAnimation", Q_ARG(const QString&, role)); return; } - _rig->restoreRoleAnimation(role); + _skeletonModel->getRig().restoreRoleAnimation(role); } void MyAvatar::saveData() { @@ -814,10 +930,15 @@ void MyAvatar::saveData() { settings.setValue("scale", _targetScale); - settings.setValue("fullAvatarURL", + // only save the fullAvatarURL if it has not been overwritten on command line + // (so the overrideURL is not valid), or it was overridden _and_ we specified + // --replaceAvatarURL (so _saveAvatarOverrideUrl is true) + if (qApp->getSaveAvatarOverrideUrl() || !qApp->getAvatarOverrideUrl().isValid() ) { + settings.setValue("fullAvatarURL", _fullAvatarURLFromPreferences == AvatarData::defaultFullAvatarModelUrl() ? "" : _fullAvatarURLFromPreferences.toString()); + } settings.setValue("fullAvatarModelName", _fullAvatarModelName); @@ -927,6 +1048,14 @@ void MyAvatar::setEnableDebugDrawIKTargets(bool isEnabled) { _enableDebugDrawIKTargets = isEnabled; } +void MyAvatar::setEnableDebugDrawIKConstraints(bool isEnabled) { + _enableDebugDrawIKConstraints = isEnabled; +} + +void MyAvatar::setEnableDebugDrawIKChains(bool isEnabled) { + _enableDebugDrawIKChains = isEnabled; +} + void MyAvatar::setEnableMeshVisible(bool isEnabled) { _skeletonModel->setVisibleInScene(isEnabled, qApp->getMain3DScene()); } @@ -937,7 +1066,7 @@ void MyAvatar::setUseAnimPreAndPostRotations(bool isEnabled) { } void MyAvatar::setEnableInverseKinematics(bool isEnabled) { - _rig->setEnableInverseKinematics(isEnabled); + _skeletonModel->getRig().setEnableInverseKinematics(isEnabled); } void MyAvatar::loadData() { @@ -1186,7 +1315,7 @@ void MyAvatar::setJointData(int index, const glm::quat& rotation, const glm::vec return; } // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority - _rig->setJointState(index, true, rotation, translation, SCRIPT_PRIORITY); + _skeletonModel->getRig().setJointState(index, true, rotation, translation, SCRIPT_PRIORITY); } void MyAvatar::setJointRotation(int index, const glm::quat& rotation) { @@ -1195,7 +1324,7 @@ void MyAvatar::setJointRotation(int index, const glm::quat& rotation) { return; } // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority - _rig->setJointRotation(index, true, rotation, SCRIPT_PRIORITY); + _skeletonModel->getRig().setJointRotation(index, true, rotation, SCRIPT_PRIORITY); } void MyAvatar::setJointTranslation(int index, const glm::vec3& translation) { @@ -1204,7 +1333,7 @@ void MyAvatar::setJointTranslation(int index, const glm::vec3& translation) { return; } // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority - _rig->setJointTranslation(index, true, translation, SCRIPT_PRIORITY); + _skeletonModel->getRig().setJointTranslation(index, true, translation, SCRIPT_PRIORITY); } void MyAvatar::clearJointData(int index) { @@ -1212,7 +1341,7 @@ void MyAvatar::clearJointData(int index) { QMetaObject::invokeMethod(this, "clearJointData", Q_ARG(int, index)); return; } - _rig->clearJointAnimationPriority(index); + _skeletonModel->getRig().clearJointAnimationPriority(index); } void MyAvatar::clearJointsData() { @@ -1220,13 +1349,14 @@ void MyAvatar::clearJointsData() { QMetaObject::invokeMethod(this, "clearJointsData"); return; } - _rig->clearJointStates(); + _skeletonModel->getRig().clearJointStates(); } void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { Avatar::setSkeletonModelURL(skeletonModelURL); _skeletonModel->setVisibleInScene(true, qApp->getMain3DScene()); _headBoneSet.clear(); + emit skeletonChanged(); } @@ -1240,7 +1370,7 @@ void MyAvatar::resetFullAvatarURL() { void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelName) { if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "useFullAvatarURL", Qt::BlockingQueuedConnection, + BLOCKING_INVOKE_METHOD(this, "useFullAvatarURL", Q_ARG(const QUrl&, fullAvatarURL), Q_ARG(const QString&, modelName)); return; @@ -1266,7 +1396,7 @@ void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelN void MyAvatar::setAttachmentData(const QVector& attachmentData) { if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "setAttachmentData", Qt::BlockingQueuedConnection, + BLOCKING_INVOKE_METHOD(this, "setAttachmentData", Q_ARG(const QVector, attachmentData)); return; } @@ -1295,28 +1425,10 @@ void MyAvatar::rebuildCollisionShape() { _characterController.setLocalBoundingBox(corner, diagonal); } -static controller::Pose applyLowVelocityFilter(const controller::Pose& oldPose, const controller::Pose& newPose) { - controller::Pose finalPose = newPose; - if (newPose.isValid()) { - // Use a velocity sensitive filter to damp small motions and preserve large ones with - // no latency. - float velocityFilter = glm::clamp(1.0f - glm::length(oldPose.getVelocity()), 0.0f, 1.0f); - finalPose.translation = oldPose.getTranslation() * velocityFilter + newPose.getTranslation() * (1.0f - velocityFilter); - finalPose.rotation = safeMix(oldPose.getRotation(), newPose.getRotation(), 1.0f - velocityFilter); - } - return finalPose; -} void MyAvatar::setHandControllerPosesInSensorFrame(const controller::Pose& left, const controller::Pose& right) { - if (controller::InputDevice::getLowVelocityFilter()) { - auto oldLeftPose = getLeftHandControllerPoseInSensorFrame(); - auto oldRightPose = getRightHandControllerPoseInSensorFrame(); - _leftHandControllerPoseInSensorFrameCache.set(applyLowVelocityFilter(oldLeftPose, left)); - _rightHandControllerPoseInSensorFrameCache.set(applyLowVelocityFilter(oldRightPose, right)); - } else { - _leftHandControllerPoseInSensorFrameCache.set(left); - _rightHandControllerPoseInSensorFrameCache.set(right); - } + _leftHandControllerPoseInSensorFrameCache.set(left); + _rightHandControllerPoseInSensorFrameCache.set(right); } controller::Pose MyAvatar::getLeftHandControllerPoseInSensorFrame() const { @@ -1345,16 +1457,22 @@ controller::Pose MyAvatar::getRightHandControllerPoseInAvatarFrame() const { return getRightHandControllerPoseInWorldFrame().transform(invAvatarMatrix); } +void MyAvatar::setFingerControllerPosesInSensorFrame(const FingerPosesMap& left, const FingerPosesMap& right) { + _leftHandFingerPosesInSensorFramceCache.set(left); + _rightHandFingerPosesInSensorFramceCache.set(right); +} + +MyAvatar::FingerPosesMap MyAvatar::getLeftHandFingerControllerPosesInSensorFrame() const { + return _leftHandFingerPosesInSensorFramceCache.get(); +} + +MyAvatar::FingerPosesMap MyAvatar::getRightHandFingerControllerPosesInSensorFrame() const { + return _rightHandFingerPosesInSensorFramceCache.get(); +} + void MyAvatar::setFootControllerPosesInSensorFrame(const controller::Pose& left, const controller::Pose& right) { - if (controller::InputDevice::getLowVelocityFilter()) { - auto oldLeftPose = getLeftFootControllerPoseInSensorFrame(); - auto oldRightPose = getRightFootControllerPoseInSensorFrame(); - _leftFootControllerPoseInSensorFrameCache.set(applyLowVelocityFilter(oldLeftPose, left)); - _rightFootControllerPoseInSensorFrameCache.set(applyLowVelocityFilter(oldRightPose, right)); - } else { - _leftFootControllerPoseInSensorFrameCache.set(left); - _rightFootControllerPoseInSensorFrameCache.set(right); - } + _leftFootControllerPoseInSensorFrameCache.set(left); + _rightFootControllerPoseInSensorFrameCache.set(right); } controller::Pose MyAvatar::getLeftFootControllerPoseInSensorFrame() const { @@ -1384,15 +1502,8 @@ controller::Pose MyAvatar::getRightFootControllerPoseInAvatarFrame() const { } void MyAvatar::setSpineControllerPosesInSensorFrame(const controller::Pose& hips, const controller::Pose& spine2) { - if (controller::InputDevice::getLowVelocityFilter()) { - auto oldHipsPose = getHipsControllerPoseInSensorFrame(); - auto oldSpine2Pose = getSpine2ControllerPoseInSensorFrame(); - _hipsControllerPoseInSensorFrameCache.set(applyLowVelocityFilter(oldHipsPose, hips)); - _spine2ControllerPoseInSensorFrameCache.set(applyLowVelocityFilter(oldSpine2Pose, spine2)); - } else { - _hipsControllerPoseInSensorFrameCache.set(hips); - _spine2ControllerPoseInSensorFrameCache.set(spine2); - } + _hipsControllerPoseInSensorFrameCache.set(hips); + _spine2ControllerPoseInSensorFrameCache.set(spine2); } controller::Pose MyAvatar::getHipsControllerPoseInSensorFrame() const { @@ -1422,12 +1533,7 @@ controller::Pose MyAvatar::getSpine2ControllerPoseInAvatarFrame() const { } void MyAvatar::setHeadControllerPoseInSensorFrame(const controller::Pose& head) { - if (controller::InputDevice::getLowVelocityFilter()) { - auto oldHeadPose = getHeadControllerPoseInSensorFrame(); - _headControllerPoseInSensorFrameCache.set(applyLowVelocityFilter(oldHeadPose, head)); - } else { - _headControllerPoseInSensorFrameCache.set(head); - } + _headControllerPoseInSensorFrameCache.set(head); } controller::Pose MyAvatar::getHeadControllerPoseInSensorFrame() const { @@ -1443,18 +1549,49 @@ controller::Pose MyAvatar::getHeadControllerPoseInAvatarFrame() const { return getHeadControllerPoseInWorldFrame().transform(invAvatarMatrix); } +void MyAvatar::setArmControllerPosesInSensorFrame(const controller::Pose& left, const controller::Pose& right) { + _leftArmControllerPoseInSensorFrameCache.set(left); + _rightArmControllerPoseInSensorFrameCache.set(right); +} + +controller::Pose MyAvatar::getLeftArmControllerPoseInSensorFrame() const { + return _leftArmControllerPoseInSensorFrameCache.get(); +} + +controller::Pose MyAvatar::getRightArmControllerPoseInSensorFrame() const { + return _rightArmControllerPoseInSensorFrameCache.get(); +} + +controller::Pose MyAvatar::getLeftArmControllerPoseInWorldFrame() const { + return getLeftArmControllerPoseInSensorFrame().transform(getSensorToWorldMatrix()); +} + +controller::Pose MyAvatar::getRightArmControllerPoseInWorldFrame() const { + return getRightArmControllerPoseInSensorFrame().transform(getSensorToWorldMatrix()); +} + +controller::Pose MyAvatar::getLeftArmControllerPoseInAvatarFrame() const { + glm::mat4 worldToAvatarMat = glm::inverse(createMatFromQuatAndPos(getOrientation(), getPosition())); + return getLeftArmControllerPoseInWorldFrame().transform(worldToAvatarMat); +} + +controller::Pose MyAvatar::getRightArmControllerPoseInAvatarFrame() const { + glm::mat4 worldToAvatarMat = glm::inverse(createMatFromQuatAndPos(getOrientation(), getPosition())); + return getRightArmControllerPoseInWorldFrame().transform(worldToAvatarMat); +} + void MyAvatar::updateMotors() { _characterController.clearMotors(); glm::quat motorRotation; if (_motionBehaviors & AVATAR_MOTION_ACTION_MOTOR_ENABLED) { if (_characterController.getState() == CharacterController::State::Hover || _characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) { - motorRotation = getMyHead()->getCameraOrientation(); + motorRotation = getMyHead()->getHeadOrientation(); } else { // non-hovering = walking: follow camera twist about vertical but not lift // so we decompose camera's rotation and store the twist part in motorRotation glm::quat liftRotation; - swingTwistDecomposition(getMyHead()->getCameraOrientation(), _worldUpDirection, liftRotation, motorRotation); + swingTwistDecomposition(getMyHead()->getHeadOrientation(), _worldUpDirection, liftRotation, motorRotation); } const float DEFAULT_MOTOR_TIMESCALE = 0.2f; const float INVALID_MOTOR_TIMESCALE = 1.0e6f; @@ -1468,7 +1605,7 @@ void MyAvatar::updateMotors() { } if (_motionBehaviors & AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED) { if (_scriptedMotorFrame == SCRIPTED_MOTOR_CAMERA_FRAME) { - motorRotation = getMyHead()->getCameraOrientation() * glm::angleAxis(PI, Vectors::UNIT_Y); + motorRotation = getMyHead()->getHeadOrientation() * glm::angleAxis(PI, Vectors::UNIT_Y); } else if (_scriptedMotorFrame == SCRIPTED_MOTOR_AVATAR_FRAME) { motorRotation = getOrientation() * glm::angleAxis(PI, Vectors::UNIT_Y); } else { @@ -1498,7 +1635,8 @@ void MyAvatar::prepareForPhysicsSimulation() { _characterController.setParentVelocity(parentVelocity); _characterController.setPositionAndOrientation(getPosition(), getOrientation()); - if (qApp->isHMDMode()) { + auto headPose = getHeadControllerPoseInAvatarFrame(); + if (headPose.isValid()) { _follow.prePhysicsUpdate(*this, deriveBodyFromHMDSensor(), _bodySensorMatrix, hasDriveInput()); } else { _follow.deactivate(); @@ -1518,6 +1656,10 @@ void MyAvatar::harvestResultsFromPhysicsSimulation(float deltaTime) { if (_characterController.isEnabledAndReady()) { setVelocity(_characterController.getLinearVelocity() + _characterController.getFollowVelocity()); + if (_characterController.isStuck()) { + _physicsSafetyPending = true; + _goToPosition = getPosition(); + } } else { setVelocity(getVelocity() + _characterController.getFollowVelocity()); } @@ -1663,7 +1805,7 @@ void MyAvatar::setAnimGraphUrl(const QUrl& url) { _skeletonModel->reset(); // Why is this necessary? Without this, we crash in the next render. _currentAnimGraphUrl.set(url); - _rig->initAnimGraph(url); + _skeletonModel->getRig().initAnimGraph(url); _bodySensorMatrix = deriveBodyFromHMDSensor(); // Based on current cached HMD position/rotation.. updateSensorToWorldMatrix(); // Uses updated position/orientation and _bodySensorMatrix changes @@ -1679,7 +1821,7 @@ void MyAvatar::initAnimGraph() { graphUrl = QUrl::fromLocalFile(PathUtils::resourcesPath() + "avatar/avatar-animation.json"); } - _rig->initAnimGraph(graphUrl); + _skeletonModel->getRig().initAnimGraph(graphUrl); _currentAnimGraphUrl.set(graphUrl); _bodySensorMatrix = deriveBodyFromHMDSensor(); // Based on current cached HMD position/rotation.. @@ -1687,7 +1829,7 @@ void MyAvatar::initAnimGraph() { } void MyAvatar::destroyAnimGraph() { - _rig->destroyAnimGraph(); + _skeletonModel->getRig().destroyAnimGraph(); } void MyAvatar::postUpdate(float deltaTime) { @@ -1703,22 +1845,23 @@ void MyAvatar::postUpdate(float deltaTime) { if (_enableDebugDrawDefaultPose || _enableDebugDrawAnimPose) { - auto animSkeleton = _rig->getAnimSkeleton(); + auto animSkeleton = _skeletonModel->getRig().getAnimSkeleton(); // the rig is in the skeletonModel frame AnimPose xform(glm::vec3(1), _skeletonModel->getRotation(), _skeletonModel->getTranslation()); if (_enableDebugDrawDefaultPose && animSkeleton) { glm::vec4 gray(0.2f, 0.2f, 0.2f, 0.2f); - AnimDebugDraw::getInstance().addAbsolutePoses("myAvatarDefaultPoses", animSkeleton, _rig->getAbsoluteDefaultPoses(), xform, gray); + AnimDebugDraw::getInstance().addAbsolutePoses("myAvatarDefaultPoses", animSkeleton, _skeletonModel->getRig().getAbsoluteDefaultPoses(), xform, gray); } if (_enableDebugDrawAnimPose && animSkeleton) { // build absolute AnimPoseVec from rig AnimPoseVec absPoses; - absPoses.reserve(_rig->getJointStateCount()); - for (int i = 0; i < _rig->getJointStateCount(); i++) { - absPoses.push_back(AnimPose(_rig->getJointTransform(i))); + const Rig& rig = _skeletonModel->getRig(); + absPoses.reserve(rig.getJointStateCount()); + for (int i = 0; i < rig.getJointStateCount(); i++) { + absPoses.push_back(AnimPose(rig.getJointTransform(i))); } glm::vec4 cyan(0.1f, 0.6f, 0.6f, 1.0f); AnimDebugDraw::getInstance().addAbsolutePoses("myAvatarAnimPoses", animSkeleton, absPoses, xform, cyan); @@ -1762,15 +1905,14 @@ void MyAvatar::preDisplaySide(RenderArgs* renderArgs) { const float RENDER_HEAD_CUTOFF_DISTANCE = 0.3f; -bool MyAvatar::cameraInsideHead() const { - const glm::vec3 cameraPosition = qApp->getCamera().getPosition(); +bool MyAvatar::cameraInsideHead(const glm::vec3& cameraPosition) const { return glm::length(cameraPosition - getHeadPosition()) < (RENDER_HEAD_CUTOFF_DISTANCE * getUniformScale()); } bool MyAvatar::shouldRenderHead(const RenderArgs* renderArgs) const { bool defaultMode = renderArgs->_renderMode == RenderArgs::DEFAULT_RENDER_MODE; bool firstPerson = qApp->getCamera().getMode() == CAMERA_MODE_FIRST_PERSON; - bool insideHead = cameraInsideHead(); + bool insideHead = cameraInsideHead(renderArgs->getViewFrustum().getPosition()); return !defaultMode || !firstPerson || !insideHead; } @@ -1806,15 +1948,17 @@ void MyAvatar::updateOrientation(float deltaTime) { // Comfort Mode: If you press any of the left/right rotation drive keys or input, you'll // get an instantaneous 15 degree turn. If you keep holding the key down you'll get another // snap turn every half second. + bool snapTurn = false; if (getDriveKey(STEP_YAW) != 0.0f) { totalBodyYaw += getDriveKey(STEP_YAW); + snapTurn = true; } // use head/HMD orientation to turn while flying if (getCharacterController()->getState() == CharacterController::State::Hover) { // This is the direction the user desires to fly in. - glm::vec3 desiredFacing = getMyHead()->getCameraOrientation() * Vectors::UNIT_Z; + glm::vec3 desiredFacing = getMyHead()->getHeadOrientation() * Vectors::UNIT_Z; desiredFacing.y = 0.0f; // This is our reference frame, it is captured when the user begins to move. @@ -1840,17 +1984,48 @@ void MyAvatar::updateOrientation(float deltaTime) { totalBodyYaw += (speedFactor * deltaAngle * (180.0f / PI)); } + // Use head/HMD roll to turn while walking or flying. + if (qApp->isHMDMode() && _hmdRollControlEnabled) { + // Turn with head roll. + const float MIN_CONTROL_SPEED = 0.01f; + float speed = glm::length(getVelocity()); + if (speed >= MIN_CONTROL_SPEED) { + // Feather turn when stopping moving. + float speedFactor; + if (getDriveKey(TRANSLATE_Z) != 0.0f || _lastDrivenSpeed == 0.0f) { + _lastDrivenSpeed = speed; + speedFactor = 1.0f; + } else { + speedFactor = glm::min(speed / _lastDrivenSpeed, 1.0f); + } + + float direction = glm::dot(getVelocity(), getRotation() * Vectors::UNIT_NEG_Z) > 0.0f ? 1.0f : -1.0f; + + float rollAngle = glm::degrees(asinf(glm::dot(IDENTITY_UP, _hmdSensorOrientation * IDENTITY_RIGHT))); + float rollSign = rollAngle < 0.0f ? -1.0f : 1.0f; + rollAngle = fabsf(rollAngle); + rollAngle = rollAngle > _hmdRollControlDeadZone ? rollSign * (rollAngle - _hmdRollControlDeadZone) : 0.0f; + + totalBodyYaw += speedFactor * direction * rollAngle * deltaTime * _hmdRollControlRate; + } + } // update body orientation by movement inputs + glm::quat initialOrientation = getOrientationOutbound(); setOrientation(getOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, totalBodyYaw, 0.0f)))); + if (snapTurn) { + // Whether or not there is an existing smoothing going on, just reset the smoothing timer and set the starting position as the avatar's current position, then smooth to the new position. + _smoothOrientationInitial = initialOrientation; + _smoothOrientationTarget = getOrientation(); + _smoothOrientationTimer = 0.0f; + } + getHead()->setBasePitch(getHead()->getBasePitch() + getDriveKey(PITCH) * _pitchSpeed * deltaTime); - if (qApp->isHMDMode()) { - glm::quat orientation = glm::quat_cast(getSensorToWorldMatrix()) * getHMDSensorOrientation(); - glm::quat bodyOrientation = getWorldBodyOrientation(); - glm::quat localOrientation = glm::inverse(bodyOrientation) * orientation; - + auto headPose = getHeadControllerPoseInAvatarFrame(); + if (headPose.isValid()) { + glm::quat localOrientation = headPose.rotation * Quaternions::Y_180; // these angles will be in radians // ... so they need to be converted to degrees before we do math... glm::vec3 euler = glm::eulerAngles(localOrientation) * DEGREES_PER_RADIAN; @@ -1964,11 +2139,14 @@ void MyAvatar::updatePosition(float deltaTime) { } // capture the head rotation, in sensor space, when the user first indicates they would like to move/fly. - if (!_hoverReferenceCameraFacingIsCaptured && (fabs(getDriveKey(TRANSLATE_Z)) > 0.1f || fabs(getDriveKey(TRANSLATE_X)) > 0.1f)) { + if (!_hoverReferenceCameraFacingIsCaptured && + (fabs(getDriveKey(TRANSLATE_Z)) > 0.1f || fabs(getDriveKey(TRANSLATE_X)) > 0.1f)) { _hoverReferenceCameraFacingIsCaptured = true; // transform the camera facing vector into sensor space. - _hoverReferenceCameraFacing = transformVectorFast(glm::inverse(_sensorToWorldMatrix), getMyHead()->getCameraOrientation() * Vectors::UNIT_Z); - } else if (_hoverReferenceCameraFacingIsCaptured && (fabs(getDriveKey(TRANSLATE_Z)) <= 0.1f && fabs(getDriveKey(TRANSLATE_X)) <= 0.1f)) { + _hoverReferenceCameraFacing = transformVectorFast(glm::inverse(_sensorToWorldMatrix), + getMyHead()->getHeadOrientation() * Vectors::UNIT_Z); + } else if (_hoverReferenceCameraFacingIsCaptured && + (fabs(getDriveKey(TRANSLATE_Z)) <= 0.1f && fabs(getDriveKey(TRANSLATE_X)) <= 0.1f)) { _hoverReferenceCameraFacingIsCaptured = false; } } @@ -2049,6 +2227,14 @@ void MyAvatar::clampScaleChangeToDomainLimits(float desiredScale) { qCDebug(interfaceapp, "Changed scale to %f", (double)_targetScale); } +float MyAvatar::getDomainMinScale() { + return _domainMinimumScale; +} + +float MyAvatar::getDomainMaxScale() { + return _domainMaximumScale; +} + void MyAvatar::increaseSize() { // make sure we're starting from an allowable scale clampTargetScaleToDomainLimits(); @@ -2179,6 +2365,144 @@ void MyAvatar::goToLocation(const glm::vec3& newPosition, emit transformChanged(); } +void MyAvatar::goToLocationAndEnableCollisions(const glm::vec3& position) { // See use case in safeLanding. + goToLocation(position); + QMetaObject::invokeMethod(this, "setCollisionsEnabled", Qt::QueuedConnection, Q_ARG(bool, true)); +} +bool MyAvatar::safeLanding(const glm::vec3& position) { + // Considers all collision hull or non-collisionless primitive intersections on a vertical line through the point. + // There needs to be a "landing" if: + // a) the closest above and the closest below are less than the avatar capsule height apart, or + // b) the above point is the top surface of an entity, indicating that we are inside it. + // If no landing is required, we go to that point directly and return false; + // When a landing is required by a, we find the highest intersection on that closest-agbove entity + // (which may be that same "nearest above intersection"). That highest intersection is the candidate landing point. + // For b, use that top surface point. + // We then place our feet there, recurse with new capsule center point, and return true. + + if (QThread::currentThread() != thread()) { + bool result; + BLOCKING_INVOKE_METHOD(this, "safeLanding", Q_RETURN_ARG(bool, result), Q_ARG(const glm::vec3&, position)); + return result; + } + glm::vec3 better; + if (!requiresSafeLanding(position, better)) { + return false; + } + if (!getCollisionsEnabled()) { + goToLocation(better); // recurses on next update + } else { // If you try to go while stuck, physics will keep you stuck. + setCollisionsEnabled(false); + // Don't goToLocation just yet. Yield so that physics can act on the above. + QMetaObject::invokeMethod(this, "goToLocationAndEnableCollisions", Qt::QueuedConnection, // The equivalent of javascript nextTick + Q_ARG(glm::vec3, better)); + } + return true; +} + +// If position is not reliably safe from being stuck by physics, answer true and place a candidate better position in betterPositionOut. +bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& betterPositionOut) { + // We begin with utilities and tests. The Algorithm in four parts is below. + auto halfHeight = _characterController.getCapsuleHalfHeight() + _characterController.getCapsuleRadius(); + if (halfHeight == 0) { + return false; // zero height avatar + } + auto entityTree = DependencyManager::get()->getTree(); + if (!entityTree) { + return false; // no entity tree + } + // More utilities. + const auto offset = getOrientation() *_characterController.getCapsuleLocalOffset(); + const auto capsuleCenter = positionIn + offset; + const auto up = _worldUpDirection, down = -up; + glm::vec3 upperIntersection, upperNormal, lowerIntersection, lowerNormal; + EntityItemID upperId, lowerId; + QVector include{}, ignore{}; + auto mustMove = [&] { // Place bottom of capsule at the upperIntersection, and check again based on the capsule center. + betterPositionOut = upperIntersection + (up * halfHeight) - offset; + return true; + }; + auto findIntersection = [&](const glm::vec3& startPointIn, const glm::vec3& directionIn, glm::vec3& intersectionOut, EntityItemID& entityIdOut, glm::vec3& normalOut) { + OctreeElementPointer element; + EntityItemPointer intersectedEntity = NULL; + float distance; + BoxFace face; + const bool visibleOnly = false; + // This isn't quite what we really want here. findRayIntersection always works on mesh, skipping entirely based on collidable. + // What we really want is to use the collision hull! + // See https://highfidelity.fogbugz.com/f/cases/5003/findRayIntersection-has-option-to-use-collidableOnly-but-doesn-t-actually-use-colliders + const bool collidableOnly = true; + const bool precisionPicking = true; + const auto lockType = Octree::Lock; // Should we refactor to take a lock just once? + bool* accurateResult = NULL; + + bool intersects = entityTree->findRayIntersection(startPointIn, directionIn, include, ignore, visibleOnly, collidableOnly, precisionPicking, + element, distance, face, normalOut, (void**)&intersectedEntity, lockType, accurateResult); + if (!intersects || !intersectedEntity) { + return false; + } + intersectionOut = startPointIn + (directionIn * distance); + entityIdOut = intersectedEntity->getEntityItemID(); + return true; + }; + + // The Algorithm, in four parts: + + if (!findIntersection(capsuleCenter, up, upperIntersection, upperId, upperNormal)) { + // We currently believe that physics will reliably push us out if our feet are embedded, + // as long as our capsule center is out and there's room above us. Here we have those + // conditions, so no need to check our feet below. + return false; // nothing above + } + + if (!findIntersection(capsuleCenter, down, lowerIntersection, lowerId, lowerNormal)) { + // Our head may be embedded, but our center is out and there's room below. See corresponding comment above. + return false; // nothing below + } + + // See if we have room between entities above and below, but that we are not contained. + // First check if the surface above us is the bottom of something, and the surface below us it the top of something. + // I.e., we are in a clearing between two objects. + if (isDown(upperNormal) && isUp(lowerNormal)) { + auto spaceBetween = glm::distance(upperIntersection, lowerIntersection); + const float halfHeightFactor = 2.5f; // Until case 5003 is fixed (and maybe after?), we need a fudge factor. Also account for content modelers not being precise. + if (spaceBetween > (halfHeightFactor * halfHeight)) { + // There is room for us to fit in that clearing. If there wasn't, physics would oscilate us between the objects above and below. + // We're now going to iterate upwards through successive upperIntersections, testing to see if we're contained within the top surface of some entity. + // There will be one of two outcomes: + // a) We're not contained, so we have enough room and our position is good. + // b) We are contained, so we'll bail out of this but try again at a position above the containing entity. + const int iterationLimit = 1000; + for (int counter = 0; counter < iterationLimit; counter++) { + ignore.push_back(upperId); + if (!findIntersection(upperIntersection, up, upperIntersection, upperId, upperNormal)) { + // We're not inside an entity, and from the nested tests, we have room between what is above and below. So position is good! + return false; // enough room + } + if (isUp(upperNormal)) { + // This new intersection is the top surface of an entity that we have not yet seen, which means we're contained within it. + // We could break here and recurse from the top of the original ceiling, but since we've already done the work to find the top + // of the enclosing entity, let's put our feet at upperIntersection and start over. + return mustMove(); + } + // We found a new bottom surface, which we're not interested in. + // But there could still be a top surface above us for an entity we haven't seen, so keep looking upward. + } + qCDebug(interfaceapp) << "Loop in requiresSafeLanding. Floor/ceiling do not make sense."; + } + } + + include.push_back(upperId); // We're now looking for the intersection from above onto this entity. + const float big = (float)TREE_SCALE; + const auto skyHigh = up * big; + auto fromAbove = capsuleCenter + skyHigh; + if (!findIntersection(fromAbove, down, upperIntersection, upperId, upperNormal)) { + return false; // Unable to find a landing + } + // Our arbitrary rule is to always go up. There's no need to look down or sideways for a "closer" safe candidate. + return mustMove(); +} + void MyAvatar::updateMotionBehaviorFromMenu() { if (QThread::currentThread() != thread()) { @@ -2200,6 +2524,45 @@ void MyAvatar::updateMotionBehaviorFromMenu() { setCollisionsEnabled(menu->isOptionChecked(MenuOption::EnableAvatarCollisions)); } +void MyAvatar::setFlyingEnabled(bool enabled) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setFlyingEnabled", Q_ARG(bool, enabled)); + return; + } + + _enableFlying = enabled; +} + +bool MyAvatar::isFlying() { + // Avatar is Flying, and is not Falling, or Taking off + return _characterController.getState() == CharacterController::State::Hover; +} + +bool MyAvatar::isInAir() { + // If Avatar is Hover, Falling, or Taking off, they are in Air. + return _characterController.getState() != CharacterController::State::Ground; +} + +bool MyAvatar::getFlyingEnabled() { + // May return true even if client is not allowed to fly in the zone. + return _enableFlying; +} + +// Public interface for targetscale +float MyAvatar::getAvatarScale() { + return getTargetScale(); +} + +void MyAvatar::setAvatarScale(float val) { + + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setAvatarScale", Q_ARG(float, val)); + return; + } + + setTargetScale(val); +} + void MyAvatar::setCollisionsEnabled(bool enabled) { if (QThread::currentThread() != thread()) { @@ -2282,35 +2645,27 @@ bool MyAvatar::isDriveKeyDisabled(DriveKeys key) const { } } -glm::vec3 MyAvatar::getWorldBodyPosition() const { - return transformPoint(_sensorToWorldMatrix, extractTranslation(_bodySensorMatrix)); -} - -glm::quat MyAvatar::getWorldBodyOrientation() const { - return glm::quat_cast(_sensorToWorldMatrix * _bodySensorMatrix); -} - // old school meat hook style glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const { - - // HMD is in sensor space. - const glm::vec3 hmdPosition = getHMDSensorPosition(); - const glm::quat hmdOrientation = getHMDSensorOrientation(); - const glm::quat hmdOrientationYawOnly = cancelOutRollAndPitch(hmdOrientation); - - int rightEyeIndex = _rig->indexOfJoint("RightEye"); - int leftEyeIndex = _rig->indexOfJoint("LeftEye"); - int neckIndex = _rig->indexOfJoint("Neck"); - int hipsIndex = _rig->indexOfJoint("Hips"); - - glm::vec3 rigMiddleEyePos = DEFAULT_AVATAR_MIDDLE_EYE_POS; - if (leftEyeIndex >= 0 && rightEyeIndex >= 0) { - rigMiddleEyePos = (_rig->getAbsoluteDefaultPose(leftEyeIndex).trans() + _rig->getAbsoluteDefaultPose(rightEyeIndex).trans()) / 2.0f; + glm::vec3 headPosition; + glm::quat headOrientation; + auto headPose = getHeadControllerPoseInSensorFrame(); + if (headPose.isValid()) { + headPosition = getHeadControllerPoseInSensorFrame().translation; + headOrientation = getHeadControllerPoseInSensorFrame().rotation * Quaternions::Y_180; } - glm::vec3 rigNeckPos = neckIndex != -1 ? _rig->getAbsoluteDefaultPose(neckIndex).trans() : DEFAULT_AVATAR_NECK_POS; - glm::vec3 rigHipsPos = hipsIndex != -1 ? _rig->getAbsoluteDefaultPose(hipsIndex).trans() : DEFAULT_AVATAR_HIPS_POS; + const glm::quat headOrientationYawOnly = cancelOutRollAndPitch(headOrientation); - glm::vec3 localEyes = (rigMiddleEyePos - rigHipsPos); + const Rig& rig = _skeletonModel->getRig(); + int headIndex = rig.indexOfJoint("Head"); + int neckIndex = rig.indexOfJoint("Neck"); + int hipsIndex = rig.indexOfJoint("Hips"); + + glm::vec3 rigHeadPos = headIndex != -1 ? rig.getAbsoluteDefaultPose(headIndex).trans() : DEFAULT_AVATAR_HEAD_POS; + glm::vec3 rigNeckPos = neckIndex != -1 ? rig.getAbsoluteDefaultPose(neckIndex).trans() : DEFAULT_AVATAR_NECK_POS; + glm::vec3 rigHipsPos = hipsIndex != -1 ? rig.getAbsoluteDefaultPose(hipsIndex).trans() : DEFAULT_AVATAR_HIPS_POS; + + glm::vec3 localHead = (rigHeadPos - rigHipsPos); glm::vec3 localNeck = (rigNeckPos - rigHipsPos); // apply simplistic head/neck model @@ -2319,11 +2674,11 @@ glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const { // eyeToNeck offset is relative full HMD orientation. // while neckToRoot offset is only relative to HMDs yaw. // Y_180 is necessary because rig is z forward and hmdOrientation is -z forward - glm::vec3 eyeToNeck = hmdOrientation * Quaternions::Y_180 * (localNeck - localEyes); - glm::vec3 neckToRoot = hmdOrientationYawOnly * Quaternions::Y_180 * -localNeck; - glm::vec3 bodyPos = hmdPosition + eyeToNeck + neckToRoot; + glm::vec3 headToNeck = headOrientation * Quaternions::Y_180 * (localNeck - localHead); + glm::vec3 neckToRoot = headOrientationYawOnly * Quaternions::Y_180 * -localNeck; + glm::vec3 bodyPos = headPosition + headToNeck + neckToRoot; - return createMatFromQuatAndPos(hmdOrientationYawOnly, bodyPos); + return createMatFromQuatAndPos(headOrientationYawOnly, bodyPos); } glm::vec3 MyAvatar::getPositionForAudio() { @@ -2439,7 +2794,7 @@ bool MyAvatar::FollowHelper::shouldActivateRotation(const MyAvatar& myAvatar, co } else { const float FOLLOW_ROTATION_THRESHOLD = cosf(PI / 6.0f); // 30 degrees glm::vec2 bodyFacing = getFacingDir2D(currentBodyMatrix); - return glm::dot(myAvatar.getHMDSensorFacingMovingAverage(), bodyFacing) < FOLLOW_ROTATION_THRESHOLD; + return glm::dot(-myAvatar.getHeadControllerFacingMovingAverage(), bodyFacing) < FOLLOW_ROTATION_THRESHOLD; } } @@ -2476,11 +2831,11 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, co return (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM); } -void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput) { - _desiredBodyMatrix = desiredBodyMatrix; +void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, + const glm::mat4& currentBodyMatrix, bool hasDriveInput) { if (myAvatar.getHMDLeanRecenterEnabled()) { - if (!isActive(Rotation) && shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix)) { + if (!isActive(Rotation) && (shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { activate(Rotation); } if (!isActive(Horizontal) && shouldActivateHorizontal(myAvatar, desiredBodyMatrix, currentBodyMatrix)) { @@ -2491,7 +2846,7 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat } } - glm::mat4 desiredWorldMatrix = myAvatar.getSensorToWorldMatrix() * _desiredBodyMatrix; + glm::mat4 desiredWorldMatrix = myAvatar.getSensorToWorldMatrix() * desiredBodyMatrix; glm::mat4 currentWorldMatrix = myAvatar.getSensorToWorldMatrix() * currentBodyMatrix; AnimPose followWorldPose(currentWorldMatrix); @@ -2586,9 +2941,10 @@ glm::mat4 MyAvatar::computeCameraRelativeHandControllerMatrix(const glm::mat4& c cameraWorldMatrix *= createMatFromScaleQuatAndPos(vec3(-1.0f, 1.0f, 1.0f), glm::quat(), glm::vec3()); } - // compute a NEW sensorToWorldMatrix for the camera. The equation is cameraWorldMatrix = cameraSensorToWorldMatrix * _hmdSensorMatrix. + // compute a NEW sensorToWorldMatrix for the camera. + // The equation is cameraWorldMatrix = cameraSensorToWorldMatrix * _hmdSensorMatrix. // here we solve for the unknown cameraSensorToWorldMatrix. - glm::mat4 cameraSensorToWorldMatrix = cameraWorldMatrix * glm::inverse(_hmdSensorMatrix); + glm::mat4 cameraSensorToWorldMatrix = cameraWorldMatrix * glm::inverse(getHMDSensorMatrix()); // Using the new cameraSensorToWorldMatrix, compute where the controller is in world space. glm::mat4 controllerWorldMatrix = cameraSensorToWorldMatrix * controllerSensorMatrix; @@ -2674,74 +3030,119 @@ glm::vec3 MyAvatar::getAbsoluteJointTranslationInObjectFrame(int index) const { glm::mat4 MyAvatar::getCenterEyeCalibrationMat() const { // TODO: as an optimization cache this computation, then invalidate the cache when the avatar model is changed. - int rightEyeIndex = _rig->indexOfJoint("RightEye"); - int leftEyeIndex = _rig->indexOfJoint("LeftEye"); + int rightEyeIndex = _skeletonModel->getRig().indexOfJoint("RightEye"); + int leftEyeIndex = _skeletonModel->getRig().indexOfJoint("LeftEye"); if (rightEyeIndex >= 0 && leftEyeIndex >= 0) { auto centerEyePos = (getAbsoluteDefaultJointTranslationInObjectFrame(rightEyeIndex) + getAbsoluteDefaultJointTranslationInObjectFrame(leftEyeIndex)) * 0.5f; auto centerEyeRot = Quaternions::Y_180; return createMatFromQuatAndPos(centerEyeRot, centerEyePos); } else { - return createMatFromQuatAndPos(DEFAULT_AVATAR_MIDDLE_EYE_POS, DEFAULT_AVATAR_MIDDLE_EYE_POS); + return createMatFromQuatAndPos(DEFAULT_AVATAR_MIDDLE_EYE_ROT, DEFAULT_AVATAR_MIDDLE_EYE_POS); } } glm::mat4 MyAvatar::getHeadCalibrationMat() const { // TODO: as an optimization cache this computation, then invalidate the cache when the avatar model is changed. - int headIndex = _rig->indexOfJoint("Head"); + int headIndex = _skeletonModel->getRig().indexOfJoint("Head"); if (headIndex >= 0) { auto headPos = getAbsoluteDefaultJointTranslationInObjectFrame(headIndex); auto headRot = getAbsoluteDefaultJointRotationInObjectFrame(headIndex); return createMatFromQuatAndPos(headRot, headPos); } else { - return createMatFromQuatAndPos(DEFAULT_AVATAR_HEAD_POS, DEFAULT_AVATAR_HEAD_POS); + return createMatFromQuatAndPos(DEFAULT_AVATAR_HEAD_ROT, DEFAULT_AVATAR_HEAD_POS); } } glm::mat4 MyAvatar::getSpine2CalibrationMat() const { // TODO: as an optimization cache this computation, then invalidate the cache when the avatar model is changed. - int spine2Index = _rig->indexOfJoint("Spine2"); + int spine2Index = _skeletonModel->getRig().indexOfJoint("Spine2"); if (spine2Index >= 0) { auto spine2Pos = getAbsoluteDefaultJointTranslationInObjectFrame(spine2Index); auto spine2Rot = getAbsoluteDefaultJointRotationInObjectFrame(spine2Index); return createMatFromQuatAndPos(spine2Rot, spine2Pos); } else { - return createMatFromQuatAndPos(DEFAULT_AVATAR_SPINE2_POS, DEFAULT_AVATAR_SPINE2_POS); + return createMatFromQuatAndPos(DEFAULT_AVATAR_SPINE2_ROT, DEFAULT_AVATAR_SPINE2_POS); } } glm::mat4 MyAvatar::getHipsCalibrationMat() const { // TODO: as an optimization cache this computation, then invalidate the cache when the avatar model is changed. - int hipsIndex = _rig->indexOfJoint("Hips"); + int hipsIndex = _skeletonModel->getRig().indexOfJoint("Hips"); if (hipsIndex >= 0) { auto hipsPos = getAbsoluteDefaultJointTranslationInObjectFrame(hipsIndex); auto hipsRot = getAbsoluteDefaultJointRotationInObjectFrame(hipsIndex); return createMatFromQuatAndPos(hipsRot, hipsPos); } else { - return createMatFromQuatAndPos(DEFAULT_AVATAR_HIPS_POS, DEFAULT_AVATAR_HIPS_POS); + return createMatFromQuatAndPos(DEFAULT_AVATAR_HIPS_ROT, DEFAULT_AVATAR_HIPS_POS); } } glm::mat4 MyAvatar::getLeftFootCalibrationMat() const { // TODO: as an optimization cache this computation, then invalidate the cache when the avatar model is changed. - int leftFootIndex = _rig->indexOfJoint("LeftFoot"); + int leftFootIndex = _skeletonModel->getRig().indexOfJoint("LeftFoot"); if (leftFootIndex >= 0) { auto leftFootPos = getAbsoluteDefaultJointTranslationInObjectFrame(leftFootIndex); auto leftFootRot = getAbsoluteDefaultJointRotationInObjectFrame(leftFootIndex); return createMatFromQuatAndPos(leftFootRot, leftFootPos); } else { - return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTFOOT_POS, DEFAULT_AVATAR_LEFTFOOT_POS); + return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTFOOT_ROT, DEFAULT_AVATAR_LEFTFOOT_POS); } } glm::mat4 MyAvatar::getRightFootCalibrationMat() const { // TODO: as an optimization cache this computation, then invalidate the cache when the avatar model is changed. - int rightFootIndex = _rig->indexOfJoint("RightFoot"); + int rightFootIndex = _skeletonModel->getRig().indexOfJoint("RightFoot"); if (rightFootIndex >= 0) { auto rightFootPos = getAbsoluteDefaultJointTranslationInObjectFrame(rightFootIndex); auto rightFootRot = getAbsoluteDefaultJointRotationInObjectFrame(rightFootIndex); return createMatFromQuatAndPos(rightFootRot, rightFootPos); } else { - return createMatFromQuatAndPos(DEFAULT_AVATAR_RIGHTFOOT_POS, DEFAULT_AVATAR_RIGHTFOOT_POS); + return createMatFromQuatAndPos(DEFAULT_AVATAR_RIGHTFOOT_ROT, DEFAULT_AVATAR_RIGHTFOOT_POS); + } +} + + +glm::mat4 MyAvatar::getRightArmCalibrationMat() const { + int rightArmIndex = _skeletonModel->getRig().indexOfJoint("RightArm"); + if (rightArmIndex >= 0) { + auto rightArmPos = getAbsoluteDefaultJointTranslationInObjectFrame(rightArmIndex); + auto rightArmRot = getAbsoluteDefaultJointRotationInObjectFrame(rightArmIndex); + return createMatFromQuatAndPos(rightArmRot, rightArmPos); + } else { + return createMatFromQuatAndPos(DEFAULT_AVATAR_RIGHTARM_ROT, DEFAULT_AVATAR_RIGHTARM_POS); + } +} + +glm::mat4 MyAvatar::getLeftArmCalibrationMat() const { + int leftArmIndex = _skeletonModel->getRig().indexOfJoint("LeftArm"); + if (leftArmIndex >= 0) { + auto leftArmPos = getAbsoluteDefaultJointTranslationInObjectFrame(leftArmIndex); + auto leftArmRot = getAbsoluteDefaultJointRotationInObjectFrame(leftArmIndex); + return createMatFromQuatAndPos(leftArmRot, leftArmPos); + } else { + return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTARM_ROT, DEFAULT_AVATAR_LEFTARM_POS); + } +} + +glm::mat4 MyAvatar::getRightHandCalibrationMat() const { + int rightHandIndex = _skeletonModel->getRig().indexOfJoint("RightHand"); + if (rightHandIndex >= 0) { + auto rightHandPos = getAbsoluteDefaultJointTranslationInObjectFrame(rightHandIndex); + auto rightHandRot = getAbsoluteDefaultJointRotationInObjectFrame(rightHandIndex); + return createMatFromQuatAndPos(rightHandRot, rightHandPos); + } else { + return createMatFromQuatAndPos(DEFAULT_AVATAR_RIGHTHAND_ROT, DEFAULT_AVATAR_RIGHTHAND_POS); + } +} + +glm::mat4 MyAvatar::getLeftHandCalibrationMat() const { + int leftHandIndex = _skeletonModel->getRig().indexOfJoint("LeftHand"); + if (leftHandIndex >= 0) { + auto leftHandPos = getAbsoluteDefaultJointTranslationInObjectFrame(leftHandIndex); + auto leftHandRot = getAbsoluteDefaultJointRotationInObjectFrame(leftHandIndex); + return createMatFromQuatAndPos(leftHandRot, leftHandPos); + } else { + return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTHAND_ROT, DEFAULT_AVATAR_LEFTHAND_POS); } } @@ -2755,7 +3156,7 @@ bool MyAvatar::pinJoint(int index, const glm::vec3& position, const glm::quat& o slamPosition(position); setOrientation(orientation); - _rig->setMaxHipsOffsetLength(0.05f); + _skeletonModel->getRig().setMaxHipsOffsetLength(0.05f); auto it = std::find(_pinnedJoints.begin(), _pinnedJoints.end(), index); if (it == _pinnedJoints.end()) { @@ -2772,7 +3173,7 @@ bool MyAvatar::clearPinOnJoint(int index) { auto hipsIndex = getJointIndex("Hips"); if (index == hipsIndex) { - _rig->setMaxHipsOffsetLength(FLT_MAX); + _skeletonModel->getRig().setMaxHipsOffsetLength(FLT_MAX); } return true; @@ -2781,7 +3182,7 @@ bool MyAvatar::clearPinOnJoint(int index) { } float MyAvatar::getIKErrorOnLastSolve() const { - return _rig->getIKErrorOnLastSolve(); + return _skeletonModel->getRig().getIKErrorOnLastSolve(); } // thread-safe @@ -2817,6 +3218,6 @@ void MyAvatar::updateHoldActions(const AnimPose& prePhysicsPose, const AnimPose& } } -const MyHead* MyAvatar::getMyHead() const { - return static_cast(getHead()); +const MyHead* MyAvatar::getMyHead() const { + return static_cast(getHead()); } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 04fa37cb1d..648a5b5f29 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -56,6 +56,7 @@ class MyAvatar : public Avatar { * * @namespace MyAvatar * @augments Avatar + * @property qmlPosition {Vec3} Used as a stopgap for position access by QML, as glm::vec3 is unavailable outside of scripts * @property shouldRenderLocally {bool} Set it to true if you would like to see MyAvatar in your local interface, * and false if you would not like to see MyAvatar in your local interface. * @property motorVelocity {Vec3} Can be used to move the avatar with this velocity. @@ -101,6 +102,10 @@ class MyAvatar : public Avatar { * "scripts/system/controllers/toggleAdvancedMovementForHandControllers.js". */ + // FIXME: `glm::vec3 position` is not accessible from QML, so this exposes position in a QML-native type + Q_PROPERTY(QVector3D qmlPosition READ getQmlPosition) + QVector3D getQmlPosition() { auto p = getPosition(); return QVector3D(p.x, p.y, p.z); } + Q_PROPERTY(bool shouldRenderLocally READ getShouldRenderLocally WRITE setShouldRenderLocally) Q_PROPERTY(glm::vec3 motorVelocity READ getScriptedMotorVelocity WRITE setScriptedMotorVelocity) Q_PROPERTY(float motorTimescale READ getScriptedMotorTimescale WRITE setScriptedMotorTimescale) @@ -125,13 +130,17 @@ class MyAvatar : public Avatar { Q_PROPERTY(controller::Pose rightHandTipPose READ getRightHandTipPose) Q_PROPERTY(float energy READ getEnergy WRITE setEnergy) - Q_PROPERTY(float isAway READ getIsAway WRITE setAway) + Q_PROPERTY(bool isAway READ getIsAway WRITE setAway) Q_PROPERTY(bool hmdLeanRecenterEnabled READ getHMDLeanRecenterEnabled WRITE setHMDLeanRecenterEnabled) Q_PROPERTY(bool collisionsEnabled READ getCollisionsEnabled WRITE setCollisionsEnabled) Q_PROPERTY(bool characterControllerEnabled READ getCharacterControllerEnabled WRITE setCharacterControllerEnabled) Q_PROPERTY(bool useAdvancedMovementControls READ useAdvancedMovementControls WRITE setUseAdvancedMovementControls) + Q_PROPERTY(bool hmdRollControlEnabled READ getHMDRollControlEnabled WRITE setHMDRollControlEnabled) + Q_PROPERTY(float hmdRollControlDeadZone READ getHMDRollControlDeadZone WRITE setHMDRollControlDeadZone) + Q_PROPERTY(float hmdRollControlRate READ getHMDRollControlRate WRITE setHMDRollControlRate) + public: enum DriveKeys { TRANSLATE_X = 0, @@ -148,7 +157,7 @@ public: }; Q_ENUM(DriveKeys) - explicit MyAvatar(QThread* thread, RigPointer rig); + explicit MyAvatar(QThread* thread); ~MyAvatar(); void instantiableAvatar() override {}; @@ -185,11 +194,12 @@ public: const glm::mat4& getHMDSensorMatrix() const { return _hmdSensorMatrix; } const glm::vec3& getHMDSensorPosition() const { return _hmdSensorPosition; } const glm::quat& getHMDSensorOrientation() const { return _hmdSensorOrientation; } - const glm::vec2& getHMDSensorFacingMovingAverage() const { return _hmdSensorFacingMovingAverage; } Q_INVOKABLE void setOrientationVar(const QVariant& newOrientationVar); Q_INVOKABLE QVariant getOrientationVar() const; + // A method intended to be overriden by MyAvatar for polling orientation for network transmission. + glm::quat getOrientationOutbound() const override; // Pass a recent sample of the HMD to the avatar. // This can also update the avatar's position to follow the HMD @@ -320,9 +330,9 @@ public: // adding one of the other handlers. While any handler may change a value in animStateDictionaryIn (or supply different values in animStateDictionaryOut) // a handler must not remove properties from animStateDictionaryIn, nor change property values that it does not intend to change. // It is not specified in what order multiple handlers are called. - Q_INVOKABLE QScriptValue addAnimationStateHandler(QScriptValue handler, QScriptValue propertiesList) { return _rig->addAnimationStateHandler(handler, propertiesList); } + Q_INVOKABLE QScriptValue addAnimationStateHandler(QScriptValue handler, QScriptValue propertiesList) { return _skeletonModel->getRig().addAnimationStateHandler(handler, propertiesList); } // Removes a handler previously added by addAnimationStateHandler. - Q_INVOKABLE void removeAnimationStateHandler(QScriptValue handler) { _rig->removeAnimationStateHandler(handler); } + Q_INVOKABLE void removeAnimationStateHandler(QScriptValue handler) { _skeletonModel->getRig().removeAnimationStateHandler(handler); } Q_INVOKABLE bool getSnapTurn() const { return _useSnapTurn; } Q_INVOKABLE void setSnapTurn(bool on) { _useSnapTurn = on; } @@ -336,6 +346,13 @@ public: void setUseAdvancedMovementControls(bool useAdvancedMovementControls) { _useAdvancedMovementControls.set(useAdvancedMovementControls); } + void setHMDRollControlEnabled(bool value) { _hmdRollControlEnabled = value; } + bool getHMDRollControlEnabled() const { return _hmdRollControlEnabled; } + void setHMDRollControlDeadZone(float value) { _hmdRollControlDeadZone = value; } + float getHMDRollControlDeadZone() const { return _hmdRollControlDeadZone; } + void setHMDRollControlRate(float value) { _hmdRollControlRate = value; } + float getHMDRollControlRate() const { return _hmdRollControlRate; } + // get/set avatar data void saveData(); void loadData(); @@ -349,7 +366,7 @@ public: float getDriveKey(DriveKeys key) const; Q_INVOKABLE float getRawDriveKey(DriveKeys key) const; void relayDriveKeysToCharacterController(); - + Q_INVOKABLE void disableDriveKey(DriveKeys key); Q_INVOKABLE void enableDriveKey(DriveKeys key); Q_INVOKABLE bool isDriveKeyDisabled(DriveKeys key) const; @@ -377,6 +394,15 @@ public: Q_INVOKABLE controller::Pose getLeftHandTipPose() const; Q_INVOKABLE controller::Pose getRightHandTipPose() const; + // world-space to avatar-space rigconversion functions + Q_INVOKABLE glm::vec3 worldToJointPoint(const glm::vec3& position, const int jointIndex = -1) const; + Q_INVOKABLE glm::vec3 worldToJointDirection(const glm::vec3& direction, const int jointIndex = -1) const; + Q_INVOKABLE glm::quat worldToJointRotation(const glm::quat& rotation, const int jointIndex = -1) const; + + Q_INVOKABLE glm::vec3 jointToWorldPoint(const glm::vec3& position, const int jointIndex = -1) const; + Q_INVOKABLE glm::vec3 jointToWorldDirection(const glm::vec3& direction, const int jointIndex = -1) const; + Q_INVOKABLE glm::quat jointToWorldRotation(const glm::quat& rotation, const int jointIndex = -1) const; + AvatarWeakPointer getLookAtTargetAvatar() const { return _lookAtTargetAvatar; } void updateLookAtTargetAvatar(); void clearLookAtTargetAvatar(); @@ -448,6 +474,11 @@ public: controller::Pose getLeftHandControllerPoseInAvatarFrame() const; controller::Pose getRightHandControllerPoseInAvatarFrame() const; + typedef std::map> FingerPosesMap; + void setFingerControllerPosesInSensorFrame(const FingerPosesMap& left, const FingerPosesMap& right); + FingerPosesMap getLeftHandFingerControllerPosesInSensorFrame() const; + FingerPosesMap getRightHandFingerControllerPosesInSensorFrame() const; + void setFootControllerPosesInSensorFrame(const controller::Pose& left, const controller::Pose& right); controller::Pose getLeftFootControllerPoseInSensorFrame() const; controller::Pose getRightFootControllerPoseInSensorFrame() const; @@ -468,9 +499,27 @@ public: controller::Pose getHeadControllerPoseInSensorFrame() const; controller::Pose getHeadControllerPoseInWorldFrame() const; controller::Pose getHeadControllerPoseInAvatarFrame() const; + const glm::vec2& getHeadControllerFacingMovingAverage() const { return _headControllerFacingMovingAverage; } + + + void setArmControllerPosesInSensorFrame(const controller::Pose& left, const controller::Pose& right); + controller::Pose getLeftArmControllerPoseInSensorFrame() const; + controller::Pose getRightArmControllerPoseInSensorFrame() const; + controller::Pose getLeftArmControllerPoseInWorldFrame() const; + controller::Pose getRightArmControllerPoseInWorldFrame() const; + controller::Pose getLeftArmControllerPoseInAvatarFrame() const; + controller::Pose getRightArmControllerPoseInAvatarFrame() const; bool hasDriveInput() const; + Q_INVOKABLE bool isFlying(); + Q_INVOKABLE bool isInAir(); + Q_INVOKABLE void setFlyingEnabled(bool enabled); + Q_INVOKABLE bool getFlyingEnabled(); + + Q_INVOKABLE float getAvatarScale(); + Q_INVOKABLE void setAvatarScale(float scale); + Q_INVOKABLE void setCollisionsEnabled(bool enabled); Q_INVOKABLE bool getCollisionsEnabled(); Q_INVOKABLE void setCharacterControllerEnabled(bool enabled); // deprecated @@ -486,6 +535,10 @@ public: glm::mat4 getHipsCalibrationMat() const; glm::mat4 getLeftFootCalibrationMat() const; glm::mat4 getRightFootCalibrationMat() const; + glm::mat4 getRightArmCalibrationMat() const; + glm::mat4 getLeftArmCalibrationMat() const; + glm::mat4 getLeftHandCalibrationMat() const; + glm::mat4 getRightHandCalibrationMat() const; void addHoldAction(AvatarActionHold* holdAction); // thread-safe void removeHoldAction(AvatarActionHold* holdAction); // thread-safe @@ -495,15 +548,23 @@ public: // results are in HMD frame glm::mat4 deriveBodyFromHMDSensor() const; + Q_INVOKABLE bool isUp(const glm::vec3& direction) { return glm::dot(direction, _worldUpDirection) > 0.0f; }; // true iff direction points up wrt avatar's definition of up. + Q_INVOKABLE bool isDown(const glm::vec3& direction) { return glm::dot(direction, _worldUpDirection) < 0.0f; }; + + public slots: void increaseSize(); void decreaseSize(); void resetSize(); + float getDomainMinScale(); + float getDomainMaxScale(); void goToLocation(const glm::vec3& newPosition, bool hasOrientation = false, const glm::quat& newOrientation = glm::quat(), bool shouldFaceLocation = false); void goToLocation(const QVariant& properties); + void goToLocationAndEnableCollisions(const glm::vec3& newPosition); + bool safeLanding(const glm::vec3& position); void restrictScaleFromDomainSettings(const QJsonObject& domainSettingsObject); void clearScaleRestriction(); @@ -521,6 +582,9 @@ public slots: void setEnableDebugDrawHandControllers(bool isEnabled); void setEnableDebugDrawSensorToWorldMatrix(bool isEnabled); void setEnableDebugDrawIKTargets(bool isEnabled); + void setEnableDebugDrawIKConstraints(bool isEnabled); + void setEnableDebugDrawIKChains(bool isEnabled); + bool getEnableMeshVisible() const { return _skeletonModel->isVisible(); } void setEnableMeshVisible(bool isEnabled); void setUseAnimPreAndPostRotations(bool isEnabled); @@ -544,14 +608,13 @@ signals: void onLoadComplete(); void wentAway(); void wentActive(); + void skeletonChanged(); private: - glm::vec3 getWorldBodyPosition() const; - glm::quat getWorldBodyOrientation() const; + bool requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& positionOut); - - virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail) override; + virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking) override; void simulate(float deltaTime); void updateFromTrackers(float deltaTime); @@ -574,7 +637,7 @@ private: float scale = 1.0f, bool isSoft = false, bool allowDuplicates = false, bool useSaved = true) override; - bool cameraInsideHead() const; + bool cameraInsideHead(const glm::vec3& cameraPosition) const; void updateEyeContactTarget(float deltaTime); @@ -595,6 +658,7 @@ private: std::array _driveKeys; std::bitset _disabledDriveKeys; + bool _enableFlying { true }; bool _wasPushing { false }; bool _isPushing { false }; bool _isBeingPushed { false }; @@ -632,6 +696,14 @@ private: Setting::Handle _realWorldFieldOfView; Setting::Handle _useAdvancedMovementControls; + // Smoothing. + const float SMOOTH_TIME_ORIENTATION = 0.5f; + + // Smoothing data for blending from one position/orientation to another on remote agents. + float _smoothOrientationTimer; + glm::quat _smoothOrientationInitial; + glm::quat _smoothOrientationTarget; + // private methods void updateOrientation(float deltaTime); void updateActionMotor(float deltaTime); @@ -649,16 +721,23 @@ private: bool _useSnapTurn { true }; bool _clearOverlayWhenMoving { true }; + const float ROLL_CONTROL_DEAD_ZONE_DEFAULT = 8.0f; // deg + const float ROLL_CONTROL_RATE_DEFAULT = 2.5f; // deg/sec/deg + bool _hmdRollControlEnabled { true }; + float _hmdRollControlDeadZone { ROLL_CONTROL_DEAD_ZONE_DEFAULT }; + float _hmdRollControlRate { ROLL_CONTROL_RATE_DEFAULT }; + float _lastDrivenSpeed { 0.0f }; + // working copies -- see AvatarData for thread-safe _sensorToWorldMatrixCache, used for outward facing access glm::mat4 _sensorToWorldMatrix { glm::mat4() }; - // cache of the current HMD sensor position and orientation - // in sensor space. + // cache of the current HMD sensor position and orientation in sensor space. glm::mat4 _hmdSensorMatrix; glm::quat _hmdSensorOrientation; glm::vec3 _hmdSensorPosition; - glm::vec2 _hmdSensorFacing; // facing vector in xz plane - glm::vec2 _hmdSensorFacingMovingAverage { 0, 0 }; // facing vector in xz plane + // cache head controller pose in sensor space + glm::vec2 _headControllerFacing; // facing vector in xz plane + glm::vec2 _headControllerFacingMovingAverage { 0, 0 }; // facing vector in xz plane // cache of the current body position and orientation of the avatar's body, // in sensor space. @@ -673,7 +752,6 @@ private: Vertical, NumFollowTypes }; - glm::mat4 _desiredBodyMatrix; float _timeRemaining[NumFollowTypes]; void deactivate(); @@ -692,12 +770,12 @@ private: }; FollowHelper _follow; - bool _goToPending; + bool _goToPending { false }; + bool _physicsSafetyPending { false }; glm::vec3 _goToPosition; glm::quat _goToOrientation; std::unordered_set _headBoneSet; - RigPointer _rig; bool _prevShouldDrawHead; bool _rigEnabled { true }; @@ -706,6 +784,8 @@ private: bool _enableDebugDrawHandControllers { false }; bool _enableDebugDrawSensorToWorldMatrix { false }; bool _enableDebugDrawIKTargets { false }; + bool _enableDebugDrawIKConstraints { false }; + bool _enableDebugDrawIKChains { false }; AudioListenerMode _audioListenerMode; glm::vec3 _customListenPosition; @@ -719,14 +799,17 @@ private: // These are stored in SENSOR frame ThreadSafeValueCache _leftHandControllerPoseInSensorFrameCache { controller::Pose() }; ThreadSafeValueCache _rightHandControllerPoseInSensorFrameCache { controller::Pose() }; - ThreadSafeValueCache _leftFootControllerPoseInSensorFrameCache{ controller::Pose() }; - ThreadSafeValueCache _rightFootControllerPoseInSensorFrameCache{ controller::Pose() }; - ThreadSafeValueCache _hipsControllerPoseInSensorFrameCache{ controller::Pose() }; - ThreadSafeValueCache _spine2ControllerPoseInSensorFrameCache{ controller::Pose() }; - ThreadSafeValueCache _headControllerPoseInSensorFrameCache{ controller::Pose() }; + ThreadSafeValueCache _leftHandFingerPosesInSensorFramceCache { }; + ThreadSafeValueCache _rightHandFingerPosesInSensorFramceCache { }; + ThreadSafeValueCache _leftFootControllerPoseInSensorFrameCache { controller::Pose() }; + ThreadSafeValueCache _rightFootControllerPoseInSensorFrameCache { controller::Pose() }; + ThreadSafeValueCache _hipsControllerPoseInSensorFrameCache { controller::Pose() }; + ThreadSafeValueCache _spine2ControllerPoseInSensorFrameCache { controller::Pose() }; + ThreadSafeValueCache _headControllerPoseInSensorFrameCache { controller::Pose() }; + ThreadSafeValueCache _leftArmControllerPoseInSensorFrameCache { controller::Pose() }; + ThreadSafeValueCache _rightArmControllerPoseInSensorFrameCache { controller::Pose() }; bool _hmdLeanRecenterEnabled = true; - AnimPose _prePhysicsRoomPose; std::mutex _holdActionsMutex; std::vector _holdActions; diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 9c1b3e8c70..acbfc8edf3 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -184,14 +184,10 @@ void setupPreferences() { { auto getter = [=]()->float { return myAvatar->getUniformScale(); }; auto setter = [=](float value) { myAvatar->setTargetScale(value); }; - - auto scaleSpinner = new SpinnerSliderPreference(AVATAR_TUNING, "Avatar Scale", getter, setter); - scaleSpinner->setMin(0.01f); - scaleSpinner->setMax(99.9f); - scaleSpinner->setDecimals(2); - scaleSpinner->setStep(1); - - preferences->addPreference(scaleSpinner); + auto preference = new SpinnerSliderPreference(AVATAR_TUNING, "Avatar Scale", getter, setter); + preference->setStep(0.1); + preference->setDecimals(2); + preferences->addPreference(preference); } { auto getter = []()->float { return DependencyManager::get()->getEyeClosingThreshold(); }; diff --git a/libraries/shared/src/Preferences.h b/libraries/shared/src/Preferences.h index 271df58951..6093cd3c8a 100644 --- a/libraries/shared/src/Preferences.h +++ b/libraries/shared/src/Preferences.h @@ -52,7 +52,7 @@ public: Browsable, Slider, Spinner, - SpinnerSlider, + SpinnerSlider, Checkbox, Button, ComboBox, From 5329c1ce420d93d362b8ccb6ac674a2771f09dc0 Mon Sep 17 00:00:00 2001 From: Cain Kilgore Date: Thu, 13 Jul 2017 19:23:28 +0100 Subject: [PATCH 26/39] Fixed Indentation, removed old debug code --- interface/resources/qml/dialogs/preferences/Section.qml | 4 +--- .../qml/dialogs/preferences/SpinnerSliderPreference.qml | 4 ++-- interface/src/ui/PreferencesDialog.cpp | 6 +++--- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/interface/resources/qml/dialogs/preferences/Section.qml b/interface/resources/qml/dialogs/preferences/Section.qml index a2bfa9ba0e..95372cf9d0 100644 --- a/interface/resources/qml/dialogs/preferences/Section.qml +++ b/interface/resources/qml/dialogs/preferences/Section.qml @@ -72,7 +72,7 @@ Preference { property var avatarBuilder: Component { AvatarPreference { } } property var buttonBuilder: Component { ButtonPreference { } } property var comboBoxBuilder: Component { ComboBoxPreference { } } - property var spinnerSliderBuilder: Component { SpinnerSliderPreference { } } + property var spinnerSliderBuilder: Component { SpinnerSliderPreference { } } property var preferences: [] property int checkBoxCount: 0 @@ -128,13 +128,11 @@ Preference { case Preference.ComboBox: checkBoxCount = 0; builder = comboBoxBuilder; - console.log("Built COMBOBOX"); break; case Preference.SpinnerSlider: checkBoxCount = 0; builder = spinnerSliderBuilder; - console.log("Built SPINNERSLIDER"); break; }; diff --git a/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml index 5b9e70a42a..13a4696fd3 100644 --- a/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml +++ b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml @@ -60,7 +60,7 @@ Preference { width: 100 minimumValue: MyAvatar.getDomainMinScale() maximumValue: MyAvatar.getDomainMaxScale() - stepSize: preference.step + stepSize: preference.step onValueChanged: { spinner.value = value } @@ -72,7 +72,7 @@ Preference { SpinBox { id: spinner decimals: preference.decimals - value: preference.value + value: preference.value minimumValue: MyAvatar.getDomainMinScale() maximumValue: MyAvatar.getDomainMaxScale() width: 100 diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index acbfc8edf3..558f6fd1b8 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -182,9 +182,9 @@ void setupPreferences() { preferences->addPreference(preference); } { - auto getter = [=]()->float { return myAvatar->getUniformScale(); }; - auto setter = [=](float value) { myAvatar->setTargetScale(value); }; - auto preference = new SpinnerSliderPreference(AVATAR_TUNING, "Avatar Scale", getter, setter); + auto getter = [=]()->float { return myAvatar->getUniformScale(); }; + auto setter = [=](float value) { myAvatar->setTargetScale(value); }; + auto preference = new SpinnerSliderPreference(AVATAR_TUNING, "Avatar Scale", getter, setter); preference->setStep(0.1); preference->setDecimals(2); preferences->addPreference(preference); From 092725ad1a200cc5d78f50f00bb7d405fd6ba19c Mon Sep 17 00:00:00 2001 From: Cain Kilgore Date: Thu, 13 Jul 2017 19:24:30 +0100 Subject: [PATCH 27/39] a thing --- interface/resources/qml/dialogs/preferences/Section.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/dialogs/preferences/Section.qml b/interface/resources/qml/dialogs/preferences/Section.qml index 95372cf9d0..2b90d402bc 100644 --- a/interface/resources/qml/dialogs/preferences/Section.qml +++ b/interface/resources/qml/dialogs/preferences/Section.qml @@ -72,7 +72,7 @@ Preference { property var avatarBuilder: Component { AvatarPreference { } } property var buttonBuilder: Component { ButtonPreference { } } property var comboBoxBuilder: Component { ComboBoxPreference { } } - property var spinnerSliderBuilder: Component { SpinnerSliderPreference { } } + property var spinnerSliderBuilder: Component { SpinnerSliderPreference { } } property var preferences: [] property int checkBoxCount: 0 From 247e7b8bed5a6b9b4969d54b604dc7d1c7878954 Mon Sep 17 00:00:00 2001 From: Cain Kilgore Date: Thu, 13 Jul 2017 19:25:31 +0100 Subject: [PATCH 28/39] indentation!!! --- .../qml/hifi/tablet/tabletWindows/preferences/Section.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml index 25279bb6bf..af1fbd0070 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml @@ -81,7 +81,7 @@ Preference { property var avatarBuilder: Component { AvatarPreference { } } property var buttonBuilder: Component { ButtonPreference { } } property var comboBoxBuilder: Component { ComboBoxPreference { } } - property var spinnerSliderBuilder: Component { SpinnerSliderPreference { } } + property var spinnerSliderBuilder: Component { SpinnerSliderPreference { } } property var preferences: [] property int checkBoxCount: 0 From 677b177dce38c63230930326adf0792ebac0fe1a Mon Sep 17 00:00:00 2001 From: Cain Kilgore Date: Thu, 13 Jul 2017 20:29:52 +0100 Subject: [PATCH 29/39] Build error fix --- interface/src/ui/PreferencesDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 558f6fd1b8..e80b7f7f34 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -185,7 +185,7 @@ void setupPreferences() { auto getter = [=]()->float { return myAvatar->getUniformScale(); }; auto setter = [=](float value) { myAvatar->setTargetScale(value); }; auto preference = new SpinnerSliderPreference(AVATAR_TUNING, "Avatar Scale", getter, setter); - preference->setStep(0.1); + preference->setStep(0.1f); preference->setDecimals(2); preferences->addPreference(preference); } From 60300f5d7b7c36dc25fb761787d4eea00bbd82a8 Mon Sep 17 00:00:00 2001 From: Cain Kilgore Date: Thu, 13 Jul 2017 22:06:44 +0100 Subject: [PATCH 30/39] changed to icon (hopefully) --- .../qml/dialogs/preferences/SpinnerSliderPreference.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml index 13a4696fd3..ea8109397e 100644 --- a/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml +++ b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml @@ -97,7 +97,8 @@ Preference { } } width: 50 - text: "Reset" + font.family: hiFiGlyphs.name + text: "a" anchors { right: parent.right } From d74c834f1237c96326900112d7648ad69671fc61 Mon Sep 17 00:00:00 2001 From: Cain Kilgore Date: Thu, 13 Jul 2017 22:40:37 +0100 Subject: [PATCH 31/39] Fixed colour scheming and changed to a reload icon --- .../qml/dialogs/preferences/SpinnerSliderPreference.qml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml index ea8109397e..85caa1befc 100644 --- a/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml +++ b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml @@ -68,6 +68,7 @@ Preference { right: spinner.left rightMargin: 10 } + colorScheme: hifi.colorSchemes.dark } SpinBox { id: spinner @@ -85,7 +86,7 @@ Preference { } colorScheme: hifi.colorSchemes.dark } - Button { + GlyphButton { id: button onClicked: { if(spinner.maximumValue >= 1) { @@ -96,12 +97,12 @@ Preference { slider.value = spinner.maximumValue } } - width: 50 - font.family: hiFiGlyphs.name - text: "a" + width: 30 + glyph: hifi.glyphs.reload anchors { right: parent.right } + colorScheme: hifi.colorSchemes.dark } } } \ No newline at end of file From 72c3e789e9f399d1e0f8e5c1cf268566e5f30b0e Mon Sep 17 00:00:00 2001 From: Cain Kilgore Date: Thu, 13 Jul 2017 23:42:20 +0100 Subject: [PATCH 32/39] More Spaces! --- interface/src/ui/PreferencesDialog.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index e80b7f7f34..4db83aab7a 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -181,14 +181,14 @@ void setupPreferences() { preference->setStep(1); preferences->addPreference(preference); } - { + { auto getter = [=]()->float { return myAvatar->getUniformScale(); }; auto setter = [=](float value) { myAvatar->setTargetScale(value); }; auto preference = new SpinnerSliderPreference(AVATAR_TUNING, "Avatar Scale", getter, setter); preference->setStep(0.1f); preference->setDecimals(2); preferences->addPreference(preference); - } + } { auto getter = []()->float { return DependencyManager::get()->getEyeClosingThreshold(); }; auto setter = [](float value) { DependencyManager::get()->setEyeClosingThreshold(value); }; From 4f8d958831e3556255d5cc0c9bcfba29b150ab38 Mon Sep 17 00:00:00 2001 From: Cain Kilgore Date: Fri, 14 Jul 2017 00:56:42 +0100 Subject: [PATCH 33/39] Some code cleaning, added comment for reasoning of specifying min and max in the qml --- .../resources/qml/controls-uit/Slider.qml | 2 +- .../qml/dialogs/preferences/Section.qml | 8 ++-- .../preferences/SpinnerSliderPreference.qml | 47 ++++++++++--------- interface/src/ui/PreferencesDialog.cpp | 3 ++ 4 files changed, 33 insertions(+), 27 deletions(-) diff --git a/interface/resources/qml/controls-uit/Slider.qml b/interface/resources/qml/controls-uit/Slider.qml index 18d0d33fe2..89bae9bcde 100644 --- a/interface/resources/qml/controls-uit/Slider.qml +++ b/interface/resources/qml/controls-uit/Slider.qml @@ -36,7 +36,7 @@ Slider { Rectangle { width: parent.height - 2 - height: slider.width * (slider.value - slider.minimumValue)/(slider.maximumValue-slider.minimumValue)-1 + height: slider.width * (slider.value - slider.minimumValue) / (slider.maximumValue - slider.minimumValue) - 1 radius: height / 2 anchors { top: parent.top diff --git a/interface/resources/qml/dialogs/preferences/Section.qml b/interface/resources/qml/dialogs/preferences/Section.qml index 2b90d402bc..3985c7d6f6 100644 --- a/interface/resources/qml/dialogs/preferences/Section.qml +++ b/interface/resources/qml/dialogs/preferences/Section.qml @@ -130,10 +130,10 @@ Preference { builder = comboBoxBuilder; break; - case Preference.SpinnerSlider: - checkBoxCount = 0; - builder = spinnerSliderBuilder; - break; + case Preference.SpinnerSlider: + checkBoxCount = 0; + builder = spinnerSliderBuilder; + break; }; if (builder) { diff --git a/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml index 85caa1befc..f5dfa3a795 100644 --- a/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml +++ b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml @@ -16,10 +16,12 @@ import "../../controls-uit" Preference { id: root property alias slider: slider + property alias spinner: spinner height: control.height + hifi.dimensions.controlInterlineHeight Component.onCompleted: { slider.value = preference.value; + spinner.value = preference.value; } function save() { @@ -27,10 +29,6 @@ Preference { preference.save(); } - function sliderToAvatarScale(sliderValue) { - return MyAvatar.getDomainMinScale() + (MyAvatar.getDomainMaxScale()-MyAvatar.getDomainMinScale())*value; - } - Item { id: control anchors { @@ -38,7 +36,7 @@ Preference { right: parent.right bottom: parent.bottom } - height: Math.max(labelText.height, slider.height) + height: Math.max(labelText.height, slider.height, spinner.height, button.height) Label { id: labelText @@ -67,9 +65,11 @@ Preference { anchors { right: spinner.left rightMargin: 10 + verticalCenter: parent.verticalCenter } colorScheme: hifi.colorSchemes.dark } + SpinBox { id: spinner decimals: preference.decimals @@ -83,26 +83,29 @@ Preference { anchors { right: button.left rightMargin: 10 + verticalCenter: parent.verticalCenter } colorScheme: hifi.colorSchemes.dark } - GlyphButton { - id: button - onClicked: { - if(spinner.maximumValue >= 1) { - spinner.value = 1 - slider.value = 1 - } else { - spinner.value = spinner.maximumValue - slider.value = spinner.maximumValue - } - } - width: 30 - glyph: hifi.glyphs.reload - anchors { - right: parent.right - } + + GlyphButton { + id: button + onClicked: { + if (spinner.maximumValue >= 1) { + spinner.value = 1 + slider.value = 1 + } else { + spinner.value = spinner.maximumValue + slider.value = spinner.maximumValue + } + } + width: 30 + glyph: hifi.glyphs.reload + anchors { + right: parent.right + verticalCenter: parent.verticalCenter + } colorScheme: hifi.colorSchemes.dark - } + } } } \ No newline at end of file diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 4db83aab7a..802bb2fd84 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -188,6 +188,9 @@ void setupPreferences() { preference->setStep(0.1f); preference->setDecimals(2); preferences->addPreference(preference); + + // When the Interface is first loaded, this section setupPreferences(); is loaded - causing the myAvatar->getDomainMinScale() and myAvatar->getDomainMaxScale() to get set to incorrect values which can't be changed across domain switches. + // Having these values loaded up when you load the Dialog each time is a way around this, therefore they're not specified here but in the QML. } { auto getter = []()->float { return DependencyManager::get()->getEyeClosingThreshold(); }; From c60c8c7b13238c03f3f94945b11dfc3720071019 Mon Sep 17 00:00:00 2001 From: Cain Kilgore Date: Fri, 14 Jul 2017 00:59:25 +0100 Subject: [PATCH 34/39] Missed two tabs.. --- .../qml/dialogs/preferences/SpinnerSliderPreference.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml index f5dfa3a795..3cba67bc82 100644 --- a/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml +++ b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml @@ -64,12 +64,12 @@ Preference { } anchors { right: spinner.left - rightMargin: 10 + rightMargin: 10 verticalCenter: parent.verticalCenter } colorScheme: hifi.colorSchemes.dark } - + SpinBox { id: spinner decimals: preference.decimals @@ -82,7 +82,7 @@ Preference { } anchors { right: button.left - rightMargin: 10 + rightMargin: 10 verticalCenter: parent.verticalCenter } colorScheme: hifi.colorSchemes.dark From f692a2e4bf7f202fb67ae34111e4afc5ccc58778 Mon Sep 17 00:00:00 2001 From: elmanytas Date: Fri, 14 Jul 2017 08:44:59 +0200 Subject: [PATCH 35/39] Create ubuntu specific documentation based on https://forums.highfidelity.com/t/how-to-compile-a-stable-build-of-the-linux-server-stack-on-ubuntu-16-04-lts/11528/16 and tested yesterday. --- BUILD_LINUX.md | 91 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/BUILD_LINUX.md b/BUILD_LINUX.md index d40576a75d..038f53154c 100644 --- a/BUILD_LINUX.md +++ b/BUILD_LINUX.md @@ -1,7 +1,96 @@ +# Linux build guide + Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only Linux specific instructions are found in this file. -### Qt5 Dependencies +## Qt5 Dependencies Should you choose not to install Qt5 via a package manager that handles dependencies for you, you may be missing some Qt5 dependencies. On Ubuntu, for example, the following additional packages are required: libasound2 libxmu-dev libxi-dev freeglut3-dev libasound2-dev libjack0 libjack-dev libxrandr-dev libudev-dev libssl-dev + +## Ubuntu 16.04 specific build guide + +### Prepare environment + +Install qt: +```bash +wget http://debian.highfidelity.com/pool/h/hi/hifi-qt5.6.1_5.6.1_amd64.deb +sudo dpkg -i hifi-qt5.6.1_5.6.1_amd64.deb +``` + +Install build dependencies: +```bash +sudo apt-get install libasound2 libxmu-dev libxi-dev freeglut3-dev libasound2-dev libjack0 libjack-dev libxrandr-dev libudev-dev libssl-dev +``` + +To compile interface in a server you must install: +```bash +sudo apt -y install libpulse0 libnss3 libnspr4 libfontconfig1 libxcursor1 libxcomposite1 libxtst6 libxslt1.1 +``` + +Install build tools: +```bash +sudo apt install cmake +``` + +### Get code and checkout the tag you need + +Clone this repository: +```bash +git clone https://github.com/highfidelity/hifi.git +``` + +To compile a RELEASE version checkout the tag you need getting a list of all tags: +```bash +git fetch -a +git tags +``` + +Then checkout last tag with: +```bash +git checkout tags/RELEASE-6819 +``` + +Or go to the highfidelity download page (https://highfidelity.com/download) to get the release version. For example, if there is a BETA 6731 type: +```bash +git checkout tags/RELEASE-6731 +``` + +### Compiling + +Create the build directory: +```bash +mkdir -p hifi/build +cd hifi/build +``` + +Prepare makefiles: +```bash +cmake -DQT_CMAKE_PREFIX_PATH=/usr/local/Qt5.6.1/5.6/gcc_64/lib/cmake .. +``` + +Start compilation and get a cup of coffee: +```bash +make domain-server assignment-client interface +``` + +In a server does not make sense to compile interface + +### Running the software + +Running domain server: +```bash +./domain-server/domain-server +``` + +Running assignment client: +```bash +./assignment-client/assignment-client -n 6 +``` + +Running interface: +```bash +./interface/interface +``` + +Go to localhost in running interface. From ee1f37190280bd2a095605a84e66c013618d7b9f Mon Sep 17 00:00:00 2001 From: Cain Kilgore Date: Fri, 14 Jul 2017 16:29:19 +0100 Subject: [PATCH 36/39] Fixed step counted not resetting to 1 properly --- interface/src/ui/PreferencesDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 802bb2fd84..bbdbb0187b 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -185,7 +185,7 @@ void setupPreferences() { auto getter = [=]()->float { return myAvatar->getUniformScale(); }; auto setter = [=](float value) { myAvatar->setTargetScale(value); }; auto preference = new SpinnerSliderPreference(AVATAR_TUNING, "Avatar Scale", getter, setter); - preference->setStep(0.1f); + preference->setStep(0.05f); preference->setDecimals(2); preferences->addPreference(preference); From b7ed3ce65920bcdb4a9846dca006617d2079af0c Mon Sep 17 00:00:00 2001 From: Cain Kilgore Date: Fri, 14 Jul 2017 17:33:17 +0100 Subject: [PATCH 37/39] Prettified the Comment --- interface/src/ui/PreferencesDialog.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index bbdbb0187b..87131e4f5c 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -189,8 +189,10 @@ void setupPreferences() { preference->setDecimals(2); preferences->addPreference(preference); - // When the Interface is first loaded, this section setupPreferences(); is loaded - causing the myAvatar->getDomainMinScale() and myAvatar->getDomainMaxScale() to get set to incorrect values which can't be changed across domain switches. - // Having these values loaded up when you load the Dialog each time is a way around this, therefore they're not specified here but in the QML. + // When the Interface is first loaded, this section setupPreferences(); is loaded - + // causing the myAvatar->getDomainMinScale() and myAvatar->getDomainMaxScale() to get set to incorrect values + // which can't be changed across domain switches. Having these values loaded up when you load the Dialog each time + // is a way around this, therefore they're not specified here but in the QML. } { auto getter = []()->float { return DependencyManager::get()->getEyeClosingThreshold(); }; From de199bff9df9f42c164bf77ef501811eb2b0bbf4 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 14 Jul 2017 09:47:37 -0700 Subject: [PATCH 38/39] code review feedback --- libraries/animation/src/AnimInverseKinematics.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 20b62c2724..57c00e7183 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -930,7 +930,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars } } - // identity joint chains that have changed types this frame. + // identify joint chains that have changed types this frame. _prevJointChainInfoVec.resize(jointChainInfoVec.size()); for (size_t i = 0; i < _prevJointChainInfoVec.size(); i++) { if (_prevJointChainInfoVec[i].timer <= 0.0f && @@ -944,7 +944,9 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars { PROFILE_RANGE_EX(simulation_animation, "ik/shiftHips", 0xffff00ff, 0); - if (_hipsTargetIndex >= 0 && _hipsTargetIndex >= 0 && _hipsTargetIndex < (int)targets.size()) { + if (_hipsTargetIndex >= 0) { + assert(_hipsTargetIndex < (int)targets.size()); + // slam the hips to match the _hipsTarget AnimPose absPose = targets[_hipsTargetIndex].getPose(); From 68fa426da97c3288bd74b94c31aeaaefb81dba90 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Fri, 14 Jul 2017 18:17:13 +0100 Subject: [PATCH 39/39] set file url as higher priority --- libraries/networking/src/ResourceCache.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 43f0e9335d..fbdfa4b87a 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -102,6 +102,8 @@ QSharedPointer ResourceCacheSharedItems::getHighestPendingRequest() { QSharedPointer highestResource; Lock lock(_mutex); + bool currentHighestIsFile = false; + for (int i = 0; i < _pendingRequests.size();) { // Clear any freed resources auto resource = _pendingRequests.at(i).lock(); @@ -112,10 +114,12 @@ QSharedPointer ResourceCacheSharedItems::getHighestPendingRequest() { // Check load priority float priority = resource->getLoadPriority(); - if (priority >= highestPriority) { + bool isFile = resource->getURL().scheme() == URL_SCHEME_FILE; + if (priority >= highestPriority && (isFile || !currentHighestIsFile)) { highestPriority = priority; highestIndex = i; highestResource = resource; + currentHighestIsFile = isFile; } i++; }