From 6564cfd5d1d44425335296eeab3cbea75ca69fbd Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 5 Jun 2017 18:33:32 -0700 Subject: [PATCH 01/95] WIP --- .../animation/src/AnimInverseKinematics.cpp | 138 ++++++++++++++---- .../animation/src/AnimInverseKinematics.h | 17 ++- libraries/animation/src/IKTarget.h | 10 +- 3 files changed, 126 insertions(+), 39 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 77437e79b9..41dae120fd 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -23,6 +23,25 @@ #include "CubicHermiteSpline.h" #include "AnimUtil.h" +static void lookupJointChainInfo(AnimInverseKinematics::JointChainInfo* jointChainInfos, size_t numJointChainInfos, + int indexA, int indexB, + AnimInverseKinematics::JointChainInfo** jointChainInfoA, + AnimInverseKinematics::JointChainInfo** jointChainInfoB) { + *jointChainInfoA = nullptr; + *jointChainInfoB = nullptr; + for (size_t i = 0; i < numJointChainInfos; i++) { + if (jointChainInfos[i].jointIndex == indexA) { + *jointChainInfoA = jointChainInfos + i; + } + if (jointChainInfos[i].jointIndex == indexB) { + *jointChainInfoB = jointChainInfos + i; + } + if (*jointChainInfoA && *jointChainInfoB) { + break; + } + } +} + AnimInverseKinematics::IKTargetVar::IKTargetVar(const QString& jointNameIn, const QString& positionVarIn, const QString& rotationVarIn, const QString& typeVarIn, const QString& weightVarIn, float weightIn, const std::vector& flexCoefficientsIn) : jointName(jointNameIn), @@ -148,6 +167,12 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std:: target.setWeight(weight); target.setFlexCoefficients(targetVar.numFlexCoefficients, targetVar.flexCoefficients); + // AJT: HACK REMOVE manually set pole vector. + if (targetVar.jointName == "RightHand") { + target.setPoleVector(glm::normalize(glm::vec3(-1, 0, 0))); + target.setPoleIndex(_skeleton->nameToJointIndex(targetVar.jointName)); + } + targets.push_back(target); if (targetVar.jointIndex > _maxTargetIndex) { @@ -298,7 +323,8 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const // the tip's parent-relative as we proceed up the chain glm::quat tipParentOrientation = absolutePoses[pivotIndex].rot(); - std::map debugJointMap; + 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. @@ -326,15 +352,8 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const } } - // store the relative rotation change in the accumulator - _rotationAccumulators[tipIndex].add(tipRelativeRotation, target.getWeight()); - glm::vec3 tipRelativeTranslation = _relativePoses[target.getIndex()].trans(); - _translationAccumulators[tipIndex].add(tipRelativeTranslation); - - if (debug) { - debugJointMap[tipIndex] = DebugJoint(tipRelativeRotation, tipRelativeTranslation, constrained); - } + jointChainInfos[chainDepth] = { tipRelativeRotation, tipRelativeTranslation, target.getWeight(), tipIndex, constrained }; } // cache tip absolute position @@ -344,6 +363,9 @@ 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); + // compute the two lines that should be aligned glm::vec3 jointPosition = absolutePoses[pivotIndex].trans(); glm::vec3 leverArm = tipPosition - jointPosition; @@ -440,15 +462,8 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const } } - // store the relative rotation change in the accumulator - _rotationAccumulators[pivotIndex].add(newRot, target.getWeight()); - glm::vec3 newTrans = _relativePoses[pivotIndex].trans(); - _translationAccumulators[pivotIndex].add(newTrans); - - if (debug) { - debugJointMap[pivotIndex] = DebugJoint(newRot, newTrans, constrained); - } + jointChainInfos[chainDepth] = { newRot, newTrans, target.getWeight(), pivotIndex, constrained }; // keep track of tip's new transform as we descend towards root tipPosition = jointPosition + deltaRotation * (tipPosition - jointPosition); @@ -461,8 +476,73 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const chainDepth++; } + if (target.getPoleIndex() != -1) { + + int topIndex = target.getPoleIndex(); + int midIndex = _skeleton->getParentIndex(topIndex); + if (midIndex != -1) { + int baseIndex = _skeleton->getParentIndex(midIndex); + if (baseIndex != -1) { + int baseParentIndex = _skeleton->getParentIndex(baseIndex); + AnimPose topPose, midPose, basePose, baseParentPose; + AnimPose postAbsPoses[MAX_CHAIN_DEPTH]; + AnimPose accum = absolutePoses[_hipsIndex]; + for (int i = (int)chainDepth - 1; i >= 0; i--) { + accum = accum * AnimPose(glm::vec3(1.0f), jointChainInfos[i].relRot, jointChainInfos[i].relTrans); + postAbsPoses[i] = accum; + if (jointChainInfos[i].jointIndex == topIndex) { + topPose = accum; + } + if (jointChainInfos[i].jointIndex == midIndex) { + midPose = accum; + } + if (jointChainInfos[i].jointIndex == baseIndex) { + basePose = accum; + } + if (jointChainInfos[i].jointIndex == baseParentIndex) { + baseParentPose = accum; + } + } + + // AJT: TODO: check for parallel edge cases. + glm::vec3 d = glm::normalize(basePose.trans() - topPose.trans()); + glm::vec3 e = midPose.trans() - topPose.trans(); + glm::vec3 dHat = glm::dot(e, d) * d + topPose.trans(); + glm::vec3 eHat = glm::normalize(midPose.trans() - dHat); + float theta = acos(glm::dot(eHat, target.getPoleVector())); + glm::vec3 axis = glm::normalize(glm::cross(eHat, target.getPoleVector())); + glm::quat poleRot = glm::angleAxis(-theta, axis); + + if (debug) { + + const vec4 RED(1.0f, 0.0f, 0.0f, 1.0f); + const vec4 GREEN(0.0f, 1.0f, 0.0f, 1.0f); + const vec4 BLUE(0.0f, 0.0f, 1.0f, 1.0f); + + AnimPose geomToWorldPose = AnimPose(context.getRigToWorldMatrix() * context.getGeometryToRigMatrix()); + + const float POLE_VECTOR_LEN = 10.0f; + DebugDraw::getInstance().drawRay(geomToWorldPose.xformPoint(basePose.trans()), geomToWorldPose.xformPoint(topPose.trans()), GREEN); + DebugDraw::getInstance().drawRay(geomToWorldPose.xformPoint(dHat), geomToWorldPose.xformPoint(dHat + POLE_VECTOR_LEN * eHat), RED); + DebugDraw::getInstance().drawRay(geomToWorldPose.xformPoint(dHat), geomToWorldPose.xformPoint(dHat + POLE_VECTOR_LEN * target.getPoleVector()), BLUE); + } + + glm::quat newBaseRelRot = glm::inverse(baseParentPose.rot()) * poleRot * basePose.rot() * glm::inverse(jointChainInfos[baseIndex].relRot); + jointChainInfos[baseIndex].relRot = glm::quat(); //newBaseRelRot * jointChainInfos[baseIndex].relRot; + + glm::quat newTopRelRot = glm::inverse(midPose.rot()) * glm::inverse(poleRot) * topPose.rot() * glm::inverse(jointChainInfos[topIndex].relRot); + jointChainInfos[topIndex].relRot = glm::quat(); //newTopRelRot * jointChainInfos[topIndex].relRot; + } + } + } + + 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(debugJointMap, context); + debugDrawIKChain(jointChainInfos, chainDepth, context); } } @@ -1338,13 +1418,14 @@ void AnimInverseKinematics::debugDrawRelativePoses(const AnimContext& context) c } } -void AnimInverseKinematics::debugDrawIKChain(std::map& debugJointMap, const AnimContext& context) const { +void AnimInverseKinematics::debugDrawIKChain(JointChainInfo* jointChainInfos, size_t numJointChainInfos, const AnimContext& context) const { AnimPoseVec poses = _relativePoses; // copy debug joint rotations into the relative poses - for (auto& debugJoint : debugJointMap) { - poses[debugJoint.first].rot() = debugJoint.second.relRot; - poses[debugJoint.first].trans() = debugJoint.second.relTrans; + for (size_t i = 0; i < numJointChainInfos; i++) { + const JointChainInfo& info = jointChainInfos[i]; + poses[info.jointIndex].rot() = info.relRot; + poses[info.jointIndex].trans() = info.relTrans; } // convert relative poses to absolute @@ -1360,11 +1441,11 @@ void AnimInverseKinematics::debugDrawIKChain(std::map& debugJoi // draw each pose for (int i = 0; i < (int)poses.size(); i++) { - - // only draw joints that are actually in debugJointMap, or their parents - auto iter = debugJointMap.find(i); - auto parentIter = debugJointMap.find(_skeleton->getParentIndex(i)); - if (iter != debugJointMap.end() || parentIter != debugJointMap.end()) { + int parentIndex = _skeleton->getParentIndex(i); + JointChainInfo* jointInfo = nullptr; + JointChainInfo* parentJointInfo = nullptr; + lookupJointChainInfo(jointChainInfos, numJointChainInfos, i, parentIndex, &jointInfo, &parentJointInfo); + if (jointInfo && parentJointInfo) { // transform local axes into world space. auto pose = poses[i]; @@ -1377,13 +1458,12 @@ void AnimInverseKinematics::debugDrawIKChain(std::map& debugJoi DebugDraw::getInstance().drawRay(pos, pos + AXIS_LENGTH * zAxis, BLUE); // draw line to parent - int parentIndex = _skeleton->getParentIndex(i); if (parentIndex != -1) { glm::vec3 parentPos = transformPoint(geomToWorldMatrix, poses[parentIndex].trans()); glm::vec4 color = GRAY; // draw constrained joints with a RED link to their parent. - if (parentIter != debugJointMap.end() && parentIter->second.constrained) { + if (parentJointInfo->constrained) { color = RED; } DebugDraw::getInstance().drawRay(pos, parentPos, color); diff --git a/libraries/animation/src/AnimInverseKinematics.h b/libraries/animation/src/AnimInverseKinematics.h index cc919c1684..2ed96ed6ed 100644 --- a/libraries/animation/src/AnimInverseKinematics.h +++ b/libraries/animation/src/AnimInverseKinematics.h @@ -26,6 +26,14 @@ class RotationConstraint; class AnimInverseKinematics : public AnimNode { public: + struct JointChainInfo { + glm::quat relRot; + glm::vec3 relTrans; + float weight; + int jointIndex; + bool constrained; + }; + explicit AnimInverseKinematics(const QString& id); virtual ~AnimInverseKinematics() override; @@ -67,14 +75,7 @@ protected: 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); virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) override; - struct DebugJoint { - DebugJoint() : relRot(), constrained(false) {} - DebugJoint(const glm::quat& relRotIn, const glm::vec3& relTransIn, bool constrainedIn) : relRot(relRotIn), relTrans(relTransIn), constrained(constrainedIn) {} - glm::quat relRot; - glm::vec3 relTrans; - bool constrained; - }; - void debugDrawIKChain(std::map& debugJointMap, const AnimContext& context) const; + void debugDrawIKChain(JointChainInfo* jointChainInfos, size_t numJointChainInfos, 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/IKTarget.h b/libraries/animation/src/IKTarget.h index 011175aedf..c476e9c9da 100644 --- a/libraries/animation/src/IKTarget.h +++ b/libraries/animation/src/IKTarget.h @@ -30,10 +30,14 @@ public: const glm::vec3& getTranslation() const { return _pose.trans(); } const glm::quat& getRotation() const { return _pose.rot(); } const AnimPose& getPose() const { return _pose; } + glm::vec3 getPoleVector() const { return _poleVector; } + int getPoleIndex() const { return _poleIndex; } int getIndex() const { return _index; } Type getType() const { return _type; } void setPose(const glm::quat& rotation, const glm::vec3& translation); + void setPoleVector(const glm::vec3& poleVector) { _poleVector = poleVector; } + void setPoleIndex(int poleIndex) { _poleIndex = poleIndex; } void setIndex(int index) { _index = index; } void setType(int); void setFlexCoefficients(size_t numFlexCoefficientsIn, const float* flexCoefficientsIn); @@ -46,8 +50,10 @@ public: private: AnimPose _pose; - int _index{-1}; - Type _type{Type::RotationAndPosition}; + glm::vec3 _poleVector; + int _poleIndex { -1 }; + int _index { -1 }; + Type _type { Type::RotationAndPosition }; float _weight; float _flexCoefficients[MAX_FLEX_COEFFICIENTS]; size_t _numFlexCoefficients; From 02f06d4d4eed054943916b152bb0cef2efdb1d40 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 6 Jun 2017 17:05:06 -0700 Subject: [PATCH 02/95] WIP pole vectors work.. but still have issues... --- .../animation/src/AnimInverseKinematics.cpp | 100 +++++++++++++----- 1 file changed, 72 insertions(+), 28 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 41dae120fd..1fdeaef483 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -169,7 +169,11 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std:: // AJT: HACK REMOVE manually set pole vector. if (targetVar.jointName == "RightHand") { - target.setPoleVector(glm::normalize(glm::vec3(-1, 0, 0))); + target.setPoleVector(glm::normalize(glm::vec3(-1, -1, 0))); + target.setPoleIndex(_skeleton->nameToJointIndex(targetVar.jointName)); + } + if (targetVar.jointName == "LeftHand") { + target.setPoleVector(glm::normalize(glm::vec3(1, -1, 0))); target.setPoleIndex(_skeleton->nameToJointIndex(targetVar.jointName)); } @@ -478,60 +482,99 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const if (target.getPoleIndex() != -1) { - int topIndex = target.getPoleIndex(); - int midIndex = _skeleton->getParentIndex(topIndex); - if (midIndex != -1) { - int baseIndex = _skeleton->getParentIndex(midIndex); - if (baseIndex != -1) { - int baseParentIndex = _skeleton->getParentIndex(baseIndex); + int topJointIndex = target.getPoleIndex(); + int midJointIndex = _skeleton->getParentIndex(topJointIndex); + if (midJointIndex != -1) { + int baseJointIndex = _skeleton->getParentIndex(midJointIndex); + if (baseJointIndex != -1) { + int baseParentJointIndex = _skeleton->getParentIndex(baseJointIndex); AnimPose topPose, midPose, basePose, baseParentPose; + int topChainIndex = -1, baseChainIndex = -1; AnimPose postAbsPoses[MAX_CHAIN_DEPTH]; AnimPose accum = absolutePoses[_hipsIndex]; for (int i = (int)chainDepth - 1; i >= 0; i--) { accum = accum * AnimPose(glm::vec3(1.0f), jointChainInfos[i].relRot, jointChainInfos[i].relTrans); postAbsPoses[i] = accum; - if (jointChainInfos[i].jointIndex == topIndex) { + if (jointChainInfos[i].jointIndex == topJointIndex) { + topChainIndex = i; topPose = accum; } - if (jointChainInfos[i].jointIndex == midIndex) { + if (jointChainInfos[i].jointIndex == midJointIndex) { midPose = accum; } - if (jointChainInfos[i].jointIndex == baseIndex) { + if (jointChainInfos[i].jointIndex == baseJointIndex) { + baseChainIndex = i; basePose = accum; } - if (jointChainInfos[i].jointIndex == baseParentIndex) { + if (jointChainInfos[i].jointIndex == baseParentJointIndex) { baseParentPose = accum; } } - // AJT: TODO: check for parallel edge cases. - glm::vec3 d = glm::normalize(basePose.trans() - topPose.trans()); - glm::vec3 e = midPose.trans() - topPose.trans(); - glm::vec3 dHat = glm::dot(e, d) * d + topPose.trans(); - glm::vec3 eHat = glm::normalize(midPose.trans() - dHat); - float theta = acos(glm::dot(eHat, target.getPoleVector())); - glm::vec3 axis = glm::normalize(glm::cross(eHat, target.getPoleVector())); - glm::quat poleRot = glm::angleAxis(-theta, axis); + glm::quat poleRot = Quaternions::IDENTITY; + glm::vec3 d = basePose.trans() - topPose.trans(); + float dLen = glm::length(d); + if (dLen > EPSILON) { + glm::vec3 dUnit = d / dLen; + glm::vec3 e = midPose.trans() - basePose.trans(); + glm::vec3 p = target.getPoleVector(); + glm::vec3 eProj = e - glm::dot(e, dUnit) * dUnit; + glm::vec3 pProj = p - glm::dot(p, dUnit) * dUnit; + float eProjLen = glm::length(eProj); + float pProjLen = glm::length(pProj); + if (eProjLen > EPSILON && pProjLen > EPSILON) { + float dot = glm::clamp(glm::dot(eProj / eProjLen, pProj / pProjLen), 0.0f, 1.0f); + float theta = acos(dot); + glm::vec3 cross = glm::cross(eProj, pProj); + float crossLen = glm::length(cross); + if (crossLen > EPSILON) { + glm::vec3 axis = cross / crossLen; + poleRot = glm::angleAxis(theta, axis); + } + } + } if (debug) { - const vec4 RED(1.0f, 0.0f, 0.0f, 1.0f); const vec4 GREEN(0.0f, 1.0f, 0.0f, 1.0f); const vec4 BLUE(0.0f, 0.0f, 1.0f, 1.0f); + const vec4 YELLOW(1.0f, 1.0f, 0.0f, 1.0f); + const vec4 WHITE(1.0f, 1.0f, 1.0f, 1.0f); AnimPose geomToWorldPose = AnimPose(context.getRigToWorldMatrix() * context.getGeometryToRigMatrix()); + glm::vec3 dUnit = d / dLen; + glm::vec3 e = midPose.trans() - basePose.trans(); + glm::vec3 p = target.getPoleVector(); + glm::vec3 eProj = e - glm::dot(e, dUnit) * dUnit; + glm::vec3 pProj = p - glm::dot(p, dUnit) * dUnit; + float eProjLen = glm::length(eProj); + float pProjLen = glm::length(pProj); + const float POLE_VECTOR_LEN = 10.0f; - DebugDraw::getInstance().drawRay(geomToWorldPose.xformPoint(basePose.trans()), geomToWorldPose.xformPoint(topPose.trans()), GREEN); - DebugDraw::getInstance().drawRay(geomToWorldPose.xformPoint(dHat), geomToWorldPose.xformPoint(dHat + POLE_VECTOR_LEN * eHat), RED); - DebugDraw::getInstance().drawRay(geomToWorldPose.xformPoint(dHat), geomToWorldPose.xformPoint(dHat + POLE_VECTOR_LEN * target.getPoleVector()), BLUE); + glm::vec3 midPoint = (basePose.trans() + topPose.trans()) * 0.5f; + DebugDraw::getInstance().drawRay(geomToWorldPose.xformPoint(basePose.trans()), + geomToWorldPose.xformPoint(topPose.trans()), + YELLOW); + DebugDraw::getInstance().drawRay(geomToWorldPose.xformPoint(midPoint), + geomToWorldPose.xformPoint(midPoint + POLE_VECTOR_LEN * glm::normalize(eProj)), + RED); + /* + DebugDraw::getInstance().drawRay(geomToWorldPose.xformPoint(midPoint), + geomToWorldPose.xformPoint(midPoint + POLE_VECTOR_LEN * glm::normalize(p)), + BLUE); + */ + DebugDraw::getInstance().drawRay(geomToWorldPose.xformPoint(midPoint), + geomToWorldPose.xformPoint(midPoint + POLE_VECTOR_LEN * glm::normalize(pProj)), + GREEN); } - glm::quat newBaseRelRot = glm::inverse(baseParentPose.rot()) * poleRot * basePose.rot() * glm::inverse(jointChainInfos[baseIndex].relRot); - jointChainInfos[baseIndex].relRot = glm::quat(); //newBaseRelRot * jointChainInfos[baseIndex].relRot; - glm::quat newTopRelRot = glm::inverse(midPose.rot()) * glm::inverse(poleRot) * topPose.rot() * glm::inverse(jointChainInfos[topIndex].relRot); - jointChainInfos[topIndex].relRot = glm::quat(); //newTopRelRot * jointChainInfos[topIndex].relRot; + glm::quat newBaseRelRot = glm::inverse(baseParentPose.rot()) * poleRot * basePose.rot(); + jointChainInfos[baseChainIndex].relRot = newBaseRelRot; + + glm::quat newTopRelRot = glm::inverse(midPose.rot()) * glm::inverse(poleRot) * topPose.rot(); + jointChainInfos[topChainIndex].relRot = newTopRelRot; } } } @@ -1222,7 +1265,7 @@ void AnimInverseKinematics::initConstraints() { // we determine the max/min angles by rotating the swing limit lines from parent- to child-frame // then measure the angles to swing the yAxis into alignment glm::vec3 hingeAxis = - mirror * Vectors::UNIT_Z; - const float MIN_ELBOW_ANGLE = 0.05f; + const float MIN_ELBOW_ANGLE = 0.15f; // ~8.6 deg (ajt-rad-to-deg 0.15) const float MAX_ELBOW_ANGLE = 11.0f * PI / 12.0f; glm::quat invReferenceRotation = glm::inverse(referenceRotation); glm::vec3 minSwingAxis = invReferenceRotation * glm::angleAxis(MIN_ELBOW_ANGLE, hingeAxis) * Vectors::UNIT_Y; @@ -1388,6 +1431,7 @@ void AnimInverseKinematics::debugDrawRelativePoses(const AnimContext& context) c // convert relative poses to absolute _skeleton->convertRelativePosesToAbsolute(poses); + mat4 geomToWorldMatrix = context.getRigToWorldMatrix() * context.getGeometryToRigMatrix(); const vec4 RED(1.0f, 0.0f, 0.0f, 1.0f); From bb45fe03888d1e2f60b7366038e059d545f61b70 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 7 Jun 2017 10:49:27 -0700 Subject: [PATCH 03/95] WIP, straight arm still rotates.. --- libraries/animation/src/AnimInverseKinematics.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 1fdeaef483..09cee28794 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -169,11 +169,11 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std:: // AJT: HACK REMOVE manually set pole vector. if (targetVar.jointName == "RightHand") { - target.setPoleVector(glm::normalize(glm::vec3(-1, -1, 0))); + target.setPoleVector(glm::normalize(glm::vec3(-1, -2, -1))); target.setPoleIndex(_skeleton->nameToJointIndex(targetVar.jointName)); } if (targetVar.jointName == "LeftHand") { - target.setPoleVector(glm::normalize(glm::vec3(1, -1, 0))); + target.setPoleVector(glm::normalize(glm::vec3(1, -2, -1))); target.setPoleIndex(_skeleton->nameToJointIndex(targetVar.jointName)); } @@ -522,7 +522,8 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const glm::vec3 pProj = p - glm::dot(p, dUnit) * dUnit; float eProjLen = glm::length(eProj); float pProjLen = glm::length(pProj); - if (eProjLen > EPSILON && pProjLen > EPSILON) { + const float MIN_E_PROJ_LEN = 0.2f; // cm + if (eProjLen > MIN_E_PROJ_LEN && pProjLen > EPSILON) { float dot = glm::clamp(glm::dot(eProj / eProjLen, pProj / pProjLen), 0.0f, 1.0f); float theta = acos(dot); glm::vec3 cross = glm::cross(eProj, pProj); @@ -1265,7 +1266,7 @@ void AnimInverseKinematics::initConstraints() { // we determine the max/min angles by rotating the swing limit lines from parent- to child-frame // then measure the angles to swing the yAxis into alignment glm::vec3 hingeAxis = - mirror * Vectors::UNIT_Z; - const float MIN_ELBOW_ANGLE = 0.15f; // ~8.6 deg (ajt-rad-to-deg 0.15) + const float MIN_ELBOW_ANGLE = 0.05f; // ~2.8 deg (ajt-rad-to-deg 0.05) const float MAX_ELBOW_ANGLE = 11.0f * PI / 12.0f; glm::quat invReferenceRotation = glm::inverse(referenceRotation); glm::vec3 minSwingAxis = invReferenceRotation * glm::angleAxis(MIN_ELBOW_ANGLE, hingeAxis) * Vectors::UNIT_Y; From e8ca1a30602f050b121e7fa47d6e4619f658948d Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 9 Jun 2017 18:06:56 -0700 Subject: [PATCH 04/95] WIP: added magnitude to damping rotation near singularities. Also knee pole constraints don't work.. why? --- .../animation/src/AnimInverseKinematics.cpp | 85 ++++++++++++++----- 1 file changed, 64 insertions(+), 21 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 09cee28794..ff9a6d0d6a 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -42,6 +42,10 @@ static void lookupJointChainInfo(AnimInverseKinematics::JointChainInfo* jointCha } } +float easeOutExpo(float t) { + return 1.0f - powf(2, -10.0f * t); +} + AnimInverseKinematics::IKTargetVar::IKTargetVar(const QString& jointNameIn, const QString& positionVarIn, const QString& rotationVarIn, const QString& typeVarIn, const QString& weightVarIn, float weightIn, const std::vector& flexCoefficientsIn) : jointName(jointNameIn), @@ -166,14 +170,50 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std:: target.setIndex(targetVar.jointIndex); target.setWeight(weight); target.setFlexCoefficients(targetVar.numFlexCoefficients, targetVar.flexCoefficients); + target.setPoleIndex(-1); + /* // AJT: HACK REMOVE manually set pole vector. if (targetVar.jointName == "RightHand") { - target.setPoleVector(glm::normalize(glm::vec3(-1, -2, -1))); + int armJointIndex = _skeleton->nameToJointIndex("RightArm"); + glm::vec3 armPosition = _skeleton->getAbsolutePose(armJointIndex, _relativePoses).trans(); + glm::vec3 d = glm::normalize(translation - armPosition); + + // project hand y-axis onto d. + glm::vec3 handY = rotation * Vectors::UNIT_Y; + glm::vec3 handYProj = handY - glm::dot(handY, d) * d; + + // project negative pole vector on to d. + glm::vec3 p = glm::normalize(glm::vec3(-1, -3, -3)); + glm::vec3 pProj = p - glm::dot(p, d) * d; + + // compute a rotation around d that will bring the pProj to to yProj + float theta = acosf(glm::dot(glm::normalize(-pProj), glm::normalize(handYProj))); + glm::vec3 axis = glm::normalize(glm::cross(-pProj, handYProj)); + + if (glm::dot(axis, d) < 0.0f) { + // as eProjLen and pProjLen become orthognal to d, reduce the amount of rotation. + float magnitude = easeOutExpo(glm::min(glm::length(handYProj), glm::length(pProj))); + glm::quat poleAdjustRot = angleAxis(magnitude * (theta / 4.0f), axis); + target.setPoleVector(poleAdjustRot * p); + } else { + target.setPoleVector(p); + } target.setPoleIndex(_skeleton->nameToJointIndex(targetVar.jointName)); } if (targetVar.jointName == "LeftHand") { - target.setPoleVector(glm::normalize(glm::vec3(1, -2, -1))); + target.setPoleVector(glm::normalize(glm::vec3(1, -3, -3))); + target.setPoleIndex(_skeleton->nameToJointIndex(targetVar.jointName)); + } + */ + + if (targetVar.jointName == "LeftFoot") { + glm::vec3 footY = rotation * Vectors::UNIT_Y; + target.setPoleVector(footY); + target.setPoleIndex(_skeleton->nameToJointIndex(targetVar.jointName)); + } else if (targetVar.jointName == "RightFoot") { + glm::vec3 footY = rotation * Vectors::UNIT_Y; + target.setPoleVector(footY); target.setPoleIndex(_skeleton->nameToJointIndex(targetVar.jointName)); } @@ -524,13 +564,17 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const float pProjLen = glm::length(pProj); const float MIN_E_PROJ_LEN = 0.2f; // cm if (eProjLen > MIN_E_PROJ_LEN && pProjLen > EPSILON) { + + // as eProjLen and pProjLen become orthognal to d, reduce the amount of rotation. + float magnitude = easeOutExpo(glm::min(eProjLen, pProjLen)); + float dot = glm::clamp(glm::dot(eProj / eProjLen, pProj / pProjLen), 0.0f, 1.0f); - float theta = acos(dot); + float theta = acosf(dot); glm::vec3 cross = glm::cross(eProj, pProj); float crossLen = glm::length(cross); if (crossLen > EPSILON) { glm::vec3 axis = cross / crossLen; - poleRot = glm::angleAxis(theta, axis); + poleRot = glm::angleAxis(magnitude * theta, axis); } } } @@ -552,25 +596,23 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const float eProjLen = glm::length(eProj); float pProjLen = glm::length(pProj); - const float POLE_VECTOR_LEN = 10.0f; + const float PROJ_VECTOR_LEN = 10.0f; + const float POLE_VECTOR_LEN = 100.0f; glm::vec3 midPoint = (basePose.trans() + topPose.trans()) * 0.5f; DebugDraw::getInstance().drawRay(geomToWorldPose.xformPoint(basePose.trans()), geomToWorldPose.xformPoint(topPose.trans()), YELLOW); DebugDraw::getInstance().drawRay(geomToWorldPose.xformPoint(midPoint), - geomToWorldPose.xformPoint(midPoint + POLE_VECTOR_LEN * glm::normalize(eProj)), + geomToWorldPose.xformPoint(midPoint + PROJ_VECTOR_LEN * glm::normalize(eProj)), RED); - /* + DebugDraw::getInstance().drawRay(geomToWorldPose.xformPoint(midPoint), + geomToWorldPose.xformPoint(midPoint + PROJ_VECTOR_LEN * glm::normalize(pProj)), + GREEN); DebugDraw::getInstance().drawRay(geomToWorldPose.xformPoint(midPoint), geomToWorldPose.xformPoint(midPoint + POLE_VECTOR_LEN * glm::normalize(p)), BLUE); - */ - DebugDraw::getInstance().drawRay(geomToWorldPose.xformPoint(midPoint), - geomToWorldPose.xformPoint(midPoint + POLE_VECTOR_LEN * glm::normalize(pProj)), - GREEN); } - glm::quat newBaseRelRot = glm::inverse(baseParentPose.rot()) * poleRot * basePose.rot(); jointChainInfos[baseChainIndex].relRot = newBaseRelRot; @@ -672,7 +714,8 @@ const std::vector* AnimInverseKinematics void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug) { - std::map debugJointMap; + const size_t MAX_CHAIN_DEPTH = 30; + JointChainInfo jointChainInfos[MAX_CHAIN_DEPTH]; const int baseIndex = _hipsIndex; @@ -732,7 +775,6 @@ void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, co ::blend(1, &absolutePoses[splineJointInfo.jointIndex], &desiredAbsPose, target.getFlexCoefficient(i), &flexedAbsPose); AnimPose relPose = parentAbsPose.inverse() * flexedAbsPose; - _rotationAccumulators[splineJointInfo.jointIndex].add(relPose.rot(), target.getWeight()); bool constrained = false; if (splineJointInfo.jointIndex != _hipsIndex) { @@ -756,18 +798,19 @@ void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, co } } - _translationAccumulators[splineJointInfo.jointIndex].add(relPose.trans(), target.getWeight()); - - if (debug) { - debugJointMap[splineJointInfo.jointIndex] = DebugJoint(relPose.rot(), relPose.trans(), constrained); - } + jointChainInfos[i] = { relPose.rot(), relPose.trans(), target.getWeight(), splineJointInfo.jointIndex, constrained }; parentAbsPose = flexedAbsPose; } } + 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(debugJointMap, context); + debugDrawIKChain(jointChainInfos, splineJointInfoVec->size(), context); } } @@ -1611,7 +1654,7 @@ void AnimInverseKinematics::debugDrawConstraints(const AnimContext& context) con glm::vec3 worldSwungAxis = transformVectorFast(geomToWorldMatrix, parentAbsRot * refRot * swungAxis); glm::vec3 swingTip = pos + SWING_LENGTH * worldSwungAxis; - float prevPhi = acos(swingTwistConstraint->getMinDots()[j]); + float prevPhi = acosf(swingTwistConstraint->getMinDots()[j]); float prevTheta = theta - D_THETA; glm::vec3 prevSwungAxis = sphericalToCartesian(prevPhi, prevTheta - PI_2); glm::vec3 prevWorldSwungAxis = transformVectorFast(geomToWorldMatrix, parentAbsRot * refRot * prevSwungAxis); From 0cde22d937175822583236e4f71787eba50d6fa5 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 12 Jun 2017 12:13:03 -0700 Subject: [PATCH 05/95] Bug fix for pole constraint on legs --- libraries/animation/src/AnimInverseKinematics.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index ff9a6d0d6a..7de6626cd5 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -528,10 +528,11 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const int baseJointIndex = _skeleton->getParentIndex(midJointIndex); if (baseJointIndex != -1) { int baseParentJointIndex = _skeleton->getParentIndex(baseJointIndex); - AnimPose topPose, midPose, basePose, baseParentPose; + AnimPose topPose, midPose, basePose; int topChainIndex = -1, baseChainIndex = -1; 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); postAbsPoses[i] = accum; @@ -1341,7 +1342,7 @@ void AnimInverseKinematics::initConstraints() { // we determine the max/min angles by rotating the swing limit lines from parent- to child-frame // then measure the angles to swing the yAxis into alignment const float MIN_KNEE_ANGLE = 0.097f; // ~5 deg - const float MAX_KNEE_ANGLE = 7.0f * PI / 8.0f; + const float MAX_KNEE_ANGLE = 7.0f * PI / 8.0f; // 157.5 deg glm::quat invReferenceRotation = glm::inverse(referenceRotation); glm::vec3 minSwingAxis = invReferenceRotation * glm::angleAxis(MIN_KNEE_ANGLE, hingeAxis) * Vectors::UNIT_Y; glm::vec3 maxSwingAxis = invReferenceRotation * glm::angleAxis(MAX_KNEE_ANGLE, hingeAxis) * Vectors::UNIT_Y; From 7521d6124e3502ebf4097489d304eccabfccd7bd Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 12 Jun 2017 17:23:23 -0700 Subject: [PATCH 06/95] WIP: added blend between hips and foot for knee pole vector --- .../animation/src/AnimInverseKinematics.cpp | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 7de6626cd5..0ff236d1b1 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -161,9 +161,9 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std:: IKTarget target; target.setType(animVars.lookup(targetVar.typeVar, (int)IKTarget::Type::RotationAndPosition)); if (target.getType() != IKTarget::Type::Unknown) { - AnimPose defaultPose = _skeleton->getAbsolutePose(targetVar.jointIndex, underPoses); - glm::quat rotation = animVars.lookupRigToGeometry(targetVar.rotationVar, defaultPose.rot()); - glm::vec3 translation = animVars.lookupRigToGeometry(targetVar.positionVar, defaultPose.trans()); + AnimPose absPose = _skeleton->getAbsolutePose(targetVar.jointIndex, underPoses); + glm::quat rotation = animVars.lookupRigToGeometry(targetVar.rotationVar, absPose.rot()); + glm::vec3 translation = animVars.lookupRigToGeometry(targetVar.positionVar, absPose.trans()); float weight = animVars.lookup(targetVar.weightVar, targetVar.weight); target.setPose(rotation, translation); @@ -207,13 +207,24 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std:: } */ - if (targetVar.jointName == "LeftFoot") { - glm::vec3 footY = rotation * Vectors::UNIT_Y; - target.setPoleVector(footY); - target.setPoleIndex(_skeleton->nameToJointIndex(targetVar.jointName)); - } else if (targetVar.jointName == "RightFoot") { - glm::vec3 footY = rotation * Vectors::UNIT_Y; - target.setPoleVector(footY); + if (targetVar.jointName == "LeftFoot" || targetVar.jointName == "RightFoot") { + + // compute the forward direction of the foot. + AnimPose defaultPose = _skeleton->getAbsoluteDefaultPose(targetVar.jointIndex); + glm::vec3 localForward = glm::inverse(defaultPose.rot()) * Vectors::UNIT_Z; + glm::vec3 footForward = rotation * localForward; + + // compute the forward direction of the hips. + glm::quat hipsRotation = _skeleton->getAbsolutePose(_hipsIndex, _relativePoses).rot(); + glm::vec3 hipsForward = hipsRotation * Vectors::UNIT_Z; + + // blend between the hips and the foot. + const float FOOT_TO_HIPS_BLEND_FACTOR = 0.5f; + glm::vec3 poleVector = glm::normalize(lerp(footForward, hipsForward, FOOT_TO_HIPS_BLEND_FACTOR)); + + // TODO: smooth toward desired pole vector from previous pole vector... to reduce jitter + + target.setPoleVector(poleVector); target.setPoleIndex(_skeleton->nameToJointIndex(targetVar.jointName)); } From f20c03fa6e033bad78278c827137ceba05f7d72d Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 13 Jun 2017 15:36:05 -0700 Subject: [PATCH 07/95] Pole vectors can be controlled via anim vars. --- .../resources/avatar/avatar-animation.json | 8 +- .../animation/src/AnimInverseKinematics.cpp | 73 ++++------------- .../animation/src/AnimInverseKinematics.h | 8 +- libraries/animation/src/AnimNodeLoader.cpp | 4 +- libraries/animation/src/Rig.cpp | 80 ++++++++++++++----- libraries/animation/src/Rig.h | 12 ++- libraries/render-utils/src/Model.cpp | 4 - libraries/render-utils/src/Model.h | 1 - 8 files changed, 96 insertions(+), 94 deletions(-) diff --git a/interface/resources/avatar/avatar-animation.json b/interface/resources/avatar/avatar-animation.json index 1412b45968..914fe8ba69 100644 --- a/interface/resources/avatar/avatar-animation.json +++ b/interface/resources/avatar/avatar-animation.json @@ -86,7 +86,9 @@ "typeVar": "rightFootType", "weightVar": "rightFootWeight", "weight": 1.0, - "flexCoefficients": [1, 0.45, 0.45] + "flexCoefficients": [1, 0.45, 0.45], + "poleJointNameVar": "rightFootPoleJointName", + "poleVectorVar": "rightFootPoleVector" }, { "jointName": "LeftFoot", @@ -95,7 +97,9 @@ "typeVar": "leftFootType", "weightVar": "leftFootWeight", "weight": 1.0, - "flexCoefficients": [1, 0.45, 0.45] + "flexCoefficients": [1, 0.45, 0.45], + "poleJointNameVar": "leftFootPoleJointName", + "poleVectorVar": "leftFootPoleVector" }, { "jointName": "Spine2", diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 0ff236d1b1..57fa6fd1e3 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -47,12 +47,15 @@ float easeOutExpo(float t) { } AnimInverseKinematics::IKTargetVar::IKTargetVar(const QString& jointNameIn, const QString& positionVarIn, const QString& rotationVarIn, - const QString& typeVarIn, const QString& weightVarIn, float weightIn, const std::vector& flexCoefficientsIn) : + const QString& typeVarIn, const QString& weightVarIn, float weightIn, const std::vector& flexCoefficientsIn, + const QString& poleJointNameVarIn, const QString& poleVectorVarIn) : jointName(jointNameIn), positionVar(positionVarIn), rotationVar(rotationVarIn), typeVar(typeVarIn), weightVar(weightVarIn), + poleJointNameVar(poleJointNameVarIn), + poleVectorVar(poleVectorVarIn), weight(weightIn), numFlexCoefficients(flexCoefficientsIn.size()), jointIndex(-1) @@ -69,6 +72,8 @@ AnimInverseKinematics::IKTargetVar::IKTargetVar(const IKTargetVar& orig) : rotationVar(orig.rotationVar), typeVar(orig.typeVar), weightVar(orig.weightVar), + poleJointNameVar(orig.poleJointNameVar), + poleVectorVar(orig.poleVectorVar), weight(orig.weight), numFlexCoefficients(orig.numFlexCoefficients), jointIndex(orig.jointIndex) @@ -122,8 +127,9 @@ void AnimInverseKinematics::computeAbsolutePoses(AnimPoseVec& absolutePoses) con } void AnimInverseKinematics::setTargetVars(const QString& jointName, const QString& positionVar, const QString& rotationVar, - const QString& typeVar, const QString& weightVar, float weight, const std::vector& flexCoefficients) { - IKTargetVar targetVar(jointName, positionVar, rotationVar, typeVar, weightVar, weight, flexCoefficients); + const QString& typeVar, const QString& weightVar, float weight, const std::vector& flexCoefficients, + const QString& poleJointNameVar, const QString& poleVectorVar) { + IKTargetVar targetVar(jointName, positionVar, rotationVar, typeVar, weightVar, weight, flexCoefficients, poleJointNameVar, poleVectorVar); // if there are dups, last one wins. bool found = false; @@ -170,63 +176,14 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std:: target.setIndex(targetVar.jointIndex); target.setWeight(weight); target.setFlexCoefficients(targetVar.numFlexCoefficients, targetVar.flexCoefficients); - target.setPoleIndex(-1); - /* - // AJT: HACK REMOVE manually set pole vector. - if (targetVar.jointName == "RightHand") { - int armJointIndex = _skeleton->nameToJointIndex("RightArm"); - glm::vec3 armPosition = _skeleton->getAbsolutePose(armJointIndex, _relativePoses).trans(); - glm::vec3 d = glm::normalize(translation - armPosition); + // AJT: TODO: cache the pole joint index. + QString poleJointName = animVars.lookup(targetVar.poleJointNameVar, QString("")); + int poleJointIndex = _skeleton->nameToJointIndex(poleJointName); + target.setPoleIndex(poleJointIndex); - // project hand y-axis onto d. - glm::vec3 handY = rotation * Vectors::UNIT_Y; - glm::vec3 handYProj = handY - glm::dot(handY, d) * d; - - // project negative pole vector on to d. - glm::vec3 p = glm::normalize(glm::vec3(-1, -3, -3)); - glm::vec3 pProj = p - glm::dot(p, d) * d; - - // compute a rotation around d that will bring the pProj to to yProj - float theta = acosf(glm::dot(glm::normalize(-pProj), glm::normalize(handYProj))); - glm::vec3 axis = glm::normalize(glm::cross(-pProj, handYProj)); - - if (glm::dot(axis, d) < 0.0f) { - // as eProjLen and pProjLen become orthognal to d, reduce the amount of rotation. - float magnitude = easeOutExpo(glm::min(glm::length(handYProj), glm::length(pProj))); - glm::quat poleAdjustRot = angleAxis(magnitude * (theta / 4.0f), axis); - target.setPoleVector(poleAdjustRot * p); - } else { - target.setPoleVector(p); - } - target.setPoleIndex(_skeleton->nameToJointIndex(targetVar.jointName)); - } - if (targetVar.jointName == "LeftHand") { - target.setPoleVector(glm::normalize(glm::vec3(1, -3, -3))); - target.setPoleIndex(_skeleton->nameToJointIndex(targetVar.jointName)); - } - */ - - if (targetVar.jointName == "LeftFoot" || targetVar.jointName == "RightFoot") { - - // compute the forward direction of the foot. - AnimPose defaultPose = _skeleton->getAbsoluteDefaultPose(targetVar.jointIndex); - glm::vec3 localForward = glm::inverse(defaultPose.rot()) * Vectors::UNIT_Z; - glm::vec3 footForward = rotation * localForward; - - // compute the forward direction of the hips. - glm::quat hipsRotation = _skeleton->getAbsolutePose(_hipsIndex, _relativePoses).rot(); - glm::vec3 hipsForward = hipsRotation * Vectors::UNIT_Z; - - // blend between the hips and the foot. - const float FOOT_TO_HIPS_BLEND_FACTOR = 0.5f; - glm::vec3 poleVector = glm::normalize(lerp(footForward, hipsForward, FOOT_TO_HIPS_BLEND_FACTOR)); - - // TODO: smooth toward desired pole vector from previous pole vector... to reduce jitter - - target.setPoleVector(poleVector); - target.setPoleIndex(_skeleton->nameToJointIndex(targetVar.jointName)); - } + glm::vec3 poleVector = animVars.lookupRigToGeometryVector(targetVar.poleVectorVar, Vectors::UNIT_Z); + target.setPoleVector(glm::normalize(poleVector)); targets.push_back(target); diff --git a/libraries/animation/src/AnimInverseKinematics.h b/libraries/animation/src/AnimInverseKinematics.h index 2ed96ed6ed..946c23883e 100644 --- a/libraries/animation/src/AnimInverseKinematics.h +++ b/libraries/animation/src/AnimInverseKinematics.h @@ -42,7 +42,8 @@ public: void computeAbsolutePoses(AnimPoseVec& absolutePoses) const; void setTargetVars(const QString& jointName, const QString& positionVar, const QString& rotationVar, - const QString& typeVar, const QString& weightVar, float weight, const std::vector& flexCoefficients); + const QString& typeVar, const QString& weightVar, float weight, const std::vector& flexCoefficients, + const QString& poleJointNameVar, const QString& poleVectorVar); virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimNode::Triggers& triggersOut) override; virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) override; @@ -108,7 +109,8 @@ protected: enum FlexCoefficients { MAX_FLEX_COEFFICIENTS = 10 }; struct IKTargetVar { IKTargetVar(const QString& jointNameIn, const QString& positionVarIn, const QString& rotationVarIn, - const QString& typeVarIn, const QString& weightVarIn, float weightIn, const std::vector& flexCoefficientsIn); + const QString& typeVarIn, const QString& weightVarIn, float weightIn, const std::vector& flexCoefficientsIn, + const QString& poleJointNameVar, const QString& poleVectorVar); IKTargetVar(const IKTargetVar& orig); QString jointName; @@ -116,6 +118,8 @@ protected: QString rotationVar; QString typeVar; QString weightVar; + QString poleJointNameVar; + QString poleVectorVar; float weight; float flexCoefficients[MAX_FLEX_COEFFICIENTS]; size_t numFlexCoefficients; diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 44ed8c6053..c4a149a35b 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -479,6 +479,8 @@ AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QS READ_OPTIONAL_STRING(typeVar, targetObj); READ_OPTIONAL_STRING(weightVar, targetObj); READ_OPTIONAL_FLOAT(weight, targetObj, 1.0f); + READ_OPTIONAL_STRING(poleJointNameVar, targetObj); + READ_OPTIONAL_STRING(poleVectorVar, targetObj); auto flexCoefficientsValue = targetObj.value("flexCoefficients"); if (!flexCoefficientsValue.isArray()) { @@ -491,7 +493,7 @@ AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QS flexCoefficients.push_back((float)value.toDouble()); } - node->setTargetVars(jointName, positionVar, rotationVar, typeVar, weightVar, weight, flexCoefficients); + node->setTargetVars(jointName, positionVar, rotationVar, typeVar, weightVar, weight, flexCoefficients, poleJointNameVar, poleVectorVar); }; READ_OPTIONAL_STRING(solutionSource, jsonObj); diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 20a2aab2b6..2966e882d2 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -479,12 +479,6 @@ bool Rig::getAbsoluteJointPoseInRigFrame(int jointIndex, AnimPose& returnPose) c } } -bool Rig::getJointCombinedRotation(int jointIndex, glm::quat& result, const glm::quat& rotation) const { - // AJT: TODO: used by attachments - ASSERT(false); - return false; -} - void Rig::calcAnimAlpha(float speed, const std::vector& referenceSpeeds, float* alphaOut) const { ASSERT(referenceSpeeds.size() > 0); @@ -950,6 +944,7 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons // evaluate the animation AnimNode::Triggers triggersOut; + _internalPoseSet._relativePoses = _animNode->evaluate(_animVars, context, deltaTime, triggersOut); if ((int)_internalPoseSet._relativePoses.size() != _animSkeleton->getNumJoints()) { // animations haven't fully loaded yet. @@ -1145,6 +1140,21 @@ void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm } } +glm::vec3 Rig::calculateKneePoleVector(int footJointIndex, const glm::quat& footTargetOrientation, int hipsIndex) const { + + AnimPose defaultPose = _animSkeleton->getAbsoluteDefaultPose(footJointIndex); + glm::vec3 localForward = glm::inverse(defaultPose.rot()) * Vectors::UNIT_Z; + glm::vec3 footForward = footTargetOrientation * localForward; + + // compute the forward direction of the hips. + glm::quat hipsRotation = _externalPoseSet._absolutePoses[hipsIndex].rot(); + glm::vec3 hipsForward = hipsRotation * Vectors::UNIT_Z; + + // blend between the hips and the foot. + const float FOOT_TO_HIPS_BLEND_FACTOR = 0.5f; + return glm::normalize(lerp(footForward, hipsForward, FOOT_TO_HIPS_BLEND_FACTOR)); +} + void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, float dt) { if (_animSkeleton && _animNode) { const float HAND_RADIUS = 0.05f; @@ -1255,16 +1265,46 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f _animVars.set("leftFootPosition", params.leftFootPosition); _animVars.set("leftFootRotation", params.leftFootOrientation); _animVars.set("leftFootType", (int)IKTarget::Type::RotationAndPosition); + + int footJointIndex = _animSkeleton->nameToJointIndex("LeftFoot"); + glm::vec3 poleVector = calculateKneePoleVector(footJointIndex, params.leftFootOrientation, hipsIndex); + + // smooth toward desired pole vector from previous pole vector... to reduce jitter + if (!_prevLeftFootPoleVectorValid) { + _prevLeftFootPoleVectorValid = true; + _prevLeftFootPoleVector = poleVector; + } + const float POLE_VECTOR_BLEND_FACTOR = 0.9f; + _prevLeftFootPoleVector = lerp(poleVector, _prevLeftFootPoleVector, POLE_VECTOR_BLEND_FACTOR); + + _animVars.set("leftFootPoleVector", _prevLeftFootPoleVector); + _animVars.set("leftFootPoleJointName", QString("LeftFoot")); + } else { _animVars.unset("leftFootPosition"); _animVars.unset("leftFootRotation"); _animVars.set("leftFootType", (int)IKTarget::Type::RotationAndPosition); + _prevLeftFootPoleVectorValid = false; } if (params.isRightFootEnabled) { _animVars.set("rightFootPosition", params.rightFootPosition); _animVars.set("rightFootRotation", params.rightFootOrientation); _animVars.set("rightFootType", (int)IKTarget::Type::RotationAndPosition); + + int footJointIndex = _animSkeleton->nameToJointIndex("RightFoot"); + glm::vec3 poleVector = calculateKneePoleVector(footJointIndex, params.rightFootOrientation, hipsIndex); + + // smooth toward desired pole vector from previous pole vector... to reduce jitter + if (!_prevRightFootPoleVectorValid) { + _prevRightFootPoleVectorValid = true; + _prevRightFootPoleVector = poleVector; + } + const float POLE_VECTOR_BLEND_FACTOR = 0.9f; + _prevRightFootPoleVector = lerp(poleVector, _prevRightFootPoleVector, POLE_VECTOR_BLEND_FACTOR); + + _animVars.set("rightFootPoleVector", _prevRightFootPoleVector); + _animVars.set("rightFootPoleJointName", QString("RightFoot")); } else { _animVars.unset("rightFootPosition"); _animVars.unset("rightFootRotation"); @@ -1455,22 +1495,18 @@ void Rig::computeAvatarBoundingCapsule( AnimInverseKinematics ikNode("boundingShape"); ikNode.setSkeleton(_animSkeleton); - ikNode.setTargetVars("LeftHand", - "leftHandPosition", - "leftHandRotation", - "leftHandType", "leftHandWeight", 1.0f, {}); - ikNode.setTargetVars("RightHand", - "rightHandPosition", - "rightHandRotation", - "rightHandType", "rightHandWeight", 1.0f, {}); - ikNode.setTargetVars("LeftFoot", - "leftFootPosition", - "leftFootRotation", - "leftFootType", "leftFootWeight", 1.0f, {}); - ikNode.setTargetVars("RightFoot", - "rightFootPosition", - "rightFootRotation", - "rightFootType", "rightFootWeight", 1.0f, {}); + ikNode.setTargetVars("LeftHand", "leftHandPosition", "leftHandRotation", + "leftHandType", "leftHandWeight", 1.0f, {}, + "leftHandPoleJointName", "leftHandPoleVector"); + ikNode.setTargetVars("RightHand", "rightHandPosition", "rightHandRotation", + "rightHandType", "rightHandWeight", 1.0f, {}, + "rightHandPoleJointName", "rightHandPoleVector"); + ikNode.setTargetVars("LeftFoot", "leftFootPosition", "leftFootRotation", + "leftFootType", "leftFootWeight", 1.0f, {}, + "leftFootPoleJointName", "leftFootPoleVector"); + ikNode.setTargetVars("RightFoot", "rightFootPosition", "rightFootRotation", + "rightFootType", "rightFootWeight", 1.0f, {}, + "rightFootPoleJointName", "rightFootPoleVector"); AnimPose geometryToRig = _modelOffset * _geometryOffset; diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 994bd4b074..4fc5f76146 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -153,9 +153,6 @@ public: bool getAbsoluteJointTranslationInRigFrame(int jointIndex, glm::vec3& translation) const; bool getAbsoluteJointPoseInRigFrame(int jointIndex, AnimPose& returnPose) const; - // legacy - bool getJointCombinedRotation(int jointIndex, glm::quat& result, const glm::quat& rotation) const; - // rig space glm::mat4 getJointTransform(int jointIndex) const; @@ -252,6 +249,8 @@ protected: void updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::vec3& lookAt, const glm::vec3& saccade); void calcAnimAlpha(float speed, const std::vector& referenceSpeeds, float* alphaOut) const; + glm::vec3 Rig::calculateKneePoleVector(int footJointIndex, const glm::quat& footTargetOrientation, int hipsIndex) const; + AnimPose _modelOffset; // model to rig space AnimPose _geometryOffset; // geometry to model space (includes unit offset & fst offsets) AnimPose _invGeometryOffset; @@ -347,7 +346,6 @@ protected: bool _enableDebugDrawIKConstraints { false }; bool _enableDebugDrawIKChains { false }; -private: QMap _stateHandlers; int _nextStateHandlerId { 0 }; QMutex _stateMutex; @@ -358,6 +356,12 @@ private: float _rightHandRelaxDuration { 0.0f }; AnimPose _lastLeftHandControlledPose; AnimPose _lastRightHandControlledPose; + + glm::vec3 _prevRightFootPoleVector = { Vectors::UNIT_Z }; + bool _prevRightFootPoleVectorValid = { false }; + + glm::vec3 _prevLeftFootPoleVector = { Vectors::UNIT_Z }; + bool _prevLeftFootPoleVectorValid = { false }; }; #endif /* defined(__hifi__Rig__) */ diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 447d0e37bd..8953ea94f0 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -866,10 +866,6 @@ bool Model::getRelativeDefaultJointTranslation(int jointIndex, glm::vec3& transl return _rig.getRelativeDefaultJointTranslation(jointIndex, translationOut); } -bool Model::getJointCombinedRotation(int jointIndex, glm::quat& rotation) const { - return _rig.getJointCombinedRotation(jointIndex, rotation, _rotation); -} - QStringList Model::getJointNames() const { if (QThread::currentThread() != thread()) { QStringList result; diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 53d446d306..a0da8a7a8c 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -176,7 +176,6 @@ public: int getJointStateCount() const { return (int)_rig.getJointStateCount(); } bool getJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const; bool getJointRotationInWorldFrame(int jointIndex, glm::quat& rotation) const; - bool getJointCombinedRotation(int jointIndex, glm::quat& rotation) const; /// \param jointIndex index of joint in model structure /// \param rotation[out] rotation of joint in model-frame From 10f94c2d605e8d7eddf38b57e82ba68afe30022f Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 14 Jun 2017 11:35:53 -0700 Subject: [PATCH 08/95] Fixed pole vectors on straight limbs by using reference vector --- .../resources/avatar/avatar-animation.json | 6 ++- .../animation/src/AnimInverseKinematics.cpp | 37 ++++++++++--------- .../animation/src/AnimInverseKinematics.h | 7 ++-- libraries/animation/src/AnimNodeLoader.cpp | 5 ++- libraries/animation/src/IKTarget.h | 9 +++-- libraries/animation/src/Rig.cpp | 21 ++++++----- 6 files changed, 48 insertions(+), 37 deletions(-) diff --git a/interface/resources/avatar/avatar-animation.json b/interface/resources/avatar/avatar-animation.json index 914fe8ba69..c9552a3ead 100644 --- a/interface/resources/avatar/avatar-animation.json +++ b/interface/resources/avatar/avatar-animation.json @@ -87,7 +87,8 @@ "weightVar": "rightFootWeight", "weight": 1.0, "flexCoefficients": [1, 0.45, 0.45], - "poleJointNameVar": "rightFootPoleJointName", + "poleVectorEnabledVar": "rightFootPoleVectorEnabled", + "poleReferenceVectorVar": "rightFootPoleReferenceVector", "poleVectorVar": "rightFootPoleVector" }, { @@ -98,7 +99,8 @@ "weightVar": "leftFootWeight", "weight": 1.0, "flexCoefficients": [1, 0.45, 0.45], - "poleJointNameVar": "leftFootPoleJointName", + "poleVectorEnabledVar": "leftFootPoleVectorEnabled", + "poleReferenceVectorVar": "leftFootPoleReferenceVector", "poleVectorVar": "leftFootPoleVector" }, { diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 57fa6fd1e3..2a493a96ab 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -48,13 +48,14 @@ float easeOutExpo(float t) { AnimInverseKinematics::IKTargetVar::IKTargetVar(const QString& jointNameIn, const QString& positionVarIn, const QString& rotationVarIn, const QString& typeVarIn, const QString& weightVarIn, float weightIn, const std::vector& flexCoefficientsIn, - const QString& poleJointNameVarIn, const QString& poleVectorVarIn) : + const QString& poleVectorEnabledVarIn, const QString& poleReferenceVectorVarIn, const QString& poleVectorVarIn) : jointName(jointNameIn), positionVar(positionVarIn), rotationVar(rotationVarIn), typeVar(typeVarIn), weightVar(weightVarIn), - poleJointNameVar(poleJointNameVarIn), + poleVectorEnabledVar(poleVectorEnabledVarIn), + poleReferenceVectorVar(poleReferenceVectorVarIn), poleVectorVar(poleVectorVarIn), weight(weightIn), numFlexCoefficients(flexCoefficientsIn.size()), @@ -72,7 +73,8 @@ AnimInverseKinematics::IKTargetVar::IKTargetVar(const IKTargetVar& orig) : rotationVar(orig.rotationVar), typeVar(orig.typeVar), weightVar(orig.weightVar), - poleJointNameVar(orig.poleJointNameVar), + poleVectorEnabledVar(orig.poleVectorEnabledVar), + poleReferenceVectorVar(orig.poleReferenceVectorVar), poleVectorVar(orig.poleVectorVar), weight(orig.weight), numFlexCoefficients(orig.numFlexCoefficients), @@ -128,8 +130,8 @@ void AnimInverseKinematics::computeAbsolutePoses(AnimPoseVec& absolutePoses) con void AnimInverseKinematics::setTargetVars(const QString& jointName, const QString& positionVar, const QString& rotationVar, const QString& typeVar, const QString& weightVar, float weight, const std::vector& flexCoefficients, - const QString& poleJointNameVar, const QString& poleVectorVar) { - IKTargetVar targetVar(jointName, positionVar, rotationVar, typeVar, weightVar, weight, flexCoefficients, poleJointNameVar, poleVectorVar); + const QString& poleVectorEnabledVar, const QString& poleReferenceVectorVar, const QString& poleVectorVar) { + IKTargetVar targetVar(jointName, positionVar, rotationVar, typeVar, weightVar, weight, flexCoefficients, poleVectorEnabledVar, poleReferenceVectorVar, poleVectorVar); // if there are dups, last one wins. bool found = false; @@ -177,14 +179,15 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std:: target.setWeight(weight); target.setFlexCoefficients(targetVar.numFlexCoefficients, targetVar.flexCoefficients); - // AJT: TODO: cache the pole joint index. - QString poleJointName = animVars.lookup(targetVar.poleJointNameVar, QString("")); - int poleJointIndex = _skeleton->nameToJointIndex(poleJointName); - target.setPoleIndex(poleJointIndex); + bool poleVectorEnabled = animVars.lookup(targetVar.poleVectorEnabledVar, false); + target.setPoleVectorEnabled(poleVectorEnabled); glm::vec3 poleVector = animVars.lookupRigToGeometryVector(targetVar.poleVectorVar, Vectors::UNIT_Z); target.setPoleVector(glm::normalize(poleVector)); + glm::vec3 poleReferenceVector = animVars.lookupRigToGeometryVector(targetVar.poleReferenceVectorVar, Vectors::UNIT_Z); + target.setPoleReferenceVector(glm::normalize(poleReferenceVector)); + targets.push_back(target); if (targetVar.jointIndex > _maxTargetIndex) { @@ -488,9 +491,8 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const chainDepth++; } - if (target.getPoleIndex() != -1) { - - int topJointIndex = target.getPoleIndex(); + if (target.getPoleVectorEnabled()) { + int topJointIndex = target.getIndex(); int midJointIndex = _skeleton->getParentIndex(topJointIndex); if (midJointIndex != -1) { int baseJointIndex = _skeleton->getParentIndex(midJointIndex); @@ -525,17 +527,16 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const float dLen = glm::length(d); if (dLen > EPSILON) { glm::vec3 dUnit = d / dLen; - glm::vec3 e = midPose.trans() - basePose.trans(); + glm::vec3 e = midPose.xformVector(target.getPoleReferenceVector()); glm::vec3 p = target.getPoleVector(); glm::vec3 eProj = e - glm::dot(e, dUnit) * dUnit; glm::vec3 pProj = p - glm::dot(p, dUnit) * dUnit; float eProjLen = glm::length(eProj); float pProjLen = glm::length(pProj); - const float MIN_E_PROJ_LEN = 0.2f; // cm - if (eProjLen > MIN_E_PROJ_LEN && pProjLen > EPSILON) { + if (eProjLen > EPSILON && pProjLen > EPSILON) { - // as eProjLen and pProjLen become orthognal to d, reduce the amount of rotation. - float magnitude = easeOutExpo(glm::min(eProjLen, pProjLen)); + // as pProjLen become orthognal to d, reduce the amount of rotation. + float magnitude = easeOutExpo(pProjLen); float dot = glm::clamp(glm::dot(eProj / eProjLen, pProj / pProjLen), 0.0f, 1.0f); float theta = acosf(dot); @@ -558,7 +559,7 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const AnimPose geomToWorldPose = AnimPose(context.getRigToWorldMatrix() * context.getGeometryToRigMatrix()); glm::vec3 dUnit = d / dLen; - glm::vec3 e = midPose.trans() - basePose.trans(); + glm::vec3 e = midPose.xformVector(target.getPoleReferenceVector()); glm::vec3 p = target.getPoleVector(); glm::vec3 eProj = e - glm::dot(e, dUnit) * dUnit; glm::vec3 pProj = p - glm::dot(p, dUnit) * dUnit; diff --git a/libraries/animation/src/AnimInverseKinematics.h b/libraries/animation/src/AnimInverseKinematics.h index 946c23883e..016317aee3 100644 --- a/libraries/animation/src/AnimInverseKinematics.h +++ b/libraries/animation/src/AnimInverseKinematics.h @@ -43,7 +43,7 @@ public: void setTargetVars(const QString& jointName, const QString& positionVar, const QString& rotationVar, const QString& typeVar, const QString& weightVar, float weight, const std::vector& flexCoefficients, - const QString& poleJointNameVar, const QString& poleVectorVar); + const QString& poleVectorEnabledVar, const QString& poleReferenceVectorVar, const QString& poleVectorVar); virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimNode::Triggers& triggersOut) override; virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) override; @@ -110,7 +110,7 @@ protected: struct IKTargetVar { IKTargetVar(const QString& jointNameIn, const QString& positionVarIn, const QString& rotationVarIn, const QString& typeVarIn, const QString& weightVarIn, float weightIn, const std::vector& flexCoefficientsIn, - const QString& poleJointNameVar, const QString& poleVectorVar); + const QString& poleVectorEnabledVar, const QString& poleReferenceVectorVar, const QString& poleVectorVar); IKTargetVar(const IKTargetVar& orig); QString jointName; @@ -118,7 +118,8 @@ protected: QString rotationVar; QString typeVar; QString weightVar; - QString poleJointNameVar; + QString poleVectorEnabledVar; + QString poleReferenceVectorVar; QString poleVectorVar; float weight; float flexCoefficients[MAX_FLEX_COEFFICIENTS]; diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index c4a149a35b..2a1978127d 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -479,7 +479,8 @@ AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QS READ_OPTIONAL_STRING(typeVar, targetObj); READ_OPTIONAL_STRING(weightVar, targetObj); READ_OPTIONAL_FLOAT(weight, targetObj, 1.0f); - READ_OPTIONAL_STRING(poleJointNameVar, targetObj); + READ_OPTIONAL_STRING(poleVectorEnabledVar, targetObj); + READ_OPTIONAL_STRING(poleReferenceVectorVar, targetObj); READ_OPTIONAL_STRING(poleVectorVar, targetObj); auto flexCoefficientsValue = targetObj.value("flexCoefficients"); @@ -493,7 +494,7 @@ AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QS flexCoefficients.push_back((float)value.toDouble()); } - node->setTargetVars(jointName, positionVar, rotationVar, typeVar, weightVar, weight, flexCoefficients, poleJointNameVar, poleVectorVar); + node->setTargetVars(jointName, positionVar, rotationVar, typeVar, weightVar, weight, flexCoefficients, poleVectorEnabledVar, poleReferenceVectorVar, poleVectorVar); }; READ_OPTIONAL_STRING(solutionSource, jsonObj); diff --git a/libraries/animation/src/IKTarget.h b/libraries/animation/src/IKTarget.h index c476e9c9da..5567539659 100644 --- a/libraries/animation/src/IKTarget.h +++ b/libraries/animation/src/IKTarget.h @@ -31,13 +31,15 @@ public: const glm::quat& getRotation() const { return _pose.rot(); } const AnimPose& getPose() const { return _pose; } glm::vec3 getPoleVector() const { return _poleVector; } - int getPoleIndex() const { return _poleIndex; } + glm::vec3 getPoleReferenceVector() const { return _poleReferenceVector; } + bool getPoleVectorEnabled() const { return _poleVectorEnabled; } int getIndex() const { return _index; } Type getType() const { return _type; } void setPose(const glm::quat& rotation, const glm::vec3& translation); void setPoleVector(const glm::vec3& poleVector) { _poleVector = poleVector; } - void setPoleIndex(int poleIndex) { _poleIndex = poleIndex; } + void setPoleReferenceVector(const glm::vec3& poleReferenceVector) { _poleReferenceVector = poleReferenceVector; } + void setPoleVectorEnabled(bool poleVectorEnabled) { _poleVectorEnabled = poleVectorEnabled; } void setIndex(int index) { _index = index; } void setType(int); void setFlexCoefficients(size_t numFlexCoefficientsIn, const float* flexCoefficientsIn); @@ -51,7 +53,8 @@ public: private: AnimPose _pose; glm::vec3 _poleVector; - int _poleIndex { -1 }; + glm::vec3 _poleReferenceVector; + bool _poleVectorEnabled { false }; int _index { -1 }; Type _type { Type::RotationAndPosition }; float _weight; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 2966e882d2..1e6965faf5 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1261,6 +1261,8 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f } } + const float POLE_VECTOR_BLEND_FACTOR = 0.9f; + if (params.isLeftFootEnabled) { _animVars.set("leftFootPosition", params.leftFootPosition); _animVars.set("leftFootRotation", params.leftFootOrientation); @@ -1274,16 +1276,16 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f _prevLeftFootPoleVectorValid = true; _prevLeftFootPoleVector = poleVector; } - const float POLE_VECTOR_BLEND_FACTOR = 0.9f; _prevLeftFootPoleVector = lerp(poleVector, _prevLeftFootPoleVector, POLE_VECTOR_BLEND_FACTOR); + _animVars.set("leftFootPoleVectorEnabled", true); + _animVars.set("leftFootPoleReferenceVector", Vectors::UNIT_Z); _animVars.set("leftFootPoleVector", _prevLeftFootPoleVector); - _animVars.set("leftFootPoleJointName", QString("LeftFoot")); - } else { _animVars.unset("leftFootPosition"); _animVars.unset("leftFootRotation"); _animVars.set("leftFootType", (int)IKTarget::Type::RotationAndPosition); + _animVars.set("leftFootPoleVectorEnabled", false); _prevLeftFootPoleVectorValid = false; } @@ -1300,14 +1302,15 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f _prevRightFootPoleVectorValid = true; _prevRightFootPoleVector = poleVector; } - const float POLE_VECTOR_BLEND_FACTOR = 0.9f; _prevRightFootPoleVector = lerp(poleVector, _prevRightFootPoleVector, POLE_VECTOR_BLEND_FACTOR); + _animVars.set("rightFootPoleVectorEnabled", true); + _animVars.set("rightFootPoleReferenceVector", Vectors::UNIT_Z); _animVars.set("rightFootPoleVector", _prevRightFootPoleVector); - _animVars.set("rightFootPoleJointName", QString("RightFoot")); } else { _animVars.unset("rightFootPosition"); _animVars.unset("rightFootRotation"); + _animVars.set("rightFootPoleVectorEnabled", false); _animVars.set("rightFootType", (int)IKTarget::Type::RotationAndPosition); } } @@ -1497,16 +1500,16 @@ void Rig::computeAvatarBoundingCapsule( ikNode.setTargetVars("LeftHand", "leftHandPosition", "leftHandRotation", "leftHandType", "leftHandWeight", 1.0f, {}, - "leftHandPoleJointName", "leftHandPoleVector"); + QString(), QString(), QString()); ikNode.setTargetVars("RightHand", "rightHandPosition", "rightHandRotation", "rightHandType", "rightHandWeight", 1.0f, {}, - "rightHandPoleJointName", "rightHandPoleVector"); + QString(), QString(), QString()); ikNode.setTargetVars("LeftFoot", "leftFootPosition", "leftFootRotation", "leftFootType", "leftFootWeight", 1.0f, {}, - "leftFootPoleJointName", "leftFootPoleVector"); + QString(), QString(), QString()); ikNode.setTargetVars("RightFoot", "rightFootPosition", "rightFootRotation", "rightFootType", "rightFootWeight", 1.0f, {}, - "rightFootPoleJointName", "rightFootPoleVector"); + QString(), QString(), QString()); AnimPose geometryToRig = _modelOffset * _geometryOffset; From fac21033e738890368ce485cc90f192d7d86c9bd Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 14 Jun 2017 11:53:54 -0700 Subject: [PATCH 09/95] Fixed matthew sitting pose by opening up UpLeg twist constraint to +- 90 degrees. --- libraries/animation/src/AnimInverseKinematics.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 2a493a96ab..eb256497b7 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -1137,7 +1137,7 @@ void AnimInverseKinematics::initConstraints() { } else if (0 == baseName.compare("UpLeg", Qt::CaseSensitive)) { SwingTwistConstraint* stConstraint = new SwingTwistConstraint(); stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot()); - stConstraint->setTwistLimits(-PI / 4.0f, PI / 4.0f); + stConstraint->setTwistLimits(-PI / 2.0f, PI / 2.0f); std::vector swungDirections; float deltaTheta = PI / 4.0f; From cdfba52488f5cea72a71968d5721ccb1fca5c819 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 15 Jun 2017 15:02:42 -0700 Subject: [PATCH 10/95] precondition initial solution before solving to reduce limb locking. --- .../animation/src/AnimInverseKinematics.cpp | 54 +++++++++++++++++-- .../animation/src/AnimInverseKinematics.h | 1 + libraries/animation/src/AnimSkeleton.cpp | 6 +-- libraries/animation/src/AnimSkeleton.h | 2 +- 4 files changed, 56 insertions(+), 7 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index eb256497b7..f55537b3d6 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -394,6 +394,7 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const const float MIN_AXIS_LENGTH = 1.0e-4f; RotationConstraint* constraint = getConstraint(pivotIndex); + // only allow swing on lowerSpine if there is a hips IK target. if (_hipsTargetIndex < 0 && constraint && constraint->isLowerSpine() && tipIndex != _headIndex) { // for these types of targets we only allow twist at the lower-spine @@ -419,6 +420,7 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const float cosAngle = glm::clamp(glm::dot(leverArm, targetLine) / (glm::length(leverArm) * glm::length(targetLine)), -1.0f, 1.0f); float angle = acosf(cosAngle); const float MIN_ADJUSTMENT_ANGLE = 1.0e-4f; + if (angle > MIN_ADJUSTMENT_ANGLE) { // reduce angle by a flexCoefficient angle *= target.getFlexCoefficient(chainDepth); @@ -791,6 +793,7 @@ const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVar return _relativePoses; } + //virtual const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) { // allows solutionSource to be overridden by an animVar @@ -904,6 +907,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars { PROFILE_RANGE_EX(simulation_animation, "ik/ccd", 0xffff00ff, 0); + preconditionRelativePosesToAvoidLimbLock(context, targets); solve(context, targets); } @@ -1279,7 +1283,7 @@ void AnimInverseKinematics::initConstraints() { // we determine the max/min angles by rotating the swing limit lines from parent- to child-frame // then measure the angles to swing the yAxis into alignment glm::vec3 hingeAxis = - mirror * Vectors::UNIT_Z; - const float MIN_ELBOW_ANGLE = 0.05f; // ~2.8 deg (ajt-rad-to-deg 0.05) + const float MIN_ELBOW_ANGLE = 0.0f; const float MAX_ELBOW_ANGLE = 11.0f * PI / 12.0f; glm::quat invReferenceRotation = glm::inverse(referenceRotation); glm::vec3 minSwingAxis = invReferenceRotation * glm::angleAxis(MIN_ELBOW_ANGLE, hingeAxis) * Vectors::UNIT_Y; @@ -1310,7 +1314,7 @@ void AnimInverseKinematics::initConstraints() { // we determine the max/min angles by rotating the swing limit lines from parent- to child-frame // then measure the angles to swing the yAxis into alignment - const float MIN_KNEE_ANGLE = 0.097f; // ~5 deg + const float MIN_KNEE_ANGLE = 0.0f; const float MAX_KNEE_ANGLE = 7.0f * PI / 8.0f; // 157.5 deg glm::quat invReferenceRotation = glm::inverse(referenceRotation); glm::vec3 minSwingAxis = invReferenceRotation * glm::angleAxis(MIN_KNEE_ANGLE, hingeAxis) * Vectors::UNIT_Y; @@ -1659,6 +1663,50 @@ void AnimInverseKinematics::blendToPoses(const AnimPoseVec& targetPoses, const A } } +void AnimInverseKinematics::preconditionRelativePosesToAvoidLimbLock(const AnimContext& context, const std::vector& targets) { + const int NUM_LIMBS = 4; + std::pair limbs[NUM_LIMBS] = { + {_skeleton->nameToJointIndex("LeftHand"), _skeleton->nameToJointIndex("LeftArm")}, + {_skeleton->nameToJointIndex("RightHand"), _skeleton->nameToJointIndex("RightArm")}, + {_skeleton->nameToJointIndex("LeftFoot"), _skeleton->nameToJointIndex("LeftUpLeg")}, + {_skeleton->nameToJointIndex("RightFoot"), _skeleton->nameToJointIndex("RightUpLeg")} + }; + const float MIN_AXIS_LENGTH = 1.0e-4f; + + for (auto& target : targets) { + if (target.getIndex() != -1) { + for (int i = 0; i < NUM_LIMBS; i++) { + if (limbs[i].first == target.getIndex()) { + int tipIndex = limbs[i].first; + int baseIndex = limbs[i].second; + + // TODO: as an optimization, these poses can be computed in one pass down the chain, instead of three. + AnimPose tipPose = _skeleton->getAbsolutePose(tipIndex, _relativePoses); + AnimPose basePose = _skeleton->getAbsolutePose(baseIndex, _relativePoses); + AnimPose baseParentPose = _skeleton->getAbsolutePose(_skeleton->getParentIndex(baseIndex), _relativePoses); + + // to help reduce limb locking, and to help the CCD solver converge faster + // rotate the limbs leverArm over the targetLine. + glm::vec3 targetLine = target.getTranslation() - basePose.trans(); + glm::vec3 leverArm = tipPose.trans() - basePose.trans(); + glm::vec3 axis = glm::cross(leverArm, targetLine); + float axisLength = glm::length(axis); + if (axisLength > MIN_AXIS_LENGTH) { + // compute angle of rotation that brings tip to target + axis /= axisLength; + float cosAngle = glm::clamp(glm::dot(leverArm, targetLine) / (glm::length(leverArm) * glm::length(targetLine)), -1.0f, 1.0f); + float angle = acosf(cosAngle); + glm::quat newBaseRotation = glm::angleAxis(angle, axis) * basePose.rot(); + + // convert base rotation into relative space of base. + _relativePoses[baseIndex].rot() = glm::inverse(baseParentPose.rot()) * newBaseRotation; + } + } + } + } + } +} + void AnimInverseKinematics::initRelativePosesFromSolutionSource(SolutionSource solutionSource, const AnimPoseVec& underPoses) { const float RELAX_BLEND_FACTOR = (1.0f / 16.0f); const float COPY_BLEND_FACTOR = 1.0f; @@ -1678,7 +1726,7 @@ void AnimInverseKinematics::initRelativePosesFromSolutionSource(SolutionSource s break; case SolutionSource::LimitCenterPoses: // essentially copy limitCenterPoses over to _relativePoses. - blendToPoses(_limitCenterPoses, underPoses, COPY_BLEND_FACTOR); + blendToPoses(underPoses, _limitCenterPoses, COPY_BLEND_FACTOR); break; } } diff --git a/libraries/animation/src/AnimInverseKinematics.h b/libraries/animation/src/AnimInverseKinematics.h index 016317aee3..d473ae3698 100644 --- a/libraries/animation/src/AnimInverseKinematics.h +++ b/libraries/animation/src/AnimInverseKinematics.h @@ -82,6 +82,7 @@ protected: void debugDrawSpineSplines(const AnimContext& context, const std::vector& targets) const; 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); // used to pre-compute information about each joint influeced by a spline IK target. struct SplineJointInfo { diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index 350fe8a534..062e016660 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -76,11 +76,11 @@ const QString& AnimSkeleton::getJointName(int jointIndex) const { return _joints[jointIndex].name; } -AnimPose AnimSkeleton::getAbsolutePose(int jointIndex, const AnimPoseVec& poses) const { - if (jointIndex < 0 || jointIndex >= (int)poses.size() || jointIndex >= _jointsSize) { +AnimPose AnimSkeleton::getAbsolutePose(int jointIndex, const AnimPoseVec& relativePoses) const { + if (jointIndex < 0 || jointIndex >= (int)relativePoses.size() || jointIndex >= _jointsSize) { return AnimPose::identity; } else { - return getAbsolutePose(_joints[jointIndex].parentIndex, poses) * poses[jointIndex]; + return getAbsolutePose(_joints[jointIndex].parentIndex, relativePoses) * relativePoses[jointIndex]; } } diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index 0988c26bdb..6315f2d62b 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -50,7 +50,7 @@ public: int getParentIndex(int jointIndex) const; - AnimPose getAbsolutePose(int jointIndex, const AnimPoseVec& poses) const; + AnimPose getAbsolutePose(int jointIndex, const AnimPoseVec& relativePoses) const; void convertRelativePosesToAbsolute(AnimPoseVec& poses) const; void convertAbsolutePosesToRelative(AnimPoseVec& poses) const; From e7991579ef6f9021cb01cf5f3aee5681c8cdeec1 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 16 Jun 2017 17:29:56 -0700 Subject: [PATCH 11/95] Enabled elbow pole vectors There are still some issues with rotations of the elbow pole vectors. * When the (hand - shoulder) vector approaches the normal vector used in Rig::calculateElbowPoleVector() unexpected twists can occur. * Also, when the (hand - shoulder) vector approaches zero, the IK system starts to flutter between two states. * The shoulder twist constraint probably needs to be opened up for more natural range of motion. --- .../resources/avatar/avatar-animation.json | 10 +- .../animation/src/AnimInverseKinematics.cpp | 2 +- libraries/animation/src/Rig.cpp | 95 ++++++++++++++++++- libraries/animation/src/Rig.h | 9 +- 4 files changed, 110 insertions(+), 6 deletions(-) diff --git a/interface/resources/avatar/avatar-animation.json b/interface/resources/avatar/avatar-animation.json index c9552a3ead..018987b58b 100644 --- a/interface/resources/avatar/avatar-animation.json +++ b/interface/resources/avatar/avatar-animation.json @@ -68,7 +68,10 @@ "typeVar": "rightHandType", "weightVar": "rightHandWeight", "weight": 1.0, - "flexCoefficients": [1, 0.5, 0.5, 0.2, 0.01, 0.005, 0.001, 0.0, 0.0] + "flexCoefficients": [1, 0.5, 0.5, 0.2, 0.01, 0.005, 0.001, 0.0, 0.0], + "poleVectorEnabledVar": "rightHandPoleVectorEnabled", + "poleReferenceVectorVar": "rightHandPoleReferenceVector", + "poleVectorVar": "rightHandPoleVector" }, { "jointName": "LeftHand", @@ -77,7 +80,10 @@ "typeVar": "leftHandType", "weightVar": "leftHandWeight", "weight": 1.0, - "flexCoefficients": [1, 0.5, 0.5, 0.2, 0.01, 0.005, 0.001, 0.0, 0.0] + "flexCoefficients": [1, 0.5, 0.5, 0.2, 0.01, 0.005, 0.001, 0.0, 0.0], + "poleVectorEnabledVar": "leftHandPoleVectorEnabled", + "poleReferenceVectorVar": "leftHandPoleReferenceVector", + "poleVectorVar": "leftHandPoleVector" }, { "jointName": "RightFoot", diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index f55537b3d6..080e6449d9 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -42,7 +42,7 @@ static void lookupJointChainInfo(AnimInverseKinematics::JointChainInfo* jointCha } } -float easeOutExpo(float t) { +static float easeOutExpo(float t) { return 1.0f - powf(2, -10.0f * t); } diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 1e6965faf5..99678eaa28 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1140,6 +1140,47 @@ void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm } } +static float easeOutExpo(float t) { + return 1.0f - powf(2, -10.0f * t); +} + +static glm::quat quatLerp(const glm::quat& q1, const glm::quat& q2, float alpha) { + float dot = glm::dot(q1, q2); + glm::quat temp; + if (dot < 0.0f) { + temp = -q2; + } else { + temp = q2; + } + return glm::normalize(glm::lerp(q1, temp, alpha)); +} + +glm::vec3 Rig::calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex, int hipsIndex, bool isLeft) const { + AnimPose hipsPose = _externalPoseSet._absolutePoses[hipsIndex]; + AnimPose handPose = _externalPoseSet._absolutePoses[handIndex]; + AnimPose elbowPose = _externalPoseSet._absolutePoses[elbowIndex]; + AnimPose armPose = _externalPoseSet._absolutePoses[armIndex]; + glm::vec3 d = glm::normalize(handPose.trans() - armPose.trans()); + + glm::quat elbowToHandDelta = handPose.rot() * glm::inverse(elbowPose.rot()); + const float WRIST_POLE_ADJUST_FACTOR = 0.5f; + glm::quat poleAdjust = quatLerp(Quaternions::IDENTITY, elbowToHandDelta, WRIST_POLE_ADJUST_FACTOR); + + glm::vec3 n; + if (isLeft) { + n = hipsPose.rot() * glm::normalize(glm::vec3(1.0f, 1.5f, -0.1f)); + } else { + n = hipsPose.rot() * -glm::normalize(glm::vec3(-1.0f, 1.5f, -0.1f)); + } + + // project d onto n. + glm::vec3 dProj = d - glm::dot(d, n) * n; + glm::vec3 dProjRot = glm::angleAxis(PI / 2.0f, n) * dProj; + glm::vec3 pole = glm::normalize(dProjRot); + + return glm::normalize(poleAdjust * pole); +} + glm::vec3 Rig::calculateKneePoleVector(int footJointIndex, const glm::quat& footTargetOrientation, int hipsIndex) const { AnimPose defaultPose = _animSkeleton->getAbsoluteDefaultPose(footJointIndex); @@ -1175,6 +1216,8 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f const float RELAX_DURATION = 0.6f; + const float POLE_VECTOR_BLEND_FACTOR = 0.9f; + if (params.isLeftEnabled) { glm::vec3 handPosition = params.leftPosition; if (!bodySensorTrackingEnabled) { @@ -1191,7 +1234,32 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f _isLeftHandControlled = true; _lastLeftHandControlledPose = AnimPose(glm::vec3(1.0f), params.leftOrientation, handPosition); + + // compute pole vector + int handJointIndex = _animSkeleton->nameToJointIndex("LeftHand"); + int armJointIndex = _animSkeleton->nameToJointIndex("LeftArm"); + int elbowJointIndex = _animSkeleton->nameToJointIndex("LeftForeArm"); + if (elbowJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0) { + glm::vec3 poleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, hipsIndex, true); + + // smooth toward desired pole vector from previous pole vector... to reduce jitter + if (!_prevLeftHandPoleVectorValid) { + _prevLeftHandPoleVectorValid = true; + _prevLeftHandPoleVector = poleVector; + } + _prevLeftHandPoleVector = lerp(poleVector, _prevLeftHandPoleVector, POLE_VECTOR_BLEND_FACTOR); + + _animVars.set("leftHandPoleVectorEnabled", true); + _animVars.set("leftHandPoleReferenceVector", Vectors::UNIT_X); + _animVars.set("leftHandPoleVector", _prevLeftHandPoleVector); + } else { + _prevLeftHandPoleVectorValid = false; + _animVars.set("leftHandPoleVectorEnabled", false); + } } else { + _prevLeftHandPoleVectorValid = false; + _animVars.set("leftHandPoleVectorEnabled", false); + if (_isLeftHandControlled) { _leftHandRelaxDuration = RELAX_DURATION; _isLeftHandControlled = false; @@ -1234,7 +1302,32 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f _isRightHandControlled = true; _lastRightHandControlledPose = AnimPose(glm::vec3(1.0f), params.rightOrientation, handPosition); + + // compute pole vector + int handJointIndex = _animSkeleton->nameToJointIndex("RightHand"); + int armJointIndex = _animSkeleton->nameToJointIndex("RightArm"); + int elbowJointIndex = _animSkeleton->nameToJointIndex("RightForeArm"); + if (elbowJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0) { + glm::vec3 poleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, hipsIndex, false); + + // smooth toward desired pole vector from previous pole vector... to reduce jitter + if (!_prevRightHandPoleVectorValid) { + _prevRightHandPoleVectorValid = true; + _prevRightHandPoleVector = poleVector; + } + _prevRightHandPoleVector = lerp(poleVector, _prevRightHandPoleVector, POLE_VECTOR_BLEND_FACTOR); + + _animVars.set("rightHandPoleVectorEnabled", true); + _animVars.set("rightHandPoleReferenceVector", -Vectors::UNIT_X); + _animVars.set("rightHandPoleVector", _prevRightHandPoleVector); + } else { + _prevRightHandPoleVectorValid = false; + _animVars.set("rightHandPoleVectorEnabled", false); + } } else { + _prevRightHandPoleVectorValid = false; + _animVars.set("rightHandPoleVectorEnabled", false); + if (_isRightHandControlled) { _rightHandRelaxDuration = RELAX_DURATION; _isRightHandControlled = false; @@ -1261,8 +1354,6 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f } } - const float POLE_VECTOR_BLEND_FACTOR = 0.9f; - if (params.isLeftFootEnabled) { _animVars.set("leftFootPosition", params.leftFootPosition); _animVars.set("leftFootRotation", params.leftFootOrientation); diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 4fc5f76146..06822d666e 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -249,7 +249,8 @@ protected: void updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::vec3& lookAt, const glm::vec3& saccade); void calcAnimAlpha(float speed, const std::vector& referenceSpeeds, float* alphaOut) const; - glm::vec3 Rig::calculateKneePoleVector(int footJointIndex, const glm::quat& footTargetOrientation, int hipsIndex) const; + glm::vec3 calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex, int hipsIndex, bool isLeft) const; + glm::vec3 calculateKneePoleVector(int footJointIndex, const glm::quat& footTargetOrientation, int hipsIndex) const; AnimPose _modelOffset; // model to rig space AnimPose _geometryOffset; // geometry to model space (includes unit offset & fst offsets) @@ -362,6 +363,12 @@ protected: glm::vec3 _prevLeftFootPoleVector = { Vectors::UNIT_Z }; bool _prevLeftFootPoleVectorValid = { false }; + + glm::vec3 _prevRightHandPoleVector = { -Vectors::UNIT_Z }; + bool _prevRightHandPoleVectorValid = { false }; + + glm::vec3 _prevLeftHandPoleVector = { -Vectors::UNIT_Z }; + bool _prevLeftHandPoleVectorValid = { false }; }; #endif /* defined(__hifi__Rig__) */ From 81852cd91c744e81f9296d09ec04f9b8590d6fcc Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 16 Jun 2017 18:15:41 -0700 Subject: [PATCH 12/95] warning fixes --- libraries/animation/src/AnimInverseKinematics.cpp | 2 -- libraries/animation/src/Rig.cpp | 4 ---- 2 files changed, 6 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 080e6449d9..3ea8937eec 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -565,8 +565,6 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const glm::vec3 p = target.getPoleVector(); glm::vec3 eProj = e - glm::dot(e, dUnit) * dUnit; glm::vec3 pProj = p - glm::dot(p, dUnit) * dUnit; - float eProjLen = glm::length(eProj); - float pProjLen = glm::length(pProj); const float PROJ_VECTOR_LEN = 10.0f; const float POLE_VECTOR_LEN = 100.0f; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 5ad664614e..43ce209420 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1140,10 +1140,6 @@ void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm } } -static float easeOutExpo(float t) { - return 1.0f - powf(2, -10.0f * t); -} - static glm::quat quatLerp(const glm::quat& q1, const glm::quat& q2, float alpha) { float dot = glm::dot(q1, q2); glm::quat temp; From 89ebf4998f775b4b72d471823eead80d6f53060d Mon Sep 17 00:00:00 2001 From: humbletim Date: Mon, 19 Jun 2017 10:28:56 -0400 Subject: [PATCH 13/95] relocate/archive original doppleganger mirror into unpublishedScripts/marketplace/doppleganger-mirror --- .../marketplace/doppleganger-mirror}/app-doppleganger.js | 4 ++-- .../marketplace/doppleganger-mirror}/doppleganger-a.svg | 0 .../marketplace/doppleganger-mirror}/doppleganger-i.svg | 0 .../marketplace/doppleganger-mirror}/doppleganger.js | 0 4 files changed, 2 insertions(+), 2 deletions(-) rename {scripts/system => unpublishedScripts/marketplace/doppleganger-mirror}/app-doppleganger.js (95%) rename {interface/resources/icons/tablet-icons => unpublishedScripts/marketplace/doppleganger-mirror}/doppleganger-a.svg (100%) rename {interface/resources/icons/tablet-icons => unpublishedScripts/marketplace/doppleganger-mirror}/doppleganger-i.svg (100%) rename {scripts/system => unpublishedScripts/marketplace/doppleganger-mirror}/doppleganger.js (100%) diff --git a/scripts/system/app-doppleganger.js b/unpublishedScripts/marketplace/doppleganger-mirror/app-doppleganger.js similarity index 95% rename from scripts/system/app-doppleganger.js rename to unpublishedScripts/marketplace/doppleganger-mirror/app-doppleganger.js index d7f85e5767..f4c7bf99c0 100644 --- a/scripts/system/app-doppleganger.js +++ b/unpublishedScripts/marketplace/doppleganger-mirror/app-doppleganger.js @@ -14,8 +14,8 @@ var DopplegangerClass = Script.require('./doppleganger.js'); var tablet = Tablet.getTablet('com.highfidelity.interface.tablet.system'), button = tablet.addButton({ - icon: "icons/tablet-icons/doppleganger-i.svg", - activeIcon: "icons/tablet-icons/doppleganger-a.svg", + icon: Script.resolvePath('./doppleganger-i.svg'), + activeIcon: Script.resolvePath('./doppleganger-a.svg'), text: 'MIRROR' }); diff --git a/interface/resources/icons/tablet-icons/doppleganger-a.svg b/unpublishedScripts/marketplace/doppleganger-mirror/doppleganger-a.svg similarity index 100% rename from interface/resources/icons/tablet-icons/doppleganger-a.svg rename to unpublishedScripts/marketplace/doppleganger-mirror/doppleganger-a.svg diff --git a/interface/resources/icons/tablet-icons/doppleganger-i.svg b/unpublishedScripts/marketplace/doppleganger-mirror/doppleganger-i.svg similarity index 100% rename from interface/resources/icons/tablet-icons/doppleganger-i.svg rename to unpublishedScripts/marketplace/doppleganger-mirror/doppleganger-i.svg diff --git a/scripts/system/doppleganger.js b/unpublishedScripts/marketplace/doppleganger-mirror/doppleganger.js similarity index 100% rename from scripts/system/doppleganger.js rename to unpublishedScripts/marketplace/doppleganger-mirror/doppleganger.js From 1a9db003e51187b206e05d14523ac98831bb232d Mon Sep 17 00:00:00 2001 From: humbletim Date: Mon, 19 Jun 2017 16:01:59 -0400 Subject: [PATCH 14/95] doppleganger + attachments --- .../doppleganger-attachments/Hat4.svg | 328 +++++++++++ .../app-doppleganger-attachments.js | 120 ++++ .../doppleganger-attachments.js | 237 ++++++++ .../doppleganger-attachments/doppleganger.js | 515 ++++++++++++++++++ .../doppleganger-attachments/model-helper.js | 333 +++++++++++ .../doppleganger-attachments/utils.js | 100 ++++ 6 files changed, 1633 insertions(+) create mode 100644 unpublishedScripts/marketplace/doppleganger-attachments/Hat4.svg create mode 100644 unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js create mode 100644 unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-attachments.js create mode 100644 unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js create mode 100644 unpublishedScripts/marketplace/doppleganger-attachments/model-helper.js create mode 100644 unpublishedScripts/marketplace/doppleganger-attachments/utils.js diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/Hat4.svg b/unpublishedScripts/marketplace/doppleganger-attachments/Hat4.svg new file mode 100644 index 0000000000..8d628cf1cd --- /dev/null +++ b/unpublishedScripts/marketplace/doppleganger-attachments/Hat4.svg @@ -0,0 +1,328 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js b/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js new file mode 100644 index 0000000000..c487232aa5 --- /dev/null +++ b/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js @@ -0,0 +1,120 @@ +// doppleganger-app.js +// +// Created by Timothy Dedischew on 04/21/2017. +// Copyright 2017 High Fidelity, Inc. +// +// This Client script creates an instance of a Doppleganger that can be toggled on/off via tablet button. +// (for more info see doppleganger.js) +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var require = function DEBUG_REQUIRE(id) { + return Script.require(id + '?'+new Date().getTime().toString(36)); +}; + +var DopplegangerClass = require('./doppleganger.js'), + DopplegangerAttachments = require('./doppleganger-attachments.js'), + modelHelper = require('./model-helper.js').modelHelper; + +var tablet = Tablet.getTablet('com.highfidelity.interface.tablet.system'), + button = tablet.addButton({ + icon: Script.resolvePath("./Hat4.svg"), + // activeIcon: Script.resolvePath("./app-a.svg"), + text: 'Mannequin' + }); + +Script.scriptEnding.connect(function() { + tablet.removeButton(button); + button = null; +}); + +var doppleganger = new DopplegangerClass({ + avatar: MyAvatar, + mirrored: false, + autoUpdate: true, + type: 'overlay' +}); + +// add support for displaying regular (non-soft) attachments on the doppleganger +{ + var RECHECK_ATTACHMENT_MS = 1000; + var dopplegangerAttachments = new DopplegangerAttachments(doppleganger), + attachmentInterval = null, + lastHash = dopplegangerAttachments.getAttachmentsHash(); + + // monitor for attachment changes, but only when the doppleganger is active + doppleganger.activeChanged.connect(function(active, reason) { + if (attachmentInterval) { + Script.clearInterval(attachmentInterval); + } + if (active) { + attachmentInterval = Script.setInterval(checkAttachmentsHash, RECHECK_ATTACHMENT_MS); + } else { + attachmentInterval = null; + } + }); + function checkAttachmentsHash() { + var currentHash = dopplegangerAttachments.getAttachmentsHash(); + if (currentHash !== lastHash) { + lastHash = currentHash; + print('app-doppleganger | detect attachment change'); + dopplegangerAttachments.refreshAttachments(); + } + } +} + +// hide the doppleganger if this client script is unloaded +Script.scriptEnding.connect(doppleganger, 'stop'); + +// hide the doppleganger if the user switches domains (which might place them arbitrarily far away in world space) +function onDomainChanged() { + if (doppleganger.active) { + doppleganger.stop('domain_changed'); + } +} +Window.domainChanged.connect(onDomainChanged); +Window.domainConnectionRefused.connect(onDomainChanged); +Script.scriptEnding.connect(function() { + Window.domainChanged.disconnect(onDomainChanged); + Window.domainConnectionRefused.disconnect(onDomainChanged); +}); + +// toggle on/off via tablet button +button.clicked.connect(doppleganger, 'toggle'); + +// highlight tablet button based on current doppleganger state +doppleganger.activeChanged.connect(function(active, reason) { + if (button) { + button.editProperties({ isActive: active }); + print('doppleganger.activeChanged', active, reason); + } +}); + +// alert the user if there was an error applying their skeletonModelURL +doppleganger.modelLoaded.connect(function(error, result) { + if (doppleganger.active && error) { + Window.alert('doppleganger | ' + error + '\n' + doppleganger.skeletonModelURL); + } +}); + +// add debug indicators, but only if the user has configured the settings value +if (Settings.getValue('debug.doppleganger', false)) { + DopplegangerClass.addDebugControls(doppleganger); +} + +UserActivityLogger.logAction('doppleganger_app_load'); +doppleganger.activeChanged.connect(function(active, reason) { + if (active) { + UserActivityLogger.logAction('doppleganger_enable'); + } else { + if (reason === 'stop') { + // user intentionally toggled the doppleganger + UserActivityLogger.logAction('doppleganger_disable'); + } else { + print('doppleganger stopped:', reason); + UserActivityLogger.logAction('doppleganger_autodisable', { reason: reason }); + } + } +}); diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-attachments.js b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-attachments.js new file mode 100644 index 0000000000..85c3d3589e --- /dev/null +++ b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-attachments.js @@ -0,0 +1,237 @@ +/* eslint-env commonjs */ +/* eslint-disable comma-dangle, no-empty */ + +"use strict"; +module.exports = DopplegangerAttachments; + +DopplegangerAttachments.version = '0.0.0'; + +var require = function DEBUG_REQUIRE(id) { + return Script.require(id + '?'+new Date().getTime().toString(36)); +}; + +var _modelHelper = require('./model-helper.js'), + modelHelper = _modelHelper.modelHelper, + ModelReadyWatcher = _modelHelper.ModelReadyWatcher, + utils = require('./utils.js'); + +function log() { + print('doppleganger-attachments | ' + [].slice.call(arguments).join(' ')); +} + +function DopplegangerAttachments(doppleganger, options) { + utils.assign(this, { + _options: options, + doppleganger: doppleganger, + attachments: undefined, + manualJointSync: true, + }); + this._initialize(); + log('DopplegangerAttachments...', JSON.stringify(options)); +} +DopplegangerAttachments.prototype = { + // "hash" the current attachments (so that changes can be detected) + getAttachmentsHash: function() { + return JSON.stringify(this.doppleganger.avatar.getAttachmentsVariant()); + }, + // create a pure Object copy of the current native attachments variant + _cloneAttachmentsVariant: function() { + return JSON.parse(JSON.stringify(this.doppleganger.avatar.getAttachmentsVariant())); + }, + // fetch and resolve attachments to include jointIndex and other relevant $metadata + _getResolvedAttachments: function() { + var attachments = this._cloneAttachmentsVariant(), + objectID = this.doppleganger.objectID; + function toString() { + return '[attachment #' + this.$index + ' ' + this.$path + ' @ ' + this.jointName + '{' + this.$jointIndex + '}]'; + } + return attachments.map(function(attachment, i) { + var jointIndex = modelHelper.getJointIndex(objectID, attachment.jointName), + path = (attachment.modelUrl+'').split(/[?#]/)[0].split('/').slice(-3).join('/'); + return Object.defineProperties(attachment, { + $hash: { value: JSON.stringify(attachment) }, + $index: { value: i }, + $jointIndex: { value: jointIndex }, + $path: { value: path }, + toString: { value: toString }, + }); + }); + }, + // compare before / after attachment sets to determine which ones need to be (re)created + refreshAttachments: function() { + var before = this.attachments || [], + beforeIndex = before.reduce(function(out, att, index) { + out[att.$hash] = index; return out; + }, {}); + var after = this._getResolvedAttachments(), + afterIndex = after.reduce(function(out, att, index) { + out[att.$hash] = index; return out; + }, {}); + + Object.keys(beforeIndex).concat(Object.keys(afterIndex)).forEach(function(hash) { + if (hash in beforeIndex && hash in afterIndex) { + // print('reusing previous attachment', hash); + after[afterIndex[hash]] = before[beforeIndex[hash]]; + } else if (!(hash in afterIndex)) { + var attachment = before[beforeIndex[hash]]; + attachment.properties && attachment.properties.objectID && + modelHelper.deleteObject(attachment.properties.objectID); + delete attachment.properties; + } + }); + this.attachments = after; + this._createAttachmentObjects(); + }, + _createAttachmentObjects: function() { + try { + var attachments = this.attachments, + parentID = this.doppleganger.objectID, + jointNames = this.doppleganger.jointNames, + properties = modelHelper.getProperties(this.doppleganger.objectID); + + log('DopplegangerAttachments..._createAttachmentObjects', JSON.stringify({ + type: properties.type, + attachments: attachments.length, + parentID: parentID, + jointNames: jointNames.join(' | '), + },0,2)); + return attachments.map(utils.bind(this, function(attachment, i) { + var type = modelHelper.type(attachment.properties && attachment.properties.objectID); + if (type === 'overlay') { + log('skipping already-provisioned attachment object', type, attachment.properties && attachment.properties.name); + return attachment; + } + var jointIndex = attachment.$jointIndex, // jointNames.indexOf(attachment.jointName), + scale = this.doppleganger.avatar.scale * (attachment.scale||1.0); + + attachment.properties = utils.assign({ + name: attachment.toString(), + type: properties.type, + modelURL: attachment.modelUrl, + scale: scale, + dimensions: modelHelper.type(parentID) === 'entity' ? + Vec3.multiply(attachment.scale||1.0, Vec3.ONE) : undefined, + visible: false, + collisionless: true, + dynamic: false, + shapeType: 'none', + lifetime: 60, + grabbable: true, + }, !this.manualJointSync && { + parentID: parentID, + parentJointIndex: jointIndex, + }); + var objectID = attachment.properties.objectID = modelHelper.addObject(attachment.properties); + attachment._resource = ModelCache.prefetch(attachment.properties.modelURL); + attachment._modelReadier = new ModelReadyWatcher( { + resource: attachment._resource, + objectID: objectID, + }); + this.doppleganger.activeChanged.connect(attachment._modelReadier, '_stop'); + + attachment._modelReadier.modelReady.connect(this, function(err, result) { + if (err) { + log('>>>>> modelReady ERROR <<<<<: ' + err, attachment.modelUrl); + modelHelper.deleteObject(objectID); + return objectID = null; + } + log('attachment model ('+modelHelper.type(result.objectID)+') is ready; # joints ==', + result.jointNames && result.jointNames.length, result.naturalDimensions, result.objectID); + var properties = modelHelper.getProperties(result.objectID), + naturalDimensions = attachment.properties.naturalDimensions = properties.naturalDimensions; + modelHelper.editObject(result.objectID, { + dimensions: naturalDimensions ? Vec3.multiply(attachment.scale, naturalDimensions) : undefined, + }); + this.onJointsUpdated(parentID); // trigger once to initialize position/rotation + // give time for model overlay to "settle", then make it visible + Script.setTimeout(utils.bind(this, function() { + modelHelper.editObject(result.objectID, { + visible: true, + }); + attachment._loaded = true; + }), 100); + }); + return attachment; + })); + } catch (e) { + log('_createAttachmentObjects ERROR:', e.stack || e, e.fileName, e.lineNumber); + } + }, + + _removeAttachmentObjects: function() { + if (this.attachments) { + this.attachments.forEach(function(attachment) { + if (attachment.properties) { + if (attachment.properties.objectID) { + modelHelper.deleteObject(attachment.properties.objectID); + } + delete attachment.properties.objectID; + } + }); + delete this.attachments; + } + }, + + onJointsUpdated: function onJointsUpdated(objectID) { + var jointOrientations = modelHelper.getJointOrientations(objectID), + jointPositions = modelHelper.getJointPositions(objectID), + parentID = objectID, + avatarScale = this.doppleganger.scale, + manualJointSync = this.manualJointSync; + + if (!this.attachments) { + this.attachments = this._getResolvedAttachments(); + this._createAttachmentObjects(); + log('created attachment objects #' + this.attachments.length); + } + var updatedObjects = this.attachments.reduce(function(updates, attachment, i) { + if (!attachment.properties || !attachment._loaded) { + return updates; + } + var objectID = attachment.properties.objectID, + jointIndex = attachment.$jointIndex, + jointOrientation = jointOrientations[jointIndex], + jointPosition = jointPositions[jointIndex]; + + var translation = Vec3.multiply(avatarScale, attachment.translation), + rotation = Quat.fromVec3Degrees(attachment.rotation), + localPosition = Vec3.multiplyQbyV(jointOrientation, translation), + localRotation = rotation; + + updates[objectID] = manualJointSync ? { + visible: true, + position: Vec3.sum(jointPosition, localPosition), + rotation: Quat.multiply(jointOrientation, localRotation), + scale: avatarScale * attachment.scale, + } : { + visible: true, + parentID: parentID, + parentJointIndex: jointIndex, + localRotation: localRotation, + localPosition: localPosition, + scale: attachment.scale, + }; + onJointsUpdated[objectID] = updates[objectID]; + return updates; + }, {}); + modelHelper.editObjects(updatedObjects); + }, + + _initialize: function() { + var doppleganger = this.doppleganger; + if ('$attachmentControls' in doppleganger) { + throw new Error('only one set of debug controls can be added per doppleganger'); + } + doppleganger.$attachmentControls = this; + doppleganger.activeChanged.connect(this, function(active) { + if (active) { + doppleganger.jointsUpdated.connect(this, 'onJointsUpdated'); + } else { + doppleganger.jointsUpdated.disconnect(this, 'onJointsUpdated'); + this._removeAttachmentObjects(); + } + }); + + Script.scriptEnding.connect(this, '_removeAttachmentObjects'); + }, +}; diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js new file mode 100644 index 0000000000..f7001f5a88 --- /dev/null +++ b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js @@ -0,0 +1,515 @@ +"use strict"; + +// doppleganger.js +// +// Created by Timothy Dedischew on 04/21/2017. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/* global module */ +// @module doppleganger +// +// This module contains the `Doppleganger` class implementation for creating an inspectable replica of +// an Avatar (as a model directly in front of and facing them). Joint positions and rotations are copied +// over in an update thread, so that the model automatically mirrors the Avatar's joint movements. +// An Avatar can then for example walk around "themselves" and examine from the back, etc. +// +// This should be helpful for inspecting your own look and debugging avatars, etc. +// +// The doppleganger is created as an overlay so that others do not see it -- and this also allows for the +// highest possible update rate when keeping joint data in sync. + +module.exports = Doppleganger; + +var require = function DEBUG_REQUIRE(id) { + return Script.require(id + '?'+new Date().getTime().toString(36)); +}; + +var _modelHelper = require('./model-helper.js'), + modelHelper = _modelHelper.modelHelper, + ModelReadyWatcher = _modelHelper.ModelReadyWatcher; + +// @property {bool} - when set true, Script.update will be used instead of setInterval for syncing joint data +Doppleganger.USE_SCRIPT_UPDATE = false; + +// @property {int} - the frame rate to target when using setInterval for joint updates +Doppleganger.TARGET_FPS = 24;/// 60; + +// @property {int} - the maximum time in seconds to wait for the model overlay to finish loading +Doppleganger.MAX_WAIT_SECS = 10; + +// @class Doppleganger - Creates a new instance of a Doppleganger. +// @param {Avatar} [options.avatar=MyAvatar] - Avatar used to retrieve position and joint data. +// @param {bool} [options.mirrored=true] - Apply "symmetric mirroring" of Left/Right joints. +// @param {bool} [options.autoUpdate=true] - Automatically sync joint data. +function Doppleganger(options) { + this.options = options = options || {}; + this.avatar = options.avatar || MyAvatar; + this.mirrored = 'mirrored' in options ? options.mirrored : true; + this.autoUpdate = 'autoUpdate' in options ? options.autoUpdate : true; + + // @public + this.active = false; // whether doppleganger is currently being displayed/updated + this.objectID = null; // current doppleganger's Overlay or Entity id + this.frame = 0; // current joint update frame + + // @signal - emitted when .active state changes + this.activeChanged = signal(function(active, reason) {}); + // @signal - emitted once model is either loaded or errors out + this.modelLoaded = signal(function(error, result){}); + // @signal - emitted each time the model's joint data has been synchronized + this.jointsUpdated = signal(function(objectID){}); +} + +Doppleganger.prototype = { + // @public @method - toggles doppleganger on/off + toggle: function() { + if (this.active) { + log('toggling off'); + this.stop(); + } else { + log('toggling on'); + this.start(); + } + return this.active; + }, + + // @public @method - synchronize the joint data between Avatar / doppleganger + update: function() { + this.frame++; + try { + if (!this.objectID) { + throw new Error('!this.objectID'); + } + + if (this.avatar.skeletonModelURL !== this.skeletonModelURL) { + return this.stop('avatar_changed'); + } + + var rotations = this.avatar.getJointRotations(); + var translations = this.avatar.getJointTranslations(); + var size = rotations.length; + + // note: this mismatch can happen when the avatar's model is actively changing + if (size !== translations.length || + (this.jointStateCount && size !== this.jointStateCount)) { + log('mismatched joint counts (avatar model likely changed)', size, translations.length, this.jointStateCount); + this.stop('avatar_changed_joints'); + return; + } + this.jointStateCount = size; + + if (this.mirrored) { + var mirroredIndexes = this.mirroredIndexes; + var outRotations = new Array(size); + var outTranslations = new Array(size); + for (var i=0; i < size; i++) { + var index = mirroredIndexes[i]; + if (index < 0 || index === false) { + index = i; + } + var rot = rotations[index]; + var trans = translations[index]; + trans.x *= -1; + rot.y *= -1; + rot.z *= -1; + outRotations[i] = rot; + outTranslations[i] = trans; + } + rotations = outRotations; + translations = outTranslations; + } + modelHelper.editObject(this.objectID, { + jointRotations: rotations, + jointTranslations: translations + }); + this.jointsUpdated(this.objectID); + } catch (e) { + log('.update error: '+ e, index, e.stack); + this.stop('update_error'); + throw e; + } + }, + + // @public @method - show the doppleganger (and start the update thread, if options.autoUpdate was specified). + // @param {vec3} [options.position=(in front of avatar)] - starting position + // @param {quat} [options.orientation=avatar.orientation] - starting orientation + start: function(options) { + options = assign(this.options, options); + if (this.objectID) { + log('start() called but object model already exists', this.objectID); + return; + } + var avatar = this.avatar; + if (!avatar.jointNames.length) { + return this.stop('joints_unavailable'); + } + + this.frame = 0; + var localPosition = Vec3.multiply(2, Quat.getForward(avatar.orientation)); + this.position = options.position || Vec3.sum(avatar.position, localPosition); + this.orientation = options.orientation || avatar.orientation; + this.skeletonModelURL = avatar.skeletonModelURL; + this.scale = avatar.scale || 1.0; + this.jointStateCount = 0; + this.jointNames = avatar.jointNames; + this.type = options.type || 'overlay'; + this.mirroredNames = modelHelper.deriveMirroredJointNames(this.jointNames); + this.mirroredIndexes = this.mirroredNames.map(function(name) { + return name ? avatar.getJointIndex(name) : false; + }); + + this.objectID = modelHelper.addObject({ + type: this.type === 'overlay' ? 'model' : 'Model', + modelURL: this.skeletonModelURL, + position: this.position, + rotation: this.orientation, + scale: this.scale + }); + Script.scriptEnding.connect(this, function() { + modelHelper.deleteObject(this.objectID); + }); + log('doppleganger created; objectID =', this.objectID); + + // trigger clean up (and stop updates) if the object gets deleted + this.onObjectDeleted = function(uuid) { + if (uuid === this.objectID) { + log('onObjectDeleted', uuid); + this.stop('object_deleted'); + } + }; + modelHelper.objectDeleted.connect(this, 'onObjectDeleted'); + + if ('onLoadComplete' in avatar) { + // stop the current doppleganger if Avatar loads a different model URL + this.onLoadComplete = function() { + if (avatar.skeletonModelURL !== this.skeletonModelURL) { + this.stop('avatar_changed_load'); + } + }; + avatar.onLoadComplete.connect(this, 'onLoadComplete'); + } + + this.onModelLoaded = function(error, result) { + if (error) { + return this.stop(error); + } + log('model ('+modelHelper.type(this.objectID)+')' + ' is ready; # joints == ' + result.jointNames.length); + var naturalDimensions = modelHelper.getProperties(this.objectID, ['naturalDimensions']).naturalDimensions; + log('naturalDimensions:', JSON.stringify(naturalDimensions)); + var props = { visible: true }; + if (naturalDimensions) { + props.dimensions = Vec3.multiply(this.scale, naturalDimensions); + } + log('scaledDimensions:', this.scale, JSON.stringify(props.dimensions)); + modelHelper.editObject(this.objectID, props); + if (!options.position) { + this.syncVerticalPosition(); + } + if (this.autoUpdate) { + this._createUpdateThread(); + } + }; + + this._resource = ModelCache.prefetch(this.skeletonModelURL); + this._modelReadier = new ModelReadyWatcher({ + resource: this._resource, + objectID: this.objectID + }); + this._modelReadier.modelReady.connect(this, 'onModelLoaded'); + this.activeChanged(this.active = true, 'start'); + }, + + // @public @method - hide the doppleganger + // @param {String} [reason=stop] - the reason stop was called + stop: function(reason) { + reason = reason || 'stop'; + if (this.onUpdate) { + Script.update.disconnect(this, 'onUpdate'); + delete this.onUpdate; + } + if (this._interval) { + Script.clearInterval(this._interval); + this._interval = undefined; + } + if (this.onObjectDeleted) { + modelHelper.objectDeleted.disconnect(this, 'onObjectDeleted'); + delete this.onObjectDeleted; + } + if (this.onLoadComplete) { + this.avatar.onLoadComplete.disconnect(this, 'onLoadComplete'); + delete this.onLoadComplete; + } + if (this.onModelLoaded) { + this._modelReadier && this._modelReadier.modelReady.disconnect(this, 'onModelLoaded'); + this._modelReadier = this.onModelLoaded = undefined; + } + if (this.objectID) { + modelHelper.deleteObject(this.objectID); + this.objectID = undefined; + } + if (this.active) { + this.activeChanged(this.active = false, reason); + } else if (reason) { + log('already stopped so not triggering another activeChanged; latest reason was:', reason); + } + }, + // @public @method - Reposition the doppleganger so it sees "eye to eye" with the Avatar. + // @param {String} [byJointName=Hips] - the reference joint used to align the Doppleganger and Avatar + syncVerticalPosition: function(byJointName) { + byJointName = byJointName || 'Hips'; + var positions = modelHelper.getJointPositions(this.objectID), + properties = modelHelper.getProperties(this.objectID), + dopplePosition = properties.position, + doppleJointIndex = modelHelper.getJointIndex(this.objectID, byJointName),// names.indexOf(byJointName), + doppleJointPosition = positions[doppleJointIndex]; + + print('........... doppleJointPosition', JSON.stringify({ + byJointName: byJointName, + dopplePosition: dopplePosition, + doppleJointIndex: doppleJointIndex, + doppleJointPosition: doppleJointPosition, + properties: properties.type, + positions: positions[0] + },0,2)); + var avatarPosition = this.avatar.position, + avatarJointIndex = this.avatar.getJointIndex(byJointName), + avatarJointPosition = this.avatar.getJointPosition(avatarJointIndex); + + var offset = (avatarJointPosition.y - doppleJointPosition.y); + log('adjusting for offset', offset); + if (properties.type === 'model') { + dopplePosition.y = avatarPosition.y + offset; + } else { + dopplePosition.y = avatarPosition.y - offset; + } + + this.position = dopplePosition; + modelHelper.editObject(this.objectID, { position: this.position }); + }, + + // @private @method - creates the update thread to synchronize joint data + _createUpdateThread: function() { + if (Doppleganger.USE_SCRIPT_UPDATE) { + log('creating Script.update thread'); + this.onUpdate = this.update; + Script.update.connect(this, 'onUpdate'); + } else { + log('creating Script.setInterval thread @ ~', Doppleganger.TARGET_FPS +'fps'); + var timeout = 1000 / Doppleganger.TARGET_FPS; + this._interval = Script.setInterval(bind(this, 'update'), timeout); + } + } + +}; + +// @function - bind a function to a `this` context +// @param {Object} - the `this` context +// @param {Function|String} - function or method name +function bind(thiz, method) { + method = thiz[method] || method; + return function() { + return method.apply(thiz, arguments); + }; +} + +// @function - Qt signal polyfill +function signal(template) { + var callbacks = []; + return Object.defineProperties(function() { + var args = [].slice.call(arguments); + callbacks.forEach(function(obj) { + obj.handler.apply(obj.scope, args); + }); + }, { + connect: { value: function(scope, handler) { + var callback = {scope: scope, handler: scope[handler] || handler || scope}; + if (!callback.handler || !callback.handler.apply) { + throw new Error('invalid arguments to connect:' + [template, scope, handler]); + } + callbacks.push({scope: scope, handler: scope[handler] || handler || scope}); + }}, + disconnect: { value: function(scope, handler) { + var match = {scope: scope, handler: scope[handler] || handler || scope}; + callbacks = callbacks.filter(function(obj) { + return !(obj.scope === match.scope && obj.handler === match.handler); + }); + }} + }); +} + +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Polyfill +/* eslint-disable */ +function assign(target, varArgs) { // .length of function is 2 + 'use strict'; + if (target == null) { // TypeError if undefined or null + throw new TypeError('Cannot convert undefined or null to object'); + } + + var to = Object(target); + + for (var index = 1; index < arguments.length; index++) { + var nextSource = arguments[index]; + + if (nextSource != null) { // Skip over if undefined or null + for (var nextKey in nextSource) { + // Avoid bugs when hasOwnProperty is shadowed + if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { + to[nextKey] = nextSource[nextKey]; + } + } + } + } + return to; +} +/* eslint-enable */ +// //https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Polyfill + +// @function - debug logging +function log() { + print('doppleganger | ' + [].slice.call(arguments).join(' ')); +} + +// -- ADVANCED DEBUGGING -- +// @function - Add debug joint indicators / extra debugging info. +// @param {Doppleganger} - existing Doppleganger instance to add controls to +// +// @note: +// * rightclick toggles mirror mode on/off +// * shift-rightclick toggles the debug indicators on/off +// * clicking on an indicator displays the joint name and mirrored joint name in the debug log. +// +// Example use: +// var doppleganger = new Doppleganger(); +// Doppleganger.addDebugControls(doppleganger); +Doppleganger.addDebugControls = function(doppleganger) { + DebugControls.COLOR_DEFAULT = { red: 255, blue: 255, green: 255 }; + DebugControls.COLOR_SELECTED = { red: 0, blue: 255, green: 0 }; + + function DebugControls() { + this.enableIndicators = true; + this.selectedJointName = null; + this.debugOverlayIDs = undefined; + this.jointSelected = signal(function(result) {}); + } + DebugControls.prototype = { + start: function() { + if (!this.onMousePressEvent) { + this.onMousePressEvent = this._onMousePressEvent; + Controller.mousePressEvent.connect(this, 'onMousePressEvent'); + } + }, + + stop: function() { + this.removeIndicators(); + if (this.onMousePressEvent) { + Controller.mousePressEvent.disconnect(this, 'onMousePressEvent'); + delete this.onMousePressEvent; + } + }, + + createIndicators: function(jointNames) { + this.jointNames = jointNames; + return jointNames.map(function(name, i) { + return Overlays.addOverlay('shape', { + shape: 'Icosahedron', + scale: 0.1, + solid: false, + alpha: 0.5 + }); + }); + }, + + removeIndicators: function() { + if (this.debugOverlayIDs) { + this.debugOverlayIDs.forEach(Overlays.deleteOverlay); + this.debugOverlayIDs = undefined; + } + }, + + onJointsUpdated: function(overlayID) { + if (!this.enableIndicators) { + return; + } + var jointNames = Overlays.getProperty(overlayID, 'jointNames'), + jointOrientations = Overlays.getProperty(overlayID, 'jointOrientations'), + jointPositions = Overlays.getProperty(overlayID, 'jointPositions'), + selectedIndex = jointNames.indexOf(this.selectedJointName); + + if (!this.debugOverlayIDs) { + this.debugOverlayIDs = this.createIndicators(jointNames); + } + + // batch all updates into a single call (using the editOverlays({ id: {props...}, ... }) API) + var updatedOverlays = this.debugOverlayIDs.reduce(function(updates, id, i) { + updates[id] = { + position: jointPositions[i], + rotation: jointOrientations[i], + color: i === selectedIndex ? DebugControls.COLOR_SELECTED : DebugControls.COLOR_DEFAULT, + solid: i === selectedIndex + }; + return updates; + }, {}); + Overlays.editOverlays(updatedOverlays); + }, + + _onMousePressEvent: function(evt) { + if (!evt.isLeftButton || !this.enableIndicators || !this.debugOverlayIDs) { + return; + } + var ray = Camera.computePickRay(evt.x, evt.y), + hit = Overlays.findRayIntersection(ray, true, this.debugOverlayIDs); + + hit.jointIndex = this.debugOverlayIDs.indexOf(hit.overlayID); + hit.jointName = this.jointNames[hit.jointIndex]; + this.jointSelected(hit); + } + }; + + if ('$debugControls' in doppleganger) { + throw new Error('only one set of debug controls can be added per doppleganger'); + } + var debugControls = new DebugControls(); + doppleganger.$debugControls = debugControls; + + function onMousePressEvent(evt) { + if (evt.isRightButton) { + if (evt.isShifted) { + debugControls.enableIndicators = !debugControls.enableIndicators; + if (!debugControls.enableIndicators) { + debugControls.removeIndicators(); + } + } else { + doppleganger.mirrored = !doppleganger.mirrored; + } + } + } + + doppleganger.activeChanged.connect(function(active) { + if (active) { + debugControls.start(); + doppleganger.jointsUpdated.connect(debugControls, 'onJointsUpdated'); + Controller.mousePressEvent.connect(onMousePressEvent); + } else { + Controller.mousePressEvent.disconnect(onMousePressEvent); + doppleganger.jointsUpdated.disconnect(debugControls, 'onJointsUpdated'); + debugControls.stop(); + } + }); + + debugControls.jointSelected.connect(function(hit) { + debugControls.selectedJointName = hit.jointName; + if (hit.jointIndex < 0) { + return; + } + hit.mirroredJointName = Doppleganger.getMirroredJointNames([hit.jointName])[0]; + log('selected joint:', JSON.stringify(hit, 0, 2)); + }); + + Script.scriptEnding.connect(debugControls, 'removeIndicators'); + + return doppleganger; +}; diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/model-helper.js b/unpublishedScripts/marketplace/doppleganger-attachments/model-helper.js new file mode 100644 index 0000000000..8987695473 --- /dev/null +++ b/unpublishedScripts/marketplace/doppleganger-attachments/model-helper.js @@ -0,0 +1,333 @@ +// model-helper.js +// +// Created by Timothy Dedischew on 06/01/2017. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/* global module */ +// @module model-helper +// +// This module provides ModelReadyWatcher (a helper class for knowing when a model becomes usable inworld) and +// also initial plumbing helpers to eliminate unnecessary API differences when working with Model Overlays and +// Model Entities at a high-level from scripting. + +function log() { + print('model-helper | ' + [].slice.call(arguments).join(' ')); +} + +var require = function DEBUG_REQUIRE(id) { + return Script.require(id + '?'+new Date().getTime().toString(36)); +}; + +var utils = require('./utils.js'), + assert = utils.assert; + +module.exports = { + version: '0.0.0', + ModelReadyWatcher: ModelReadyWatcher +}; + +var _objectDeleted = utils.signal(function objectDeleted(objectID){}); +// proxy for _objectDeleted that only binds deletion tracking if script actually connects to the unified signal +var objectDeleted = utils.assign(function objectDeleted(objectID){}, { + connect: function() { + Overlays.overlayDeleted.connect(_objectDeleted); + // Entities.deletingEntity.connect(objectDeleted); + Script.scriptEnding.connect(function() { + Overlays.overlayDeleted.disconnect(_objectDeleted); + // Entities.deletingEntity.disconnect(objectDeleted); + }); + // hereafter _objectDeleted.connect will be used instead + this.connect = utils.bind(_objectDeleted, 'connect'); + return this.connect.apply(this, arguments); + }, + disconnect: utils.bind(_objectDeleted, 'disconnect') +}); + +var modelHelper = module.exports.modelHelper = { + // Entity <-> Overlay property translations + _entityFromOverlay: { + modelURL: function url() { + return this.url; + }, + dimensions: function dimensions() { + return Vec3.multiply(this.scale, this.naturalDimensions); + } + }, + _overlayFromEntity: { + url: function modelURL() { + return this.modelURL; + }, + scale: function scale() { + return this.dimensions && this.naturalDimensions && { + x: this.dimensions.x / this.naturalDimensions.x, + y: this.dimensions.y / this.naturalDimensions.y, + z: this.dimensions.z / this.naturalDimensions.z + }; + } + }, + objectDeleted: objectDeleted, + type: function(objectID) { + // TODO: support Model Entities (by detecting type from objectID, which is already possible) + return !Uuid.isNull(objectID) ? 'overlay' : null; + }, + addObject: function(properties) { + var type = 'overlay'; // this.resolveType(properties) + switch (type) { + case 'overlay': return Overlays.addOverlay(properties.type, this.toOverlayProps(properties)); + } + return false; + }, + editObject: function(objectID, properties) { + switch (this.type(objectID)) { + case 'overlay': return Overlays.editOverlay(objectID, this.toOverlayProps(properties)); + } + return false; + }, + deleteObject: function(objectID) { + return this.type(objectID) === 'overlay' && Overlays.deleteOverlay(objectID); + }, + getProperty: function(objectID, propertyName) { + return this.type(objectID) === 'overlay' && Overlays.getProperty(objectID, propertyName); + }, + getProperties: function(objectID, filter) { + switch (this.type(objectID)) { + case 'overlay': + filter = Array.isArray(filter) ? filter : [ + 'position', 'rotation', 'localPosition', 'localRotation', 'parentID', + 'parentJointIndex', 'scale', 'name', 'visible', 'type', 'url', + 'dimensions', 'naturalDimensions', 'grabbable' + ]; + var properties = filter.reduce(function(out, propertyName) { + out[propertyName] = Overlays.getProperty(objectID, propertyName); + return out; + }, {}); + return this.toEntityProps(properties); + } + return null; + }, + // adapt Entity conventions to Overlay (eg: { modelURL: ... } -> { url: ... }) + toOverlayProps: function(properties) { + var result = {}; + for (var from in this._overlayFromEntity) { + var adapter = this._overlayFromEntity[from]; + result[from] = adapter.call(properties, from, adapter.name); + } + return utils.assign(result, properties); + }, + // adapt Overlay conventions to Entity (eg: { url: ... } -> { modelURL: ... }) + toEntityProps: function(properties) { + var result = {}; + for (var from in this._entityToOverlay) { + var adapter = this._entityToOverlay[from]; + result[from] = adapter.call(properties, from, adapter.name); + } + return utils.assign(result, properties); + }, + editObjects: function(updatedObjects) { + var objectIDs = Object.keys(updatedObjects), + type = this.type(objectIDs[0]); + switch (type) { + case 'overlay': + var translated = {}; + for (var objectID in updatedObjects) { + translated[objectID] = this.toOverlayProps(updatedObjects[objectID]); + } + return Overlays.editOverlays(translated); + } + return false; + }, + getJointIndex: function(objectID, name) { + switch (this.type(objectID)) { + case 'overlay': return Overlays.getProperty(objectID, 'jointNames').indexOf(name); + } + return -1; + }, + getJointNames: function(objectID) { + switch (this.type(objectID)) { + case 'overlay': return Overlays.getProperty(objectID, 'jointNames'); + } + return []; + }, + // @function - derives mirrored joint names from a list of regular joint names + // @param {Array} - list of joint names to mirror + // @return {Array} - list of mirrored joint names (note: entries for non-mirrored joints will be `undefined`) + deriveMirroredJointNames: function(jointNames) { + return jointNames.map(function(name, i) { + if (/Left/.test(name)) { + return name.replace('Left', 'Right'); + } + if (/Right/.test(name)) { + return name.replace('Right', 'Left'); + } + return undefined; + }); + }, + getJointPosition: function(objectID, index) { + switch (this.type(objectID)) { + case 'overlay': return Overlays.getProperty(objectID, 'jointPositions')[index]; + } + return Vec3.ZERO; + }, + getJointPositions: function(objectID) { + switch (this.type(objectID)) { + case 'overlay': return Overlays.getProperty(objectID, 'jointPositions'); + } + return []; + }, + getJointOrientation: function(objectID, index) { + switch (this.type(objectID)) { + case 'overlay': return Overlays.getProperty(objectID, 'jointOrientations')[index]; + } + return Quat.normalize({}); + }, + getJointOrientations: function(objectID) { + switch (this.type(objectID)) { + case 'overlay': return Overlays.getProperty(objectID, 'jointOrientations'); + } + }, + getJointTranslation: function(objectID, index) { + switch (this.type(objectID)) { + case 'overlay': return Overlays.getProperty(objectID, 'jointTranslations')[index]; + } + return Vec3.ZERO; + }, + getJointTranslations: function(objectID) { + switch (this.type(objectID)) { + case 'overlay': return Overlays.getProperty(objectID, 'jointTranslations'); + } + return []; + }, + getJointRotation: function(objectID, index) { + switch (this.type(objectID)) { + case 'overlay': return Overlays.getProperty(objectID, 'jointRotations')[index]; + } + return Quat.normalize({}); + }, + getJointRotations: function(objectID) { + switch (this.type(objectID)) { + case 'overlay': return Overlays.getProperty(objectID, 'jointRotations'); + } + return []; + } +}; // modelHelper + + +// @property {PreconditionFunction} - indicates when the model's jointNames have become available +ModelReadyWatcher.JOINTS = function(state) { + return Array.isArray(state.jointNames); +}; +// @property {PreconditionFunction} - indicates when a model's naturalDimensions have become available +ModelReadyWatcher.DIMENSIONS = function(state) { + return Vec3.length(state.naturalDimensions) > 0; +}; +// @property {PreconditionFunction} - indicates when both a model's naturalDimensions and jointNames have become available +ModelReadyWatcher.JOINTS_AND_DIMENSIONS = function(state) { + // eslint-disable-next-line new-cap + return ModelReadyWatcher.JOINTS(state) && ModelReadyWatcher.DIMENSIONS(state); +}; +// @property {int} - interval used for continually rechecking model readiness, until ready or a timeout occurs +ModelReadyWatcher.RECHECK_MS = 50; +// @property {int} - default wait time before considering a model unready-able. +ModelReadyWatcher.DEFAULT_TIMEOUT_SECS = 10; + +// @private @class - waits for model to become usable inworld and tracks errors/timeouts +// @param [Object} options -- key/value config options: +// @param {ModelResource} options.resource - a ModelCache prefetched resource to observe for determining load state +// @param {Uuid} options.objectID - an inworld object to observe for determining readiness states +// @param {Function} [options.precondition=ModelReadyWatcher.JOINTS] - the precondition used to determine if the model is usable +// @param {int} [options.maxWaitSeconds=10] - max seconds to wait for the model to become usable, after which a timeout error is emitted +// @return {ModelReadyWatcher} +function ModelReadyWatcher(options) { + options = utils.assign({ + precondition: ModelReadyWatcher.JOINTS, + maxWaitSeconds: ModelReadyWatcher.DEFAULT_TIMEOUT_SECS + }, options); + + assert(!Uuid.isNull(options.objectID), 'Error: invalid options.objectID'); + assert(options.resource && 'state' in options.resource, 'Error: invalid options.resource'); + assert(typeof options.precondition === 'function', 'Error: invalid options.precondition'); + + utils.assign(this, { + resource: options.resource, + objectID: options.objectID, + precondition: options.precondition, + + // @signal - emitted when the model becomes ready, or with the error that prevented it + modelReady: utils.signal(function modelReady(error, result){}), + + // @public + ready: false, // tracks readiness state + jointNames: null, // populated with detected jointNames + naturalDimensions: null, // populated with detected naturalDimensions + + _startTime: new Date, + _watchdogTimer: Script.setTimeout(utils.bind(this, function() { + this._watchdogTimer = null; + }), options.maxWaitSeconds * 1000), + _interval: Script.setInterval(utils.bind(this, '_waitUntilReady'), ModelReadyWatcher.RECHECK_MS) + }); + + this.modelReady.connect(this, function(error, result) { + this.ready = !error && result; + }); +} + +ModelReadyWatcher.prototype = { + contructor: ModelReadyWatcher, + // @public method -- cancels monitoring and (if model was not yet ready) emits an error + cancel: function() { + return this._stop() && !this.ready && this.modelReady('cancelled', null); + }, + // stop pending timers + _stop: function() { + var stopped = 0; + if (this._watchdogTimer) { + Script.clearTimeout(this._watchdogTimer); + this._watchdogTimer = null; + stopped++; + } + if (this._interval) { + Script.clearInterval(this._interval); + this._interval = null; + stopped++; + } + return stopped; + }, + // the monitoring thread func + _waitUntilReady: function() { + var error = null, result = null; + if (!this._watchdogTimer) { + error = this.precondition.name || 'timeout'; + } else if (this.resource.state === Resource.State.FAILED) { + error = 'prefetch_failed'; + } else if (this.resource.state === Resource.State.FINISHED) { + // in theory there will always be at least one "joint name" that represents the main submesh + var names = modelHelper.getJointNames(this.objectID); + if (Array.isArray(names) && names.length) { + this.jointNames = names; + } + var props = modelHelper.getProperties(this.objectID, ['naturalDimensions']); + if (props && props.naturalDimensions && Vec3.length(props.naturalDimensions)) { + this.naturalDimensions = props.naturalDimensions; + } + var state = { + resource: this.resource, + objectID: this.objectID, + waitTime: (new Date - this._startTime) / 1000, + jointNames: this.jointNames, + naturalDimensions: this.naturalDimensions + }; + if (this.precondition(state)) { + result = state; + } + } + if (error || result !== null) { + this._stop(); + this.modelReady(error, result); + } + } +}; // ModelReadyWatcher.prototype diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/utils.js b/unpublishedScripts/marketplace/doppleganger-attachments/utils.js new file mode 100644 index 0000000000..53140bf342 --- /dev/null +++ b/unpublishedScripts/marketplace/doppleganger-attachments/utils.js @@ -0,0 +1,100 @@ +/* eslint-env commonjs */ +/* eslint-disable comma-dangle, no-empty */ + +module.exports = { + bind: bind, + signal: signal, + assign: assign, + assert: assert, +}; + +// @function - bind a function to a `this` context +// @param {Object} - the `this` context +// @param {Function|String} - function or method name +// @param {value} varargs... - optional curry-right arguments (passed to method after any explicit arguments) +function bind(thiz, method, varargs) { + method = thiz[method] || method; + varargs = [].slice.call(arguments, 2); + return function() { + var args = [].slice.call(arguments).concat(varargs); + return method.apply(thiz, args); + }; +} + +// @function - Qt signal polyfill +function signal(template) { + var callbacks = []; + return Object.defineProperties(function() { + var args = [].slice.call(arguments); + callbacks.forEach(function(obj) { + obj.handler.apply(obj.scope, args); + }); + }, { + connect: { value: function(scope, handler) { + var callback = {scope: scope, handler: scope[handler] || handler || scope}; + if (!callback.handler || !callback.handler.apply) { + throw new Error('invalid arguments to connect:' + [template, scope, handler]); + } + callbacks.push({scope: scope, handler: scope[handler] || handler || scope}); + }}, + disconnect: { value: function(scope, handler) { + var match = {scope: scope, handler: scope[handler] || handler || scope}; + callbacks = callbacks.filter(function(obj) { + return !(obj.scope === match.scope && obj.handler === match.handler); + }); + }} + }); +} + +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Polyfill +/* eslint-disable */ +function assign(target, varArgs) { // .length of function is 2 + 'use strict'; + if (target == null) { // TypeError if undefined or null + throw new TypeError('Cannot convert undefined or null to object'); + } + + var to = Object(target); + + for (var index = 1; index < arguments.length; index++) { + var nextSource = arguments[index]; + + if (nextSource != null) { // Skip over if undefined or null + for (var nextKey in nextSource) { + // Avoid bugs when hasOwnProperty is shadowed + if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { + to[nextKey] = nextSource[nextKey]; + } + } + } + } + return to; +} +/* eslint-enable */ +// //https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Polyfill + +// examples: +// assert(function assertion() { return (conditions === true) }, 'assertion failed!') +// var neededValue = assert(idString, 'idString not specified!'); +// assert(false, 'unexpected state'); +function assert(truthy, message) { + message = message || 'Assertion Failed:'; + + if (typeof truthy === 'function' && truthy.name === 'assertion') { + // extract function body to display with the assertion message + var assertion = (truthy+'').replace(/[\r\n]/g, ' ') + .replace(/^[^{]+\{|\}$|^\s*|\s*$/g, '').trim() + .replace(/^return /,'').replace(/\s[\r\n\t\s]+/g, ' '); + message += ' ' + JSON.stringify(assertion); + try { + truthy = truthy(); + } catch (e) { + message += '(exception: ' + e +')'; + } + } + if (!truthy) { + message += ' ('+truthy+')'; + throw new Error(message); + } + return truthy; +} From e25b567ff136f1d22e788a49addf9647bf2eb9bf Mon Sep 17 00:00:00 2001 From: humbletim Date: Mon, 19 Jun 2017 16:09:20 -0400 Subject: [PATCH 15/95] comment-out debug requires, bump back to 60fps --- .../app-doppleganger-attachments.js | 6 ++++-- .../doppleganger-attachments/doppleganger-attachments.js | 4 ++-- .../marketplace/doppleganger-attachments/doppleganger.js | 6 +++--- .../marketplace/doppleganger-attachments/model-helper.js | 4 ++-- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js b/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js index c487232aa5..b7702d9348 100644 --- a/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js +++ b/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js @@ -10,9 +10,11 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -var require = function DEBUG_REQUIRE(id) { +/*var require = function DEBUG_REQUIRE(id) { return Script.require(id + '?'+new Date().getTime().toString(36)); -}; +};*/ + +var require = Script.require; var DopplegangerClass = require('./doppleganger.js'), DopplegangerAttachments = require('./doppleganger-attachments.js'), diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-attachments.js b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-attachments.js index 85c3d3589e..7c6f846fa1 100644 --- a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-attachments.js +++ b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-attachments.js @@ -6,9 +6,9 @@ module.exports = DopplegangerAttachments; DopplegangerAttachments.version = '0.0.0'; -var require = function DEBUG_REQUIRE(id) { +/*var require = function DEBUG_REQUIRE(id) { return Script.require(id + '?'+new Date().getTime().toString(36)); -}; +};*/ var _modelHelper = require('./model-helper.js'), modelHelper = _modelHelper.modelHelper, diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js index f7001f5a88..58280a09f2 100644 --- a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js +++ b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js @@ -24,9 +24,9 @@ module.exports = Doppleganger; -var require = function DEBUG_REQUIRE(id) { +/*var require = function DEBUG_REQUIRE(id) { return Script.require(id + '?'+new Date().getTime().toString(36)); -}; +};*/ var _modelHelper = require('./model-helper.js'), modelHelper = _modelHelper.modelHelper, @@ -36,7 +36,7 @@ var _modelHelper = require('./model-helper.js'), Doppleganger.USE_SCRIPT_UPDATE = false; // @property {int} - the frame rate to target when using setInterval for joint updates -Doppleganger.TARGET_FPS = 24;/// 60; +Doppleganger.TARGET_FPS = 60; // @property {int} - the maximum time in seconds to wait for the model overlay to finish loading Doppleganger.MAX_WAIT_SECS = 10; diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/model-helper.js b/unpublishedScripts/marketplace/doppleganger-attachments/model-helper.js index 8987695473..e3dcc0de1f 100644 --- a/unpublishedScripts/marketplace/doppleganger-attachments/model-helper.js +++ b/unpublishedScripts/marketplace/doppleganger-attachments/model-helper.js @@ -18,9 +18,9 @@ function log() { print('model-helper | ' + [].slice.call(arguments).join(' ')); } -var require = function DEBUG_REQUIRE(id) { +/*var require = function DEBUG_REQUIRE(id) { return Script.require(id + '?'+new Date().getTime().toString(36)); -}; +};*/ var utils = require('./utils.js'), assert = utils.assert; From 3f5aba26557be43e31c98c73d395ca028ca0dbd6 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 19 Jun 2017 13:21:12 -0700 Subject: [PATCH 16/95] improved elbow pole vector calculation Also, pole vectors are blended spherical linearly, this might help fast moving pole vectors from rotating too quickly. --- libraries/animation/src/Rig.cpp | 57 ++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 43ce209420..b1d0dca9e6 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1158,23 +1158,27 @@ glm::vec3 Rig::calculateElbowPoleVector(int handIndex, int elbowIndex, int armIn AnimPose armPose = _externalPoseSet._absolutePoses[armIndex]; glm::vec3 d = glm::normalize(handPose.trans() - armPose.trans()); + float sign = isLeft ? 1.0f : -1.0f; + + glm::vec3 n = hipsPose.rot() * Vectors::UNIT_X; + glm::vec3 y = hipsPose.rot() * Vectors::UNIT_Y; + // project d onto n. + glm::vec3 dProj = d - glm::dot(d, n) * n; + const float LATERAL_OFFSET = 0.333f; + const float VERTICAL_OFFSET = -0.333f; + + // give dProj a bit of offset away from the body. + glm::vec3 dProjWithOffset = dProj + sign * LATERAL_OFFSET * n + y * VERTICAL_OFFSET; + + // rotate dProj by 90 degrees to get the poleVector. + glm::vec3 poleVector = glm::angleAxis(PI / 2.0f, n) * dProjWithOffset; + + // blend the wrist oreintation into the pole vector to reduce the painfully bent wrist problem. glm::quat elbowToHandDelta = handPose.rot() * glm::inverse(elbowPose.rot()); const float WRIST_POLE_ADJUST_FACTOR = 0.5f; glm::quat poleAdjust = quatLerp(Quaternions::IDENTITY, elbowToHandDelta, WRIST_POLE_ADJUST_FACTOR); - glm::vec3 n; - if (isLeft) { - n = hipsPose.rot() * glm::normalize(glm::vec3(1.0f, 1.5f, -0.1f)); - } else { - n = hipsPose.rot() * -glm::normalize(glm::vec3(-1.0f, 1.5f, -0.1f)); - } - - // project d onto n. - glm::vec3 dProj = d - glm::dot(d, n) * n; - glm::vec3 dProjRot = glm::angleAxis(PI / 2.0f, n) * dProj; - glm::vec3 pole = glm::normalize(dProjRot); - - return glm::normalize(poleAdjust * pole); + return glm::normalize(poleAdjust * poleVector); } glm::vec3 Rig::calculateKneePoleVector(int footJointIndex, const glm::quat& footTargetOrientation, int hipsIndex) const { @@ -1217,7 +1221,8 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f const bool LEFT_HAND = true; const bool RIGHT_HAND = false; - const float POLE_VECTOR_BLEND_FACTOR = 0.9f; + const float KNEE_POLE_VECTOR_BLEND_FACTOR = 0.9f; + const float ELBOW_POLE_VECTOR_BLEND_FACTOR = 0.9f; if (params.isLeftEnabled) { if (!_isLeftHandControlled) { @@ -1232,8 +1237,8 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f // 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)) { + if (transitionHandPose(_leftHandControlTimeRemaining, CONTROL_DURATION, handPose, + LEFT_HAND, TO_CONTROLLED, handPose)) { handPosition = handPose.trans(); handRotation = handPose.rot(); } @@ -1268,7 +1273,9 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f _prevLeftHandPoleVectorValid = true; _prevLeftHandPoleVector = poleVector; } - _prevLeftHandPoleVector = lerp(poleVector, _prevLeftHandPoleVector, POLE_VECTOR_BLEND_FACTOR); + glm::quat deltaRot = rotationBetween(_prevLeftHandPoleVector, poleVector); + glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, ELBOW_POLE_VECTOR_BLEND_FACTOR); + _prevLeftHandPoleVector = smoothDeltaRot * _prevLeftHandPoleVector; _animVars.set("leftHandPoleVectorEnabled", true); _animVars.set("leftHandPoleReferenceVector", Vectors::UNIT_X); @@ -1290,8 +1297,8 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f // 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)) { + 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); @@ -1352,7 +1359,9 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f _prevRightHandPoleVectorValid = true; _prevRightHandPoleVector = poleVector; } - _prevRightHandPoleVector = lerp(poleVector, _prevRightHandPoleVector, POLE_VECTOR_BLEND_FACTOR); + glm::quat deltaRot = rotationBetween(_prevRightHandPoleVector, poleVector); + glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, ELBOW_POLE_VECTOR_BLEND_FACTOR); + _prevRightHandPoleVector = smoothDeltaRot * _prevRightHandPoleVector; _animVars.set("rightHandPoleVectorEnabled", true); _animVars.set("rightHandPoleReferenceVector", -Vectors::UNIT_X); @@ -1401,7 +1410,9 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f _prevLeftFootPoleVectorValid = true; _prevLeftFootPoleVector = poleVector; } - _prevLeftFootPoleVector = lerp(poleVector, _prevLeftFootPoleVector, POLE_VECTOR_BLEND_FACTOR); + glm::quat deltaRot = rotationBetween(_prevLeftFootPoleVector, poleVector); + glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, KNEE_POLE_VECTOR_BLEND_FACTOR); + _prevLeftFootPoleVector = smoothDeltaRot * _prevLeftFootPoleVector; _animVars.set("leftFootPoleVectorEnabled", true); _animVars.set("leftFootPoleReferenceVector", Vectors::UNIT_Z); @@ -1427,7 +1438,9 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f _prevRightFootPoleVectorValid = true; _prevRightFootPoleVector = poleVector; } - _prevRightFootPoleVector = lerp(poleVector, _prevRightFootPoleVector, POLE_VECTOR_BLEND_FACTOR); + glm::quat deltaRot = rotationBetween(_prevRightFootPoleVector, poleVector); + glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, KNEE_POLE_VECTOR_BLEND_FACTOR); + _prevRightFootPoleVector = smoothDeltaRot * _prevRightFootPoleVector; _animVars.set("rightFootPoleVectorEnabled", true); _animVars.set("rightFootPoleReferenceVector", Vectors::UNIT_Z); From 6bbc5bfbea17c16fde201d2a655aacdaf17e4617 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 19 Jun 2017 13:42:39 -0700 Subject: [PATCH 17/95] formatting on initializers --- libraries/animation/src/Rig.h | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index c9033b6e6e..d101425a77 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -351,8 +351,8 @@ protected: int _nextStateHandlerId { 0 }; QMutex _stateMutex; - bool transitionHandPose(float deltaTime, float totalDuration, AnimPose& controlledHandPose, bool isLeftHand, - bool isToControlled, AnimPose& returnHandPose); + bool transitionHandPose(float deltaTime, float totalDuration, AnimPose& controlledHandPose, bool isLeftHand, + bool isToControlled, AnimPose& returnHandPose); bool _isLeftHandControlled { false }; bool _isRightHandControlled { false }; @@ -363,17 +363,17 @@ protected: AnimPose _lastLeftHandControlledPose; AnimPose _lastRightHandControlledPose; - glm::vec3 _prevRightFootPoleVector = { Vectors::UNIT_Z }; - bool _prevRightFootPoleVectorValid = { false }; + glm::vec3 _prevRightFootPoleVector { Vectors::UNIT_Z }; + bool _prevRightFootPoleVectorValid { false }; - glm::vec3 _prevLeftFootPoleVector = { Vectors::UNIT_Z }; - bool _prevLeftFootPoleVectorValid = { false }; + glm::vec3 _prevLeftFootPoleVector { Vectors::UNIT_Z }; + bool _prevLeftFootPoleVectorValid { false }; - glm::vec3 _prevRightHandPoleVector = { -Vectors::UNIT_Z }; - bool _prevRightHandPoleVectorValid = { false }; + glm::vec3 _prevRightHandPoleVector { -Vectors::UNIT_Z }; + bool _prevRightHandPoleVectorValid { false }; - glm::vec3 _prevLeftHandPoleVector = { -Vectors::UNIT_Z }; - bool _prevLeftHandPoleVectorValid = { false }; + glm::vec3 _prevLeftHandPoleVector { -Vectors::UNIT_Z }; + bool _prevLeftHandPoleVectorValid { false }; }; #endif /* defined(__hifi__Rig__) */ From 46e9ada424163d2bb0d86f14820fad3de510b61f Mon Sep 17 00:00:00 2001 From: humbletim Date: Mon, 19 Jun 2017 16:58:06 -0400 Subject: [PATCH 18/95] cleanup pass --- .../app-doppleganger-attachments.js | 4 ---- .../doppleganger-attachments.js | 10 +++------- .../doppleganger-attachments/doppleganger.js | 6 +----- .../doppleganger-attachments/model-helper.js | 10 +--------- .../marketplace/doppleganger-attachments/utils.js | 3 +-- 5 files changed, 6 insertions(+), 27 deletions(-) diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js b/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js index b7702d9348..e0ba47162e 100644 --- a/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js +++ b/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js @@ -10,10 +10,6 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/*var require = function DEBUG_REQUIRE(id) { - return Script.require(id + '?'+new Date().getTime().toString(36)); -};*/ - var require = Script.require; var DopplegangerClass = require('./doppleganger.js'), diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-attachments.js b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-attachments.js index 7c6f846fa1..894d627fc5 100644 --- a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-attachments.js +++ b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-attachments.js @@ -1,15 +1,11 @@ -/* eslint-env commonjs */ -/* eslint-disable comma-dangle, no-empty */ - "use strict"; +/* eslint-env commonjs */ +/* eslint-disable comma-dangle */ + module.exports = DopplegangerAttachments; DopplegangerAttachments.version = '0.0.0'; -/*var require = function DEBUG_REQUIRE(id) { - return Script.require(id + '?'+new Date().getTime().toString(36)); -};*/ - var _modelHelper = require('./model-helper.js'), modelHelper = _modelHelper.modelHelper, ModelReadyWatcher = _modelHelper.ModelReadyWatcher, diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js index 58280a09f2..c743357e0c 100644 --- a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js +++ b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global module */ +/* eslint-env commonjs */ // @module doppleganger // // This module contains the `Doppleganger` class implementation for creating an inspectable replica of @@ -24,10 +24,6 @@ module.exports = Doppleganger; -/*var require = function DEBUG_REQUIRE(id) { - return Script.require(id + '?'+new Date().getTime().toString(36)); -};*/ - var _modelHelper = require('./model-helper.js'), modelHelper = _modelHelper.modelHelper, ModelReadyWatcher = _modelHelper.ModelReadyWatcher; diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/model-helper.js b/unpublishedScripts/marketplace/doppleganger-attachments/model-helper.js index e3dcc0de1f..2dda2c12ec 100644 --- a/unpublishedScripts/marketplace/doppleganger-attachments/model-helper.js +++ b/unpublishedScripts/marketplace/doppleganger-attachments/model-helper.js @@ -7,21 +7,13 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global module */ +/* eslint-env commonjs */ // @module model-helper // // This module provides ModelReadyWatcher (a helper class for knowing when a model becomes usable inworld) and // also initial plumbing helpers to eliminate unnecessary API differences when working with Model Overlays and // Model Entities at a high-level from scripting. -function log() { - print('model-helper | ' + [].slice.call(arguments).join(' ')); -} - -/*var require = function DEBUG_REQUIRE(id) { - return Script.require(id + '?'+new Date().getTime().toString(36)); -};*/ - var utils = require('./utils.js'), assert = utils.assert; diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/utils.js b/unpublishedScripts/marketplace/doppleganger-attachments/utils.js index 53140bf342..76c6e1ef7f 100644 --- a/unpublishedScripts/marketplace/doppleganger-attachments/utils.js +++ b/unpublishedScripts/marketplace/doppleganger-attachments/utils.js @@ -1,11 +1,10 @@ /* eslint-env commonjs */ -/* eslint-disable comma-dangle, no-empty */ module.exports = { bind: bind, signal: signal, assign: assign, - assert: assert, + assert: assert }; // @function - bind a function to a `this` context From e6e1ae16db76ef9485aa0be94db24081e39271b9 Mon Sep 17 00:00:00 2001 From: humbletim Date: Mon, 19 Jun 2017 17:21:52 -0400 Subject: [PATCH 19/95] swap-in different icon for now --- .../doppleganger-attachments/Hat4.svg | 328 --------- .../Pullover-lineart-inverted.svg | 647 ++++++++++++++++++ .../Pullover-lineart-normal.svg | 646 +++++++++++++++++ .../app-doppleganger-attachments.js | 4 +- 4 files changed, 1295 insertions(+), 330 deletions(-) delete mode 100644 unpublishedScripts/marketplace/doppleganger-attachments/Hat4.svg create mode 100644 unpublishedScripts/marketplace/doppleganger-attachments/Pullover-lineart-inverted.svg create mode 100644 unpublishedScripts/marketplace/doppleganger-attachments/Pullover-lineart-normal.svg diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/Hat4.svg b/unpublishedScripts/marketplace/doppleganger-attachments/Hat4.svg deleted file mode 100644 index 8d628cf1cd..0000000000 --- a/unpublishedScripts/marketplace/doppleganger-attachments/Hat4.svg +++ /dev/null @@ -1,328 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/Pullover-lineart-inverted.svg b/unpublishedScripts/marketplace/doppleganger-attachments/Pullover-lineart-inverted.svg new file mode 100644 index 0000000000..52aed00285 --- /dev/null +++ b/unpublishedScripts/marketplace/doppleganger-attachments/Pullover-lineart-inverted.svg @@ -0,0 +1,647 @@ + + + + + Pullover + + + + + + image/svg+xml + + Pullover + + 2014-02-03 + + + Frank Tremmel + + + + + pulli + pullover + sweater + shirt + clothes + clothings + kleidung + + + + outline pullover + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/Pullover-lineart-normal.svg b/unpublishedScripts/marketplace/doppleganger-attachments/Pullover-lineart-normal.svg new file mode 100644 index 0000000000..9cab6592aa --- /dev/null +++ b/unpublishedScripts/marketplace/doppleganger-attachments/Pullover-lineart-normal.svg @@ -0,0 +1,646 @@ + + + + + Pullover + + + + + + image/svg+xml + + Pullover + + 2014-02-03 + + + Frank Tremmel + + + + + pulli + pullover + sweater + shirt + clothes + clothings + kleidung + + + + outline pullover + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js b/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js index e0ba47162e..71be8d72af 100644 --- a/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js +++ b/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js @@ -18,8 +18,8 @@ var DopplegangerClass = require('./doppleganger.js'), var tablet = Tablet.getTablet('com.highfidelity.interface.tablet.system'), button = tablet.addButton({ - icon: Script.resolvePath("./Hat4.svg"), - // activeIcon: Script.resolvePath("./app-a.svg"), + icon: Script.resolvePath('./Pullover-lineart-normal.svg'), + activeIcon: Script.resolvePath('./Pullover-lineart-inverted.svg'), text: 'Mannequin' }); From c236afe68c9ccf08ffed40f81e95d38f2db9300d Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 19 Jun 2017 16:54:39 -0700 Subject: [PATCH 20/95] Simplify passing data from MySkeletonModel to Rig --- interface/src/avatar/MySkeletonModel.cpp | 125 ++--- libraries/animation/src/AnimPose.h | 2 + libraries/animation/src/Rig.cpp | 607 ++++++++++++----------- libraries/animation/src/Rig.h | 63 +-- 4 files changed, 407 insertions(+), 390 deletions(-) diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index e74df4cf0f..89b6c36c63 100644 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -47,110 +47,113 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { MyAvatar* myAvatar = static_cast(_owningAvatar); - Rig::HeadParameters headParams; + Rig::ControllerParameters params; + + AnimPose avatarToRigPose(glm::vec3(1.0f), Quaternions::Y_180, glm::vec3(0.0f)); // input action is the highest priority source for head orientation. auto avatarHeadPose = myAvatar->getHeadControllerPoseInAvatarFrame(); if (avatarHeadPose.isValid()) { - glm::mat4 rigHeadMat = Matrices::Y_180 * - createMatFromQuatAndPos(avatarHeadPose.getRotation(), avatarHeadPose.getTranslation()); - headParams.rigHeadPosition = extractTranslation(rigHeadMat); - headParams.rigHeadOrientation = glmExtractRotation(rigHeadMat); - headParams.headEnabled = true; + AnimPose pose(avatarHeadPose.getRotation(), avatarHeadPose.getTranslation()); + params.controllerPoses[Rig::ControllerType_Head] = avatarToRigPose * pose; + params.controllerActiveFlags[Rig::ControllerType_Head] = true; } else { // even though full head IK is disabled, the rig still needs the head orientation to rotate the head up and // down in desktop mode. // preMult 180 is necessary to convert from avatar to rig coordinates. // postMult 180 is necessary to convert head from -z forward to z forward. - headParams.rigHeadOrientation = Quaternions::Y_180 * head->getFinalOrientationInLocalFrame() * Quaternions::Y_180; - headParams.headEnabled = false; + glm::quat headRot = Quaternions::Y_180 * head->getFinalOrientationInLocalFrame() * Quaternions::Y_180; + params.controllerPoses[Rig::ControllerType_Head] = AnimPose(glm::vec3(1.0f), headRot, glm::vec3(0.0f)); + params.controllerActiveFlags[Rig::ControllerType_Head] = false; } auto avatarHipsPose = myAvatar->getHipsControllerPoseInAvatarFrame(); if (avatarHipsPose.isValid()) { - glm::mat4 rigHipsMat = Matrices::Y_180 * createMatFromQuatAndPos(avatarHipsPose.getRotation(), avatarHipsPose.getTranslation()); - headParams.hipsMatrix = rigHipsMat; - headParams.hipsEnabled = true; + AnimPose pose(avatarHipsPose.getRotation(), avatarHipsPose.getTranslation()); + params.controllerPoses[Rig::ControllerType_Hips] = avatarToRigPose * pose; + params.controllerActiveFlags[Rig::ControllerType_Hips] = true; } else { - headParams.hipsEnabled = false; + params.controllerPoses[Rig::ControllerType_Hips] = AnimPose::identity; + params.controllerActiveFlags[Rig::ControllerType_Hips] = false; } auto avatarSpine2Pose = myAvatar->getSpine2ControllerPoseInAvatarFrame(); if (avatarSpine2Pose.isValid()) { - glm::mat4 rigSpine2Mat = Matrices::Y_180 * createMatFromQuatAndPos(avatarSpine2Pose.getRotation(), avatarSpine2Pose.getTranslation()); - headParams.spine2Matrix = rigSpine2Mat; - headParams.spine2Enabled = true; + AnimPose pose(avatarSpine2Pose.getRotation(), avatarSpine2Pose.getTranslation()); + params.controllerPoses[Rig::ControllerType_Spine2] = avatarToRigPose * pose; + params.controllerActiveFlags[Rig::ControllerType_Spine2] = true; } else { - headParams.spine2Enabled = false; + params.controllerPoses[Rig::ControllerType_Spine2] = AnimPose::identity; + params.controllerActiveFlags[Rig::ControllerType_Spine2] = false; } auto avatarRightArmPose = myAvatar->getRightArmControllerPoseInAvatarFrame(); if (avatarRightArmPose.isValid()) { - glm::mat4 rightArmMat = Matrices::Y_180 * createMatFromQuatAndPos(avatarRightArmPose.getRotation(), avatarRightArmPose.getTranslation()); - headParams.rightArmPosition = extractTranslation(rightArmMat); - headParams.rightArmRotation = glmExtractRotation(rightArmMat); - headParams.rightArmEnabled = true; + AnimPose pose(avatarRightArmPose.getRotation(), avatarRightArmPose.getTranslation()); + params.controllerPoses[Rig::ControllerType_RightArm] = avatarToRigPose * pose; + params.controllerActiveFlags[Rig::ControllerType_RightArm] = true; } else { - headParams.rightArmEnabled = false; + params.controllerPoses[Rig::ControllerType_RightArm] = AnimPose::identity; + params.controllerActiveFlags[Rig::ControllerType_RightArm] = false; } - + auto avatarLeftArmPose = myAvatar->getLeftArmControllerPoseInAvatarFrame(); if (avatarLeftArmPose.isValid()) { - glm::mat4 leftArmMat = Matrices::Y_180 * createMatFromQuatAndPos(avatarLeftArmPose.getRotation(), avatarLeftArmPose.getTranslation()); - headParams.leftArmPosition = extractTranslation(leftArmMat); - headParams.leftArmRotation = glmExtractRotation(leftArmMat); - headParams.leftArmEnabled = true; + AnimPose pose(avatarLeftArmPose.getRotation(), avatarLeftArmPose.getTranslation()); + params.controllerPoses[Rig::ControllerType_LeftArm] = avatarToRigPose * pose; + params.controllerActiveFlags[Rig::ControllerType_LeftArm] = true; } else { - headParams.leftArmEnabled = false; + params.controllerPoses[Rig::ControllerType_LeftArm] = AnimPose::identity; + params.controllerActiveFlags[Rig::ControllerType_LeftArm] = false; } - headParams.isTalking = head->getTimeWithoutTalking() <= 1.5f; - - _rig.updateFromHeadParameters(headParams, deltaTime); - - Rig::HandAndFeetParameters handAndFeetParams; - - auto leftPose = myAvatar->getLeftHandControllerPoseInAvatarFrame(); - if (leftPose.isValid()) { - handAndFeetParams.isLeftEnabled = true; - handAndFeetParams.leftPosition = Quaternions::Y_180 * leftPose.getTranslation(); - handAndFeetParams.leftOrientation = Quaternions::Y_180 * leftPose.getRotation(); + auto avatarLeftHandPose = myAvatar->getLeftHandControllerPoseInAvatarFrame(); + if (avatarLeftHandPose.isValid()) { + AnimPose pose(avatarLeftHandPose.getRotation(), avatarLeftHandPose.getTranslation()); + params.controllerPoses[Rig::ControllerType_LeftHand] = avatarToRigPose * pose; + params.controllerActiveFlags[Rig::ControllerType_LeftHand] = true; } else { - handAndFeetParams.isLeftEnabled = false; + params.controllerPoses[Rig::ControllerType_LeftHand] = AnimPose::identity; + params.controllerActiveFlags[Rig::ControllerType_LeftHand] = false; } - auto rightPose = myAvatar->getRightHandControllerPoseInAvatarFrame(); - if (rightPose.isValid()) { - handAndFeetParams.isRightEnabled = true; - handAndFeetParams.rightPosition = Quaternions::Y_180 * rightPose.getTranslation(); - handAndFeetParams.rightOrientation = Quaternions::Y_180 * rightPose.getRotation(); + auto avatarRightHandPose = myAvatar->getRightHandControllerPoseInAvatarFrame(); + if (avatarRightHandPose.isValid()) { + AnimPose pose(avatarRightHandPose.getRotation(), avatarRightHandPose.getTranslation()); + params.controllerPoses[Rig::ControllerType_RightHand] = avatarToRigPose * pose; + params.controllerActiveFlags[Rig::ControllerType_RightHand] = true; } else { - handAndFeetParams.isRightEnabled = false; + params.controllerPoses[Rig::ControllerType_RightHand] = AnimPose::identity; + params.controllerActiveFlags[Rig::ControllerType_RightHand] = false; } - auto leftFootPose = myAvatar->getLeftFootControllerPoseInAvatarFrame(); - if (leftFootPose.isValid()) { - handAndFeetParams.isLeftFootEnabled = true; - handAndFeetParams.leftFootPosition = Quaternions::Y_180 * leftFootPose.getTranslation(); - handAndFeetParams.leftFootOrientation = Quaternions::Y_180 * leftFootPose.getRotation(); + auto avatarLeftFootPose = myAvatar->getLeftFootControllerPoseInAvatarFrame(); + if (avatarLeftFootPose.isValid()) { + AnimPose pose(avatarLeftFootPose.getRotation(), avatarLeftFootPose.getTranslation()); + params.controllerPoses[Rig::ControllerType_LeftFoot] = avatarToRigPose * pose; + params.controllerActiveFlags[Rig::ControllerType_LeftFoot] = true; } else { - handAndFeetParams.isLeftFootEnabled = false; + params.controllerPoses[Rig::ControllerType_LeftFoot] = AnimPose::identity; + params.controllerActiveFlags[Rig::ControllerType_LeftFoot] = false; } - auto rightFootPose = myAvatar->getRightFootControllerPoseInAvatarFrame(); - if (rightFootPose.isValid()) { - handAndFeetParams.isRightFootEnabled = true; - handAndFeetParams.rightFootPosition = Quaternions::Y_180 * rightFootPose.getTranslation(); - handAndFeetParams.rightFootOrientation = Quaternions::Y_180 * rightFootPose.getRotation(); + auto avatarRightFootPose = myAvatar->getRightFootControllerPoseInAvatarFrame(); + if (avatarRightFootPose.isValid()) { + AnimPose pose(avatarRightFootPose.getRotation(), avatarRightFootPose.getTranslation()); + params.controllerPoses[Rig::ControllerType_RightFoot] = avatarToRigPose * pose; + params.controllerActiveFlags[Rig::ControllerType_RightFoot] = true; } else { - handAndFeetParams.isRightFootEnabled = false; + params.controllerPoses[Rig::ControllerType_RightFoot] = AnimPose::identity; + params.controllerActiveFlags[Rig::ControllerType_RightFoot] = false; } - handAndFeetParams.bodyCapsuleRadius = myAvatar->getCharacterController()->getCapsuleRadius(); - handAndFeetParams.bodyCapsuleHalfHeight = myAvatar->getCharacterController()->getCapsuleHalfHeight(); - handAndFeetParams.bodyCapsuleLocalOffset = myAvatar->getCharacterController()->getCapsuleLocalOffset(); + params.bodyCapsuleRadius = myAvatar->getCharacterController()->getCapsuleRadius(); + params.bodyCapsuleHalfHeight = myAvatar->getCharacterController()->getCapsuleHalfHeight(); + params.bodyCapsuleLocalOffset = myAvatar->getCharacterController()->getCapsuleLocalOffset(); - _rig.updateFromHandAndFeetParameters(handAndFeetParams, deltaTime); + params.isTalking = head->getTimeWithoutTalking() <= 1.5f; + + _rig.updateFromControllerParameters(params, deltaTime); Rig::CharacterControllerState ccState = convertCharacterControllerState(myAvatar->getCharacterController()->getState()); diff --git a/libraries/animation/src/AnimPose.h b/libraries/animation/src/AnimPose.h index a2e22a24be..2df3d1f2e4 100644 --- a/libraries/animation/src/AnimPose.h +++ b/libraries/animation/src/AnimPose.h @@ -21,6 +21,8 @@ class AnimPose { public: AnimPose() {} explicit AnimPose(const glm::mat4& mat); + explicit AnimPose(const glm::quat& rotIn) : _scale(1.0f), _rot(rotIn), _trans(0.0f) {} + AnimPose(const glm::quat& rotIn, const glm::vec3& transIn) : _scale(1.0f), _rot(rotIn), _trans(transIn) {} AnimPose(const glm::vec3& scaleIn, const glm::quat& rotIn, const glm::vec3& transIn) : _scale(scaleIn), _rot(rotIn), _trans(transIn) {} static const AnimPose identity; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index b1d0dca9e6..0ee42372b1 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1010,46 +1010,6 @@ glm::quat Rig::getJointDefaultRotationInParentFrame(int jointIndex) { return glm::quat(); } -void Rig::updateFromHeadParameters(const HeadParameters& params, float dt) { - updateHeadAnimVars(params); - - _animVars.set("isTalking", params.isTalking); - _animVars.set("notIsTalking", !params.isTalking); - - if (params.hipsEnabled) { - _animVars.set("solutionSource", (int)AnimInverseKinematics::SolutionSource::RelaxToLimitCenterPoses); - _animVars.set("hipsType", (int)IKTarget::Type::RotationAndPosition); - _animVars.set("hipsPosition", extractTranslation(params.hipsMatrix)); - _animVars.set("hipsRotation", glmExtractRotation(params.hipsMatrix)); - } else { - _animVars.set("solutionSource", (int)AnimInverseKinematics::SolutionSource::RelaxToUnderPoses); - _animVars.set("hipsType", (int)IKTarget::Type::Unknown); - } - - if (params.hipsEnabled && params.spine2Enabled) { - _animVars.set("spine2Type", (int)IKTarget::Type::Spline); - _animVars.set("spine2Position", extractTranslation(params.spine2Matrix)); - _animVars.set("spine2Rotation", glmExtractRotation(params.spine2Matrix)); - } else { - _animVars.set("spine2Type", (int)IKTarget::Type::Unknown); - } - - if (params.leftArmEnabled) { - _animVars.set("leftArmType", (int)IKTarget::Type::RotationAndPosition); - _animVars.set("leftArmPosition", params.leftArmPosition); - _animVars.set("leftArmRotation", params.leftArmRotation); - } else { - _animVars.set("leftArmType", (int)IKTarget::Type::Unknown); - } - - if (params.rightArmEnabled) { - _animVars.set("rightArmType", (int)IKTarget::Type::RotationAndPosition); - _animVars.set("rightArmPosition", params.rightArmPosition); - _animVars.set("rightArmRotation", params.rightArmRotation); - } else { - _animVars.set("rightArmType", (int)IKTarget::Type::Unknown); - } -} void Rig::updateFromEyeParameters(const EyeParameters& params) { updateEyeJoint(params.leftEyeJointIndex, params.modelTranslation, params.modelRotation, params.eyeLookAt, params.eyeSaccade); @@ -1081,12 +1041,12 @@ void Rig::computeHeadFromHMD(const AnimPose& hmdPose, glm::vec3& headPositionOut headOrientationOut = hmdOrientation; } -void Rig::updateHeadAnimVars(const HeadParameters& params) { +void Rig::updateHead(bool headEnabled, bool hipsEnabled, const AnimPose& headPose) { if (_animSkeleton) { - if (params.headEnabled) { - _animVars.set("headPosition", params.rigHeadPosition); - _animVars.set("headRotation", params.rigHeadOrientation); - if (params.hipsEnabled) { + if (headEnabled) { + _animVars.set("headPosition", headPose.trans()); + _animVars.set("headRotation", headPose.rot()); + if (hipsEnabled) { // Since there is an explicit hips ik target, switch the head to use the more flexible Spline IK chain type. // this will allow the spine to compress/expand and bend more natrually, ensuring that it can reach the head target position. _animVars.set("headType", (int)IKTarget::Type::Spline); @@ -1099,12 +1059,271 @@ void Rig::updateHeadAnimVars(const HeadParameters& params) { } } else { _animVars.unset("headPosition"); - _animVars.set("headRotation", params.rigHeadOrientation); + _animVars.set("headRotation", headPose.rot()); _animVars.set("headType", (int)IKTarget::Type::RotationOnly); } } } +void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnabled, float dt, + const AnimPose& leftHandPose, const AnimPose& rightHandPose, + float bodyCapsuleRadius, float bodyCapsuleHalfHeight, const glm::vec3& bodyCapsuleLocalOffset) { + + // Use this capsule to represent the avatar body. + int hipsIndex = indexOfJoint("Hips"); + glm::vec3 hipsTrans; + if (hipsIndex >= 0) { + hipsTrans = _internalPoseSet._absolutePoses[hipsIndex].trans(); + } + + const glm::vec3 bodyCapsuleCenter = hipsTrans - bodyCapsuleLocalOffset; + const glm::vec3 bodyCapsuleStart = bodyCapsuleCenter - glm::vec3(0, bodyCapsuleHalfHeight, 0); + 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.9f; + + 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; + if (findSphereCapsulePenetration(handPosition, HAND_RADIUS, bodyCapsuleStart, bodyCapsuleEnd, bodyCapsuleRadius, displacement)) { + handPosition -= displacement; + } + } + + _animVars.set("leftHandPosition", handPosition); + _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"); + int elbowJointIndex = _animSkeleton->nameToJointIndex("LeftForeArm"); + if (elbowJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0) { + glm::vec3 poleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, hipsIndex, true); + + // smooth toward desired pole vector from previous pole vector... to reduce jitter + if (!_prevLeftHandPoleVectorValid) { + _prevLeftHandPoleVectorValid = true; + _prevLeftHandPoleVector = poleVector; + } + glm::quat deltaRot = rotationBetween(_prevLeftHandPoleVector, poleVector); + glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, ELBOW_POLE_VECTOR_BLEND_FACTOR); + _prevLeftHandPoleVector = smoothDeltaRot * _prevLeftHandPoleVector; + + _animVars.set("leftHandPoleVectorEnabled", true); + _animVars.set("leftHandPoleReferenceVector", Vectors::UNIT_X); + _animVars.set("leftHandPoleVector", _prevLeftHandPoleVector); + } else { + _prevLeftHandPoleVectorValid = false; + _animVars.set("leftHandPoleVectorEnabled", false); + } + } else { + _prevLeftHandPoleVectorValid = false; + _animVars.set("leftHandPoleVectorEnabled", false); + + if (_isLeftHandControlled) { + _leftHandRelaxTimeRemaining = RELAX_DURATION; + _isLeftHandControlled = false; + } + + 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; + if (findSphereCapsulePenetration(handPosition, HAND_RADIUS, bodyCapsuleStart, bodyCapsuleEnd, bodyCapsuleRadius, displacement)) { + handPosition -= displacement; + } + } + + _animVars.set("rightHandPosition", handPosition); + _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"); + int elbowJointIndex = _animSkeleton->nameToJointIndex("RightForeArm"); + if (elbowJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0) { + glm::vec3 poleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, hipsIndex, false); + + // smooth toward desired pole vector from previous pole vector... to reduce jitter + if (!_prevRightHandPoleVectorValid) { + _prevRightHandPoleVectorValid = true; + _prevRightHandPoleVector = poleVector; + } + glm::quat deltaRot = rotationBetween(_prevRightHandPoleVector, poleVector); + glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, ELBOW_POLE_VECTOR_BLEND_FACTOR); + _prevRightHandPoleVector = smoothDeltaRot * _prevRightHandPoleVector; + + _animVars.set("rightHandPoleVectorEnabled", true); + _animVars.set("rightHandPoleReferenceVector", -Vectors::UNIT_X); + _animVars.set("rightHandPoleVector", _prevRightHandPoleVector); + } else { + _prevRightHandPoleVectorValid = false; + _animVars.set("rightHandPoleVectorEnabled", false); + } + } else { + _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); + } + } +} + +void Rig::updateFeet(bool leftFootEnabled, bool rightFootEnabled, const AnimPose& leftFootPose, const AnimPose& rightFootPose) { + + const float KNEE_POLE_VECTOR_BLEND_FACTOR = 0.9f; + + int hipsIndex = indexOfJoint("Hips"); + + if (leftFootEnabled) { + glm::vec3 footPosition = leftFootPose.trans(); + glm::quat footRotation = leftFootPose.rot(); + _animVars.set("leftFootPosition", footPosition); + _animVars.set("leftFootRotation", footRotation); + _animVars.set("leftFootType", (int)IKTarget::Type::RotationAndPosition); + + int footJointIndex = _animSkeleton->nameToJointIndex("LeftFoot"); + glm::vec3 poleVector = calculateKneePoleVector(footJointIndex, footRotation, hipsIndex); + + // smooth toward desired pole vector from previous pole vector... to reduce jitter + if (!_prevLeftFootPoleVectorValid) { + _prevLeftFootPoleVectorValid = true; + _prevLeftFootPoleVector = poleVector; + } + glm::quat deltaRot = rotationBetween(_prevLeftFootPoleVector, poleVector); + glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, KNEE_POLE_VECTOR_BLEND_FACTOR); + _prevLeftFootPoleVector = smoothDeltaRot * _prevLeftFootPoleVector; + + _animVars.set("leftFootPoleVectorEnabled", true); + _animVars.set("leftFootPoleReferenceVector", Vectors::UNIT_Z); + _animVars.set("leftFootPoleVector", _prevLeftFootPoleVector); + } else { + _animVars.unset("leftFootPosition"); + _animVars.unset("leftFootRotation"); + _animVars.set("leftFootType", (int)IKTarget::Type::RotationAndPosition); + _animVars.set("leftFootPoleVectorEnabled", false); + _prevLeftFootPoleVectorValid = false; + } + + if (rightFootEnabled) { + glm::vec3 footPosition = rightFootPose.trans(); + glm::quat footRotation = rightFootPose.rot(); + _animVars.set("rightFootPosition", footPosition); + _animVars.set("rightFootRotation", footRotation); + _animVars.set("rightFootType", (int)IKTarget::Type::RotationAndPosition); + + int footJointIndex = _animSkeleton->nameToJointIndex("RightFoot"); + glm::vec3 poleVector = calculateKneePoleVector(footJointIndex, footRotation, hipsIndex); + + // smooth toward desired pole vector from previous pole vector... to reduce jitter + if (!_prevRightFootPoleVectorValid) { + _prevRightFootPoleVectorValid = true; + _prevRightFootPoleVector = poleVector; + } + glm::quat deltaRot = rotationBetween(_prevRightFootPoleVector, poleVector); + glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, KNEE_POLE_VECTOR_BLEND_FACTOR); + _prevRightFootPoleVector = smoothDeltaRot * _prevRightFootPoleVector; + + _animVars.set("rightFootPoleVectorEnabled", true); + _animVars.set("rightFootPoleReferenceVector", Vectors::UNIT_Z); + _animVars.set("rightFootPoleVector", _prevRightFootPoleVector); + } else { + _animVars.unset("rightFootPosition"); + _animVars.unset("rightFootRotation"); + _animVars.set("rightFootPoleVectorEnabled", false); + _animVars.set("rightFootType", (int)IKTarget::Type::RotationAndPosition); + } +} + void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::vec3& lookAtSpot, const glm::vec3& saccade) { // TODO: does not properly handle avatar scale. @@ -1196,261 +1415,65 @@ glm::vec3 Rig::calculateKneePoleVector(int footJointIndex, const glm::quat& foot return glm::normalize(lerp(footForward, hipsForward, FOOT_TO_HIPS_BLEND_FACTOR)); } -void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, float dt) { - if (_animSkeleton && _animNode) { - const float HAND_RADIUS = 0.05f; - int hipsIndex = indexOfJoint("Hips"); - glm::vec3 hipsTrans; - if (hipsIndex >= 0) { - hipsTrans = _internalPoseSet._absolutePoses[hipsIndex].trans(); - } +void Rig::updateFromControllerParameters(const ControllerParameters& params, float dt) { + if (!_animSkeleton || !_animNode) { + return; + } - // Use this capsule to represent the avatar body. - const float bodyCapsuleRadius = params.bodyCapsuleRadius; - const glm::vec3 bodyCapsuleCenter = hipsTrans - params.bodyCapsuleLocalOffset; - const glm::vec3 bodyCapsuleStart = bodyCapsuleCenter - glm::vec3(0, params.bodyCapsuleHalfHeight, 0); - const glm::vec3 bodyCapsuleEnd = bodyCapsuleCenter + glm::vec3(0, params.bodyCapsuleHalfHeight, 0); + _animVars.set("isTalking", params.isTalking); + _animVars.set("notIsTalking", !params.isTalking); - // TODO: add isHipsEnabled - bool bodySensorTrackingEnabled = params.isLeftFootEnabled || params.isRightFootEnabled; + bool headEnabled = params.controllerActiveFlags[ControllerType_Head]; + bool leftHandEnabled = params.controllerActiveFlags[ControllerType_LeftHand]; + bool rightHandEnabled = params.controllerActiveFlags[ControllerType_RightHand]; + bool hipsEnabled = params.controllerActiveFlags[ControllerType_Hips]; + bool leftFootEnabled = params.controllerActiveFlags[ControllerType_LeftFoot]; + bool rightFootEnabled = params.controllerActiveFlags[ControllerType_RightFoot]; + bool leftArmEnabled = params.controllerActiveFlags[ControllerType_LeftArm]; + bool rightArmEnabled = params.controllerActiveFlags[ControllerType_RightArm]; + bool spine2Enabled = params.controllerActiveFlags[ControllerType_Spine2]; - 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; + updateHead(headEnabled, hipsEnabled, params.controllerPoses[ControllerType_Head]); - const float KNEE_POLE_VECTOR_BLEND_FACTOR = 0.9f; - const float ELBOW_POLE_VECTOR_BLEND_FACTOR = 0.9f; + updateHands(leftHandEnabled, rightHandEnabled, hipsEnabled, dt, + params.controllerPoses[ControllerType_LeftHand], params.controllerPoses[ControllerType_RightHand], + params.bodyCapsuleRadius, params.bodyCapsuleHalfHeight, params.bodyCapsuleLocalOffset); - if (params.isLeftEnabled) { - if (!_isLeftHandControlled) { - _leftHandControlTimeRemaining = CONTROL_DURATION; - _isLeftHandControlled = true; - } + updateFeet(leftFootEnabled, rightFootEnabled, + params.controllerPoses[ControllerType_LeftFoot], params.controllerPoses[ControllerType_RightFoot]); - glm::vec3 handPosition = params.leftPosition; - glm::quat handRotation = params.leftOrientation; + if (hipsEnabled) { + _animVars.set("solutionSource", (int)AnimInverseKinematics::SolutionSource::RelaxToLimitCenterPoses); + _animVars.set("hipsType", (int)IKTarget::Type::RotationAndPosition); + _animVars.set("hipsPosition", params.controllerPoses[ControllerType_Hips].trans()); + _animVars.set("hipsRotation", params.controllerPoses[ControllerType_Hips].rot()); + } else { + _animVars.set("solutionSource", (int)AnimInverseKinematics::SolutionSource::RelaxToUnderPoses); + _animVars.set("hipsType", (int)IKTarget::Type::Unknown); + } - 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 && spine2Enabled) { + _animVars.set("spine2Type", (int)IKTarget::Type::Spline); + _animVars.set("spine2Position", params.controllerPoses[ControllerType_Spine2].trans()); + _animVars.set("spine2Rotation", params.controllerPoses[ControllerType_Spine2].rot()); + } else { + _animVars.set("spine2Type", (int)IKTarget::Type::Unknown); + } - if (!bodySensorTrackingEnabled) { - // prevent the hand IK targets from intersecting the body capsule - glm::vec3 displacement; - if (findSphereCapsulePenetration(handPosition, HAND_RADIUS, bodyCapsuleStart, bodyCapsuleEnd, bodyCapsuleRadius, displacement)) { - handPosition -= displacement; - } - } + if (leftArmEnabled) { + _animVars.set("leftArmType", (int)IKTarget::Type::RotationAndPosition); + _animVars.set("leftArmPosition", params.controllerPoses[ControllerType_LeftArm].trans()); + _animVars.set("leftArmRotation", params.controllerPoses[ControllerType_LeftArm].rot()); + } else { + _animVars.set("leftArmType", (int)IKTarget::Type::Unknown); + } - _animVars.set("leftHandPosition", handPosition); - _animVars.set("leftHandRotation", handRotation); - _animVars.set("leftHandType", (int)IKTarget::Type::RotationAndPosition); - - _lastLeftHandControlledPose = AnimPose(Vectors::ONE, handRotation, handPosition); - - _isLeftHandControlled = true; - _lastLeftHandControlledPose = AnimPose(glm::vec3(1.0f), params.leftOrientation, handPosition); - - // compute pole vector - int handJointIndex = _animSkeleton->nameToJointIndex("LeftHand"); - int armJointIndex = _animSkeleton->nameToJointIndex("LeftArm"); - int elbowJointIndex = _animSkeleton->nameToJointIndex("LeftForeArm"); - if (elbowJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0) { - glm::vec3 poleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, hipsIndex, true); - - // smooth toward desired pole vector from previous pole vector... to reduce jitter - if (!_prevLeftHandPoleVectorValid) { - _prevLeftHandPoleVectorValid = true; - _prevLeftHandPoleVector = poleVector; - } - glm::quat deltaRot = rotationBetween(_prevLeftHandPoleVector, poleVector); - glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, ELBOW_POLE_VECTOR_BLEND_FACTOR); - _prevLeftHandPoleVector = smoothDeltaRot * _prevLeftHandPoleVector; - - _animVars.set("leftHandPoleVectorEnabled", true); - _animVars.set("leftHandPoleReferenceVector", Vectors::UNIT_X); - _animVars.set("leftHandPoleVector", _prevLeftHandPoleVector); - } else { - _prevLeftHandPoleVectorValid = false; - _animVars.set("leftHandPoleVectorEnabled", false); - } - } else { - _prevLeftHandPoleVectorValid = false; - _animVars.set("leftHandPoleVectorEnabled", false); - - if (_isLeftHandControlled) { - _leftHandRelaxTimeRemaining = RELAX_DURATION; - _isLeftHandControlled = false; - } - - 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 (params.isRightEnabled) { - if (!_isRightHandControlled) { - _rightHandControlTimeRemaining = CONTROL_DURATION; - _isRightHandControlled = true; - } - - glm::vec3 handPosition = params.rightPosition; - glm::quat handRotation = params.rightOrientation; - - 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 (!bodySensorTrackingEnabled) { - // prevent the hand IK targets from intersecting the body capsule - glm::vec3 displacement; - if (findSphereCapsulePenetration(handPosition, HAND_RADIUS, bodyCapsuleStart, bodyCapsuleEnd, bodyCapsuleRadius, displacement)) { - handPosition -= displacement; - } - } - - _animVars.set("rightHandPosition", handPosition); - _animVars.set("rightHandRotation", handRotation); - _animVars.set("rightHandType", (int)IKTarget::Type::RotationAndPosition); - - _lastRightHandControlledPose = AnimPose(Vectors::ONE, handRotation, handPosition); - - _isRightHandControlled = true; - _lastRightHandControlledPose = AnimPose(glm::vec3(1.0f), params.rightOrientation, handPosition); - - // compute pole vector - int handJointIndex = _animSkeleton->nameToJointIndex("RightHand"); - int armJointIndex = _animSkeleton->nameToJointIndex("RightArm"); - int elbowJointIndex = _animSkeleton->nameToJointIndex("RightForeArm"); - if (elbowJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0) { - glm::vec3 poleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, hipsIndex, false); - - // smooth toward desired pole vector from previous pole vector... to reduce jitter - if (!_prevRightHandPoleVectorValid) { - _prevRightHandPoleVectorValid = true; - _prevRightHandPoleVector = poleVector; - } - glm::quat deltaRot = rotationBetween(_prevRightHandPoleVector, poleVector); - glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, ELBOW_POLE_VECTOR_BLEND_FACTOR); - _prevRightHandPoleVector = smoothDeltaRot * _prevRightHandPoleVector; - - _animVars.set("rightHandPoleVectorEnabled", true); - _animVars.set("rightHandPoleReferenceVector", -Vectors::UNIT_X); - _animVars.set("rightHandPoleVector", _prevRightHandPoleVector); - } else { - _prevRightHandPoleVectorValid = false; - _animVars.set("rightHandPoleVectorEnabled", false); - } - - } else { - _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); - } - } - - if (params.isLeftFootEnabled) { - _animVars.set("leftFootPosition", params.leftFootPosition); - _animVars.set("leftFootRotation", params.leftFootOrientation); - _animVars.set("leftFootType", (int)IKTarget::Type::RotationAndPosition); - - int footJointIndex = _animSkeleton->nameToJointIndex("LeftFoot"); - glm::vec3 poleVector = calculateKneePoleVector(footJointIndex, params.leftFootOrientation, hipsIndex); - - // smooth toward desired pole vector from previous pole vector... to reduce jitter - if (!_prevLeftFootPoleVectorValid) { - _prevLeftFootPoleVectorValid = true; - _prevLeftFootPoleVector = poleVector; - } - glm::quat deltaRot = rotationBetween(_prevLeftFootPoleVector, poleVector); - glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, KNEE_POLE_VECTOR_BLEND_FACTOR); - _prevLeftFootPoleVector = smoothDeltaRot * _prevLeftFootPoleVector; - - _animVars.set("leftFootPoleVectorEnabled", true); - _animVars.set("leftFootPoleReferenceVector", Vectors::UNIT_Z); - _animVars.set("leftFootPoleVector", _prevLeftFootPoleVector); - } else { - _animVars.unset("leftFootPosition"); - _animVars.unset("leftFootRotation"); - _animVars.set("leftFootType", (int)IKTarget::Type::RotationAndPosition); - _animVars.set("leftFootPoleVectorEnabled", false); - _prevLeftFootPoleVectorValid = false; - } - - if (params.isRightFootEnabled) { - _animVars.set("rightFootPosition", params.rightFootPosition); - _animVars.set("rightFootRotation", params.rightFootOrientation); - _animVars.set("rightFootType", (int)IKTarget::Type::RotationAndPosition); - - int footJointIndex = _animSkeleton->nameToJointIndex("RightFoot"); - glm::vec3 poleVector = calculateKneePoleVector(footJointIndex, params.rightFootOrientation, hipsIndex); - - // smooth toward desired pole vector from previous pole vector... to reduce jitter - if (!_prevRightFootPoleVectorValid) { - _prevRightFootPoleVectorValid = true; - _prevRightFootPoleVector = poleVector; - } - glm::quat deltaRot = rotationBetween(_prevRightFootPoleVector, poleVector); - glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, KNEE_POLE_VECTOR_BLEND_FACTOR); - _prevRightFootPoleVector = smoothDeltaRot * _prevRightFootPoleVector; - - _animVars.set("rightFootPoleVectorEnabled", true); - _animVars.set("rightFootPoleReferenceVector", Vectors::UNIT_Z); - _animVars.set("rightFootPoleVector", _prevRightFootPoleVector); - } else { - _animVars.unset("rightFootPosition"); - _animVars.unset("rightFootRotation"); - _animVars.set("rightFootPoleVectorEnabled", false); - _animVars.set("rightFootType", (int)IKTarget::Type::RotationAndPosition); - } + if (rightArmEnabled) { + _animVars.set("rightArmType", (int)IKTarget::Type::RotationAndPosition); + _animVars.set("rightArmPosition", params.controllerPoses[ControllerType_RightArm].trans()); + _animVars.set("rightArmRotation", params.controllerPoses[ControllerType_RightArm].rot()); + } else { + _animVars.set("rightArmType", (int)IKTarget::Type::Unknown); } } diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index d101425a77..d876b8c22c 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -41,21 +41,26 @@ public: bool useNames; }; - struct HeadParameters { - glm::mat4 hipsMatrix = glm::mat4(); // rig space - glm::mat4 spine2Matrix = glm::mat4(); // rig space - glm::quat rigHeadOrientation = glm::quat(); // rig space (-z forward) - glm::vec3 rigHeadPosition = glm::vec3(); // rig space - glm::vec3 rightArmPosition = glm::vec3(); // rig space - glm::quat rightArmRotation = glm::quat(); // rig space - glm::vec3 leftArmPosition = glm::vec3(); // rig space - glm::quat leftArmRotation = glm::quat(); // rig space - bool hipsEnabled = false; - bool headEnabled = false; - bool spine2Enabled = false; - bool leftArmEnabled = false; - bool rightArmEnabled = false; - bool isTalking = false; + enum ControllerType { + ControllerType_Head = 0, + ControllerType_LeftHand, + ControllerType_RightHand, + ControllerType_Hips, + ControllerType_LeftFoot, + ControllerType_RightFoot, + ControllerType_LeftArm, + ControllerType_RightArm, + ControllerType_Spine2, + NumControllerTypes + }; + + struct ControllerParameters { + AnimPose controllerPoses[NumControllerTypes]; // rig space + bool controllerActiveFlags[NumControllerTypes]; + bool isTalking; + float bodyCapsuleRadius; + float bodyCapsuleHalfHeight; + glm::vec3 bodyCapsuleLocalOffset; }; struct EyeParameters { @@ -67,25 +72,6 @@ public: int rightEyeJointIndex = -1; }; - struct HandAndFeetParameters { - bool isLeftEnabled; - bool isRightEnabled; - float bodyCapsuleRadius; - float bodyCapsuleHalfHeight; - glm::vec3 bodyCapsuleLocalOffset; - glm::vec3 leftPosition = glm::vec3(); // rig space - glm::quat leftOrientation = glm::quat(); // rig space (z forward) - glm::vec3 rightPosition = glm::vec3(); // rig space - glm::quat rightOrientation = glm::quat(); // rig space (z forward) - - bool isLeftFootEnabled; - bool isRightFootEnabled; - glm::vec3 leftFootPosition = glm::vec3(); // rig space - glm::quat leftFootOrientation = glm::quat(); // rig space (z forward) - glm::vec3 rightFootPosition = glm::vec3(); // rig space - glm::quat rightFootOrientation = glm::quat(); // rig space (z forward) - }; - enum class CharacterControllerState { Ground = 0, Takeoff, @@ -192,9 +178,8 @@ public: // legacy void clearJointStatePriorities(); - void updateFromHeadParameters(const HeadParameters& params, float dt); + void updateFromControllerParameters(const ControllerParameters& params, float dt); void updateFromEyeParameters(const EyeParameters& params); - void updateFromHandAndFeetParameters(const HandAndFeetParameters& params, float dt); void initAnimGraph(const QUrl& url); @@ -244,7 +229,11 @@ protected: void applyOverridePoses(); void buildAbsoluteRigPoses(const AnimPoseVec& relativePoses, AnimPoseVec& absolutePosesOut); - void updateHeadAnimVars(const HeadParameters& params); + void updateHead(bool headEnabled, bool hipsEnabled, const AnimPose& headMatrix); + void updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnabled, float dt, + const AnimPose& leftHandPose, const AnimPose& rightHandPose, + float bodyCapsuleRadius, float bodyCapsuleHalfHeight, const glm::vec3& bodyCapsuleLocalOffset); + void updateFeet(bool leftFootEnabled, bool rightFootEnabled, const AnimPose& leftFootPose, const AnimPose& rightFootPose); void updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::vec3& lookAt, const glm::vec3& saccade); void calcAnimAlpha(float speed, const std::vector& referenceSpeeds, float* alphaOut) const; From 1979ed7f3a08557d8fae5942b55d7d109befd057 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 19 Jun 2017 16:58:11 -0700 Subject: [PATCH 21/95] Disable elbow pole vector if arm/shoulder pucks are enabled --- libraries/animation/src/Rig.cpp | 8 ++++---- libraries/animation/src/Rig.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 0ee42372b1..ef5bd3c032 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1065,7 +1065,7 @@ void Rig::updateHead(bool headEnabled, bool hipsEnabled, const AnimPose& headPos } } -void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnabled, float dt, +void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnabled, bool leftArmEnabled, bool rightArmEnabled, float dt, const AnimPose& leftHandPose, const AnimPose& rightHandPose, float bodyCapsuleRadius, float bodyCapsuleHalfHeight, const glm::vec3& bodyCapsuleLocalOffset) { @@ -1130,7 +1130,7 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab int handJointIndex = _animSkeleton->nameToJointIndex("LeftHand"); int armJointIndex = _animSkeleton->nameToJointIndex("LeftArm"); int elbowJointIndex = _animSkeleton->nameToJointIndex("LeftForeArm"); - if (elbowJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0) { + if (!leftArmEnabled && elbowJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0) { glm::vec3 poleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, hipsIndex, true); // smooth toward desired pole vector from previous pole vector... to reduce jitter @@ -1213,7 +1213,7 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab int handJointIndex = _animSkeleton->nameToJointIndex("RightHand"); int armJointIndex = _animSkeleton->nameToJointIndex("RightArm"); int elbowJointIndex = _animSkeleton->nameToJointIndex("RightForeArm"); - if (elbowJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0) { + if (!rightArmEnabled && elbowJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0) { glm::vec3 poleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, hipsIndex, false); // smooth toward desired pole vector from previous pole vector... to reduce jitter @@ -1435,7 +1435,7 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo updateHead(headEnabled, hipsEnabled, params.controllerPoses[ControllerType_Head]); - updateHands(leftHandEnabled, rightHandEnabled, hipsEnabled, dt, + updateHands(leftHandEnabled, rightHandEnabled, hipsEnabled, leftArmEnabled, rightArmEnabled, dt, params.controllerPoses[ControllerType_LeftHand], params.controllerPoses[ControllerType_RightHand], params.bodyCapsuleRadius, params.bodyCapsuleHalfHeight, params.bodyCapsuleLocalOffset); diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index d876b8c22c..413b598079 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -230,7 +230,7 @@ protected: void buildAbsoluteRigPoses(const AnimPoseVec& relativePoses, AnimPoseVec& absolutePosesOut); void updateHead(bool headEnabled, bool hipsEnabled, const AnimPose& headMatrix); - void updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnabled, float dt, + void updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnabled, bool leftArmEnabled, bool rightArmEnabled, float dt, const AnimPose& leftHandPose, const AnimPose& rightHandPose, float bodyCapsuleRadius, float bodyCapsuleHalfHeight, const glm::vec3& bodyCapsuleLocalOffset); void updateFeet(bool leftFootEnabled, bool rightFootEnabled, const AnimPose& leftFootPose, const AnimPose& rightFootPose); From cd1beceb7582583f4c8f5cece2e5948be21a999e Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 20 Jun 2017 17:02:50 -0700 Subject: [PATCH 22/95] Added script that will attach an entity to a vive sensor. --- scripts/developer/tests/puck-attach.js | 131 +++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 scripts/developer/tests/puck-attach.js diff --git a/scripts/developer/tests/puck-attach.js b/scripts/developer/tests/puck-attach.js new file mode 100644 index 0000000000..ff86d31809 --- /dev/null +++ b/scripts/developer/tests/puck-attach.js @@ -0,0 +1,131 @@ +// +// debugatar.js +// +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ +/* global Xform */ +Script.include("/~/system/libraries/Xform.js"); + +(function() { // BEGIN LOCAL_SCOPE + +var TABLET_BUTTON_NAME = "PUCKATTACH"; +var HTML_URL = "https://s3.amazonaws.com/hifi-public/tony/html/puck-attach.html"; + +var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); +var tabletButton = tablet.addButton({ + text: TABLET_BUTTON_NAME, + icon: "https://s3.amazonaws.com/hifi-public/tony/icons/tpose-i.svg", + activeIcon: "https://s3.amazonaws.com/hifi-public/tony/icons/tpose-a.svg" +}); + +tabletButton.clicked.connect(function () { + if (shown) { + tablet.gotoHomeScreen(); + } else { + tablet.gotoWebScreen(HTML_URL); + } +}); + +var shown = false; +var attachedEntity; +var attachedObj; + +function onScreenChanged(type, url) { + if (type === "Web" && url === HTML_URL) { + tabletButton.editProperties({isActive: true}); + if (!shown) { + // hook up to event bridge + tablet.webEventReceived.connect(onWebEventReceived); + } + shown = true; + } else { + tabletButton.editProperties({isActive: false}); + if (shown) { + // disconnect from event bridge + tablet.webEventReceived.disconnect(onWebEventReceived); + } + shown = false; + } +} + +tablet.screenChanged.connect(onScreenChanged); + +function attach(obj) { + attachedEntity = Entities.addEntity({ + type: "Model", + name: "puck-attach-entity", + modelURL: obj.modelurl + }); + attachedObj = obj; + var localPos = {x: Number(obj.posx), y: Number(obj.posy), z: Number(obj.posz)}; + var localRot = Quat.fromVec3Degrees({x: Number(obj.rotx), y: Number(obj.roty), z: Number(obj.rotz)}); + attachedObj.localXform = new Xform(localRot, localPos); + var key = "TrackedObject" + pad(attachedObj.puckno, 2); + attachedObj.key = key; + + print("AJT: attachedObj = " + JSON.stringify(attachedObj)); + + Script.update.connect(update); + update(0.001); +} + +function remove() { + if (attachedEntity) { + Script.update.disconnect(update); + Entities.deleteEntity(attachedEntity); + attachedEntity = undefined; + } + attachedObj = undefined; +} + +function pad(num, size) { + var s = "000000000" + num; + return s.substr(s.length-size); +} + +function update(dt) { + if (attachedEntity && attachedObj && Controller.Hardware.Vive) { + var pose = Controller.getPoseValue(Controller.Hardware.Vive[attachedObj.key]); + var avatarXform = new Xform(MyAvatar.orientation, MyAvatar.position); + var puckXform = new Xform(pose.rotation, pose.translation); + var finalXform = Xform.mul(avatarXform, Xform.mul(puckXform, attachedObj.localXform)); + if (pose && pose.valid) { + Entities.editEntity(attachedEntity, { + position: finalXform.pos, + rotation: finalXform.rot + }); + } else { + if (pose) { + print("AJT: WARNING: invalid pose for " + attachedObj.key); + } else { + print("AJT: WARNING: could not find key " + attachedObj.key); + } + } + } +} + +function onWebEventReceived(msg) { + var obj = {}; + try { + obj = JSON.parse(msg); + } catch (err) { + return; + } + if (obj.cmd === "attach") { + remove(); + attach(obj); + } else if (obj.cmd === "detach") { + remove(); + } +} + +Script.scriptEnding.connect(function () { + remove(); + tablet.removeButton(tabletButton); + if (shown) { + tablet.webEventReceived.disconnect(onWebEventReceived); + tablet.gotoHomeScreen(); + } + tablet.screenChanged.disconnect(onScreenChanged); +}); + +}()); // END LOCAL_SCOPE From 287dc3e1b83bd68ba6b85d09d5fc084d84a43c2b Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 20 Jun 2017 17:10:53 -0700 Subject: [PATCH 23/95] updated license and button name --- scripts/developer/tests/puck-attach.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/scripts/developer/tests/puck-attach.js b/scripts/developer/tests/puck-attach.js index ff86d31809..7ed38eb5d0 100644 --- a/scripts/developer/tests/puck-attach.js +++ b/scripts/developer/tests/puck-attach.js @@ -1,13 +1,18 @@ // -// debugatar.js +// Created by Anthony J. Thibault on 2017/06/20 +// Copyright 2017 High Fidelity, Inc. // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + /* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ /* global Xform */ Script.include("/~/system/libraries/Xform.js"); (function() { // BEGIN LOCAL_SCOPE -var TABLET_BUTTON_NAME = "PUCKATTACH"; +var TABLET_BUTTON_NAME = "PUCKTACH"; var HTML_URL = "https://s3.amazonaws.com/hifi-public/tony/html/puck-attach.html"; var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); From e84d0358cc9d55aef5f35a161be68698b8dd9351 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 20 Jun 2017 17:18:35 -0700 Subject: [PATCH 24/95] updated images --- scripts/developer/tests/puck-attach.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/developer/tests/puck-attach.js b/scripts/developer/tests/puck-attach.js index 7ed38eb5d0..69d6c9e39b 100644 --- a/scripts/developer/tests/puck-attach.js +++ b/scripts/developer/tests/puck-attach.js @@ -18,8 +18,8 @@ var HTML_URL = "https://s3.amazonaws.com/hifi-public/tony/html/puck-attach.html" var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var tabletButton = tablet.addButton({ text: TABLET_BUTTON_NAME, - icon: "https://s3.amazonaws.com/hifi-public/tony/icons/tpose-i.svg", - activeIcon: "https://s3.amazonaws.com/hifi-public/tony/icons/tpose-a.svg" + icon: "https://s3.amazonaws.com/hifi-public/tony/icons/puck-i.svg", + activeIcon: "https://s3.amazonaws.com/hifi-public/tony/icons/puck-a.svg" }); tabletButton.clicked.connect(function () { From b78e2781020c44737c3e406de83b5675361433e9 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 20 Jun 2017 17:19:57 -0700 Subject: [PATCH 25/95] adjusted print statements --- scripts/developer/tests/puck-attach.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/developer/tests/puck-attach.js b/scripts/developer/tests/puck-attach.js index 69d6c9e39b..807977794d 100644 --- a/scripts/developer/tests/puck-attach.js +++ b/scripts/developer/tests/puck-attach.js @@ -67,7 +67,7 @@ function attach(obj) { var key = "TrackedObject" + pad(attachedObj.puckno, 2); attachedObj.key = key; - print("AJT: attachedObj = " + JSON.stringify(attachedObj)); + print("PUCK-ATTACH: attachedObj = " + JSON.stringify(attachedObj)); Script.update.connect(update); update(0.001); @@ -100,9 +100,9 @@ function update(dt) { }); } else { if (pose) { - print("AJT: WARNING: invalid pose for " + attachedObj.key); + print("PUCK-ATTACH: WARNING: invalid pose for " + attachedObj.key); } else { - print("AJT: WARNING: could not find key " + attachedObj.key); + print("PUCK-ATTACH: WARNING: could not find key " + attachedObj.key); } } } From 8ce1474d9addf058ea2fbbd65abb52414aa5a1d6 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 21 Jun 2017 09:48:34 -0700 Subject: [PATCH 26/95] Add isReplicated to avatar identity data --- assignment-client/src/avatars/AvatarMixer.cpp | 4 ++-- assignment-client/src/avatars/AvatarMixerSlave.cpp | 2 +- libraries/avatars/src/AvatarData.cpp | 9 ++++++++- libraries/avatars/src/AvatarData.h | 9 ++++++++- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 136d5f2e8e..10edd21258 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -236,7 +236,7 @@ void AvatarMixer::start() { auto start = usecTimestampNow(); nodeList->nestedEach([&](NodeList::const_iterator cbegin, NodeList::const_iterator cend) { std::for_each(cbegin, cend, [&](const SharedNodePointer& node) { - if (node->getType() == NodeType::Agent && !node->isUpstream()) { + if (node->getType() == NodeType::Agent) { manageIdentityData(node); } @@ -332,7 +332,7 @@ void AvatarMixer::manageIdentityData(const SharedNodePointer& node) { sendIdentity = true; } } - if (sendIdentity) { + if (sendIdentity && !node->isUpstream()) { sendIdentityPacket(nodeData, node); // Tell node whose name changed about its new session display name or avatar. } } diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 4d5e507923..1392344376 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -81,7 +81,7 @@ int AvatarMixerSlave::sendIdentityPacket(const AvatarMixerClientData* nodeData, int AvatarMixerSlave::sendReplicatedIdentityPacket(const AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode) { if (destinationNode->getType() == NodeType::DownstreamAvatarMixer) { - QByteArray individualData = nodeData->getConstAvatarData()->identityByteArray(true); + QByteArray individualData = nodeData->getConstAvatarData()->identityByteArray(true, true); individualData.replace(0, NUM_BYTES_RFC4122_UUID, nodeData->getNodeID().toRfc4122()); // FIXME, this looks suspicious auto identityPacket = NLPacket::create(PacketType::ReplicatedAvatarIdentity); identityPacket->write(individualData); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 6ec2b45c89..36fc991958 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -1509,6 +1509,7 @@ void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& ide >> identity.attachmentData >> identity.displayName >> identity.sessionDisplayName + >> identity.isReplicated >> identity.avatarEntityData; // set the store identity sequence number to match the incoming identity @@ -1531,6 +1532,11 @@ void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& ide } maybeUpdateSessionDisplayNameFromTransport(identity.sessionDisplayName); + if (identity.isReplicated != _isReplicated) { + _isReplicated = identity.isReplicated; + identityChanged = true; + } + if (identity.attachmentData != _attachmentData) { setAttachmentData(identity.attachmentData); identityChanged = true; @@ -1563,7 +1569,7 @@ void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& ide } } -QByteArray AvatarData::identityByteArray(bool shouldForwardIncomingSequenceNumber) const { +QByteArray AvatarData::identityByteArray(bool shouldForwardIncomingSequenceNumber, bool setIsReplicated) const { QByteArray identityData; QDataStream identityStream(&identityData, QIODevice::Append); const QUrl& urlToSend = cannonicalSkeletonModelURL(emptyURL); // depends on _skeletonModelURL @@ -1584,6 +1590,7 @@ QByteArray AvatarData::identityByteArray(bool shouldForwardIncomingSequenceNumbe << _attachmentData << _displayName << getSessionDisplayNameForTransport() // depends on _sessionDisplayName + << (_isReplicated || setIsReplicated) << _avatarEntityData; }); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 44d910b571..27b9cd7194 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -531,6 +531,7 @@ public: QVector attachmentData; QString displayName; QString sessionDisplayName; + bool isReplicated; AvatarEntityMap avatarEntityData; }; @@ -539,7 +540,7 @@ public: void processAvatarIdentity(const QByteArray& identityData, bool& identityChanged, bool& displayNameChanged, bool& skeletonModelUrlChanged); - QByteArray identityByteArray(bool shouldForwardIncomingSequenceNumber = false) const; + QByteArray identityByteArray(bool shouldForwardIncomingSequenceNumber = false, bool setIsReplicated = false) const; const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; } const QString& getDisplayName() const { return _displayName; } @@ -627,6 +628,8 @@ public: float getDensity() const { return _density; } + bool getIsReplicated() const { return _isReplicated; } + signals: void displayNameChanged(); @@ -663,6 +666,10 @@ protected: bool hasParent() const { return !getParentID().isNull(); } bool hasFaceTracker() const { return _headData ? _headData->_isFaceTrackerConnected : false; } + // isReplicated will be true on downstream Avatar Mixers and their clients, but false on the upstream "master" + // Audio Mixer that the replicated avatar is connected to. + bool _isReplicated{ false }; + glm::vec3 _handPosition; virtual const QString& getSessionDisplayNameForTransport() const { return _sessionDisplayName; } virtual void maybeUpdateSessionDisplayNameFromTransport(const QString& sessionDisplayName) { } // No-op in AvatarMixer From 0e7ddfd29f478ad8f39c14cf2dc59ad5584643a4 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 21 Jun 2017 09:49:16 -0700 Subject: [PATCH 27/95] Disable silence/ban buttons in PAL if avatar is replicated --- interface/resources/qml/hifi/NameCard.qml | 1 + interface/resources/qml/hifi/Pal.qml | 2 ++ libraries/avatars/src/ScriptAvatarData.cpp | 9 +++++++++ libraries/avatars/src/ScriptAvatarData.h | 2 ++ scripts/system/pal.js | 3 ++- 5 files changed, 16 insertions(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/NameCard.qml b/interface/resources/qml/hifi/NameCard.qml index a86defdfd7..6e881a7fbf 100644 --- a/interface/resources/qml/hifi/NameCard.qml +++ b/interface/resources/qml/hifi/NameCard.qml @@ -43,6 +43,7 @@ Item { property bool selected: false property bool isAdmin: false property bool isPresent: true + property bool isReplicated: false property string placeName: "" property string profilePicBorderColor: (connectionStatus == "connection" ? hifi.colors.indigoAccent : (connectionStatus == "friend" ? hifi.colors.greenHighlight : "transparent")) property alias avImage: avatarImage diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index bbb42e61ac..8db04a0f5b 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -473,6 +473,7 @@ Rectangle { visible: !isCheckBox && !isButton && !isAvgAudio; uuid: model ? model.sessionId : ""; selected: styleData.selected; + isReplicated: model.isReplicated; isAdmin: model && model.admin; isPresent: model && model.isPresent; // Size @@ -553,6 +554,7 @@ Rectangle { id: actionButton; color: 2; // Red visible: isButton; + enabled: !nameCard.isReplicated; anchors.centerIn: parent; width: 32; height: 32; diff --git a/libraries/avatars/src/ScriptAvatarData.cpp b/libraries/avatars/src/ScriptAvatarData.cpp index 01d7f293d8..90ec7ec309 100644 --- a/libraries/avatars/src/ScriptAvatarData.cpp +++ b/libraries/avatars/src/ScriptAvatarData.cpp @@ -152,6 +152,15 @@ QString ScriptAvatarData::getSessionDisplayName() const { return QString(); } } + +bool ScriptAvatarData::getIsReplicated() const { + if (AvatarSharedPointer sharedAvatarData = _avatarData.lock()) { + return sharedAvatarData->getIsReplicated(); + } else { + return false; + } +} + // // IDENTIFIER PROPERTIES // END diff --git a/libraries/avatars/src/ScriptAvatarData.h b/libraries/avatars/src/ScriptAvatarData.h index d763b6e97a..1b6944e01d 100644 --- a/libraries/avatars/src/ScriptAvatarData.h +++ b/libraries/avatars/src/ScriptAvatarData.h @@ -45,6 +45,7 @@ class ScriptAvatarData : public QObject { Q_PROPERTY(QUuid sessionUUID READ getSessionUUID) Q_PROPERTY(QString displayName READ getDisplayName NOTIFY displayNameChanged) Q_PROPERTY(QString sessionDisplayName READ getSessionDisplayName) + Q_PROPERTY(bool isReplicated READ getIsReplicated) // // ATTACHMENT AND JOINT PROPERTIES @@ -95,6 +96,7 @@ public: QUuid getSessionUUID() const; QString getDisplayName() const; QString getSessionDisplayName() const; + bool getIsReplicated() const; // // ATTACHMENT AND JOINT PROPERTIES diff --git a/scripts/system/pal.js b/scripts/system/pal.js index 0500c13f9b..6c1652c700 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -479,7 +479,8 @@ function populateNearbyUserList(selectData, oldAudioData) { admin: false, personalMute: !!id && Users.getPersonalMuteStatus(id), // expects proper boolean, not null ignore: !!id && Users.getIgnoreStatus(id), // ditto - isPresent: true + isPresent: true, + isReplicated: avatar.isReplicated }; if (id) { addAvatarNode(id); // No overlay for ourselves From 0f51236fb06103773c8a0aac3210f95b9ad17506 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 21 Jun 2017 14:36:42 -0700 Subject: [PATCH 28/95] Rig.cpp: take avatar scale into account when computing elbow pole vector --- libraries/animation/src/Rig.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index ef5bd3c032..51480e8ef1 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1383,8 +1383,10 @@ glm::vec3 Rig::calculateElbowPoleVector(int handIndex, int elbowIndex, int armIn glm::vec3 y = hipsPose.rot() * Vectors::UNIT_Y; // project d onto n. glm::vec3 dProj = d - glm::dot(d, n) * n; - const float LATERAL_OFFSET = 0.333f; - const float VERTICAL_OFFSET = -0.333f; + + float avatarScale = extractUniformScale(_modelOffset); + const float LATERAL_OFFSET = 0.333f * avatarScale; + const float VERTICAL_OFFSET = -0.333f * avatarScale; // give dProj a bit of offset away from the body. glm::vec3 dProjWithOffset = dProj + sign * LATERAL_OFFSET * n + y * VERTICAL_OFFSET; From 30714d5d254cdb924ad35302a81355dd296a2e58 Mon Sep 17 00:00:00 2001 From: Vladyslav Stelmakhovskyi Date: Thu, 22 Jun 2017 16:47:40 +0200 Subject: [PATCH 29/95] Make background with default color. Fix few warnings --- interface/resources/qml/controls-uit/ComboBox.qml | 2 +- .../qml/hifi/tablet/tabletWindows/TabletFileDialog.qml | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/interface/resources/qml/controls-uit/ComboBox.qml b/interface/resources/qml/controls-uit/ComboBox.qml index 3ce297ef80..d672fa6387 100644 --- a/interface/resources/qml/controls-uit/ComboBox.qml +++ b/interface/resources/qml/controls-uit/ComboBox.qml @@ -217,7 +217,7 @@ FocusScope { anchors.leftMargin: hifi.dimensions.textPadding anchors.verticalCenter: parent.verticalCenter id: popupText - text: listView.model[index] ? listView.model[index] : (listView.model.get(index).text ? listView.model.get(index).text : "") + text: listView.model[index] ? listView.model[index] : (listView.model.get && listView.model.get(index).text ? listView.model.get(index).text : "") size: hifi.fontSizes.textFieldInput color: hifi.colors.baseGray } diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml b/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml index 26e35c4dcf..7b91cfeba9 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml @@ -23,11 +23,13 @@ import "../../../windows" import "../../../dialogs/fileDialog" //FIXME implement shortcuts for favorite location -Item { +Rectangle { id: root - anchors.top: parent.top + anchors.top: parent ? parent.top : undefined HifiConstants { id: hifi } + color: hifi.colors.baseGray; + Settings { category: "FileDialog" property alias width: root.width From 0ef623ba069818da60463a47188488c9fe58392c Mon Sep 17 00:00:00 2001 From: humbletim Date: Thu, 22 Jun 2017 12:45:53 -0400 Subject: [PATCH 30/95] * revert button icons and name * add new attachmentsUpdated signal and connect to 'doppleganger_attachments' logAction * fix typo in debug mode --- .../Pullover-lineart-inverted.svg | 647 ------------------ .../Pullover-lineart-normal.svg | 646 ----------------- .../app-doppleganger-attachments.js | 14 +- .../doppleganger-a.svg | 94 +++ .../doppleganger-attachments.js | 6 +- .../doppleganger-i.svg | 94 +++ .../doppleganger-attachments/doppleganger.js | 2 +- 7 files changed, 203 insertions(+), 1300 deletions(-) delete mode 100644 unpublishedScripts/marketplace/doppleganger-attachments/Pullover-lineart-inverted.svg delete mode 100644 unpublishedScripts/marketplace/doppleganger-attachments/Pullover-lineart-normal.svg create mode 100644 unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-a.svg create mode 100644 unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-i.svg diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/Pullover-lineart-inverted.svg b/unpublishedScripts/marketplace/doppleganger-attachments/Pullover-lineart-inverted.svg deleted file mode 100644 index 52aed00285..0000000000 --- a/unpublishedScripts/marketplace/doppleganger-attachments/Pullover-lineart-inverted.svg +++ /dev/null @@ -1,647 +0,0 @@ - - - - - Pullover - - - - - - image/svg+xml - - Pullover - - 2014-02-03 - - - Frank Tremmel - - - - - pulli - pullover - sweater - shirt - clothes - clothings - kleidung - - - - outline pullover - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/Pullover-lineart-normal.svg b/unpublishedScripts/marketplace/doppleganger-attachments/Pullover-lineart-normal.svg deleted file mode 100644 index 9cab6592aa..0000000000 --- a/unpublishedScripts/marketplace/doppleganger-attachments/Pullover-lineart-normal.svg +++ /dev/null @@ -1,646 +0,0 @@ - - - - - Pullover - - - - - - image/svg+xml - - Pullover - - 2014-02-03 - - - Frank Tremmel - - - - - pulli - pullover - sweater - shirt - clothes - clothings - kleidung - - - - outline pullover - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js b/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js index 71be8d72af..4142ff332e 100644 --- a/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js +++ b/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js @@ -18,9 +18,9 @@ var DopplegangerClass = require('./doppleganger.js'), var tablet = Tablet.getTablet('com.highfidelity.interface.tablet.system'), button = tablet.addButton({ - icon: Script.resolvePath('./Pullover-lineart-normal.svg'), - activeIcon: Script.resolvePath('./Pullover-lineart-inverted.svg'), - text: 'Mannequin' + icon: Script.resolvePath('./doppleganger-i.svg'), + activeIcon: Script.resolvePath('./doppleganger-a.svg'), + text: 'MIRROR' }); Script.scriptEnding.connect(function() { @@ -97,11 +97,15 @@ doppleganger.modelLoaded.connect(function(error, result) { } }); +// ---------------------------------------------------------------------------- + // add debug indicators, but only if the user has configured the settings value if (Settings.getValue('debug.doppleganger', false)) { DopplegangerClass.addDebugControls(doppleganger); } +// ---------------------------------------------------------------------------- + UserActivityLogger.logAction('doppleganger_app_load'); doppleganger.activeChanged.connect(function(active, reason) { if (active) { @@ -116,3 +120,7 @@ doppleganger.activeChanged.connect(function(active, reason) { } } }); +dopplegangerAttachments.attachmentsUpdated.connect(function(attachments) { + UserActivityLogger.logAction('doppleganger_attachments', { count: attachments.length }); +}); + diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-a.svg b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-a.svg new file mode 100644 index 0000000000..100986647e --- /dev/null +++ b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-a.svg @@ -0,0 +1,94 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-attachments.js b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-attachments.js index 894d627fc5..1d3847cad2 100644 --- a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-attachments.js +++ b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-attachments.js @@ -21,6 +21,7 @@ function DopplegangerAttachments(doppleganger, options) { doppleganger: doppleganger, attachments: undefined, manualJointSync: true, + attachmentsUpdated: utils.signal(function attachmentsUpdated(currentAttachments, previousAttachments){}), }); this._initialize(); log('DopplegangerAttachments...', JSON.stringify(options)); @@ -77,6 +78,7 @@ DopplegangerAttachments.prototype = { }); this.attachments = after; this._createAttachmentObjects(); + this.attachmentsUpdated(after, before); }, _createAttachmentObjects: function() { try { @@ -176,9 +178,7 @@ DopplegangerAttachments.prototype = { manualJointSync = this.manualJointSync; if (!this.attachments) { - this.attachments = this._getResolvedAttachments(); - this._createAttachmentObjects(); - log('created attachment objects #' + this.attachments.length); + this.refreshAttachments(); } var updatedObjects = this.attachments.reduce(function(updates, attachment, i) { if (!attachment.properties || !attachment._loaded) { diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-i.svg b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-i.svg new file mode 100644 index 0000000000..0c55e0e0c7 --- /dev/null +++ b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-i.svg @@ -0,0 +1,94 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js index c743357e0c..375105e722 100644 --- a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js +++ b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js @@ -501,7 +501,7 @@ Doppleganger.addDebugControls = function(doppleganger) { if (hit.jointIndex < 0) { return; } - hit.mirroredJointName = Doppleganger.getMirroredJointNames([hit.jointName])[0]; + hit.mirroredJointName = modelHelper.deriveMirroredJointNames([hit.jointName])[0]; log('selected joint:', JSON.stringify(hit, 0, 2)); }); From 7ff576ef1897b97ba4a05f3c804e6f060d108637 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 22 Jun 2017 10:07:39 -0700 Subject: [PATCH 31/95] add memory-debugging macro to some cmake files --- assignment-client/CMakeLists.txt | 2 ++ domain-server/CMakeLists.txt | 2 ++ ice-server/CMakeLists.txt | 2 ++ interface/CMakeLists.txt | 2 ++ libraries/gl/CMakeLists.txt | 1 + tests/controllers/CMakeLists.txt | 4 +++- tests/qt59/CMakeLists.txt | 4 +++- tests/recording/CMakeLists.txt | 1 + tools/vhacd-util/CMakeLists.txt | 2 ++ 9 files changed, 18 insertions(+), 2 deletions(-) diff --git a/assignment-client/CMakeLists.txt b/assignment-client/CMakeLists.txt index 54afabfd21..6b3101f4e3 100644 --- a/assignment-client/CMakeLists.txt +++ b/assignment-client/CMakeLists.txt @@ -7,6 +7,8 @@ if (APPLE) set_target_properties(${TARGET_NAME} PROPERTIES INSTALL_RPATH "@executable_path/../Frameworks") endif () +setup_memory_debugger() + # link in the shared libraries link_hifi_libraries( audio avatars octree gpu model fbx entities diff --git a/domain-server/CMakeLists.txt b/domain-server/CMakeLists.txt index 2ce537a5a0..c1e275e4d3 100644 --- a/domain-server/CMakeLists.txt +++ b/domain-server/CMakeLists.txt @@ -14,6 +14,8 @@ if (APPLE) set_target_properties(${TARGET_NAME} PROPERTIES INSTALL_RPATH "@executable_path/../Frameworks") endif () +setup_memory_debugger() + # TODO: find a solution that will handle web file changes in resources on windows without a re-build. # Currently the resources are only copied on post-build. If one is changed but the domain-server is not, they will # not be re-copied. This is worked-around on OS X/UNIX by using a symlink. diff --git a/ice-server/CMakeLists.txt b/ice-server/CMakeLists.txt index e5bdffe2e2..07b90b369e 100644 --- a/ice-server/CMakeLists.txt +++ b/ice-server/CMakeLists.txt @@ -18,5 +18,7 @@ endif () include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}") +setup_memory_debugger() + # append OpenSSL to our list of libraries to link target_link_libraries(${TARGET_NAME} ${OPENSSL_LIBRARIES}) diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 71341f3f11..fe42b1432b 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -356,6 +356,8 @@ if (ANDROID) qt_create_apk() endif () +setup_memory_debugger() + add_dependency_external_projects(GifCreator) find_package(GifCreator REQUIRED) target_include_directories(${TARGET_NAME} PUBLIC ${GIFCREATOR_INCLUDE_DIRS}) diff --git a/libraries/gl/CMakeLists.txt b/libraries/gl/CMakeLists.txt index fd3197410b..0a0ca2fc5f 100644 --- a/libraries/gl/CMakeLists.txt +++ b/libraries/gl/CMakeLists.txt @@ -1,5 +1,6 @@ set(TARGET_NAME gl) setup_hifi_library(OpenGL Qml Quick) +setup_memory_debugger() link_hifi_libraries(shared networking) target_opengl() diff --git a/tests/controllers/CMakeLists.txt b/tests/controllers/CMakeLists.txt index 3aac4db0a8..3221070837 100644 --- a/tests/controllers/CMakeLists.txt +++ b/tests/controllers/CMakeLists.txt @@ -5,6 +5,8 @@ set(TARGET_NAME controllers-test) setup_hifi_project(Script Qml) set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") +setup_memory_debugger() + # link in the shared libraries link_hifi_libraries(shared gl script-engine plugins render-utils ui-plugins input-plugins display-plugins controllers) @@ -16,4 +18,4 @@ if (WIN32) target_link_libraries(${TARGET_NAME} ${OPENVR_LIBRARIES}) endif() -package_libraries_for_deployment() \ No newline at end of file +package_libraries_for_deployment() diff --git a/tests/qt59/CMakeLists.txt b/tests/qt59/CMakeLists.txt index 32cc125ecf..e0e8138a1e 100644 --- a/tests/qt59/CMakeLists.txt +++ b/tests/qt59/CMakeLists.txt @@ -1,10 +1,12 @@ set(TARGET_NAME qt59) - + if (WIN32) SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /ignore:4049 /ignore:4217") endif() +setup_memory_debugger() + # This is not a testcase -- just set it up as a regular hifi project setup_hifi_project(Gui) set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") diff --git a/tests/recording/CMakeLists.txt b/tests/recording/CMakeLists.txt index 4e881fcbd9..b5b1e6a54e 100644 --- a/tests/recording/CMakeLists.txt +++ b/tests/recording/CMakeLists.txt @@ -2,6 +2,7 @@ set(TARGET_NAME recording-test) # This is not a testcase -- just set it up as a regular hifi project setup_hifi_project(Test) set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") +setup_memory_debugger() link_hifi_libraries(shared recording) package_libraries_for_deployment() diff --git a/tools/vhacd-util/CMakeLists.txt b/tools/vhacd-util/CMakeLists.txt index 810d13ffd7..c28aa9efa4 100644 --- a/tools/vhacd-util/CMakeLists.txt +++ b/tools/vhacd-util/CMakeLists.txt @@ -8,6 +8,8 @@ find_package(VHACD REQUIRED) target_include_directories(${TARGET_NAME} PUBLIC ${VHACD_INCLUDE_DIRS}) target_link_libraries(${TARGET_NAME} ${VHACD_LIBRARIES}) +setup_memory_debugger() + if (UNIX AND NOT APPLE) include(FindOpenMP) if(OPENMP_FOUND) From 9ce173f682cf04a8fb3e32bd2f1d65503e639884 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 22 Jun 2017 10:56:08 -0700 Subject: [PATCH 32/95] missed a few --- tools/ac-client/CMakeLists.txt | 1 + tools/atp-get/CMakeLists.txt | 1 + tools/skeleton-dump/CMakeLists.txt | 1 + 3 files changed, 3 insertions(+) diff --git a/tools/ac-client/CMakeLists.txt b/tools/ac-client/CMakeLists.txt index 9e623b02e9..24eeadba9c 100644 --- a/tools/ac-client/CMakeLists.txt +++ b/tools/ac-client/CMakeLists.txt @@ -1,3 +1,4 @@ set(TARGET_NAME ac-client) setup_hifi_project(Core Widgets) +setup_memory_debugger() link_hifi_libraries(shared networking) diff --git a/tools/atp-get/CMakeLists.txt b/tools/atp-get/CMakeLists.txt index b1646dc023..75f92b787d 100644 --- a/tools/atp-get/CMakeLists.txt +++ b/tools/atp-get/CMakeLists.txt @@ -1,3 +1,4 @@ set(TARGET_NAME atp-get) setup_hifi_project(Core Widgets) +setup_memory_debugger() link_hifi_libraries(shared networking) diff --git a/tools/skeleton-dump/CMakeLists.txt b/tools/skeleton-dump/CMakeLists.txt index 04d450d9c2..bb2fe24f51 100644 --- a/tools/skeleton-dump/CMakeLists.txt +++ b/tools/skeleton-dump/CMakeLists.txt @@ -1,4 +1,5 @@ set(TARGET_NAME skeleton-dump) setup_hifi_project(Core Widgets) +setup_memory_debugger() link_hifi_libraries(shared fbx model gpu gl animation) From 3184d4e9d13ae105e531fffff9a4e61959cb48be Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 22 Jun 2017 12:17:22 -0700 Subject: [PATCH 33/95] Puck attach only shows available tracked objects. --- scripts/developer/tests/puck-attach.js | 35 +++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/scripts/developer/tests/puck-attach.js b/scripts/developer/tests/puck-attach.js index 807977794d..7fe13e6ba1 100644 --- a/scripts/developer/tests/puck-attach.js +++ b/scripts/developer/tests/puck-attach.js @@ -13,7 +13,8 @@ Script.include("/~/system/libraries/Xform.js"); (function() { // BEGIN LOCAL_SCOPE var TABLET_BUTTON_NAME = "PUCKTACH"; -var HTML_URL = "https://s3.amazonaws.com/hifi-public/tony/html/puck-attach.html"; +// var HTML_URL = "https://s3.amazonaws.com/hifi-public/tony/html/puck-attach.html"; +var HTML_URL = "file:///C:/msys64/home/anthony/code/hifi/examples/html/puck-attach.html"; var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var tabletButton = tablet.addButton({ @@ -40,6 +41,14 @@ function onScreenChanged(type, url) { if (!shown) { // hook up to event bridge tablet.webEventReceived.connect(onWebEventReceived); + + Script.setTimeout(function () { + // send available tracked objects to the html running in the tablet. + var availableTrackedObjects = getAvailableTrackedObjects(); + tablet.emitScriptEvent(JSON.stringify(availableTrackedObjects)); + + print("PUCK-ATTACH: availableTrackedObjects = " + JSON.stringify(availableTrackedObjects)); + }, 1000); // wait 1 sec before sending.. } shown = true; } else { @@ -54,6 +63,24 @@ function onScreenChanged(type, url) { tablet.screenChanged.connect(onScreenChanged); +function indexToTrackedObjectName(index) { + return "TrackedObject" + pad(index, 2); +} + +function getAvailableTrackedObjects() { + var available = []; + var NUM_TRACKED_OBJECTS = 16; + var i; + for (i = 0; i < NUM_TRACKED_OBJECTS; i++) { + var key = indexToTrackedObjectName(i); + var pose = Controller.getPoseValue(Controller.Hardware.Vive[key]); + if (pose && pose.valid) { + available.push(i); + } + } + return available; +} + function attach(obj) { attachedEntity = Entities.addEntity({ type: "Model", @@ -64,7 +91,7 @@ function attach(obj) { var localPos = {x: Number(obj.posx), y: Number(obj.posy), z: Number(obj.posz)}; var localRot = Quat.fromVec3Degrees({x: Number(obj.rotx), y: Number(obj.roty), z: Number(obj.rotz)}); attachedObj.localXform = new Xform(localRot, localPos); - var key = "TrackedObject" + pad(attachedObj.puckno, 2); + var key = indexToTrackedObjectName(Number(attachedObj.puckno)); attachedObj.key = key; print("PUCK-ATTACH: attachedObj = " + JSON.stringify(attachedObj)); @@ -83,8 +110,8 @@ function remove() { } function pad(num, size) { - var s = "000000000" + num; - return s.substr(s.length-size); + var tempString = "000000000" + num; + return tempString.substr(tempString.length - size); } function update(dt) { From e531468ac7dacbe5b99e5292141a8f0158f13e8a Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 22 Jun 2017 13:14:47 -0700 Subject: [PATCH 34/95] Fix for html url --- scripts/developer/tests/puck-attach.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/developer/tests/puck-attach.js b/scripts/developer/tests/puck-attach.js index 7fe13e6ba1..00d2ca5fa3 100644 --- a/scripts/developer/tests/puck-attach.js +++ b/scripts/developer/tests/puck-attach.js @@ -13,8 +13,7 @@ Script.include("/~/system/libraries/Xform.js"); (function() { // BEGIN LOCAL_SCOPE var TABLET_BUTTON_NAME = "PUCKTACH"; -// var HTML_URL = "https://s3.amazonaws.com/hifi-public/tony/html/puck-attach.html"; -var HTML_URL = "file:///C:/msys64/home/anthony/code/hifi/examples/html/puck-attach.html"; +var HTML_URL = "https://s3.amazonaws.com/hifi-public/tony/html/puck-attach.html"; var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var tabletButton = tablet.addButton({ From a053a1da4ba402cbcae380ba91c837d5a08a866c Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 22 Jun 2017 13:50:46 -0700 Subject: [PATCH 35/95] more HIFI_MEMORY_DEBUGGING fixes --- libraries/gl/CMakeLists.txt | 1 - tests/entities/CMakeLists.txt | 2 +- tools/ice-client/CMakeLists.txt | 1 + 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/gl/CMakeLists.txt b/libraries/gl/CMakeLists.txt index 0a0ca2fc5f..fd3197410b 100644 --- a/libraries/gl/CMakeLists.txt +++ b/libraries/gl/CMakeLists.txt @@ -1,6 +1,5 @@ set(TARGET_NAME gl) setup_hifi_library(OpenGL Qml Quick) -setup_memory_debugger() link_hifi_libraries(shared networking) target_opengl() diff --git a/tests/entities/CMakeLists.txt b/tests/entities/CMakeLists.txt index 448ea83956..080ae7cdd9 100644 --- a/tests/entities/CMakeLists.txt +++ b/tests/entities/CMakeLists.txt @@ -3,7 +3,7 @@ set(TARGET_NAME "entities-test") # This is not a testcase -- just set it up as a regular hifi project setup_hifi_project(Network Script) - +setup_memory_debugger() set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") # link in the shared libraries diff --git a/tools/ice-client/CMakeLists.txt b/tools/ice-client/CMakeLists.txt index a80145974c..ae42d79f7e 100644 --- a/tools/ice-client/CMakeLists.txt +++ b/tools/ice-client/CMakeLists.txt @@ -1,3 +1,4 @@ set(TARGET_NAME ice-client) setup_hifi_project(Core Widgets) +setup_memory_debugger() link_hifi_libraries(shared networking) From b48efc431789fe3ec55dfe8544c60930fc07f800 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 22 Jun 2017 22:48:32 +0100 Subject: [PATCH 36/95] fixed wide stance issue --- plugins/openvr/src/ViveControllerManager.cpp | 34 +++++++++++++++----- plugins/openvr/src/ViveControllerManager.h | 1 + 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index d914cdcfad..648373ccc2 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -959,15 +959,33 @@ void ViveControllerManager::InputDevice::calibrateFeet(glm::mat4& defaultToRefer controller::Pose& secondFootPose = secondFoot.second; if (determineLimbOrdering(firstFootPose, secondFootPose, headXAxis, headPosition)) { - _jointToPuckMap[controller::LEFT_FOOT] = firstFoot.first; - _pucksOffset[firstFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultLeftFoot, firstFootPose); - _jointToPuckMap[controller::RIGHT_FOOT] = secondFoot.first; - _pucksOffset[secondFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultRightFoot, secondFootPose); + calibrateFoot(defaultToReferenceMat, inputCalibration, firstFoot, true); + calibrateFoot(defaultToReferenceMat, inputCalibration, secondFoot, false); } else { - _jointToPuckMap[controller::LEFT_FOOT] = secondFoot.first; - _pucksOffset[secondFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultLeftFoot, secondFootPose); - _jointToPuckMap[controller::RIGHT_FOOT] = firstFoot.first; - _pucksOffset[firstFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultRightFoot, firstFootPose); + calibrateFoot(defaultToReferenceMat, inputCalibration, secondFoot, true); + calibrateFoot(defaultToReferenceMat, inputCalibration, firstFoot, false); + } +} + +void ViveControllerManager::InputDevice::calibrateFoot(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration, PuckPosePair& footPair, bool isLeftFoot){ + controller::Pose footPose = footPair.second; + glm::mat4 puckPoseAvatarMat = createMatFromQuatAndPos(footPose.getRotation(), footPose.getTranslation()); + glm::mat4 defaultFoot = isLeftFoot ? inputCalibration.defaultLeftFoot : inputCalibration.defaultRightFoot; + glm::mat4 footOffset = computeOffset(defaultToReferenceMat, defaultFoot, footPose); + + glm::quat rotationOffset = glmExtractRotation(footOffset); + glm::vec3 translationOffset = extractTranslation(footOffset); + glm::vec3 avatarXAxisInPuckFrame = glm::normalize(transformVectorFast(glm::inverse(puckPoseAvatarMat), glm::vec3(-1.0f, 0.0f, 0.0f))); + float distance = glm::dot(translationOffset, avatarXAxisInPuckFrame); + glm::vec3 finalTranslation = translationOffset - (distance * avatarXAxisInPuckFrame); + glm::mat4 finalOffset = createMatFromQuatAndPos(rotationOffset, finalTranslation); + + if (isLeftFoot) { + _jointToPuckMap[controller::LEFT_FOOT] = footPair.first; + _pucksOffset[footPair.first] = finalOffset; + } else { + _jointToPuckMap[controller::RIGHT_FOOT] = footPair.first; + _pucksOffset[footPair.first] = finalOffset; } } diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index 67a9ff46fd..0b0406bb60 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -98,6 +98,7 @@ private: void calibrateLeftHand(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration, PuckPosePair& handPair); void calibrateRightHand(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration, PuckPosePair& handPair); void calibrateFeet(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration); + void calibrateFoot(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration, PuckPosePair& footPair, bool isLeftFoot); void calibrateHips(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration); void calibrateChest(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration); void calibrateShoulders(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration, From bda52c33e615e6be9210d041e4196854d5fef7d0 Mon Sep 17 00:00:00 2001 From: samcake Date: Thu, 22 Jun 2017 15:35:00 -0700 Subject: [PATCH 37/95] Adding the function source when profiling, to explore perf issues --- libraries/script-engine/src/ScriptEngine.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 67b16df1ce..d14df662e2 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -1284,7 +1284,8 @@ void ScriptEngine::timerFired() { // call the associated JS function, if it exists if (timerData.function.isValid()) { - PROFILE_RANGE(script, __FUNCTION__); + //PROFILE_RANGE(script, __FUNCTION__); + PROFILE_RANGE(script, timerData.function.toString().toStdString().c_str()); auto preTimer = p_high_resolution_clock::now(); callWithEnvironment(timerData.definingEntityIdentifier, timerData.definingSandboxURL, timerData.function, timerData.function, QScriptValueList()); auto postTimer = p_high_resolution_clock::now(); @@ -1311,6 +1312,7 @@ QObject* ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int // make sure the timer stops when the script does connect(this, &ScriptEngine::scriptEnding, newTimer, &QTimer::stop); + CallbackData timerData = { function, currentEntityIdentifier, currentSandboxURL }; _timerFunctionMap.insert(newTimer, timerData); From c24e254257deee74bb7e0da65c2eeff367b2e1d9 Mon Sep 17 00:00:00 2001 From: samcake Date: Thu, 22 Jun 2017 18:10:20 -0700 Subject: [PATCH 38/95] Add support for profiling from scripts --- libraries/script-engine/src/ScriptEngine.cpp | 17 +++++++++++++++-- libraries/script-engine/src/ScriptEngine.h | 3 +++ .../system/controllers/handControllerGrab.js | 2 ++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index d14df662e2..fd61c41583 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -1284,8 +1284,8 @@ void ScriptEngine::timerFired() { // call the associated JS function, if it exists if (timerData.function.isValid()) { - //PROFILE_RANGE(script, __FUNCTION__); - PROFILE_RANGE(script, timerData.function.toString().toStdString().c_str()); + PROFILE_RANGE(script, __FUNCTION__); + // PROFILE_RANGE(script, timerData.function.toString().toStdString().c_str()); auto preTimer = p_high_resolution_clock::now(); callWithEnvironment(timerData.definingEntityIdentifier, timerData.definingSandboxURL, timerData.function, timerData.function, QScriptValueList()); auto postTimer = p_high_resolution_clock::now(); @@ -1396,6 +1396,19 @@ void ScriptEngine::print(const QString& message) { emit printedMessage(message, getFilename()); } + +void ScriptEngine::beginProfileRange(const QString& label) const { + if (trace_script().isDebugEnabled()) { + tracing::traceEvent(trace_script(), label.toStdString().c_str(), tracing::DurationBegin); + } +} + +void ScriptEngine::endProfileRange(const QString& label) const { + if (trace_script().isDebugEnabled()) { + tracing::traceEvent(trace_script(), label.toStdString().c_str(), tracing::DurationEnd); + } +} + // Script.require.resolve -- like resolvePath, but performs more validation and throws exceptions on invalid module identifiers (for consistency with Node.js) QString ScriptEngine::_requireResolve(const QString& moduleId, const QString& relativeTo) { if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 9da8603814..7c473a305b 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -43,6 +43,7 @@ #include "Vec3.h" #include "ConsoleScriptingInterface.h" #include "SettingHandle.h" +#include "Profile.h" class QScriptEngineDebugger; @@ -182,6 +183,8 @@ public: Q_INVOKABLE void print(const QString& message); Q_INVOKABLE QUrl resolvePath(const QString& path) const; Q_INVOKABLE QUrl resourcesPath() const; + Q_INVOKABLE void beginProfileRange(const QString& label) const; + Q_INVOKABLE void endProfileRange(const QString& label) const; // Entity Script Related methods Q_INVOKABLE bool isEntityScriptRunning(const EntityItemID& entityID) { diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 04921fe14d..8631a84fcf 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -1173,6 +1173,7 @@ function MyController(hand) { }; this.update = function(deltaTime, timestamp) { + Script.beginProfileRange("handControllerGrab:MyController:update") this.updateSmoothedTrigger(); this.maybeScaleMyAvatar(); @@ -1209,6 +1210,7 @@ function MyController(hand) { } else { print("WARNING: could not find state " + this.state + " in state machine"); } + Script.endProfileRange("handControllerGrab:MyController:update") }; this.callEntityMethodOnGrabbed = function(entityMethodName) { From 5af07dfb627e5803bf36e295020c7f9bd6120843 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 23 Jun 2017 08:16:48 -0700 Subject: [PATCH 39/95] more memory-debugger --- interface/CMakeLists.txt | 4 ++-- tests/gpu-test/CMakeLists.txt | 1 + tests/render-perf/CMakeLists.txt | 2 ++ tests/render-texture-load/CMakeLists.txt | 2 ++ tests/render-utils/CMakeLists.txt | 2 ++ tests/shaders/CMakeLists.txt | 2 ++ 6 files changed, 11 insertions(+), 2 deletions(-) diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index fe42b1432b..dcb1cacef9 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -4,6 +4,8 @@ project(${TARGET_NAME}) # set a default root dir for each of our optional externals if it was not passed set(OPTIONAL_EXTERNALS "LeapMotion") +setup_memory_debugger() + foreach(EXTERNAL ${OPTIONAL_EXTERNALS}) string(TOUPPER ${EXTERNAL} ${EXTERNAL}_UPPERCASE) if (NOT ${${EXTERNAL}_UPPERCASE}_ROOT_DIR) @@ -356,8 +358,6 @@ if (ANDROID) qt_create_apk() endif () -setup_memory_debugger() - add_dependency_external_projects(GifCreator) find_package(GifCreator REQUIRED) target_include_directories(${TARGET_NAME} PUBLIC ${GIFCREATOR_INCLUDE_DIRS}) diff --git a/tests/gpu-test/CMakeLists.txt b/tests/gpu-test/CMakeLists.txt index c37e36b53b..d73d7a111d 100644 --- a/tests/gpu-test/CMakeLists.txt +++ b/tests/gpu-test/CMakeLists.txt @@ -2,6 +2,7 @@ set(TARGET_NAME gpu-test) AUTOSCRIBE_SHADER_LIB(gpu model render-utils) # This is not a testcase -- just set it up as a regular hifi project setup_hifi_project(Quick Gui OpenGL Script Widgets) +setup_memory_debugger() set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") link_hifi_libraries(networking gl gpu gpu-gl procedural shared fbx model model-networking animation script-engine render render-utils octree image ktx) package_libraries_for_deployment() diff --git a/tests/render-perf/CMakeLists.txt b/tests/render-perf/CMakeLists.txt index a8e4ab286c..b6989a57b7 100644 --- a/tests/render-perf/CMakeLists.txt +++ b/tests/render-perf/CMakeLists.txt @@ -5,6 +5,8 @@ if (WIN32) SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /ignore:4049 /ignore:4217") endif() +setup_memory_debugger() + # This is not a testcase -- just set it up as a regular hifi project setup_hifi_project(Quick Gui OpenGL) set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") diff --git a/tests/render-texture-load/CMakeLists.txt b/tests/render-texture-load/CMakeLists.txt index 1f0c0a069a..30030914ab 100644 --- a/tests/render-texture-load/CMakeLists.txt +++ b/tests/render-texture-load/CMakeLists.txt @@ -5,6 +5,8 @@ if (WIN32) SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /ignore:4049 /ignore:4217") endif() +setup_memory_debugger() + # This is not a testcase -- just set it up as a regular hifi project setup_hifi_project(Quick Gui OpenGL) set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") diff --git a/tests/render-utils/CMakeLists.txt b/tests/render-utils/CMakeLists.txt index 5ec6a28b5c..4944ad2cbc 100644 --- a/tests/render-utils/CMakeLists.txt +++ b/tests/render-utils/CMakeLists.txt @@ -5,6 +5,8 @@ set(TARGET_NAME render-utils-test) setup_hifi_project(Quick Gui OpenGL) set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") +setup_memory_debugger() + # link in the shared libraries link_hifi_libraries(render-utils gl gpu gpu-gl shared) target_link_libraries(${TARGET_NAME} ${CMAKE_THREAD_LIBS_INIT}) diff --git a/tests/shaders/CMakeLists.txt b/tests/shaders/CMakeLists.txt index 5b38f473e8..bab1e0dcdc 100644 --- a/tests/shaders/CMakeLists.txt +++ b/tests/shaders/CMakeLists.txt @@ -5,6 +5,8 @@ set(TARGET_NAME shaders-test) setup_hifi_project(Quick Gui OpenGL) set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") +setup_memory_debugger() + # link in the shared libraries link_hifi_libraries(shared octree gl gpu gpu-gl model render fbx networking entities script-engine physics From 04160f2ca160881256d2de9d9ecf59877106f885 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Fri, 23 Jun 2017 09:19:41 -0700 Subject: [PATCH 40/95] switch to change ktx_cache directory (only) --- interface/src/main.cpp | 10 ++++++++-- libraries/shared/src/PathUtils.cpp | 15 ++++++++++++++- libraries/shared/src/PathUtils.h | 2 +- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 83cac6d9bb..0ab00b1e57 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -71,15 +71,17 @@ int main(int argc, const char* argv[]) { QCommandLineOption runServerOption("runServer", "Whether to run the server"); QCommandLineOption serverContentPathOption("serverContentPath", "Where to find server content", "serverContentPath"); QCommandLineOption allowMultipleInstancesOption("allowMultipleInstances", "Allow multiple instances to run"); + QCommandLineOption overrideAppLocalDataPathOption("cache", "Override cache directory", "value"); parser.addOption(urlOption); parser.addOption(noUpdaterOption); parser.addOption(checkMinSpecOption); parser.addOption(runServerOption); parser.addOption(serverContentPathOption); + parser.addOption(overrideAppLocalDataPathOption); parser.addOption(allowMultipleInstancesOption); parser.parse(arguments); - + const QString& applicationName = getInterfaceSharedMemoryName(); bool instanceMightBeRunning = true; #ifdef Q_OS_WIN @@ -96,6 +98,10 @@ int main(int argc, const char* argv[]) { if (allowMultipleInstances) { instanceMightBeRunning = false; } + if (parser.isSet(overrideAppLocalDataPathOption)) { + QString appLocalDataPath = parser.value(overrideAppLocalDataPathOption); + PathUtils::getAppLocalDataPath(appLocalDataPath); + } if (instanceMightBeRunning) { // Try to connect and send message to existing interface instance @@ -179,7 +185,7 @@ int main(int argc, const char* argv[]) { QString openvrDllPath = appPath + "/plugins/openvr.dll"; HMODULE openvrDll; CHECKMINSPECPROC checkMinSpecPtr; - if ((openvrDll = LoadLibrary(openvrDllPath.toLocal8Bit().data())) && + if ((openvrDll = LoadLibrary(openvrDllPath.toLocal8Bit().data())) && (checkMinSpecPtr = (CHECKMINSPECPROC)GetProcAddress(openvrDll, "CheckMinSpec"))) { if (!checkMinSpecPtr()) { return -1; diff --git a/libraries/shared/src/PathUtils.cpp b/libraries/shared/src/PathUtils.cpp index 9bf9d7bdcf..63a94c1696 100644 --- a/libraries/shared/src/PathUtils.cpp +++ b/libraries/shared/src/PathUtils.cpp @@ -34,7 +34,20 @@ QString PathUtils::getAppDataPath() { return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/"; } -QString PathUtils::getAppLocalDataPath() { +QString PathUtils::getAppLocalDataPath(const QString& overridePath) { + static QString overriddenPath = ""; + + // set the overridden path if one was passed in + if (!overridePath.isEmpty()) { + overriddenPath = overridePath; + } + + // return overridden path if there is one + if (!overriddenPath.isEmpty()) { + return overriddenPath; + } + + // otherwise return the standard path return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/"; } diff --git a/libraries/shared/src/PathUtils.h b/libraries/shared/src/PathUtils.h index 14eb81dd9a..1160fd8106 100644 --- a/libraries/shared/src/PathUtils.h +++ b/libraries/shared/src/PathUtils.h @@ -28,7 +28,7 @@ public: static const QString& resourcesPath(); static QString getAppDataPath(); - static QString getAppLocalDataPath(); + static QString getAppLocalDataPath(const QString& overrideDataPath = ""); static QString getAppDataFilePath(const QString& filename); static QString getAppLocalDataFilePath(const QString& filename); From d6eb77e815147b818fe3129fb6ea5cb2f827d98f Mon Sep 17 00:00:00 2001 From: humbletim Date: Fri, 23 Jun 2017 13:09:22 -0400 Subject: [PATCH 41/95] changes per feedback to quiet print debugs throughout --- .../app-doppleganger-attachments.js | 14 ++++++-- .../doppleganger-attachments.js | 13 ++++--- .../doppleganger-attachments/doppleganger.js | 34 +++++++++++-------- 3 files changed, 39 insertions(+), 22 deletions(-) diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js b/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js index 4142ff332e..4617cf47b6 100644 --- a/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js +++ b/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js @@ -12,6 +12,8 @@ var require = Script.require; +var WANT_DEBUG = false; + var DopplegangerClass = require('./doppleganger.js'), DopplegangerAttachments = require('./doppleganger-attachments.js'), modelHelper = require('./model-helper.js').modelHelper; @@ -57,7 +59,7 @@ var doppleganger = new DopplegangerClass({ var currentHash = dopplegangerAttachments.getAttachmentsHash(); if (currentHash !== lastHash) { lastHash = currentHash; - print('app-doppleganger | detect attachment change'); + debugPrint('app-doppleganger | detect attachment change'); dopplegangerAttachments.refreshAttachments(); } } @@ -86,7 +88,7 @@ button.clicked.connect(doppleganger, 'toggle'); doppleganger.activeChanged.connect(function(active, reason) { if (button) { button.editProperties({ isActive: active }); - print('doppleganger.activeChanged', active, reason); + debugPrint('doppleganger.activeChanged', active, reason); } }); @@ -101,9 +103,15 @@ doppleganger.modelLoaded.connect(function(error, result) { // add debug indicators, but only if the user has configured the settings value if (Settings.getValue('debug.doppleganger', false)) { + WANT_DEBUG = true; DopplegangerClass.addDebugControls(doppleganger); } +function debugPrint() { + if (WANT_DEBUG) { + print('app-doppleganger | ' + [].slice.call(arguments).join(' ')); + } +} // ---------------------------------------------------------------------------- UserActivityLogger.logAction('doppleganger_app_load'); @@ -115,7 +123,7 @@ doppleganger.activeChanged.connect(function(active, reason) { // user intentionally toggled the doppleganger UserActivityLogger.logAction('doppleganger_disable'); } else { - print('doppleganger stopped:', reason); + debugPrint('doppleganger stopped:', reason); UserActivityLogger.logAction('doppleganger_autodisable', { reason: reason }); } } diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-attachments.js b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-attachments.js index 1d3847cad2..a3b3873c2d 100644 --- a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-attachments.js +++ b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-attachments.js @@ -5,6 +5,7 @@ module.exports = DopplegangerAttachments; DopplegangerAttachments.version = '0.0.0'; +DopplegangerAttachments.WANT_DEBUG = false; var _modelHelper = require('./model-helper.js'), modelHelper = _modelHelper.modelHelper, @@ -15,6 +16,10 @@ function log() { print('doppleganger-attachments | ' + [].slice.call(arguments).join(' ')); } +function debugPrint() { + DopplegangerAttachments.WANT_DEBUG && log.apply(this, arguments); +} + function DopplegangerAttachments(doppleganger, options) { utils.assign(this, { _options: options, @@ -24,7 +29,7 @@ function DopplegangerAttachments(doppleganger, options) { attachmentsUpdated: utils.signal(function attachmentsUpdated(currentAttachments, previousAttachments){}), }); this._initialize(); - log('DopplegangerAttachments...', JSON.stringify(options)); + debugPrint('DopplegangerAttachments...', JSON.stringify(options)); } DopplegangerAttachments.prototype = { // "hash" the current attachments (so that changes can be detected) @@ -87,7 +92,7 @@ DopplegangerAttachments.prototype = { jointNames = this.doppleganger.jointNames, properties = modelHelper.getProperties(this.doppleganger.objectID); - log('DopplegangerAttachments..._createAttachmentObjects', JSON.stringify({ + debugPrint('DopplegangerAttachments..._createAttachmentObjects', JSON.stringify({ type: properties.type, attachments: attachments.length, parentID: parentID, @@ -96,7 +101,7 @@ DopplegangerAttachments.prototype = { return attachments.map(utils.bind(this, function(attachment, i) { var type = modelHelper.type(attachment.properties && attachment.properties.objectID); if (type === 'overlay') { - log('skipping already-provisioned attachment object', type, attachment.properties && attachment.properties.name); + debugPrint('skipping already-provisioned attachment object', type, attachment.properties && attachment.properties.name); return attachment; } var jointIndex = attachment.$jointIndex, // jointNames.indexOf(attachment.jointName), @@ -133,7 +138,7 @@ DopplegangerAttachments.prototype = { modelHelper.deleteObject(objectID); return objectID = null; } - log('attachment model ('+modelHelper.type(result.objectID)+') is ready; # joints ==', + debugPrint('attachment model ('+modelHelper.type(result.objectID)+') is ready; # joints ==', result.jointNames && result.jointNames.length, result.naturalDimensions, result.objectID); var properties = modelHelper.getProperties(result.objectID), naturalDimensions = attachment.properties.naturalDimensions = properties.naturalDimensions; diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js index 375105e722..bebd36df45 100644 --- a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js +++ b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js @@ -28,15 +28,15 @@ var _modelHelper = require('./model-helper.js'), modelHelper = _modelHelper.modelHelper, ModelReadyWatcher = _modelHelper.ModelReadyWatcher; +// @property {bool} - toggle verbose debug logging on/off +Doppleganger.WANT_DEBUG = false; + // @property {bool} - when set true, Script.update will be used instead of setInterval for syncing joint data Doppleganger.USE_SCRIPT_UPDATE = false; // @property {int} - the frame rate to target when using setInterval for joint updates Doppleganger.TARGET_FPS = 60; -// @property {int} - the maximum time in seconds to wait for the model overlay to finish loading -Doppleganger.MAX_WAIT_SECS = 10; - // @class Doppleganger - Creates a new instance of a Doppleganger. // @param {Avatar} [options.avatar=MyAvatar] - Avatar used to retrieve position and joint data. // @param {bool} [options.mirrored=true] - Apply "symmetric mirroring" of Left/Right joints. @@ -64,10 +64,10 @@ Doppleganger.prototype = { // @public @method - toggles doppleganger on/off toggle: function() { if (this.active) { - log('toggling off'); + debugPrint('toggling off'); this.stop(); } else { - log('toggling on'); + debugPrint('toggling on'); this.start(); } return this.active; @@ -92,7 +92,7 @@ Doppleganger.prototype = { // note: this mismatch can happen when the avatar's model is actively changing if (size !== translations.length || (this.jointStateCount && size !== this.jointStateCount)) { - log('mismatched joint counts (avatar model likely changed)', size, translations.length, this.jointStateCount); + debugPrint('mismatched joint counts (avatar model likely changed)', size, translations.length, this.jointStateCount); this.stop('avatar_changed_joints'); return; } @@ -168,7 +168,7 @@ Doppleganger.prototype = { Script.scriptEnding.connect(this, function() { modelHelper.deleteObject(this.objectID); }); - log('doppleganger created; objectID =', this.objectID); + debugPrint('doppleganger created; objectID =', this.objectID); // trigger clean up (and stop updates) if the object gets deleted this.onObjectDeleted = function(uuid) { @@ -193,14 +193,14 @@ Doppleganger.prototype = { if (error) { return this.stop(error); } - log('model ('+modelHelper.type(this.objectID)+')' + ' is ready; # joints == ' + result.jointNames.length); + debugPrint('model ('+modelHelper.type(this.objectID)+')' + ' is ready; # joints == ' + result.jointNames.length); var naturalDimensions = modelHelper.getProperties(this.objectID, ['naturalDimensions']).naturalDimensions; - log('naturalDimensions:', JSON.stringify(naturalDimensions)); + debugPrint('naturalDimensions:', JSON.stringify(naturalDimensions)); var props = { visible: true }; if (naturalDimensions) { props.dimensions = Vec3.multiply(this.scale, naturalDimensions); } - log('scaledDimensions:', this.scale, JSON.stringify(props.dimensions)); + debugPrint('scaledDimensions:', this.scale, JSON.stringify(props.dimensions)); modelHelper.editObject(this.objectID, props); if (!options.position) { this.syncVerticalPosition(); @@ -250,7 +250,7 @@ Doppleganger.prototype = { if (this.active) { this.activeChanged(this.active = false, reason); } else if (reason) { - log('already stopped so not triggering another activeChanged; latest reason was:', reason); + debugPrint('already stopped so not triggering another activeChanged; latest reason was:', reason); } }, // @public @method - Reposition the doppleganger so it sees "eye to eye" with the Avatar. @@ -263,7 +263,7 @@ Doppleganger.prototype = { doppleJointIndex = modelHelper.getJointIndex(this.objectID, byJointName),// names.indexOf(byJointName), doppleJointPosition = positions[doppleJointIndex]; - print('........... doppleJointPosition', JSON.stringify({ + debugPrint('........... doppleJointPosition', JSON.stringify({ byJointName: byJointName, dopplePosition: dopplePosition, doppleJointIndex: doppleJointIndex, @@ -276,7 +276,7 @@ Doppleganger.prototype = { avatarJointPosition = this.avatar.getJointPosition(avatarJointIndex); var offset = (avatarJointPosition.y - doppleJointPosition.y); - log('adjusting for offset', offset); + debugPrint('adjusting for offset', offset); if (properties.type === 'model') { dopplePosition.y = avatarPosition.y + offset; } else { @@ -290,11 +290,11 @@ Doppleganger.prototype = { // @private @method - creates the update thread to synchronize joint data _createUpdateThread: function() { if (Doppleganger.USE_SCRIPT_UPDATE) { - log('creating Script.update thread'); + debugPrint('creating Script.update thread'); this.onUpdate = this.update; Script.update.connect(this, 'onUpdate'); } else { - log('creating Script.setInterval thread @ ~', Doppleganger.TARGET_FPS +'fps'); + debugPrint('creating Script.setInterval thread @ ~', Doppleganger.TARGET_FPS +'fps'); var timeout = 1000 / Doppleganger.TARGET_FPS; this._interval = Script.setInterval(bind(this, 'update'), timeout); } @@ -369,6 +369,10 @@ function log() { print('doppleganger | ' + [].slice.call(arguments).join(' ')); } +function debugPrint() { + Doppleganger.WANT_DEBUG && log.apply(this, arguments); +} + // -- ADVANCED DEBUGGING -- // @function - Add debug joint indicators / extra debugging info. // @param {Doppleganger} - existing Doppleganger instance to add controls to From 416852c0cd216a772558f4079efaba9dfd8b2142 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 23 Jun 2017 10:19:16 -0700 Subject: [PATCH 42/95] Fix warnings about creating QObjects with parents in different thread --- interface/src/Application.cpp | 6 +++++- libraries/gpu-gl/src/gpu/gl/GLBackend.cpp | 5 +++++ libraries/gpu-gl/src/gpu/gl/GLTexture.cpp | 5 ----- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index a7bed9fc9c..1ce21f9ec7 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -713,9 +713,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo updateHeartbeat(); // setup a timer for domain-server check ins - QTimer* domainCheckInTimer = new QTimer(nodeList.data()); + QTimer* domainCheckInTimer = new QTimer(this); connect(domainCheckInTimer, &QTimer::timeout, nodeList.data(), &NodeList::sendDomainServerCheckIn); domainCheckInTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS); + connect(this, &QCoreApplication::aboutToQuit, [domainCheckInTimer] { + domainCheckInTimer->stop(); + domainCheckInTimer->deleteLater(); + }); auto audioIO = DependencyManager::get(); diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp index 791130ef6e..3441407f62 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp @@ -178,6 +178,11 @@ void GLBackend::init() { int swapInterval = wglGetSwapIntervalEXT(); qCDebug(gpugllogging, "V-Sync is %s\n", (swapInterval > 0 ? "ON" : "OFF")); }*/ +#endif +#if THREADED_TEXTURE_BUFFERING + // This has to happen on the main thread in order to give the thread + // pool a reasonable parent object + GLVariableAllocationSupport::TransferJob::startBufferingThread(); #endif }); } diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp index 4161242a24..7758ddaf49 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp @@ -461,11 +461,6 @@ void GLVariableAllocationSupport::updateMemoryPressure() { if (newState != _memoryPressureState) { _memoryPressureState = newState; -#if THREADED_TEXTURE_BUFFERING - if (MemoryPressureState::Transfer == _memoryPressureState) { - TransferJob::startBufferingThread(); - } -#endif // Clear the existing queue _transferQueue = WorkQueue(); _promoteQueue = WorkQueue(); From 54af6af651117bbc0c4703abda38b14ff56b505a Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 23 Jun 2017 10:38:21 -0700 Subject: [PATCH 43/95] Fix for pole vector stability and knee pole vector computation --- .../animation/src/AnimInverseKinematics.cpp | 45 +++++++++----- libraries/animation/src/Rig.cpp | 62 ++++++++++++------- libraries/animation/src/Rig.h | 2 +- 3 files changed, 71 insertions(+), 38 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 3ea8937eec..e2e0d72d66 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -530,22 +530,33 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const if (dLen > EPSILON) { glm::vec3 dUnit = d / dLen; glm::vec3 e = midPose.xformVector(target.getPoleReferenceVector()); - glm::vec3 p = target.getPoleVector(); glm::vec3 eProj = e - glm::dot(e, dUnit) * dUnit; - glm::vec3 pProj = p - glm::dot(p, dUnit) * dUnit; float eProjLen = glm::length(eProj); - float pProjLen = glm::length(pProj); - if (eProjLen > EPSILON && pProjLen > EPSILON) { + const float MIN_EPROJ_LEN = 0.5f; + if (eProjLen < MIN_EPROJ_LEN) { + glm::vec3 midPoint = topPose.trans() + d * 0.5f; + e = midPose.trans() - midPoint; + eProj = e - glm::dot(e, dUnit) * dUnit; + eProjLen = glm::length(eProj); + } + + glm::vec3 p = target.getPoleVector(); + glm::vec3 pProj = p - glm::dot(p, dUnit) * dUnit; + float pProjLen = glm::length(pProj); + + if (eProjLen > EPSILON && pProjLen > EPSILON) { // as pProjLen become orthognal to d, reduce the amount of rotation. float magnitude = easeOutExpo(pProjLen); - float dot = glm::clamp(glm::dot(eProj / eProjLen, pProj / pProjLen), 0.0f, 1.0f); float theta = acosf(dot); glm::vec3 cross = glm::cross(eProj, pProj); - float crossLen = glm::length(cross); - if (crossLen > EPSILON) { - glm::vec3 axis = cross / crossLen; + const float MIN_ADJUSTMENT_ANGLE = 0.001745f; // 0.1 degree + if (theta > MIN_ADJUSTMENT_ANGLE) { + glm::vec3 axis = dUnit; + if (glm::dot(cross, dUnit) < 0) { + axis = -dUnit; + } poleRot = glm::angleAxis(magnitude * theta, axis); } } @@ -562,8 +573,17 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const glm::vec3 dUnit = d / dLen; glm::vec3 e = midPose.xformVector(target.getPoleReferenceVector()); - glm::vec3 p = target.getPoleVector(); glm::vec3 eProj = e - glm::dot(e, dUnit) * dUnit; + float eProjLen = glm::length(eProj); + const float MIN_EPROJ_LEN = 0.5f; + if (eProjLen < MIN_EPROJ_LEN) { + glm::vec3 midPoint = topPose.trans() + d * 0.5f; + e = midPose.trans() - midPoint; + eProj = e - glm::dot(e, dUnit) * dUnit; + eProjLen = glm::length(eProj); + } + + glm::vec3 p = target.getPoleVector(); glm::vec3 pProj = p - glm::dot(p, dUnit) * dUnit; const float PROJ_VECTOR_LEN = 10.0f; @@ -573,11 +593,8 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const geomToWorldPose.xformPoint(topPose.trans()), YELLOW); DebugDraw::getInstance().drawRay(geomToWorldPose.xformPoint(midPoint), - geomToWorldPose.xformPoint(midPoint + PROJ_VECTOR_LEN * glm::normalize(eProj)), + geomToWorldPose.xformPoint(midPoint + PROJ_VECTOR_LEN * glm::normalize(e)), RED); - DebugDraw::getInstance().drawRay(geomToWorldPose.xformPoint(midPoint), - geomToWorldPose.xformPoint(midPoint + PROJ_VECTOR_LEN * glm::normalize(pProj)), - GREEN); DebugDraw::getInstance().drawRay(geomToWorldPose.xformPoint(midPoint), geomToWorldPose.xformPoint(midPoint + POLE_VECTOR_LEN * glm::normalize(p)), BLUE); @@ -1060,7 +1077,7 @@ void AnimInverseKinematics::initConstraints() { // y | // | | // | O---O---O RightUpLeg - // z | | Hips2 | + // z | | Hips | // \ | | | // \| | | // x -----+ O O RightLeg diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 51480e8ef1..8466b79d1d 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1265,14 +1265,14 @@ void Rig::updateFeet(bool leftFootEnabled, bool rightFootEnabled, const AnimPose int hipsIndex = indexOfJoint("Hips"); if (leftFootEnabled) { - glm::vec3 footPosition = leftFootPose.trans(); - glm::quat footRotation = leftFootPose.rot(); - _animVars.set("leftFootPosition", footPosition); - _animVars.set("leftFootRotation", footRotation); + _animVars.set("leftFootPosition", leftFootPose.trans()); + _animVars.set("leftFootRotation", leftFootPose.rot()); _animVars.set("leftFootType", (int)IKTarget::Type::RotationAndPosition); int footJointIndex = _animSkeleton->nameToJointIndex("LeftFoot"); - glm::vec3 poleVector = calculateKneePoleVector(footJointIndex, footRotation, hipsIndex); + int kneeJointIndex = _animSkeleton->nameToJointIndex("LeftLeg"); + int upLegJointIndex = _animSkeleton->nameToJointIndex("LeftUpLeg"); + glm::vec3 poleVector = calculateKneePoleVector(footJointIndex, kneeJointIndex, upLegJointIndex, hipsIndex, leftFootPose); // smooth toward desired pole vector from previous pole vector... to reduce jitter if (!_prevLeftFootPoleVectorValid) { @@ -1295,14 +1295,14 @@ void Rig::updateFeet(bool leftFootEnabled, bool rightFootEnabled, const AnimPose } if (rightFootEnabled) { - glm::vec3 footPosition = rightFootPose.trans(); - glm::quat footRotation = rightFootPose.rot(); - _animVars.set("rightFootPosition", footPosition); - _animVars.set("rightFootRotation", footRotation); + _animVars.set("rightFootPosition", rightFootPose.trans()); + _animVars.set("rightFootRotation", rightFootPose.rot()); _animVars.set("rightFootType", (int)IKTarget::Type::RotationAndPosition); int footJointIndex = _animSkeleton->nameToJointIndex("RightFoot"); - glm::vec3 poleVector = calculateKneePoleVector(footJointIndex, footRotation, hipsIndex); + int kneeJointIndex = _animSkeleton->nameToJointIndex("RightLeg"); + int upLegJointIndex = _animSkeleton->nameToJointIndex("RightUpLeg"); + glm::vec3 poleVector = calculateKneePoleVector(footJointIndex, kneeJointIndex, upLegJointIndex, hipsIndex, rightFootPose); // smooth toward desired pole vector from previous pole vector... to reduce jitter if (!_prevRightFootPoleVectorValid) { @@ -1375,20 +1375,23 @@ glm::vec3 Rig::calculateElbowPoleVector(int handIndex, int elbowIndex, int armIn AnimPose handPose = _externalPoseSet._absolutePoses[handIndex]; AnimPose elbowPose = _externalPoseSet._absolutePoses[elbowIndex]; AnimPose armPose = _externalPoseSet._absolutePoses[armIndex]; + + // ray from hand to arm. glm::vec3 d = glm::normalize(handPose.trans() - armPose.trans()); float sign = isLeft ? 1.0f : -1.0f; + // form a plane normal to the hips x-axis. glm::vec3 n = hipsPose.rot() * Vectors::UNIT_X; glm::vec3 y = hipsPose.rot() * Vectors::UNIT_Y; - // project d onto n. + + // project d onto this plane glm::vec3 dProj = d - glm::dot(d, n) * n; + // give dProj a bit of offset away from the body. float avatarScale = extractUniformScale(_modelOffset); const float LATERAL_OFFSET = 0.333f * avatarScale; const float VERTICAL_OFFSET = -0.333f * avatarScale; - - // give dProj a bit of offset away from the body. glm::vec3 dProjWithOffset = dProj + sign * LATERAL_OFFSET * n + y * VERTICAL_OFFSET; // rotate dProj by 90 degrees to get the poleVector. @@ -1402,19 +1405,32 @@ glm::vec3 Rig::calculateElbowPoleVector(int handIndex, int elbowIndex, int armIn return glm::normalize(poleAdjust * poleVector); } -glm::vec3 Rig::calculateKneePoleVector(int footJointIndex, const glm::quat& footTargetOrientation, int hipsIndex) const { +glm::vec3 Rig::calculateKneePoleVector(int footJointIndex, int kneeIndex, int upLegIndex, int hipsIndex, const AnimPose& targetFootPose) const { - AnimPose defaultPose = _animSkeleton->getAbsoluteDefaultPose(footJointIndex); - glm::vec3 localForward = glm::inverse(defaultPose.rot()) * Vectors::UNIT_Z; - glm::vec3 footForward = footTargetOrientation * localForward; + AnimPose hipsPose = _externalPoseSet._absolutePoses[hipsIndex]; + AnimPose footPose = targetFootPose; + AnimPose kneePose = _externalPoseSet._absolutePoses[kneeIndex]; + AnimPose upLegPose = _externalPoseSet._absolutePoses[upLegIndex]; - // compute the forward direction of the hips. - glm::quat hipsRotation = _externalPoseSet._absolutePoses[hipsIndex].rot(); - glm::vec3 hipsForward = hipsRotation * Vectors::UNIT_Z; + // ray from foot to upLeg + glm::vec3 d = glm::normalize(footPose.trans() - upLegPose.trans()); - // blend between the hips and the foot. - const float FOOT_TO_HIPS_BLEND_FACTOR = 0.5f; - return glm::normalize(lerp(footForward, hipsForward, FOOT_TO_HIPS_BLEND_FACTOR)); + // form a plane normal to the hips x-axis + glm::vec3 n = hipsPose.rot() * Vectors::UNIT_X; + glm::vec3 y = hipsPose.rot() * Vectors::UNIT_Y; + + // project d onto this plane + glm::vec3 dProj = d - glm::dot(d, n) * n; + + // rotate dProj by 90 degrees to get the poleVector. + glm::vec3 poleVector = glm::angleAxis(-PI / 2.0f, n) * dProj; + + // blend the foot oreintation into the pole vector + glm::quat kneeToFootDelta = footPose.rot() * glm::inverse(kneePose.rot()); + const float WRIST_POLE_ADJUST_FACTOR = 0.5f; + glm::quat poleAdjust = quatLerp(Quaternions::IDENTITY, kneeToFootDelta, WRIST_POLE_ADJUST_FACTOR); + + return glm::normalize(poleAdjust * poleVector); } void Rig::updateFromControllerParameters(const ControllerParameters& params, float dt) { diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 413b598079..c17a7b9c8f 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -239,7 +239,7 @@ protected: void calcAnimAlpha(float speed, const std::vector& referenceSpeeds, float* alphaOut) const; glm::vec3 calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex, int hipsIndex, bool isLeft) const; - glm::vec3 calculateKneePoleVector(int footJointIndex, const glm::quat& footTargetOrientation, int hipsIndex) const; + glm::vec3 calculateKneePoleVector(int footJointIndex, int kneeJoint, int upLegIndex, int hipsIndex, const AnimPose& targetFootPose) const; AnimPose _modelOffset; // model to rig space AnimPose _geometryOffset; // geometry to model space (includes unit offset & fst offsets) From 8f89ef09319045f735e6e327c987f196f7131163 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 23 Jun 2017 10:52:23 -0700 Subject: [PATCH 44/95] Fix for eye direction when no look at targets are present. --- 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 e12c73d0bc..28094dddb1 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4165,7 +4165,7 @@ void Application::updateMyAvatarLookAtPosition() { lookAtSpot = transformPoint(worldHeadMat, glm::vec3(0.0f, 0.0f, TREE_SCALE)); } else { lookAtSpot = myAvatar->getHead()->getEyePosition() + - (myAvatar->getHead()->getFinalOrientationInWorldFrame() * glm::vec3(0.0f, 0.0f, TREE_SCALE)); + (myAvatar->getHead()->getFinalOrientationInWorldFrame() * glm::vec3(0.0f, 0.0f, -TREE_SCALE)); } } From bda50af78098ea0fd549a201bcd0622906ecd23e Mon Sep 17 00:00:00 2001 From: David Kelly Date: Fri, 23 Jun 2017 11:06:48 -0700 Subject: [PATCH 45/95] this uses qt's test location for AppData/Local, but leaves Roaming alone --- interface/src/main.cpp | 12 +++++++++--- libraries/shared/src/PathUtils.cpp | 15 +-------------- libraries/shared/src/PathUtils.h | 2 +- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 0ab00b1e57..81bf0244d9 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -71,7 +71,7 @@ int main(int argc, const char* argv[]) { QCommandLineOption runServerOption("runServer", "Whether to run the server"); QCommandLineOption serverContentPathOption("serverContentPath", "Where to find server content", "serverContentPath"); QCommandLineOption allowMultipleInstancesOption("allowMultipleInstances", "Allow multiple instances to run"); - QCommandLineOption overrideAppLocalDataPathOption("cache", "Override cache directory", "value"); + QCommandLineOption overrideAppLocalDataPathOption("cache", "set test cache directory"); parser.addOption(urlOption); parser.addOption(noUpdaterOption); parser.addOption(checkMinSpecOption); @@ -99,8 +99,14 @@ int main(int argc, const char* argv[]) { instanceMightBeRunning = false; } if (parser.isSet(overrideAppLocalDataPathOption)) { - QString appLocalDataPath = parser.value(overrideAppLocalDataPathOption); - PathUtils::getAppLocalDataPath(appLocalDataPath); + // get standard path + auto standardAppDataLocation = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); + // set to test so all future paths are the test paths + QStandardPaths::setTestModeEnabled(true); + // now, we need to link everything in AppDataLocation to the test AppDataLocation. This + // leaves the test location for AppLocalDataLocation alone, but allows all the stuff in + // AppDataLocation to be usable + QFile::link(standardAppDataLocation, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)); } if (instanceMightBeRunning) { diff --git a/libraries/shared/src/PathUtils.cpp b/libraries/shared/src/PathUtils.cpp index 63a94c1696..9bf9d7bdcf 100644 --- a/libraries/shared/src/PathUtils.cpp +++ b/libraries/shared/src/PathUtils.cpp @@ -34,20 +34,7 @@ QString PathUtils::getAppDataPath() { return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/"; } -QString PathUtils::getAppLocalDataPath(const QString& overridePath) { - static QString overriddenPath = ""; - - // set the overridden path if one was passed in - if (!overridePath.isEmpty()) { - overriddenPath = overridePath; - } - - // return overridden path if there is one - if (!overriddenPath.isEmpty()) { - return overriddenPath; - } - - // otherwise return the standard path +QString PathUtils::getAppLocalDataPath() { return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/"; } diff --git a/libraries/shared/src/PathUtils.h b/libraries/shared/src/PathUtils.h index 1160fd8106..14eb81dd9a 100644 --- a/libraries/shared/src/PathUtils.h +++ b/libraries/shared/src/PathUtils.h @@ -28,7 +28,7 @@ public: static const QString& resourcesPath(); static QString getAppDataPath(); - static QString getAppLocalDataPath(const QString& overrideDataPath = ""); + static QString getAppLocalDataPath(); static QString getAppDataFilePath(const QString& filename); static QString getAppLocalDataFilePath(const QString& filename); From fe7d4b1d232a291c3fe8ff403d8adab9c9c5e8cb Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 23 Jun 2017 11:54:35 -0700 Subject: [PATCH 46/95] Remove dependency between codec plugins and UI --- assignment-client/CMakeLists.txt | 2 +- assignment-client/src/audio/AudioMixer.cpp | 13 ------- interface/src/Application.cpp | 10 ++++++ libraries/plugins/src/plugins/Forward.h | 5 +++ .../plugins/src/plugins/PluginManager.cpp | 35 +++++++++++++------ libraries/plugins/src/plugins/PluginManager.h | 12 +++++++ plugins/hifiCodec/CMakeLists.txt | 2 +- plugins/hifiNeuron/CMakeLists.txt | 2 +- plugins/hifiSdl2/CMakeLists.txt | 2 +- plugins/hifiSixense/CMakeLists.txt | 2 +- plugins/pcmCodec/CMakeLists.txt | 2 +- 11 files changed, 58 insertions(+), 29 deletions(-) diff --git a/assignment-client/CMakeLists.txt b/assignment-client/CMakeLists.txt index 54afabfd21..c9af474949 100644 --- a/assignment-client/CMakeLists.txt +++ b/assignment-client/CMakeLists.txt @@ -11,7 +11,7 @@ endif () link_hifi_libraries( audio avatars octree gpu model fbx entities networking animation recording shared script-engine embedded-webserver - controllers physics plugins + physics plugins ) if (WIN32) diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 5e0cd740e6..93b9b10eb7 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -166,19 +166,6 @@ void AudioMixer::handleMuteEnvironmentPacket(QSharedPointer mes } } -DisplayPluginList getDisplayPlugins() { - DisplayPluginList result; - return result; -} - -InputPluginList getInputPlugins() { - InputPluginList result; - return result; -} - -// must be here to satisfy a reference in PluginManager::saveSettings() -void saveInputPluginSettings(const InputPluginList& plugins) {} - const std::pair AudioMixer::negotiateCodec(std::vector codecs) { QString selectedCodecName; CodecPluginPointer selectedCodec; diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index a7bed9fc9c..3ca8c29288 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -441,6 +441,11 @@ static const QString STATE_ADVANCED_MOVEMENT_CONTROLS = "AdvancedMovement"; static const QString STATE_GROUNDED = "Grounded"; static const QString STATE_NAV_FOCUSED = "NavigationFocused"; +// Statically provided display and input plugins +extern DisplayPluginList getDisplayPlugins(); +extern InputPluginList getInputPlugins(); +extern void saveInputPluginSettings(const InputPluginList& plugins); + bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { const char** constArgv = const_cast(argv); @@ -480,6 +485,11 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { Setting::init(); + // Tell the plugin manager about our statically linked plugins + PluginManager::setInputPluginProvider([] { return getInputPlugins(); }); + PluginManager::setDisplayPluginProvider([] { return getDisplayPlugins(); }); + PluginManager::setInputPluginSettingsPersister([](const InputPluginList& plugins) { saveInputPluginSettings(plugins); }); + if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) { steamClient->init(); } diff --git a/libraries/plugins/src/plugins/Forward.h b/libraries/plugins/src/plugins/Forward.h index b95bf4c2e6..90746d648e 100644 --- a/libraries/plugins/src/plugins/Forward.h +++ b/libraries/plugins/src/plugins/Forward.h @@ -9,6 +9,7 @@ #include #include +#include enum class PluginType { DISPLAY_PLUGIN, @@ -26,8 +27,12 @@ class PluginManager; using DisplayPluginPointer = std::shared_ptr; using DisplayPluginList = std::vector; +using DisplayPluginProvider = std::function; using InputPluginPointer = std::shared_ptr; using InputPluginList = std::vector; +using InputPluginProvider = std::function; using CodecPluginPointer = std::shared_ptr; using CodecPluginList = std::vector; +using CodecPluginProvider = std::function; using SteamClientPluginPointer = std::shared_ptr; +using InputPluginSettingsPersister = std::function; diff --git a/libraries/plugins/src/plugins/PluginManager.cpp b/libraries/plugins/src/plugins/PluginManager.cpp index 8471dbc7e8..e90d3e3a0f 100644 --- a/libraries/plugins/src/plugins/PluginManager.cpp +++ b/libraries/plugins/src/plugins/PluginManager.cpp @@ -23,6 +23,26 @@ #include "InputPlugin.h" #include "PluginLogging.h" +DisplayPluginProvider PluginManager::_displayPluginProvider = []()->DisplayPluginList { return {}; }; +InputPluginProvider PluginManager::_inputPluginProvider = []()->InputPluginList { return {}; }; +CodecPluginProvider PluginManager::_codecPluginProvider = []()->CodecPluginList { return {}; }; +InputPluginSettingsPersister PluginManager::_inputSettingsPersister = [](const InputPluginList& list) {}; + +void PluginManager::setDisplayPluginProvider(const DisplayPluginProvider& provider) { + _displayPluginProvider = provider; +} + +void PluginManager::setInputPluginProvider(const InputPluginProvider& provider) { + _inputPluginProvider = provider; +} + +void PluginManager::setCodecPluginProvider(const CodecPluginProvider& provider) { + _codecPluginProvider = provider; +} + +void PluginManager::setInputPluginSettingsPersister(const InputPluginSettingsPersister& persister) { + _inputSettingsPersister = persister; +} PluginManager* PluginManager::getInstance() { static PluginManager _manager; @@ -117,12 +137,12 @@ const LoaderList& getLoadedPlugins() { PluginManager::PluginManager() { } -extern CodecPluginList getCodecPlugins(); - const CodecPluginList& PluginManager::getCodecPlugins() { static CodecPluginList codecPlugins; static std::once_flag once; std::call_once(once, [&] { + codecPlugins = _codecPluginProvider(); + // Now grab the dynamic plugins for (auto loader : getLoadedPlugins()) { CodecProvider* codecProvider = qobject_cast(loader->instance()); @@ -163,11 +183,6 @@ const SteamClientPluginPointer PluginManager::getSteamClientPlugin() { #ifndef Q_OS_ANDROID -// TODO migrate to a DLL model where plugins are discovered and loaded at runtime by the PluginManager class -extern DisplayPluginList getDisplayPlugins(); -extern InputPluginList getInputPlugins(); - -extern void saveInputPluginSettings(const InputPluginList& plugins); static DisplayPluginList displayPlugins; const DisplayPluginList& PluginManager::getDisplayPlugins() { @@ -183,7 +198,7 @@ const DisplayPluginList& PluginManager::getDisplayPlugins() { std::call_once(once, [&] { // Grab the built in plugins - displayPlugins = ::getDisplayPlugins(); + displayPlugins = _displayPluginProvider(); // Now grab the dynamic plugins @@ -229,7 +244,7 @@ const InputPluginList& PluginManager::getInputPlugins() { }; std::call_once(once, [&] { - inputPlugins = ::getInputPlugins(); + inputPlugins = _inputPluginProvider(); // Now grab the dynamic plugins for (auto loader : getLoadedPlugins()) { @@ -288,7 +303,7 @@ void PluginManager::disableInputs(const QStringList& inputs) { } void PluginManager::saveSettings() { - saveInputPluginSettings(getInputPlugins()); + _inputSettingsPersister(getInputPlugins()); } void PluginManager::shutdown() { diff --git a/libraries/plugins/src/plugins/PluginManager.h b/libraries/plugins/src/plugins/PluginManager.h index 687e5c9e9b..cb011392a4 100644 --- a/libraries/plugins/src/plugins/PluginManager.h +++ b/libraries/plugins/src/plugins/PluginManager.h @@ -31,6 +31,18 @@ public: void setContainer(PluginContainer* container) { _container = container; } void shutdown(); + + // Application that have statically linked plugins can expose them to the plugin manager with these function + static void setDisplayPluginProvider(const DisplayPluginProvider& provider); + static void setInputPluginProvider(const InputPluginProvider& provider); + static void setCodecPluginProvider(const CodecPluginProvider& provider); + static void setInputPluginSettingsPersister(const InputPluginSettingsPersister& persister); + private: + static DisplayPluginProvider _displayPluginProvider; + static InputPluginProvider _inputPluginProvider; + static CodecPluginProvider _codecPluginProvider; + static InputPluginSettingsPersister _inputSettingsPersister; + PluginContainer* _container { nullptr }; }; diff --git a/plugins/hifiCodec/CMakeLists.txt b/plugins/hifiCodec/CMakeLists.txt index 15572e8266..28c1dc3807 100644 --- a/plugins/hifiCodec/CMakeLists.txt +++ b/plugins/hifiCodec/CMakeLists.txt @@ -8,7 +8,7 @@ set(TARGET_NAME hifiCodec) setup_hifi_client_server_plugin() -link_hifi_libraries(audio plugins input-plugins display-plugins) +link_hifi_libraries(audio plugins) add_dependency_external_projects(hifiAudioCodec) target_include_directories(${TARGET_NAME} PRIVATE ${HIFIAUDIOCODEC_INCLUDE_DIRS}) target_link_libraries(${TARGET_NAME} ${HIFIAUDIOCODEC_LIBRARIES}) diff --git a/plugins/hifiNeuron/CMakeLists.txt b/plugins/hifiNeuron/CMakeLists.txt index e3a725ca2f..a9ed8cca6e 100644 --- a/plugins/hifiNeuron/CMakeLists.txt +++ b/plugins/hifiNeuron/CMakeLists.txt @@ -10,7 +10,7 @@ if (APPLE OR WIN32) set(TARGET_NAME hifiNeuron) setup_hifi_plugin(Script Qml Widgets) - link_hifi_libraries(shared controllers ui plugins input-plugins display-plugins) + link_hifi_libraries(shared controllers ui plugins input-plugins) target_neuron() endif() diff --git a/plugins/hifiSdl2/CMakeLists.txt b/plugins/hifiSdl2/CMakeLists.txt index 86bda5a15d..7e499e314a 100644 --- a/plugins/hifiSdl2/CMakeLists.txt +++ b/plugins/hifiSdl2/CMakeLists.txt @@ -8,5 +8,5 @@ set(TARGET_NAME hifiSdl2) setup_hifi_plugin(Script Qml Widgets) -link_hifi_libraries(shared controllers ui plugins input-plugins script-engine display-plugins) +link_hifi_libraries(shared controllers ui plugins input-plugins script-engine) target_sdl2() diff --git a/plugins/hifiSixense/CMakeLists.txt b/plugins/hifiSixense/CMakeLists.txt index 54884bddff..14676217db 100644 --- a/plugins/hifiSixense/CMakeLists.txt +++ b/plugins/hifiSixense/CMakeLists.txt @@ -9,6 +9,6 @@ if (NOT ANDROID) set(TARGET_NAME hifiSixense) setup_hifi_plugin(Script Qml Widgets) - link_hifi_libraries(shared controllers ui plugins ui-plugins input-plugins display-plugins) + link_hifi_libraries(shared controllers ui plugins ui-plugins input-plugins) target_sixense() endif () diff --git a/plugins/pcmCodec/CMakeLists.txt b/plugins/pcmCodec/CMakeLists.txt index 5e52705033..900a642a88 100644 --- a/plugins/pcmCodec/CMakeLists.txt +++ b/plugins/pcmCodec/CMakeLists.txt @@ -8,6 +8,6 @@ set(TARGET_NAME pcmCodec) setup_hifi_client_server_plugin() -link_hifi_libraries(shared plugins input-plugins display-plugins) +link_hifi_libraries(shared plugins) install_beside_console() From f3206106812ceb2e376f6dd41c7d9aa3d8a8da64 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 23 Jun 2017 14:06:00 -0700 Subject: [PATCH 47/95] opened up shoulder constraint + other fixes * bent elbows will be away from the body a bit more. * sped up smoothing of pole vectors --- libraries/animation/src/AnimInverseKinematics.cpp | 6 ++++-- libraries/animation/src/Rig.cpp | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index e2e0d72d66..0395d4d391 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -1122,7 +1122,9 @@ void AnimInverseKinematics::initConstraints() { if (0 == baseName.compare("Arm", Qt::CaseSensitive)) { SwingTwistConstraint* stConstraint = new SwingTwistConstraint(); stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot()); - stConstraint->setTwistLimits(-PI / 2.0f, PI / 2.0f); + //stConstraint->setTwistLimits(-PI / 2.0f, PI / 2.0f); + const float TWIST_LIMIT = 5.0f * PI / 8.0f; + stConstraint->setTwistLimits(-TWIST_LIMIT, TWIST_LIMIT); /* KEEP THIS CODE for future experimentation // these directions are approximate swing limits in root-frame @@ -1148,7 +1150,7 @@ void AnimInverseKinematics::initConstraints() { // simple cone std::vector minDots; - const float MAX_HAND_SWING = PI / 2.0f; + const float MAX_HAND_SWING = 5.0f * PI / 8.0f; minDots.push_back(cosf(MAX_HAND_SWING)); stConstraint->setSwingLimits(minDots); diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 8466b79d1d..53e3f3ddc0 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1089,7 +1089,7 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab const bool LEFT_HAND = true; const bool RIGHT_HAND = false; - const float ELBOW_POLE_VECTOR_BLEND_FACTOR = 0.9f; + const float ELBOW_POLE_VECTOR_BLEND_FACTOR = 0.95f; if (leftHandEnabled) { if (!_isLeftHandControlled) { @@ -1260,7 +1260,7 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab void Rig::updateFeet(bool leftFootEnabled, bool rightFootEnabled, const AnimPose& leftFootPose, const AnimPose& rightFootPose) { - const float KNEE_POLE_VECTOR_BLEND_FACTOR = 0.9f; + const float KNEE_POLE_VECTOR_BLEND_FACTOR = 0.95f; int hipsIndex = indexOfJoint("Hips"); @@ -1390,7 +1390,7 @@ glm::vec3 Rig::calculateElbowPoleVector(int handIndex, int elbowIndex, int armIn // give dProj a bit of offset away from the body. float avatarScale = extractUniformScale(_modelOffset); - const float LATERAL_OFFSET = 0.333f * avatarScale; + const float LATERAL_OFFSET = 1.0f * avatarScale; const float VERTICAL_OFFSET = -0.333f * avatarScale; glm::vec3 dProjWithOffset = dProj + sign * LATERAL_OFFSET * n + y * VERTICAL_OFFSET; From c0e8b6c8bcf617b7ef1b64360e6c08085a312811 Mon Sep 17 00:00:00 2001 From: samcake Date: Fri, 23 Jun 2017 14:18:56 -0700 Subject: [PATCH 48/95] clean up --- libraries/script-engine/src/ScriptEngine.cpp | 9 ++------- libraries/shared/src/Profile.h | 16 ++++++++++++++++ scripts/system/controllers/handControllerGrab.js | 2 -- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 16927e0d2e..df74c04aaf 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -1283,7 +1283,6 @@ void ScriptEngine::timerFired() { // call the associated JS function, if it exists if (timerData.function.isValid()) { PROFILE_RANGE(script, __FUNCTION__); - // PROFILE_RANGE(script, timerData.function.toString().toStdString().c_str()); auto preTimer = p_high_resolution_clock::now(); callWithEnvironment(timerData.definingEntityIdentifier, timerData.definingSandboxURL, timerData.function, timerData.function, QScriptValueList()); auto postTimer = p_high_resolution_clock::now(); @@ -1396,15 +1395,11 @@ void ScriptEngine::print(const QString& message) { void ScriptEngine::beginProfileRange(const QString& label) const { - if (trace_script().isDebugEnabled()) { - tracing::traceEvent(trace_script(), label.toStdString().c_str(), tracing::DurationBegin); - } + PROFILE_SYNC_BEGIN(script, label.toStdString().c_str(), label.toStdString().c_str()); } void ScriptEngine::endProfileRange(const QString& label) const { - if (trace_script().isDebugEnabled()) { - tracing::traceEvent(trace_script(), label.toStdString().c_str(), tracing::DurationEnd); - } + PROFILE_SYNC_END(script, label.toStdString().c_str(), label.toStdString().c_str()); } // Script.require.resolve -- like resolvePath, but performs more validation and throws exceptions on invalid module identifiers (for consistency with Node.js) diff --git a/libraries/shared/src/Profile.h b/libraries/shared/src/Profile.h index ee09298deb..5de4e8f41a 100644 --- a/libraries/shared/src/Profile.h +++ b/libraries/shared/src/Profile.h @@ -46,6 +46,20 @@ private: const QLoggingCategory& _category; }; + +inline void syncBegin(const QLoggingCategory& category, const QString& name, const QString& id, const QVariantMap& args = QVariantMap(), const QVariantMap& extra = QVariantMap()) { + if (category.isDebugEnabled()) { + tracing::traceEvent(category, name, tracing::DurationBegin, id, args, extra); + } +} + + +inline void syncEnd(const QLoggingCategory& category, const QString& name, const QString& id, const QVariantMap& args = QVariantMap(), const QVariantMap& extra = QVariantMap()) { + if (category.isDebugEnabled()) { + tracing::traceEvent(category, name, tracing::DurationEnd, id, args, extra); + } +} + inline void asyncBegin(const QLoggingCategory& category, const QString& name, const QString& id, const QVariantMap& args = QVariantMap(), const QVariantMap& extra = QVariantMap()) { if (category.isDebugEnabled()) { tracing::traceEvent(category, name, tracing::AsyncNestableStart, id, args, extra); @@ -80,6 +94,8 @@ inline void metadata(const QString& metadataType, const QVariantMap& args) { #define PROFILE_RANGE_EX(category, name, argbColor, payload, ...) Duration profileRangeThis(trace_##category(), name, argbColor, (uint64_t)payload, ##__VA_ARGS__); #define PROFILE_RANGE_BEGIN(category, rangeId, name, argbColor) rangeId = Duration::beginRange(trace_##category(), name, argbColor) #define PROFILE_RANGE_END(category, rangeId) Duration::endRange(trace_##category(), rangeId) +#define PROFILE_SYNC_BEGIN(category, name, id, ...) syncBegin(trace_##category(), name, id, ##__VA_ARGS__); +#define PROFILE_SYNC_END(category, name, id, ...) syncEnd(trace_##category(), name, id, ##__VA_ARGS__); #define PROFILE_ASYNC_BEGIN(category, name, id, ...) asyncBegin(trace_##category(), name, id, ##__VA_ARGS__); #define PROFILE_ASYNC_END(category, name, id, ...) asyncEnd(trace_##category(), name, id, ##__VA_ARGS__); #define PROFILE_COUNTER_IF_CHANGED(category, name, type, value) { static type lastValue = 0; type newValue = value; if (newValue != lastValue) { counter(trace_##category(), name, { { name, newValue }}); lastValue = newValue; } } diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 8631a84fcf..04921fe14d 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -1173,7 +1173,6 @@ function MyController(hand) { }; this.update = function(deltaTime, timestamp) { - Script.beginProfileRange("handControllerGrab:MyController:update") this.updateSmoothedTrigger(); this.maybeScaleMyAvatar(); @@ -1210,7 +1209,6 @@ function MyController(hand) { } else { print("WARNING: could not find state " + this.state + " in state machine"); } - Script.endProfileRange("handControllerGrab:MyController:update") }; this.callEntityMethodOnGrabbed = function(entityMethodName) { From 86ed61a15dccda8fd29196b72e8ad347c9ad6786 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 23 Jun 2017 14:26:13 -0700 Subject: [PATCH 49/95] Push avatar packet version for isReplicated --- libraries/networking/src/udt/PacketHeaders.cpp | 2 +- libraries/networking/src/udt/PacketHeaders.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index ad15d04db1..240697d890 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -69,7 +69,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::AvatarData: case PacketType::BulkAvatarData: case PacketType::KillAvatar: - return static_cast(AvatarMixerPacketVersion::AvatarIdentitySequenceFront); + return static_cast(AvatarMixerPacketVersion::IsReplicatedInAvatarIdentity); case PacketType::MessagesData: return static_cast(MessageDataVersion::TextOrBinaryData); case PacketType::ICEServerHeartbeat: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 2944c1ce93..6c42193e11 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -247,7 +247,8 @@ enum class AvatarMixerPacketVersion : PacketVersion { IdentityPacketsIncludeUpdateTime, AvatarIdentitySequenceId, MannequinDefaultAvatar, - AvatarIdentitySequenceFront + AvatarIdentitySequenceFront, + IsReplicatedInAvatarIdentity }; enum class DomainConnectRequestVersion : PacketVersion { From b8c638b2b7214d5e7f508366443a33761dcf8ca1 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 23 Jun 2017 14:36:59 -0700 Subject: [PATCH 50/95] warning fixes --- libraries/animation/src/AnimInverseKinematics.cpp | 2 -- libraries/animation/src/Rig.cpp | 1 - 2 files changed, 3 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 0395d4d391..3e948a4f5b 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -584,8 +584,6 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const } glm::vec3 p = target.getPoleVector(); - glm::vec3 pProj = p - glm::dot(p, dUnit) * dUnit; - const float PROJ_VECTOR_LEN = 10.0f; const float POLE_VECTOR_LEN = 100.0f; glm::vec3 midPoint = (basePose.trans() + topPose.trans()) * 0.5f; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 53e3f3ddc0..3f63f226e7 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1417,7 +1417,6 @@ glm::vec3 Rig::calculateKneePoleVector(int footJointIndex, int kneeIndex, int up // form a plane normal to the hips x-axis glm::vec3 n = hipsPose.rot() * Vectors::UNIT_X; - glm::vec3 y = hipsPose.rot() * Vectors::UNIT_Y; // project d onto this plane glm::vec3 dProj = d - glm::dot(d, n) * n; From aabbcfd23dafecd99beb85a6c3ab3cf7a7e6b3dc Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 23 Jun 2017 16:47:53 -0700 Subject: [PATCH 51/95] code review feedback --- scripts/developer/tests/puck-attach.js | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/scripts/developer/tests/puck-attach.js b/scripts/developer/tests/puck-attach.js index 00d2ca5fa3..2d0a2e6d02 100644 --- a/scripts/developer/tests/puck-attach.js +++ b/scripts/developer/tests/puck-attach.js @@ -5,6 +5,18 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +// When this script is running, a new app button, named "PUCKTACH", will be added to the toolbar/tablet. +// Click this app to bring up the puck attachment panel. This panel contains the following fields. +// +// * Tracked Object - A drop down list of all the available pucks found. If no pucks are found this list will only have a single NONE entry. +// Closing and re-opening the app will refresh this list. +// * Model URL - A model url of the model you wish to be attached to the specified puck. +// * Position X, Y, Z - used to apply an offset between the puck and the attached model. +// * Rot X, Y, Z - used to apply euler angle offsets, in degrees, between the puck and the attached model. +// * Create Attachment - when this button is pressed a new Entity will be created at the specified puck's location. +// If a puck atttachment already exists, it will be destroyed before the new entity is created. +// * Destroy Attachmetn - destroies the entity attached to the puck. +// /* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ /* global Xform */ @@ -46,7 +58,7 @@ function onScreenChanged(type, url) { var availableTrackedObjects = getAvailableTrackedObjects(); tablet.emitScriptEvent(JSON.stringify(availableTrackedObjects)); - print("PUCK-ATTACH: availableTrackedObjects = " + JSON.stringify(availableTrackedObjects)); + // print("PUCK-ATTACH: availableTrackedObjects = " + JSON.stringify(availableTrackedObjects)); }, 1000); // wait 1 sec before sending.. } shown = true; @@ -93,10 +105,10 @@ function attach(obj) { var key = indexToTrackedObjectName(Number(attachedObj.puckno)); attachedObj.key = key; - print("PUCK-ATTACH: attachedObj = " + JSON.stringify(attachedObj)); + // print("PUCK-ATTACH: attachedObj = " + JSON.stringify(attachedObj)); Script.update.connect(update); - update(0.001); + update(); } function remove() { @@ -113,7 +125,7 @@ function pad(num, size) { return tempString.substr(tempString.length - size); } -function update(dt) { +function update() { if (attachedEntity && attachedObj && Controller.Hardware.Vive) { var pose = Controller.getPoseValue(Controller.Hardware.Vive[attachedObj.key]); var avatarXform = new Xform(MyAvatar.orientation, MyAvatar.position); From b546d977f4ec3ff2874e62901f8355e7959e9121 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 24 Jun 2017 12:07:20 +1200 Subject: [PATCH 52/95] "EZRECORD" app script and button --- scripts/developer/EZrecord.js | 60 +++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 scripts/developer/EZrecord.js diff --git a/scripts/developer/EZrecord.js b/scripts/developer/EZrecord.js new file mode 100644 index 0000000000..ae397afb36 --- /dev/null +++ b/scripts/developer/EZrecord.js @@ -0,0 +1,60 @@ +"use strict"; + +// +// EZrecord.js +// +// Created by David Rowe on 24 Jun 2017. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function () { + + var APP_NAME = "EZRECORD", + APP_ICON_INACTIVE = "icons/tablet-icons/avatar-record-i.svg", + APP_ICON_ACTIVE = "icons/tablet-icons/avatar-record-a.svg", + tablet, + button; + + function onButtonClicked() { + // TODO + print("Clicked"); + } + + function setUp() { + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + if (!tablet) { + return; + } + + // Tablet/toolbar button. + button = tablet.addButton({ + icon: APP_ICON_INACTIVE, + activeIcon: APP_ICON_ACTIVE, + text: APP_NAME, + isActive: false + }); + if (button) { + button.clicked.connect(onButtonClicked); + } + } + + function tearDown() { + if (!tablet) { + return; + } + + if (button) { + button.clicked.disconnect(onButtonClicked); + tablet.removeButton(button); + button = null; + } + + tablet = null; + } + + setUp(); + Script.scriptEnding.connect(tearDown); +}()); From ced9473eb34d624889206d54dfee1c9024ec8a8b Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 24 Jun 2017 12:49:45 +1200 Subject: [PATCH 53/95] Icon toggling and keyboard shortcut --- scripts/developer/EZrecord.js | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/scripts/developer/EZrecord.js b/scripts/developer/EZrecord.js index ae397afb36..d28c182ea0 100644 --- a/scripts/developer/EZrecord.js +++ b/scripts/developer/EZrecord.js @@ -15,12 +15,25 @@ var APP_NAME = "EZRECORD", APP_ICON_INACTIVE = "icons/tablet-icons/avatar-record-i.svg", APP_ICON_ACTIVE = "icons/tablet-icons/avatar-record-a.svg", + SHORTCUT_KEY = "r", // Ctrl modifier is assumed. tablet, - button; + button, + isRecording = false; + + function toggleRecording() { + isRecording = !isRecording; + button.editProperties({ isActive: isRecording }); + // TODO: Start/cancel/finish recording. + } + + function onKeyPressEvent(event) { + if (event.isControl && event.text === SHORTCUT_KEY && !event.isMeta && !event.isAlt && !event.isAutoRepeat) { + toggleRecording(); + } + } function onButtonClicked() { - // TODO - print("Clicked"); + toggleRecording(); } function setUp() { @@ -39,9 +52,15 @@ if (button) { button.clicked.connect(onButtonClicked); } + + Controller.keyPressEvent.connect(onKeyPressEvent); } function tearDown() { + if (isRecording) { + // TODO: Cancel recording. + } + if (!tablet) { return; } @@ -53,6 +72,8 @@ } tablet = null; + + Controller.keyPressEvent.disconnect(onKeyPressEvent); } setUp(); From 1ddae1c61ad02c82538d0360cfebbccd937a2c5a Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 24 Jun 2017 13:19:25 +1200 Subject: [PATCH 54/95] Change keyboard shortcut to avoid conflict --- scripts/developer/EZrecord.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/developer/EZrecord.js b/scripts/developer/EZrecord.js index d28c182ea0..bb173343df 100644 --- a/scripts/developer/EZrecord.js +++ b/scripts/developer/EZrecord.js @@ -15,7 +15,7 @@ var APP_NAME = "EZRECORD", APP_ICON_INACTIVE = "icons/tablet-icons/avatar-record-i.svg", APP_ICON_ACTIVE = "icons/tablet-icons/avatar-record-a.svg", - SHORTCUT_KEY = "r", // Ctrl modifier is assumed. + SHORTCUT_KEY = "r", // Alt modifier is assumed. tablet, button, isRecording = false; @@ -27,7 +27,7 @@ } function onKeyPressEvent(event) { - if (event.isControl && event.text === SHORTCUT_KEY && !event.isMeta && !event.isAlt && !event.isAutoRepeat) { + if (event.isAlt && event.text === SHORTCUT_KEY && !event.isControl && !event.isMeta && !event.isAutoRepeat) { toggleRecording(); } } From ce32f1704e6830dc7c533adf277067b444609b83 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 24 Jun 2017 13:22:00 +1200 Subject: [PATCH 55/95] Start/cancel/finish recording logic --- scripts/developer/EZrecord.js | 85 +++++++++++++++++++++++++++++++---- 1 file changed, 77 insertions(+), 8 deletions(-) diff --git a/scripts/developer/EZrecord.js b/scripts/developer/EZrecord.js index bb173343df..f6ff15fa57 100644 --- a/scripts/developer/EZrecord.js +++ b/scripts/developer/EZrecord.js @@ -18,12 +18,79 @@ SHORTCUT_KEY = "r", // Alt modifier is assumed. tablet, button, - isRecording = false; + + Recorder; + + Recorder = (function () { + var IDLE = 0, + COUNTING_DOWN = 1, + RECORDING = 2, + recordingState = IDLE; + + function isRecording() { + return recordingState === COUNTING_DOWN || recordingState === RECORDING; + } + + function startRecording() { + + if (recordingState !== IDLE) { + return; + } + + // TODO + + recordingState = COUNTING_DOWN; + + } + + function cancelRecording() { + + // TODO + + recordingState = IDLE; + } + + function finishRecording() { + + // TODO + + recordingState = IDLE; + } + + function stopRecording() { + if (recordingState === COUNTING_DOWN) { + cancelRecording(); + } else { + finishRecording(); + } + } + + function setUp() { + + } + + function tearDown() { + if (recordingState !== IDLE) { + cancelRecording(); + } + } + + return { + isRecording: isRecording, + startRecording: startRecording, + stopRecording: stopRecording, + setUp: setUp, + tearDown: tearDown + }; + }()); function toggleRecording() { - isRecording = !isRecording; - button.editProperties({ isActive: isRecording }); - // TODO: Start/cancel/finish recording. + if (Recorder.isRecording()) { + Recorder.stopRecording(); + } else { + Recorder.startRecording(); + } + button.editProperties({ isActive: Recorder.isRecording() }); } function onKeyPressEvent(event) { @@ -42,6 +109,8 @@ return; } + Recorder.setUp(); + // Tablet/toolbar button. button = tablet.addButton({ icon: APP_ICON_INACTIVE, @@ -57,9 +126,10 @@ } function tearDown() { - if (isRecording) { - // TODO: Cancel recording. - } + + Controller.keyPressEvent.disconnect(onKeyPressEvent); + + Recorder.tearDown(); if (!tablet) { return; @@ -73,7 +143,6 @@ tablet = null; - Controller.keyPressEvent.disconnect(onKeyPressEvent); } setUp(); From 6543b74b52a1ae18004e3d4b0be5f3947263c7dd Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 24 Jun 2017 14:26:01 +1200 Subject: [PATCH 56/95] Add beeps --- scripts/developer/EZrecord.js | 65 ++++++++++++++++-- .../system/assets/sounds/countdown-tick.wav | Bin 0 -> 9702 bytes .../system/assets/sounds/finish-recording.wav | Bin 0 -> 63128 bytes .../system/assets/sounds/start-recording.wav | Bin 0 -> 63128 bytes 4 files changed, 58 insertions(+), 7 deletions(-) create mode 100644 scripts/system/assets/sounds/countdown-tick.wav create mode 100644 scripts/system/assets/sounds/finish-recording.wav create mode 100644 scripts/system/assets/sounds/start-recording.wav diff --git a/scripts/developer/EZrecord.js b/scripts/developer/EZrecord.js index f6ff15fa57..593f9d5fbb 100644 --- a/scripts/developer/EZrecord.js +++ b/scripts/developer/EZrecord.js @@ -21,36 +21,85 @@ Recorder; + function log(message) { + print(APP_NAME + ": " + message); + } + Recorder = (function () { var IDLE = 0, COUNTING_DOWN = 1, RECORDING = 2, - recordingState = IDLE; + recordingState = IDLE, + + countdownTimer, + countdownSeconds, + COUNTDOWN_SECONDS = 3, + + tickSound, + startRecordingSound, + finishRecordingSound, + TICK_SOUND = "../system/assets/sounds/countdown-tick.wav", + START_RECORDING_SOUND = "../system/assets/sounds/start-recording.wav", + FINISH_RECORDING_SOUND = "../system/assets/sounds/finish-recording.wav", + START_RECORDING_SOUND_DURATION = 1200, + SOUND_VOLUME = 0.2; + + function playSound(sound) { + Audio.playSound(sound, { + position: MyAvatar.position, + localOnly: true, + volume: SOUND_VOLUME + }); + } function isRecording() { return recordingState === COUNTING_DOWN || recordingState === RECORDING; } function startRecording() { - if (recordingState !== IDLE) { return; } - // TODO + log("Start countdown"); + countdownSeconds = COUNTDOWN_SECONDS; + playSound(tickSound); + countdownTimer = Script.setInterval(function () { + countdownSeconds -= 1; + if (countdownSeconds <= 0) { + Script.clearInterval(countdownTimer); + playSound(startRecordingSound); + log("Start recording"); + Script.setTimeout(function () { + // Delay start so that start beep is not included in recorded sound. + + // TODO + + }, START_RECORDING_SOUND_DURATION); + recordingState = RECORDING; + } else { + playSound(tickSound); + } + }, 1000); recordingState = COUNTING_DOWN; - } function cancelRecording() { + log("Cancel recording"); + if (recordingState === COUNTING_DOWN) { + Script.clearInterval(countdownTimer); + } else { - // TODO + // TODO + } recordingState = IDLE; } function finishRecording() { + log("Finish recording"); + playSound(finishRecordingSound); // TODO @@ -60,13 +109,15 @@ function stopRecording() { if (recordingState === COUNTING_DOWN) { cancelRecording(); - } else { + } else if (recordingState === RECORDING) { finishRecording(); } } function setUp() { - + tickSound = SoundCache.getSound(Script.resolvePath(TICK_SOUND)); + startRecordingSound = SoundCache.getSound(Script.resolvePath(START_RECORDING_SOUND)); + finishRecordingSound = SoundCache.getSound(Script.resolvePath(FINISH_RECORDING_SOUND)); } function tearDown() { diff --git a/scripts/system/assets/sounds/countdown-tick.wav b/scripts/system/assets/sounds/countdown-tick.wav new file mode 100644 index 0000000000000000000000000000000000000000..015e1f642e91b72578bca1c318f7a7c0d515d843 GIT binary patch literal 9702 zcmZ{q=T=m0v##%|XEultF{6N}hzO!!Kru%Y6%`S5{aa(~vHq=p>mc@te6N@z=A5%A ziWoo)h=__Jf_BfU+GEacczwS$_9=8%-Op9mJu5e?Ti22U)U2#t`(OY4Ka*1d5D07b z1OIm?fsu;;^Pm6opJ#Faz=rZfoxvr#hPGon)}q8_@I)EvOuykBJ+&Kj+V;{RJ4bh| ziQ27)ou+b@7Vu(O@74Gfk(IGx&cQgOAcim2!jJR{)!AQkj4sOq*DRmhZ%yJcGQ}0? zGTLlAX`?OS**Y1cIO6w4I{X)W9~bQ~?XeTMP7T&dUFOpe8&A15k5*%wR=F}+$oa_7 z;Y`+zHax>^oW%hgz**d;XY$_r;lmptlPDjHP-a!UO;)-2GTlwk!4T@QR%)Qy!-2oTm$Ttya=@thEK4 zr|CRQjk@t3jktv~*pGuahdXG(2lRx0R3-{gYGqW7tyqP5mP6w$g?xOq*Zf$nx)ZY7 z9o38cKwt7F?V}`&#uS>POL>#*a+`E1&A}8KMM?N&9rOwh>>?l4-E@Mk=_78{FYIdy zkGC9~>sI+&ylN?WZ(ew@KAG~zwF0Sw0^%)~;hLlr8q1`99) z=@<&dH@w9Y+`uX9#bI2qyY7YG?)Cc4OY^h5LVvMWE;U@C%c&UCFxHYes-4t=dcEu& z_y6S`_bhWqE1hsFyz4bGm2eaTt5)lwGGMcxzp( zGQ_1zu3Mn1QOQ-Z)-9Ago@B!q@}1wKQEz%@V*9*%GNNfGiqm%s=%*Fg0`@hiosMwi}d~8X9Kd z)X-Y1r0uqbN^AyAw4pHij(7HiZrUmA<3s+r$lX|z*DgKmFj6z!EGcyxw3@cq3Yu%v zXdF^3N?)vn9$8{LaoHYFvwh;k#ZhhA-WFXXPf zpofB~(d+Jsym8+|+)$aoGq6BcdzG>6k#(_>$jsQp$Z+3Wx4xsqcG>|xr?5uKy9B+IqCCQI|iM0BU+%>6lwQ|H=lKbwJbnq{W&}hr1IkuEG*)G~-e{83A zxL0!DU6LcNR_gegJwhvewHOVyahOi?Y$a{A9azt$lJ7EQgk$|dAM6?3!8toX`|UK{ zLZiJyH-+12d9={ha+Ph1ZzrYO5DI^1YoHr;GQOR3&z_?lKjCPkX7Vg8g

>|!Po6E-f)-rJVeL4Tz_6we;Vd1n{WwdvwUOJb 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 VwQ2KIs$3-ONOGe9gW(vXV1KJl2V?*M literal 0 HcmV?d00001 From bae04966f912d83d3ea5c40622a45c0d452c31a3 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 24 Jun 2017 14:35:28 +1200 Subject: [PATCH 57/95] Add "REC" indicator --- scripts/developer/EZrecord.js | 67 +++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/scripts/developer/EZrecord.js b/scripts/developer/EZrecord.js index 593f9d5fbb..f8dd7bc26e 100644 --- a/scripts/developer/EZrecord.js +++ b/scripts/developer/EZrecord.js @@ -19,12 +19,76 @@ tablet, button, + RecordingIndicator, Recorder; function log(message) { print(APP_NAME + ": " + message); } + RecordingIndicator = (function () { + // Displays "recording" overlay. + + var hmdOverlay, + HMD_FONT_SIZE = 0.08, + desktopOverlay, + DESKTOP_FONT_SIZE = 24; + + function show() { + // Create both overlays in case user switches desktop/HMD mode. + var screenSize = Controller.getViewportDimensions(), + recordingText = "REC", // Unicode circle \u25cf doesn't render in HMD. + CAMERA_JOINT_INDEX = -7; + + if (HMD.active) { + // 3D overlay attached to avatar. + hmdOverlay = Overlays.addOverlay("text3d", { + text: recordingText, + dimensions: { x: 3 * HMD_FONT_SIZE, y: HMD_FONT_SIZE }, + parentID: MyAvatar.sessionUUID, + parentJointIndex: CAMERA_JOINT_INDEX, + localPosition: { x: 0.95, y: 0.95, z: -2.0 }, + color: { red: 255, green: 0, blue: 0 }, + alpha: 0.9, + lineHeight: HMD_FONT_SIZE, + backgroundAlpha: 0, + ignoreRayIntersection: true, + isFacingAvatar: true, + drawInFront: true, + visible: true + }); + } else { + // 2D overlay on desktop. + desktopOverlay = Overlays.addOverlay("text", { + text: recordingText, + width: 3 * DESKTOP_FONT_SIZE, + height: DESKTOP_FONT_SIZE, + x: screenSize.x - 4 * DESKTOP_FONT_SIZE, + y: DESKTOP_FONT_SIZE, + font: { size: DESKTOP_FONT_SIZE }, + color: { red: 255, green: 8, blue: 8 }, + alpha: 1.0, + backgroundAlpha: 0, + visible: true + }); + } + } + + function hide() { + if (desktopOverlay) { + Overlays.deleteOverlay(desktopOverlay); + } + if (hmdOverlay) { + Overlays.deleteOverlay(hmdOverlay); + } + } + + return { + show: show, + hide: hide + }; + }()); + Recorder = (function () { var IDLE = 0, COUNTING_DOWN = 1, @@ -75,6 +139,7 @@ // TODO + RecordingIndicator.show(); }, START_RECORDING_SOUND_DURATION); recordingState = RECORDING; } else { @@ -93,6 +158,7 @@ // TODO + RecordingIndicator.hide(); } recordingState = IDLE; } @@ -103,6 +169,7 @@ // TODO + RecordingIndicator.hide(); recordingState = IDLE; } From 3de1c0a312ffbd10d0fb78d3b418a1bed1d184cf Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 24 Jun 2017 15:57:57 +1200 Subject: [PATCH 58/95] Make and save recording --- .../src/RecordingScriptingInterface.cpp | 23 +++++++++++++------ .../src/RecordingScriptingInterface.h | 1 + scripts/developer/EZrecord.js | 18 +++++++-------- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/libraries/script-engine/src/RecordingScriptingInterface.cpp b/libraries/script-engine/src/RecordingScriptingInterface.cpp index 98838441d2..7583f562e6 100644 --- a/libraries/script-engine/src/RecordingScriptingInterface.cpp +++ b/libraries/script-engine/src/RecordingScriptingInterface.cpp @@ -8,9 +8,17 @@ #include "RecordingScriptingInterface.h" +#include #include +#include +#include +#include +#include +#include +#include #include +#include #include #include #include @@ -18,13 +26,6 @@ #include #include - -#include -#include -#include -#include -#include - #include "ScriptEngineLogging.h" using namespace recording; @@ -188,6 +189,14 @@ void RecordingScriptingInterface::stopRecording() { _lastClip->seek(0); } +QString RecordingScriptingInterface::getDefaultRecordingSaveDirectory() { + QString directory = PathUtils::getAppLocalDataPath() + "Avatar Recordings/"; + if (!QDir(directory).exists()) { + QDir().mkdir(directory); + } + return directory; +} + void RecordingScriptingInterface::saveRecording(const QString& filename) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "saveRecording", Qt::BlockingQueuedConnection, diff --git a/libraries/script-engine/src/RecordingScriptingInterface.h b/libraries/script-engine/src/RecordingScriptingInterface.h index a9fdf1deb4..c4220958a2 100644 --- a/libraries/script-engine/src/RecordingScriptingInterface.h +++ b/libraries/script-engine/src/RecordingScriptingInterface.h @@ -65,6 +65,7 @@ public slots: float recorderElapsed() const; + QString getDefaultRecordingSaveDirectory(); void saveRecording(const QString& filename); bool saveRecordingToAsset(QScriptValue getClipAtpUrl); void loadLastRecording(); diff --git a/scripts/developer/EZrecord.js b/scripts/developer/EZrecord.js index f8dd7bc26e..382633f4f2 100644 --- a/scripts/developer/EZrecord.js +++ b/scripts/developer/EZrecord.js @@ -136,9 +136,7 @@ log("Start recording"); Script.setTimeout(function () { // Delay start so that start beep is not included in recorded sound. - - // TODO - + Recording.startRecording(); RecordingIndicator.show(); }, START_RECORDING_SOUND_DURATION); recordingState = RECORDING; @@ -155,21 +153,21 @@ if (recordingState === COUNTING_DOWN) { Script.clearInterval(countdownTimer); } else { - - // TODO - + Recording.stopRecording(); RecordingIndicator.hide(); } recordingState = IDLE; } function finishRecording() { - log("Finish recording"); playSound(finishRecordingSound); - - // TODO - + Recording.stopRecording(); RecordingIndicator.hide(); + var filename = (new Date()).toISOString(); // yyyy-mm-ddThh:mm:ss.sssZ + filename = filename.replace(/[\-:]|\.\d*Z$/g, "").replace("T", "-") + ".hfr"; // yyyymmmdd-hhmmss.hfr + filename = Recording.getDefaultRecordingSaveDirectory() + filename; + log("Finish recording: " + filename); + Recording.saveRecording(filename); recordingState = IDLE; } From 48caad1ad252d7841fe1fbabc87f3d9a688412cb Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 24 Jun 2017 16:03:55 +1200 Subject: [PATCH 59/95] Add user activity logging --- scripts/developer/EZrecord.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/scripts/developer/EZrecord.js b/scripts/developer/EZrecord.js index 382633f4f2..7fdebada79 100644 --- a/scripts/developer/EZrecord.js +++ b/scripts/developer/EZrecord.js @@ -26,6 +26,12 @@ print(APP_NAME + ": " + message); } + function logDetails() { + return { + current_domain: location.placename + }; + } + RecordingIndicator = (function () { // Displays "recording" overlay. @@ -169,6 +175,7 @@ log("Finish recording: " + filename); Recording.saveRecording(filename); recordingState = IDLE; + UserActivityLogger.logAction("ezrecord_finish_recording", logDetails()); } function stopRecording() { @@ -239,6 +246,8 @@ } Controller.keyPressEvent.connect(onKeyPressEvent); + + UserActivityLogger.logAction("ezrecord_run_script", logDetails()); } function tearDown() { From 05f0ec267e2f1fadbcfafbd4f1166928b73acfd3 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 24 Jun 2017 18:53:00 -0700 Subject: [PATCH 60/95] fix an initialization order fiasco --- libraries/controllers/src/controllers/Input.cpp | 7 ++++++- libraries/controllers/src/controllers/Input.h | 2 ++ libraries/controllers/src/controllers/UserInputMapper.cpp | 4 ++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/libraries/controllers/src/controllers/Input.cpp b/libraries/controllers/src/controllers/Input.cpp index 6f8bd547a2..dbc9071f43 100644 --- a/libraries/controllers/src/controllers/Input.cpp +++ b/libraries/controllers/src/controllers/Input.cpp @@ -9,9 +9,14 @@ #include "Input.h" namespace controller { - const Input Input::INVALID_INPUT = Input(0x7fffffff); + const Input Input::INVALID_INPUT = invalidInput(); const uint16_t Input::INVALID_DEVICE = Input::INVALID_INPUT.device; const uint16_t Input::INVALID_CHANNEL = Input::INVALID_INPUT.channel; const uint16_t Input::INVALID_TYPE = Input::INVALID_INPUT.type; + + const Input& Input::invalidInput() { + static const Input INVALID_INPUT = Input(0x7fffffff); + return INVALID_INPUT; + } } diff --git a/libraries/controllers/src/controllers/Input.h b/libraries/controllers/src/controllers/Input.h index b74ad48c6f..3ca4076de2 100644 --- a/libraries/controllers/src/controllers/Input.h +++ b/libraries/controllers/src/controllers/Input.h @@ -83,6 +83,8 @@ struct Input { using NamedPair = QPair; using NamedVector = QVector; + + static const Input& invalidInput(); }; } diff --git a/libraries/controllers/src/controllers/UserInputMapper.cpp b/libraries/controllers/src/controllers/UserInputMapper.cpp index 79f4325ae6..29f011fba2 100755 --- a/libraries/controllers/src/controllers/UserInputMapper.cpp +++ b/libraries/controllers/src/controllers/UserInputMapper.cpp @@ -47,8 +47,8 @@ namespace controller { const uint16_t UserInputMapper::STANDARD_DEVICE = 0; - const uint16_t UserInputMapper::ACTIONS_DEVICE = Input::INVALID_DEVICE - 0x00FF; - const uint16_t UserInputMapper::STATE_DEVICE = Input::INVALID_DEVICE - 0x0100; + const uint16_t UserInputMapper::ACTIONS_DEVICE = Input::invalidInput().device - 0x00FF; + const uint16_t UserInputMapper::STATE_DEVICE = Input::invalidInput().device - 0x0100; } // Default contruct allocate the poutput size with the current hardcoded action channels From 960017ddd704cfa77321a1edcc6df25e91330595 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Sat, 24 Jun 2017 22:22:47 -0700 Subject: [PATCH 61/95] Fix static plugin initialization on Linux --- interface/src/Application.cpp | 10 +++++----- libraries/plugins/src/plugins/PluginManager.cpp | 4 ---- libraries/plugins/src/plugins/PluginManager.h | 17 ++++++++--------- 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c80626ad5c..0951e0973b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -486,11 +486,11 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { Setting::init(); // Tell the plugin manager about our statically linked plugins - PluginManager::setInputPluginProvider([] { return getInputPlugins(); }); - PluginManager::setDisplayPluginProvider([] { return getDisplayPlugins(); }); - PluginManager::setInputPluginSettingsPersister([](const InputPluginList& plugins) { saveInputPluginSettings(plugins); }); - - if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) { + auto pluginManager = PluginManager::getInstance(); + pluginManager->setInputPluginProvider([] { return getInputPlugins(); }); + pluginManager->setDisplayPluginProvider([] { return getDisplayPlugins(); }); + pluginManager->setInputPluginSettingsPersister([](const InputPluginList& plugins) { saveInputPluginSettings(plugins); }); + if (auto steamClient = pluginManager->getSteamClientPlugin()) { steamClient->init(); } diff --git a/libraries/plugins/src/plugins/PluginManager.cpp b/libraries/plugins/src/plugins/PluginManager.cpp index e90d3e3a0f..18ac905ef1 100644 --- a/libraries/plugins/src/plugins/PluginManager.cpp +++ b/libraries/plugins/src/plugins/PluginManager.cpp @@ -23,10 +23,6 @@ #include "InputPlugin.h" #include "PluginLogging.h" -DisplayPluginProvider PluginManager::_displayPluginProvider = []()->DisplayPluginList { return {}; }; -InputPluginProvider PluginManager::_inputPluginProvider = []()->InputPluginList { return {}; }; -CodecPluginProvider PluginManager::_codecPluginProvider = []()->CodecPluginList { return {}; }; -InputPluginSettingsPersister PluginManager::_inputSettingsPersister = [](const InputPluginList& list) {}; void PluginManager::setDisplayPluginProvider(const DisplayPluginProvider& provider) { _displayPluginProvider = provider; diff --git a/libraries/plugins/src/plugins/PluginManager.h b/libraries/plugins/src/plugins/PluginManager.h index cb011392a4..08fe4fde20 100644 --- a/libraries/plugins/src/plugins/PluginManager.h +++ b/libraries/plugins/src/plugins/PluginManager.h @@ -33,16 +33,15 @@ public: void shutdown(); // Application that have statically linked plugins can expose them to the plugin manager with these function - static void setDisplayPluginProvider(const DisplayPluginProvider& provider); - static void setInputPluginProvider(const InputPluginProvider& provider); - static void setCodecPluginProvider(const CodecPluginProvider& provider); - static void setInputPluginSettingsPersister(const InputPluginSettingsPersister& persister); + void setDisplayPluginProvider(const DisplayPluginProvider& provider); + void setInputPluginProvider(const InputPluginProvider& provider); + void setCodecPluginProvider(const CodecPluginProvider& provider); + void setInputPluginSettingsPersister(const InputPluginSettingsPersister& persister); private: - static DisplayPluginProvider _displayPluginProvider; - static InputPluginProvider _inputPluginProvider; - static CodecPluginProvider _codecPluginProvider; - static InputPluginSettingsPersister _inputSettingsPersister; - + DisplayPluginProvider _displayPluginProvider { []()->DisplayPluginList { return {}; } }; + InputPluginProvider _inputPluginProvider { []()->InputPluginList { return {}; } }; + CodecPluginProvider _codecPluginProvider { []()->CodecPluginList { return {}; } }; + InputPluginSettingsPersister _inputSettingsPersister { [](const InputPluginList& list) {} }; PluginContainer* _container { nullptr }; }; From 08784ff30c6006d4225d8b0d2a1718250cdcd049 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Sun, 25 Jun 2017 02:34:58 -0700 Subject: [PATCH 62/95] Fix toolbar button mouse click when they were added via the tablet scripting interface --- libraries/ui/src/ui/TabletScriptingInterface.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp index 8b3dc342e2..1e426dd8f0 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -623,7 +623,8 @@ TabletButtonProxy* TabletProxy::addButton(const QVariant& properties) { auto toolbarProxy = DependencyManager::get()->getSystemToolbarProxy(); if (toolbarProxy) { // copy properties from tablet button proxy to toolbar button proxy. - toolbarProxy->addButton(tabletButtonProxy->getProperties()); + auto toolbarButtonProxy = toolbarProxy->addButton(tabletButtonProxy->getProperties()); + tabletButtonProxy->setToolbarButtonProxy(toolbarButtonProxy); } } return tabletButtonProxy.data(); From db2b702221fbf0b02f8eab9f7b707f1e6398c143 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Mon, 26 Jun 2017 08:08:52 -0700 Subject: [PATCH 63/95] Fix reverbTest script --- scripts/developer/utilities/tools/reverbTest.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/developer/utilities/tools/reverbTest.js b/scripts/developer/utilities/tools/reverbTest.js index a7a6bad9d7..8d83140ecd 100644 --- a/scripts/developer/utilities/tools/reverbTest.js +++ b/scripts/developer/utilities/tools/reverbTest.js @@ -35,8 +35,8 @@ var audioOptions = new AudioEffectOptions({ wetDryMix: 50, }); -AudioDevice.setReverbOptions(audioOptions); -AudioDevice.setReverb(true); +Audio.setReverbOptions(audioOptions); +Audio.setReverb(true); print("Reverb is ON."); var panel = new Panel(10, 160); @@ -66,7 +66,7 @@ var parameters = [ ] function setter(name) { - return function(value) { audioOptions[name] = value; AudioDevice.setReverbOptions(audioOptions); } + return function(value) { audioOptions[name] = value; Audio.setReverbOptions(audioOptions); } } function getter(name) { @@ -89,7 +89,7 @@ Controller.mouseReleaseEvent.connect(function(event) { return panel.mouseRelease function scriptEnding() { panel.destroy(); - AudioDevice.setReverb(false); + Audio.setReverb(false); print("Reverb is OFF."); } Script.scriptEnding.connect(scriptEnding); From fba1a8ddaa284bb2be4ca791bdd5f6be8a553f76 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Mon, 26 Jun 2017 08:28:29 -0700 Subject: [PATCH 64/95] Fix halfDuplex script --- scripts/tutorials/halfDuplex.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/scripts/tutorials/halfDuplex.js b/scripts/tutorials/halfDuplex.js index e1ed132233..d4a993ae06 100644 --- a/scripts/tutorials/halfDuplex.js +++ b/scripts/tutorials/halfDuplex.js @@ -20,7 +20,7 @@ var averageLoudness = 0.0; var AVERAGING_TIME = 0.9; var LOUDNESS_THRESHOLD = 100; -var HYSTERESIS_GAP = 1.41; // 3db gap +var HYSTERESIS_GAP = 1.41; // 3dB gap var MICROPHONE_DISPLAY_NAME = "Microphone"; var debug = false; @@ -54,17 +54,13 @@ Script.update.connect(function () { print("Muted!"); } isMuted = true; - if (!AudioDevice.getMuted()) { - AudioDevice.toggleMute(); - } + Audio.muted = true; } else if (isMuted && (averageLoudness < LOUDNESS_THRESHOLD)) { if (debug) { print("UnMuted!"); } isMuted = false; - if (AudioDevice.getMuted()) { - AudioDevice.toggleMute(); - } + Audio.muted = false; } }); From d7416434bf9c869e26a96c5503252665ca98bc84 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 26 Jun 2017 11:38:24 -0700 Subject: [PATCH 65/95] added tool to visualize avatar-animation.json using graphviz. --- tools/avatar-json-to-dot.js | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 tools/avatar-json-to-dot.js diff --git a/tools/avatar-json-to-dot.js b/tools/avatar-json-to-dot.js new file mode 100644 index 0000000000..fcd75a99c1 --- /dev/null +++ b/tools/avatar-json-to-dot.js @@ -0,0 +1,27 @@ +// usage: +// node avatar-json-to-dot.js /path/to/avatar-animaton.json > out.dot +// +// Then if you have graphviz installed you can run the following command to generate a png. +// dot -Tpng out.dot > out.png + +var fs = require('fs'); +var filename = process.argv[2]; + +function dumpNodes(node) { + node.children.forEach(function (child) { + console.log(' ' + node.id + ' -> ' + child.id + ';'); + dumpNodes(child); + }); +} + +fs.readFile(filename, 'utf8', function (err, data) { + if (err) { + console.log('error opening ' + filename + ', err = ' + err); + } else { + var graph = JSON.parse(data); + console.log('digraph graphname {'); + console.log(' rankdir = "LR";'); + dumpNodes(graph.root); + console.log('}'); + } +}); From 8602d57a577c8344962bffd4d2a11a272ac17561 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 26 Jun 2017 14:09:10 -0700 Subject: [PATCH 66/95] Eliminated to wiggle while pucks are enabled. * Added new anim node AnimDefaultPose * AnimNodeLoader was changed to support the addition of the AnimDefaultPose node * Edited default avatar-animation.json to insert an AnimOverlay and AnimDefaultPose between the IK node and the rest of the "underPose". * Rig uses this to fade in default pose for the toes when the hip/feet puck are active. This effectively deadens the toe animations, without effecting the hand animations. * Also, the rig was changed to use the LimitCenter solution when the feet are enabled but the hips are not. --- .../resources/avatar/avatar-animation.json | 21 ++++------- libraries/animation/src/AnimDefaultPose.cpp | 34 ++++++++++++++++++ libraries/animation/src/AnimDefaultPose.h | 36 +++++++++++++++++++ libraries/animation/src/AnimNode.h | 1 + libraries/animation/src/AnimNodeLoader.cpp | 13 ++++++- libraries/animation/src/AnimOverlay.cpp | 15 ++++++++ libraries/animation/src/AnimOverlay.h | 2 ++ libraries/animation/src/Rig.cpp | 20 +++++++++-- 8 files changed, 124 insertions(+), 18 deletions(-) create mode 100644 libraries/animation/src/AnimDefaultPose.cpp create mode 100644 libraries/animation/src/AnimDefaultPose.h diff --git a/interface/resources/avatar/avatar-animation.json b/interface/resources/avatar/avatar-animation.json index 018987b58b..a493d8e9af 100644 --- a/interface/resources/avatar/avatar-animation.json +++ b/interface/resources/avatar/avatar-animation.json @@ -150,28 +150,19 @@ "children": [] }, { - "id": "hipsManipulatorOverlay", + "id": "defaultPoseOverlay", "type": "overlay", "data": { "alpha": 0.0, - "boneSet": "hipsOnly" + "alphaVar": "defaultPoseOverlayAlpha", + "boneSet": "fullBody", + "boneSetVar": "defaultPoseOverlayBoneSet" }, "children": [ { - "id": "hipsManipulator", - "type": "manipulator", + "id": "defaultPose", + "type": "defaultPose", "data": { - "alpha": 0.0, - "alphaVar": "hipsManipulatorAlpha", - "joints": [ - { - "jointName": "Hips", - "rotationType": "absolute", - "translationType": "absolute", - "rotationVar": "hipsManipulatorRotation", - "translationVar": "hipsManipulatorPosition" - } - ] }, "children": [] }, diff --git a/libraries/animation/src/AnimDefaultPose.cpp b/libraries/animation/src/AnimDefaultPose.cpp new file mode 100644 index 0000000000..70bcbe7c21 --- /dev/null +++ b/libraries/animation/src/AnimDefaultPose.cpp @@ -0,0 +1,34 @@ +// +// AnimDefaultPose.cpp +// +// Created by Anthony J. Thibault on 6/26/17. +// Copyright (c) 2017 High Fidelity, Inc. All rights reserved. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "AnimDefaultPose.h" + +AnimDefaultPose::AnimDefaultPose(const QString& id) : + AnimNode(AnimNode::Type::DefaultPose, id) +{ + +} + +AnimDefaultPose::~AnimDefaultPose() { + +} + +const AnimPoseVec& AnimDefaultPose::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) { + if (_skeleton) { + _poses = _skeleton->getRelativeDefaultPoses(); + } else { + _poses.clear(); + } + return _poses; +} + +const AnimPoseVec& AnimDefaultPose::getPosesInternal() const { + return _poses; +} diff --git a/libraries/animation/src/AnimDefaultPose.h b/libraries/animation/src/AnimDefaultPose.h new file mode 100644 index 0000000000..eefefac7af --- /dev/null +++ b/libraries/animation/src/AnimDefaultPose.h @@ -0,0 +1,36 @@ +// +// AnimDefaultPose.h +// +// Created by Anthony J. Thibault on 6/26/17. +// Copyright (c) 2017 High Fidelity, Inc. All rights reserved. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_AnimDefaultPose_h +#define hifi_AnimDefaultPose_h + +#include +#include "AnimNode.h" + +// Always returns the default pose of the current skeleton. + +class AnimDefaultPose : public AnimNode { +public: + AnimDefaultPose(const QString& id); + virtual ~AnimDefaultPose() override; + + virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) override; +protected: + // for AnimDebugDraw rendering + virtual const AnimPoseVec& getPosesInternal() const override; + + AnimPoseVec _poses; + + // no copies + AnimDefaultPose(const AnimDefaultPose&) = delete; + AnimDefaultPose& operator=(const AnimDefaultPose&) = delete; +}; + +#endif // hifi_AnimDefaultPose_h diff --git a/libraries/animation/src/AnimNode.h b/libraries/animation/src/AnimNode.h index 10db38f42e..6d9d35b19b 100644 --- a/libraries/animation/src/AnimNode.h +++ b/libraries/animation/src/AnimNode.h @@ -44,6 +44,7 @@ public: StateMachine, Manipulator, InverseKinematics, + DefaultPose, NumTypes }; using Pointer = std::shared_ptr; diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 2a1978127d..33f3d72756 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -23,6 +23,7 @@ #include "AnimStateMachine.h" #include "AnimManipulator.h" #include "AnimInverseKinematics.h" +#include "AnimDefaultPose.h" using NodeLoaderFunc = AnimNode::Pointer (*)(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); using NodeProcessFunc = bool (*)(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); @@ -35,6 +36,7 @@ static AnimNode::Pointer loadOverlayNode(const QJsonObject& jsonObj, const QStri static AnimNode::Pointer loadStateMachineNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer loadManipulatorNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); +static AnimNode::Pointer loadDefaultPoseNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); // called after children have been loaded // returns node on success, nullptr on failure. @@ -50,6 +52,7 @@ static const char* animNodeTypeToString(AnimNode::Type type) { case AnimNode::Type::StateMachine: return "stateMachine"; case AnimNode::Type::Manipulator: return "manipulator"; case AnimNode::Type::InverseKinematics: return "inverseKinematics"; + case AnimNode::Type::DefaultPose: return "defaultPose"; case AnimNode::Type::NumTypes: return nullptr; }; return nullptr; @@ -109,6 +112,7 @@ static NodeLoaderFunc animNodeTypeToLoaderFunc(AnimNode::Type type) { case AnimNode::Type::StateMachine: return loadStateMachineNode; case AnimNode::Type::Manipulator: return loadManipulatorNode; case AnimNode::Type::InverseKinematics: return loadInverseKinematicsNode; + case AnimNode::Type::DefaultPose: return loadDefaultPoseNode; case AnimNode::Type::NumTypes: return nullptr; }; return nullptr; @@ -123,6 +127,7 @@ static NodeProcessFunc animNodeTypeToProcessFunc(AnimNode::Type type) { case AnimNode::Type::StateMachine: return processStateMachineNode; case AnimNode::Type::Manipulator: return processDoNothing; case AnimNode::Type::InverseKinematics: return processDoNothing; + case AnimNode::Type::DefaultPose: return processDoNothing; case AnimNode::Type::NumTypes: return nullptr; }; return nullptr; @@ -347,7 +352,8 @@ static const char* boneSetStrings[AnimOverlay::NumBoneSets] = { "empty", "leftHand", "rightHand", - "hipsOnly" + "hipsOnly", + "bothFeet" }; static AnimOverlay::BoneSet stringToBoneSetEnum(const QString& str) { @@ -517,6 +523,11 @@ AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QS return node; } +static AnimNode::Pointer loadDefaultPoseNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { + auto node = std::make_shared(id); + return node; +} + void buildChildMap(std::map& map, AnimNode::Pointer node) { for (int i = 0; i < (int)node->getChildCount(); ++i) { map.insert(std::pair(node->getChild(i)->getID(), i)); diff --git a/libraries/animation/src/AnimOverlay.cpp b/libraries/animation/src/AnimOverlay.cpp index e086413dde..10594af20a 100644 --- a/libraries/animation/src/AnimOverlay.cpp +++ b/libraries/animation/src/AnimOverlay.cpp @@ -35,6 +35,7 @@ void AnimOverlay::buildBoneSet(BoneSet boneSet) { case LeftHandBoneSet: buildLeftHandBoneSet(); break; case RightHandBoneSet: buildRightHandBoneSet(); break; case HipsOnlyBoneSet: buildHipsOnlyBoneSet(); break; + case BothFeetBoneSet: buildBothFeetBoneSet(); break; default: case EmptyBoneSet: buildEmptyBoneSet(); break; } @@ -196,6 +197,20 @@ void AnimOverlay::buildHipsOnlyBoneSet() { _boneSetVec[hipsJoint] = 1.0f; } +void AnimOverlay::buildBothFeetBoneSet() { + assert(_skeleton); + buildEmptyBoneSet(); + int rightFoot = _skeleton->nameToJointIndex("RightFoot"); + for_each_child_joint(_skeleton, rightFoot, [&](int i) { + _boneSetVec[i] = 1.0f; + }); + int leftFoot = _skeleton->nameToJointIndex("LeftFoot"); + for_each_child_joint(_skeleton, leftFoot, [&](int i) { + _boneSetVec[i] = 1.0f; + }); +} + + // for AnimDebugDraw rendering const AnimPoseVec& AnimOverlay::getPosesInternal() const { return _poses; diff --git a/libraries/animation/src/AnimOverlay.h b/libraries/animation/src/AnimOverlay.h index ed9439feb7..8b6e1529fc 100644 --- a/libraries/animation/src/AnimOverlay.h +++ b/libraries/animation/src/AnimOverlay.h @@ -38,6 +38,7 @@ public: LeftHandBoneSet, RightHandBoneSet, HipsOnlyBoneSet, + BothFeetBoneSet, NumBoneSets }; @@ -77,6 +78,7 @@ public: void buildLeftHandBoneSet(); void buildRightHandBoneSet(); void buildHipsOnlyBoneSet(); + void buildBothFeetBoneSet(); // no copies AnimOverlay(const AnimOverlay&) = delete; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 3f63f226e7..3d04b0b26f 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -27,6 +27,7 @@ #include "AnimationLogging.h" #include "AnimClip.h" #include "AnimInverseKinematics.h" +#include "AnimOverlay.h" #include "AnimSkeleton.h" #include "AnimUtil.h" #include "IKTarget.h" @@ -1459,13 +1460,28 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo updateFeet(leftFootEnabled, rightFootEnabled, params.controllerPoses[ControllerType_LeftFoot], params.controllerPoses[ControllerType_RightFoot]); - if (hipsEnabled) { + // if the hips or the feet are being controlled. + if (hipsEnabled || rightFootEnabled || leftFootEnabled) { + // for more predictable IK solve from the center of the joint limits, not from the underpose _animVars.set("solutionSource", (int)AnimInverseKinematics::SolutionSource::RelaxToLimitCenterPoses); + + // replace the feet animation with the default pose, this is to prevent unexpected toe wiggling. + _animVars.set("defaultPoseOverlayAlpha", 1.0f); + _animVars.set("defaultPoseOverlayBoneSet", (int)AnimOverlay::BothFeetBoneSet); + } else { + // augment the IK with the underPose. + _animVars.set("solutionSource", (int)AnimInverseKinematics::SolutionSource::RelaxToUnderPoses); + + // feet should follow source animation + _animVars.unset("defaultPoseOverlayAlpha"); + _animVars.unset("defaultPoseOverlayBoneSet"); + } + + if (hipsEnabled) { _animVars.set("hipsType", (int)IKTarget::Type::RotationAndPosition); _animVars.set("hipsPosition", params.controllerPoses[ControllerType_Hips].trans()); _animVars.set("hipsRotation", params.controllerPoses[ControllerType_Hips].rot()); } else { - _animVars.set("solutionSource", (int)AnimInverseKinematics::SolutionSource::RelaxToUnderPoses); _animVars.set("hipsType", (int)IKTarget::Type::Unknown); } From bf7da71eafcbe89124c220e0be166e6c96bed940 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 26 Jun 2017 15:07:22 -0700 Subject: [PATCH 67/95] use QEventLoop and a timer for a less CPU using sleep + event-processing --- libraries/script-engine/src/ScriptEngine.cpp | 29 +++++++++----------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index d9b41bb55d..017124af1c 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -1067,24 +1067,21 @@ void ScriptEngine::run() { // on shutdown and stop... so we want to loop and sleep until we've spent our time in // purgatory, constantly checking to see if our script was asked to end bool processedEvents = false; - while (!_isFinished && clock::now() < sleepUntil) { - - { - PROFILE_RANGE(script, "processEvents-sleep"); - QCoreApplication::processEvents(); // before we sleep again, give events a chance to process + if (!_isFinished) { + PROFILE_RANGE(script, "processEvents-sleep"); + std::chrono::milliseconds sleepFor = + std::chrono::duration_cast(sleepUntil - clock::now()); + if (sleepFor > std::chrono::milliseconds(0)) { + QEventLoop loop; + QTimer timer; + timer.setSingleShot(true); + connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit())); + timer.start(sleepFor); + loop.exec(); + } else { + QCoreApplication::processEvents(); } processedEvents = true; - - // If after processing events, we're past due, exit asap - if (clock::now() >= sleepUntil) { - break; - } - - // We only want to sleep a small amount so that any pending events (like timers or invokeMethod events) - // will be able to process quickly. - static const int SMALL_SLEEP_AMOUNT = 100; - auto smallSleepUntil = clock::now() + static_cast(SMALL_SLEEP_AMOUNT); - std::this_thread::sleep_until(smallSleepUntil); } PROFILE_RANGE(script, "ScriptMainLoop"); From 4f672f93d20ed87aec24b57f3fd37c3a6d2e76d8 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 26 Jun 2017 16:03:39 -0700 Subject: [PATCH 68/95] avoid cast that isn't always available --- libraries/script-engine/src/ScriptEngine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 017124af1c..81b815a34b 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -1076,7 +1076,7 @@ void ScriptEngine::run() { QTimer timer; timer.setSingleShot(true); connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit())); - timer.start(sleepFor); + timer.start(sleepFor.count()); loop.exec(); } else { QCoreApplication::processEvents(); From 2814c945821edc60f14d692df69812896407e9c2 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 26 Jun 2017 17:43:51 -0700 Subject: [PATCH 69/95] Hips should no longer waggle when feet are active --- libraries/animation/src/AnimInverseKinematics.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 3e948a4f5b..5d98357ea3 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -870,9 +870,9 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars float scaleFactor = ((offsetLength - MIN_HIPS_OFFSET_LENGTH) / offsetLength); glm::vec3 hipsOffset = scaleFactor * _hipsOffset; if (_hipsParentIndex == -1) { - _relativePoses[_hipsIndex].trans() = underPoses[_hipsIndex].trans() + hipsOffset; + _relativePoses[_hipsIndex].trans() = _relativePoses[_hipsIndex].trans() + hipsOffset; } else { - auto absHipsPose = _skeleton->getAbsolutePose(_hipsIndex, underPoses); + auto absHipsPose = _skeleton->getAbsolutePose(_hipsIndex, _relativePoses); absHipsPose.trans() += hipsOffset; _relativePoses[_hipsIndex] = _skeleton->getAbsolutePose(_hipsParentIndex, _relativePoses).inverse() * absHipsPose; } @@ -1732,6 +1732,10 @@ void AnimInverseKinematics::initRelativePosesFromSolutionSource(SolutionSource s break; case SolutionSource::RelaxToLimitCenterPoses: blendToPoses(_limitCenterPoses, underPoses, RELAX_BLEND_FACTOR); + // special case for hips: copy over hips pose whether or not IK is enabled. + if (_hipsIndex >= 0 && _hipsIndex < _relativePoses.size()) { + _relativePoses[_hipsIndex] = _limitCenterPoses[_hipsIndex]; + } break; case SolutionSource::PreviousSolution: // do nothing... _relativePoses is already the previous solution From de88a34e977b1cd33913216a911ab813129e195d Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 26 Jun 2017 23:18:21 -0700 Subject: [PATCH 70/95] Fix toolbar button activation states --- scripts/system/audio.js | 12 ++++-------- scripts/system/help.js | 3 ++- scripts/system/marketplaces/marketplaces.js | 12 ++---------- scripts/system/menu.js | 9 ++------- scripts/system/pal.js | 16 ++++++---------- scripts/system/snapshot.js | 10 +++------- 6 files changed, 19 insertions(+), 43 deletions(-) diff --git a/scripts/system/audio.js b/scripts/system/audio.js index cb9589ae9e..0a3471fa81 100644 --- a/scripts/system/audio.js +++ b/scripts/system/audio.js @@ -15,6 +15,7 @@ var TABLET_BUTTON_NAME = "AUDIO"; var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; +var AUDIO_QML_SOURCE = "../audio/Audio.qml"; var MUTE_ICONS = { icon: "icons/tablet-icons/mic-mute-i.svg", @@ -34,7 +35,6 @@ function onMuteToggled() { } } -var shouldActivateButton = false; var onAudioScreen = false; function onClicked() { @@ -44,18 +44,14 @@ function onClicked() { } else { var entity = HMD.tabletID; Entities.editEntity(entity, { textures: JSON.stringify({ "tex.close": HOME_BUTTON_TEXTURE }) }); - shouldActivateButton = true; - shouldActivateButton = true; - tablet.loadQMLSource("../audio/Audio.qml"); - onAudioScreen = true; + tablet.loadQMLSource(AUDIO_QML_SOURCE); } } function onScreenChanged(type, url) { + onAudioScreen = (type === "QML" && url === AUDIO_QML_SOURCE); // for toolbar mode: change button to active when window is first openend, false otherwise. - button.editProperties({isActive: shouldActivateButton}); - shouldActivateButton = false; - onAudioScreen = false; + button.editProperties({isActive: onAudioScreen}); } var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); diff --git a/scripts/system/help.js b/scripts/system/help.js index a335b2ef9c..1265a5597b 100644 --- a/scripts/system/help.js +++ b/scripts/system/help.js @@ -40,7 +40,8 @@ } function onScreenChanged(type, url) { - onHelpScreen = false; + onHelpScreen = type === "Web" && url.startsWith("../../../html/tabletHelp.html"); + button.editProperties({ isActive: onHelpScreen }); } button.clicked.connect(onClicked); diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 4d26bcadb6..3be8143830 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -52,17 +52,11 @@ function onMessageBoxClosed(id, button) { Window.messageBoxClosed.connect(onMessageBoxClosed); -var shouldActivateButton = false; var onMarketplaceScreen = false; function showMarketplace() { UserActivityLogger.openedMarketplace(); - - shouldActivateButton = true; - tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); - onMarketplaceScreen = true; - tablet.webEventReceived.connect(function (message) { if (message === GOTO_DIRECTORY) { @@ -122,7 +116,6 @@ function onClick() { if (onMarketplaceScreen) { // for toolbar-mode: go back to home screen, this will close the window. tablet.gotoHomeScreen(); - onMarketplaceScreen = false; } else { var entity = HMD.tabletID; Entities.editEntity(entity, {textures: JSON.stringify({"tex.close": HOME_BUTTON_TEXTURE})}); @@ -131,10 +124,9 @@ function onClick() { } function onScreenChanged(type, url) { + onMarketplaceScreen = type === "Web" && url === MARKETPLACE_URL_INITIAL // for toolbar mode: change button to active when window is first openend, false otherwise. - marketplaceButton.editProperties({isActive: shouldActivateButton}); - shouldActivateButton = false; - onMarketplaceScreen = false; + marketplaceButton.editProperties({isActive: onMarketplaceScreen}); } marketplaceButton.clicked.connect(onClick); diff --git a/scripts/system/menu.js b/scripts/system/menu.js index 4ad5958144..c7a44d3e48 100644 --- a/scripts/system/menu.js +++ b/scripts/system/menu.js @@ -21,7 +21,6 @@ var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet- sortOrder: 3 }); - var shouldActivateButton = false; var onMenuScreen = false; function onClicked() { @@ -31,17 +30,13 @@ var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet- } else { var entity = HMD.tabletID; Entities.editEntity(entity, {textures: JSON.stringify({"tex.close": HOME_BUTTON_TEXTURE})}); - shouldActivateButton = true; tablet.gotoMenuScreen(); - onMenuScreen = true; } } function onScreenChanged(type, url) { - // for toolbar mode: change button to active when window is first openend, false otherwise. - button.editProperties({isActive: shouldActivateButton}); - shouldActivateButton = false; - onMenuScreen = false; + onMenuScreen = type === "Menu"; + button.editProperties({isActive: onMenuScreen}); } button.clicked.connect(onClicked); diff --git a/scripts/system/pal.js b/scripts/system/pal.js index 6c1652c700..2c81622668 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -40,7 +40,7 @@ var HOVER_TEXTURES = { var UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6}; var SELECTED_COLOR = {red: 0xF3, green: 0x91, blue: 0x29}; var HOVER_COLOR = {red: 0xD0, green: 0xD0, blue: 0xD0}; // almost white for now - +var PAL_QML_SOURCE = "../Pal.qml"; var conserveResources = true; Script.include("/~/system/libraries/controllers.js"); @@ -727,17 +727,14 @@ function tabletVisibilityChanged() { } var onPalScreen = false; -var shouldActivateButton = false; function onTabletButtonClicked() { if (onPalScreen) { // for toolbar-mode: go back to home screen, this will close the window. tablet.gotoHomeScreen(); } else { - shouldActivateButton = true; - tablet.loadQMLSource("../Pal.qml"); + tablet.loadQMLSource(PAL_QML_SOURCE); tablet.tabletShownChanged.connect(tabletVisibilityChanged); - onPalScreen = true; Users.requestsDomainListData = true; populateNearbyUserList(); isWired = true; @@ -765,14 +762,13 @@ function wireEventBridge(on) { } function onTabletScreenChanged(type, url) { - wireEventBridge(shouldActivateButton); + onPalScreen = (type === "QML" && url === PAL_QML_SOURCE); + wireEventBridge(onPalScreen); // for toolbar mode: change button to active when window is first openend, false otherwise. - button.editProperties({isActive: shouldActivateButton}); - shouldActivateButton = false; - onPalScreen = false; + button.editProperties({isActive: onPalScreen}); // disable sphere overlays when not on pal screen. - if (type !== "QML" || url !== "../Pal.qml") { + if (!onPalScreen) { off(); } } diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 6321c17ded..c60e3a67f7 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -377,18 +377,15 @@ function fillImageDataFromPrevious() { var SNAPSHOT_REVIEW_URL = Script.resolvePath("html/SnapshotReview.html"); var isInSnapshotReview = false; -var shouldActivateButton = false; function onButtonClicked() { if (isInSnapshotReview){ // for toolbar-mode: go back to home screen, this will close the window. tablet.gotoHomeScreen(); } else { - shouldActivateButton = true; fillImageDataFromPrevious(); tablet.gotoWebScreen(SNAPSHOT_REVIEW_URL); tablet.webEventReceived.connect(onMessage); HMD.openTablet(); - isInSnapshotReview = true; } } @@ -662,11 +659,10 @@ function maybeDeleteSnapshotStories() { storyIDsToMaybeDelete = []; } function onTabletScreenChanged(type, url) { - button.editProperties({ isActive: shouldActivateButton }); - shouldActivateButton = false; - if (isInSnapshotReview) { + isInSnapshotReview = (type === "Web" && url === SNAPSHOT_REVIEW_URL); + button.editProperties({ isActive: isInSnapshotReview }); + if (!isInSnapshotReview) { tablet.webEventReceived.disconnect(onMessage); - isInSnapshotReview = false; } } function onUsernameChanged() { From 01155ba4e38e90800650a5a2324a61eeae61b15f Mon Sep 17 00:00:00 2001 From: David Kelly Date: Tue, 27 Jun 2017 08:47:54 -0700 Subject: [PATCH 71/95] this handles redirecting ktx_cache, data8, prepared --- interface/src/Application.cpp | 2 -- interface/src/Application.h | 3 +++ interface/src/main.cpp | 22 ++++++++++++-------- libraries/networking/src/AssetClient.cpp | 13 ++++++------ libraries/networking/src/AssetClient.h | 4 +++- libraries/networking/src/ResourceManager.cpp | 8 +++++-- libraries/networking/src/ResourceManager.h | 5 +++++ libraries/shared/src/PathUtils.cpp | 13 +++++++++++- libraries/shared/src/PathUtils.h | 2 +- 9 files changed, 50 insertions(+), 22 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 35d00ba34a..9052f082dc 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -37,8 +37,6 @@ #include #include -#include - #include #include diff --git a/interface/src/Application.h b/interface/src/Application.h index cab42d1e1c..a7aded006b 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -297,6 +297,7 @@ public: void setAvatarOverrideUrl(const QUrl& url, bool save); QUrl getAvatarOverrideUrl() { return _avatarOverrideUrl; } bool getSaveAvatarOverrideUrl() { return _saveAvatarOverrideUrl; } + void setCacheOverrideDir(const QString& dirName) { _cacheDir = dirName; } signals: void svoImportRequested(const QString& url); @@ -688,5 +689,7 @@ private: QUrl _avatarOverrideUrl; bool _saveAvatarOverrideUrl { false }; + + QString _cacheDir; }; #endif // hifi_Application_h diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 81bf0244d9..67e248506f 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include "AddressManager.h" #include "Application.h" @@ -71,7 +72,7 @@ int main(int argc, const char* argv[]) { QCommandLineOption runServerOption("runServer", "Whether to run the server"); QCommandLineOption serverContentPathOption("serverContentPath", "Where to find server content", "serverContentPath"); QCommandLineOption allowMultipleInstancesOption("allowMultipleInstances", "Allow multiple instances to run"); - QCommandLineOption overrideAppLocalDataPathOption("cache", "set test cache directory"); + QCommandLineOption overrideAppLocalDataPathOption("cache", "set test cache

", "dir"); parser.addOption(urlOption); parser.addOption(noUpdaterOption); parser.addOption(checkMinSpecOption); @@ -99,14 +100,17 @@ int main(int argc, const char* argv[]) { instanceMightBeRunning = false; } if (parser.isSet(overrideAppLocalDataPathOption)) { - // get standard path - auto standardAppDataLocation = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); - // set to test so all future paths are the test paths - QStandardPaths::setTestModeEnabled(true); - // now, we need to link everything in AppDataLocation to the test AppDataLocation. This - // leaves the test location for AppLocalDataLocation alone, but allows all the stuff in - // AppDataLocation to be usable - QFile::link(standardAppDataLocation, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)); + // get dir to use for cache + QString cacheDir = parser.value(overrideAppLocalDataPathOption); + if (!cacheDir.isEmpty()) { + // tell everyone to use the right cache location + // + // this handles data8 and prepared + ResourceManager::setCacheDir(cacheDir); + + // this does the ktx_cache + PathUtils::getAppLocalDataPath(cacheDir); + } } if (instanceMightBeRunning) { diff --git a/libraries/networking/src/AssetClient.cpp b/libraries/networking/src/AssetClient.cpp index 3faa9981dc..e97660da4c 100644 --- a/libraries/networking/src/AssetClient.cpp +++ b/libraries/networking/src/AssetClient.cpp @@ -31,7 +31,7 @@ MessageID AssetClient::_currentID = 0; -AssetClient::AssetClient() { +AssetClient::AssetClient(const QString& cacheDir) : _cacheDir(cacheDir) { setCustomDeleter([](Dependency* dependency){ static_cast(dependency)->deleteLater(); }); @@ -55,14 +55,15 @@ void AssetClient::init() { // Setup disk cache if not already auto& networkAccessManager = NetworkAccessManager::getInstance(); if (!networkAccessManager.cache()) { - QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation); - cachePath = !cachePath.isEmpty() ? cachePath : "interfaceCache"; - + if (_cacheDir.isEmpty()) { + QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation); + _cacheDir = !cachePath.isEmpty() ? cachePath : "interfaceCache"; + } QNetworkDiskCache* cache = new QNetworkDiskCache(); cache->setMaximumCacheSize(MAXIMUM_CACHE_SIZE); - cache->setCacheDirectory(cachePath); + cache->setCacheDirectory(_cacheDir); networkAccessManager.setCache(cache); - qInfo() << "ResourceManager disk cache setup at" << cachePath + qInfo() << "ResourceManager disk cache setup at" << _cacheDir << "(size:" << MAXIMUM_CACHE_SIZE / BYTES_PER_GIGABYTES << "GB)"; } } diff --git a/libraries/networking/src/AssetClient.h b/libraries/networking/src/AssetClient.h index cab4a4f5b0..2bc694f367 100644 --- a/libraries/networking/src/AssetClient.h +++ b/libraries/networking/src/AssetClient.h @@ -49,7 +49,7 @@ using ProgressCallback = std::function class AssetClient : public QObject, public Dependency { Q_OBJECT public: - AssetClient(); + AssetClient(const QString& cacheDir=""); Q_INVOKABLE GetMappingRequest* createGetMappingRequest(const AssetPath& path); Q_INVOKABLE GetAllMappingsRequest* createGetAllMappingsRequest(); @@ -109,6 +109,8 @@ private: std::unordered_map> _pendingInfoRequests; std::unordered_map> _pendingUploads; + QString _cacheDir; + friend class AssetRequest; friend class AssetUpload; friend class MappingRequest; diff --git a/libraries/networking/src/ResourceManager.cpp b/libraries/networking/src/ResourceManager.cpp index e2c1cf2431..e4357ca507 100644 --- a/libraries/networking/src/ResourceManager.cpp +++ b/libraries/networking/src/ResourceManager.cpp @@ -27,7 +27,7 @@ QThread ResourceManager::_thread; ResourceManager::PrefixMap ResourceManager::_prefixMap; QMutex ResourceManager::_prefixMapLock; - +QString ResourceManager::_cacheDir; void ResourceManager::setUrlPrefixOverride(const QString& prefix, const QString& replacement) { QMutexLocker locker(&_prefixMapLock); @@ -78,7 +78,7 @@ QUrl ResourceManager::normalizeURL(const QUrl& originalUrl) { void ResourceManager::init() { _thread.setObjectName("Resource Manager Thread"); - auto assetClient = DependencyManager::set(); + auto assetClient = DependencyManager::set(_cacheDir); assetClient->moveToThread(&_thread); QObject::connect(&_thread, &QThread::started, assetClient.data(), &AssetClient::init); @@ -164,3 +164,7 @@ bool ResourceManager::resourceExists(const QUrl& url) { return false; } +void ResourceManager::setCacheDir(const QString& cacheDir) { + // TODO: check for existence? + _cacheDir = cacheDir; +} diff --git a/libraries/networking/src/ResourceManager.h b/libraries/networking/src/ResourceManager.h index 41da892701..699573ddd6 100644 --- a/libraries/networking/src/ResourceManager.h +++ b/libraries/networking/src/ResourceManager.h @@ -40,6 +40,9 @@ public: // to return to the calling thread so that events can still be processed. static bool resourceExists(const QUrl& url); + // adjust where we persist the cache + static void setCacheDir(const QString& cacheDir); + private: static QThread _thread; @@ -47,6 +50,8 @@ private: static PrefixMap _prefixMap; static QMutex _prefixMapLock; + + static QString _cacheDir; }; #endif diff --git a/libraries/shared/src/PathUtils.cpp b/libraries/shared/src/PathUtils.cpp index 9bf9d7bdcf..1fe9e2f83d 100644 --- a/libraries/shared/src/PathUtils.cpp +++ b/libraries/shared/src/PathUtils.cpp @@ -34,7 +34,18 @@ QString PathUtils::getAppDataPath() { return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/"; } -QString PathUtils::getAppLocalDataPath() { +QString PathUtils::getAppLocalDataPath(const QString& overridePath /* = "" */) { + static QString overriddenPath = ""; + // set the overridden path if one was passed in + if (!overridePath.isEmpty()) { + overriddenPath = overridePath; + } + // return overridden path if set + if (!overriddenPath.isEmpty()) { + return overriddenPath; + } + + // otherwise return standard path return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/"; } diff --git a/libraries/shared/src/PathUtils.h b/libraries/shared/src/PathUtils.h index 14eb81dd9a..42dd09c8b0 100644 --- a/libraries/shared/src/PathUtils.h +++ b/libraries/shared/src/PathUtils.h @@ -28,7 +28,7 @@ public: static const QString& resourcesPath(); static QString getAppDataPath(); - static QString getAppLocalDataPath(); + static QString getAppLocalDataPath(const QString& overridePath = ""); static QString getAppDataFilePath(const QString& filename); static QString getAppLocalDataFilePath(const QString& filename); From d4122cff5ad20cf5b9db00d62f2e72c3cc9b4a61 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 27 Jun 2017 11:29:08 -0700 Subject: [PATCH 72/95] warning fix --- libraries/animation/src/AnimInverseKinematics.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 5d98357ea3..d7076a443e 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -1733,7 +1733,7 @@ void AnimInverseKinematics::initRelativePosesFromSolutionSource(SolutionSource s case SolutionSource::RelaxToLimitCenterPoses: blendToPoses(_limitCenterPoses, underPoses, RELAX_BLEND_FACTOR); // special case for hips: copy over hips pose whether or not IK is enabled. - if (_hipsIndex >= 0 && _hipsIndex < _relativePoses.size()) { + if (_hipsIndex >= 0 && _hipsIndex < (int)_relativePoses.size()) { _relativePoses[_hipsIndex] = _limitCenterPoses[_hipsIndex]; } break; From dcdf07191b87f2bcaf4ee6ace7a9dd4b90fb2e69 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Tue, 27 Jun 2017 11:29:44 -0700 Subject: [PATCH 73/95] Fast acosf() using polynomial approximation --- libraries/shared/src/AudioHelpers.h | 42 +++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/libraries/shared/src/AudioHelpers.h b/libraries/shared/src/AudioHelpers.h index b43764ef5d..d75733e678 100644 --- a/libraries/shared/src/AudioHelpers.h +++ b/libraries/shared/src/AudioHelpers.h @@ -66,6 +66,48 @@ static inline float fastExp2f(float x) { return x * xi.f; } +// +// on x86 architecture, assume that SSE2 is present +// +#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__x86_64__) + +#include +// inline sqrtss, without requiring /fp:fast +static inline float fastSqrtf(float x) { + return _mm_cvtss_f32(_mm_sqrt_ss(_mm_set_ss(x))); +} + +#else + +static inline float fastSqrtf(float x) { + return sqrtf(x); +} + +#endif + +// +// for -1 <= x <= 1, returns acos(x) +// otherwise, returns NaN +// +// abs |error| < 7e-5, smooth +// +static inline float fastAcosf(float x) { + + union { float f; int32_t i; } xi = { x }; + + int32_t sign = xi.i & 0x80000000; + xi.i ^= sign; // fabs(x) + + // compute sqrt(1-x) in parallel + float r = fastSqrtf(1.0f - xi.f); + + // polynomial for acos(x)/sqrt(1-x) over x=[0,1] + xi.f = ((-0.0198439236f * xi.f + 0.0762021306f) * xi.f + -0.212940971f) * xi.f + 1.57079633f; + + xi.f *= r; + return (sign ? 3.141592654f - xi.f : xi.f); +} + // // Quantize a non-negative gain value to the nearest 0.5dB, and pack to a byte. // From 241231c00ceb173cb3139fe78b5db60fde4b8872 Mon Sep 17 00:00:00 2001 From: Vladyslav Stelmakhovskyi Date: Tue, 27 Jun 2017 22:14:58 +0200 Subject: [PATCH 74/95] file lists works, but not initial yet --- .../qml/dialogs/TabletFileDialog.qml | 50 ++++++++++++++----- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/interface/resources/qml/dialogs/TabletFileDialog.qml b/interface/resources/qml/dialogs/TabletFileDialog.qml index 5e33663436..f77cc7c78a 100644 --- a/interface/resources/qml/dialogs/TabletFileDialog.qml +++ b/interface/resources/qml/dialogs/TabletFileDialog.qml @@ -25,11 +25,14 @@ import "fileDialog" //FIXME implement shortcuts for favorite location TabletModalWindow { id: root + anchors.fill: parent width: parent.width height: parent.height HifiConstants { id: hifi } + property var filesModel: ListModel { } + Settings { category: "FileDialog" property alias width: root.width @@ -250,7 +253,9 @@ TabletModalWindow { } currentSelectionUrl = helper.pathToUrl(fileTableView.model.get(row).filePath); - currentSelectionIsFolder = fileTableView.model.isFolder(row); + currentSelectionIsFolder = fileTableView.model !== filesModel ? + fileTableView.model.isFolder(row) : + fileTableModel.isFolder(row); if (root.selectDirectory || !currentSelectionIsFolder) { currentSelection.text = capitalizeDrive(helper.urlToPath(currentSelectionUrl)); } else { @@ -288,7 +293,7 @@ TabletModalWindow { } onFolderChanged: { - fileTableModel.update(); // Update once the data from the folder change is available. + refreshTimer.start() } function getItem(index, field) { @@ -328,7 +333,20 @@ TabletModalWindow { } } - ListModel { + Timer { + id: refreshTimer + interval: 100 + repeat: false + running: false + onTriggered: fileTableModel.update() + } + + Component { + id: filesModelBuilder + ListModel { } + } + + QtObject { id: fileTableModel // FolderListModel has a couple of problems: @@ -380,7 +398,11 @@ TabletModalWindow { if (row === -1) { return false; } - return get(row).fileIsDir; + return filesModel.get(row).fileIsDir; + } + + function get(row) { + return filesModel.get(row) } function update() { @@ -398,7 +420,8 @@ TabletModalWindow { rows = 0, i; - clear(); + console.log("dialog.qml updating") + var newFilesModel = filesModelBuilder.createObject(root); comparisonFunction = sortOrder === Qt.AscendingOrder ? function(a, b) { return a < b; } @@ -420,7 +443,7 @@ TabletModalWindow { while (lower < upper) { middle = Math.floor((lower + upper) / 2); var lessThan; - if (comparisonFunction(sortValue, get(middle)[sortField])) { + if (comparisonFunction(sortValue, newFilesModel.get(middle)[sortField])) { lessThan = true; upper = middle; } else { @@ -429,7 +452,7 @@ TabletModalWindow { } } - insert(lower, { + newFilesModel.insert(lower, { fileName: fileName, fileModified: (fileIsDir ? new Date(0) : model.getItem(i, "fileModified")), fileSize: model.getItem(i, "fileSize"), @@ -440,6 +463,7 @@ TabletModalWindow { rows++; } + filesModel = newFilesModel; d.clearSelection(); } @@ -467,7 +491,7 @@ TabletModalWindow { sortIndicatorOrder: Qt.AscendingOrder sortIndicatorVisible: true - model: fileTableModel + model: filesModel function updateSort() { model.sortOrder = sortIndicatorOrder; @@ -559,11 +583,12 @@ TabletModalWindow { } function navigateToCurrentRow() { + var currentModel = fileTableView.model !== filesModel ? fileTableView.model : fileTableModel var row = fileTableView.currentRow - var isFolder = model.isFolder(row); - var file = model.get(row).filePath; + var isFolder = currentModel.isFolder(row); + var file = currentModel.get(row).filePath; if (isFolder) { - fileTableView.model.folder = helper.pathToUrl(file); + currentModel.folder = helper.pathToUrl(file); } else { okAction.trigger(); } @@ -578,7 +603,8 @@ TabletModalWindow { var newPrefix = prefix + event.text.toLowerCase(); var matchedIndex = -1; for (var i = 0; i < model.count; ++i) { - var name = model.get(i).fileName.toLowerCase(); + var name = model !== filesModel ? model.get(i).fileName.toLowerCase() : + filesModel.get(i).fileName.toLowerCase(); if (0 === name.indexOf(newPrefix)) { matchedIndex = i; break; From ad970c9f5c8ec1712ecd7974f83086df4d8a3bcb Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Tue, 27 Jun 2017 13:45:18 -0700 Subject: [PATCH 75/95] fast compute of HRTF azimuth --- assignment-client/src/audio/AudioMixerSlave.cpp | 17 +++++++++++------ libraries/audio-client/src/AudioClient.cpp | 17 ++++++++++------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/assignment-client/src/audio/AudioMixerSlave.cpp b/assignment-client/src/audio/AudioMixerSlave.cpp index 2d800c3561..14b4fdea68 100644 --- a/assignment-client/src/audio/AudioMixerSlave.cpp +++ b/assignment-client/src/audio/AudioMixerSlave.cpp @@ -545,7 +545,6 @@ float computeAzimuth(const AvatarAudioStream& listeningNodeStream, const Positio const glm::vec3& relativePosition) { glm::quat inverseOrientation = glm::inverse(listeningNodeStream.getOrientation()); - // Compute sample delay for the two ears to create phase panning glm::vec3 rotatedSourcePosition = inverseOrientation * relativePosition; // project the rotated source position vector onto the XZ plane @@ -553,11 +552,17 @@ float computeAzimuth(const AvatarAudioStream& listeningNodeStream, const Positio const float SOURCE_DISTANCE_THRESHOLD = 1e-30f; - if (glm::length2(rotatedSourcePosition) > SOURCE_DISTANCE_THRESHOLD) { + float rotatedSourcePositionLength2 = glm::length2(rotatedSourcePosition); + if (rotatedSourcePositionLength2 > SOURCE_DISTANCE_THRESHOLD) { + // produce an oriented angle about the y-axis - return glm::orientedAngle(glm::vec3(0.0f, 0.0f, -1.0f), glm::normalize(rotatedSourcePosition), glm::vec3(0.0f, -1.0f, 0.0f)); - } else { - // there is no distance between listener and source - return no azimuth - return 0; + // return glm::orientedAngle(glm::vec3(0.0f, 0.0f, -1.0f), glm::normalize(rotatedSourcePosition), glm::vec3(0.0f, -1.0f, 0.0f)); + glm::vec3 direction = rotatedSourcePosition * (1.0f / fastSqrtf(rotatedSourcePositionLength2)); + float angle = fastAcosf(glm::clamp(-direction.z, -1.0f, 1.0f)); + return (direction.x < 0.0f) ? -angle : angle; + + } else { + // no azimuth if they are in same spot + return 0.0f; } } diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index fc54a04a5e..27e70e5f01 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -48,6 +48,7 @@ #include "AudioClientLogging.h" #include "AudioLogging.h" +#include "AudioHelpers.h" #include "AudioClient.h" @@ -1688,23 +1689,25 @@ int AudioClient::calculateNumberOfFrameSamples(int numBytes) const { } float AudioClient::azimuthForSource(const glm::vec3& relativePosition) { - // copied from AudioMixer, more or less glm::quat inverseOrientation = glm::inverse(_orientationGetter()); - // compute sample delay for the 2 ears to create phase panning glm::vec3 rotatedSourcePosition = inverseOrientation * relativePosition; - // project the rotated source position vector onto x-y plane + // project the rotated source position vector onto the XZ plane rotatedSourcePosition.y = 0.0f; static const float SOURCE_DISTANCE_THRESHOLD = 1e-30f; - if (glm::length2(rotatedSourcePosition) > SOURCE_DISTANCE_THRESHOLD) { + float rotatedSourcePositionLength2 = glm::length2(rotatedSourcePosition); + if (rotatedSourcePositionLength2 > SOURCE_DISTANCE_THRESHOLD) { // produce an oriented angle about the y-axis - return glm::orientedAngle(glm::vec3(0.0f, 0.0f, -1.0f), glm::normalize(rotatedSourcePosition), glm::vec3(0.0f, -1.0f, 0.0f)); - } else { - + // return glm::orientedAngle(glm::vec3(0.0f, 0.0f, -1.0f), glm::normalize(rotatedSourcePosition), glm::vec3(0.0f, -1.0f, 0.0f)); + glm::vec3 direction = rotatedSourcePosition * (1.0f / fastSqrtf(rotatedSourcePositionLength2)); + float angle = fastAcosf(glm::clamp(-direction.z, -1.0f, 1.0f)); + return (direction.x < 0.0f) ? -angle : angle; + + } else { // no azimuth if they are in same spot return 0.0f; } From a00cd425668ac30b92bc3fe8c612da25c3a94830 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Tue, 27 Jun 2017 14:01:28 -0700 Subject: [PATCH 76/95] fast compute of HRTF gain --- assignment-client/src/audio/AudioMixerSlave.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/assignment-client/src/audio/AudioMixerSlave.cpp b/assignment-client/src/audio/AudioMixerSlave.cpp index 14b4fdea68..9cfc659c14 100644 --- a/assignment-client/src/audio/AudioMixerSlave.cpp +++ b/assignment-client/src/audio/AudioMixerSlave.cpp @@ -497,13 +497,14 @@ float computeGain(const AvatarAudioStream& listeningNodeStream, const Positional // avatar: apply fixed off-axis attenuation to make them quieter as they turn away } else if (!isEcho && (streamToAdd.getType() == PositionalAudioStream::Microphone)) { glm::vec3 rotatedListenerPosition = glm::inverse(streamToAdd.getOrientation()) * relativePosition; - float angleOfDelivery = glm::angle(glm::vec3(0.0f, 0.0f, -1.0f), - glm::normalize(rotatedListenerPosition)); + + // float angleOfDelivery = glm::angle(glm::vec3(0.0f, 0.0f, -1.0f), glm::normalize(rotatedListenerPosition)); + glm::vec3 direction = glm::normalize(rotatedListenerPosition); + float angleOfDelivery = fastAcosf(glm::clamp(-direction.z, -1.0f, 1.0f)); const float MAX_OFF_AXIS_ATTENUATION = 0.2f; const float OFF_AXIS_ATTENUATION_STEP = (1 - MAX_OFF_AXIS_ATTENUATION) / 2.0f; - float offAxisCoefficient = MAX_OFF_AXIS_ATTENUATION + - (angleOfDelivery * (OFF_AXIS_ATTENUATION_STEP / PI_OVER_TWO)); + float offAxisCoefficient = MAX_OFF_AXIS_ATTENUATION + (angleOfDelivery * (OFF_AXIS_ATTENUATION_STEP / PI_OVER_TWO)); gain *= offAxisCoefficient; } From 9417a176a17d5d7e899be8563199c5fac69dcd07 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Tue, 27 Jun 2017 18:10:54 -0700 Subject: [PATCH 77/95] make scripts tree show scripts in directory passed in with --scripts --- interface/src/Application.cpp | 12 ++++++------ interface/src/main.cpp | 10 ++++++++++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 9052f082dc..72e14656f3 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -915,11 +915,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo _saveAvatarOverrideUrl = true; } - QString defaultScriptsLocation = getCmdOption(argc, constArgv, "--scripts"); - if (!defaultScriptsLocation.isEmpty()) { - PathUtils::defaultScriptsLocation(defaultScriptsLocation); - } - _glWidget = new GLCanvas(); getApplicationCompositor().setRenderingWidget(_glWidget); _window->setCentralWidget(_glWidget); @@ -1186,7 +1181,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // do this as late as possible so that all required subsystems are initialized // If we've overridden the default scripts location, just load default scripts // otherwise, load 'em all - if (!defaultScriptsLocation.isEmpty()) { + + // we just want to see if --scripts was set, we've already parsed it and done + // the change in PathUtils. Rather than pass that in the constructor, lets just + // look (this could be debated) + QDir defaultScriptsLocation(getCmdOption(argc, constArgv, "--scripts")); + if (!defaultScriptsLocation.exists()) { scriptEngines->loadDefaultScripts(); scriptEngines->defaultScriptsLocationOverridden(true); } else { diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 67e248506f..ac1b6d5010 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -73,12 +73,14 @@ int main(int argc, const char* argv[]) { QCommandLineOption serverContentPathOption("serverContentPath", "Where to find server content", "serverContentPath"); QCommandLineOption allowMultipleInstancesOption("allowMultipleInstances", "Allow multiple instances to run"); QCommandLineOption overrideAppLocalDataPathOption("cache", "set test cache ", "dir"); + QCommandLineOption overrideScriptsPathOption("scripts", "set scripts ", "path"); parser.addOption(urlOption); parser.addOption(noUpdaterOption); parser.addOption(checkMinSpecOption); parser.addOption(runServerOption); parser.addOption(serverContentPathOption); parser.addOption(overrideAppLocalDataPathOption); + parser.addOption(overrideScriptsPathOption); parser.addOption(allowMultipleInstancesOption); parser.parse(arguments); @@ -99,6 +101,14 @@ int main(int argc, const char* argv[]) { if (allowMultipleInstances) { instanceMightBeRunning = false; } + + if (parser.isSet(overrideScriptsPathOption)) { + QDir scriptsPath(parser.value(overrideScriptsPathOption)); + if (scriptsPath.exists()) { + PathUtils::defaultScriptsLocation(scriptsPath.path()); + } + } + if (parser.isSet(overrideAppLocalDataPathOption)) { // get dir to use for cache QString cacheDir = parser.value(overrideAppLocalDataPathOption); From 8130a91b8c403fd1e78936b2834954c446717717 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 21 Jun 2017 12:43:48 -0700 Subject: [PATCH 78/95] Cleanup entity dependencies --- interface/src/Application.cpp | 5 +- libraries/animation/src/AnimationCache.cpp | 35 ------ libraries/animation/src/AnimationCache.h | 21 ---- .../src/EntityTreeRenderer.cpp | 34 +----- .../src/EntityTreeRenderer.h | 5 +- .../src/RenderableEntityItem.h | 5 + .../src/RenderableModelEntityItem.cpp | 60 +++++++--- .../src/RenderableModelEntityItem.h | 18 ++- .../src/RenderablePolyVoxEntityItem.cpp | 8 +- .../src/RenderablePolyVoxEntityItem.h | 8 +- libraries/entities/CMakeLists.txt | 8 +- .../entities/src/AnimationPropertyGroup.cpp | 4 +- .../entities/src/AnimationPropertyGroup.h | 2 +- libraries/entities/src/EntityItem.cpp | 23 +--- libraries/entities/src/EntityItem.h | 7 +- libraries/entities/src/EntityItemID.cpp | 2 +- .../entities/src/EntityItemProperties.cpp | 1 + libraries/entities/src/EntityItemProperties.h | 1 + .../entities/src/EntityScriptingInterface.cpp | 13 --- libraries/entities/src/EntityTree.cpp | 14 ++- libraries/entities/src/EntityTree.h | 18 --- libraries/entities/src/EntityTreeElement.cpp | 5 +- .../entities/src/KeyLightPropertyGroup.cpp | 5 +- libraries/entities/src/ModelEntityItem.cpp | 25 ---- libraries/entities/src/ModelEntityItem.h | 11 -- .../src/model-networking/MeshFace.cpp | 44 ------- .../src/model-networking/MeshFace.h | 43 ------- .../src/model-networking/MeshProxy.cpp | 48 -------- .../src/model-networking/MeshProxy.h | 52 --------- .../src/model-networking/SimpleMeshProxy.cpp | 27 +++++ .../src/model-networking/SimpleMeshProxy.h | 36 ++++++ libraries/model/src/model/Forward.h | 19 +++ .../src/ModelScriptingInterface.cpp | 12 +- .../src/ModelScriptingInterface.h | 12 +- libraries/shared/src/GLMHelpers.h | 1 + libraries/shared/src/RegisteredMetaTypes.cpp | 109 +++++++++++++++++- libraries/shared/src/RegisteredMetaTypes.h | 70 +++++++++++ .../src/shared/types}/AnimationLoop.cpp | 10 +- .../src/shared/types}/AnimationLoop.h | 7 +- tests/render-perf/src/main.cpp | 1 + 40 files changed, 391 insertions(+), 438 deletions(-) delete mode 100644 libraries/model-networking/src/model-networking/MeshFace.cpp delete mode 100644 libraries/model-networking/src/model-networking/MeshFace.h delete mode 100644 libraries/model-networking/src/model-networking/MeshProxy.cpp delete mode 100644 libraries/model-networking/src/model-networking/MeshProxy.h create mode 100644 libraries/model-networking/src/model-networking/SimpleMeshProxy.cpp create mode 100644 libraries/model-networking/src/model-networking/SimpleMeshProxy.h create mode 100644 libraries/model/src/model/Forward.h rename libraries/{animation/src => shared/src/shared/types}/AnimationLoop.cpp (95%) rename libraries/{animation/src => shared/src/shared/types}/AnimationLoop.h (93%) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 9052f082dc..c6741ee7e4 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4094,7 +4094,10 @@ void Application::init() { EntityTreePointer tree = getEntities()->getTree(); if (auto entity = tree->findEntityByEntityItemID(id)) { auto sound = DependencyManager::get()->getSound(newURL); - entity->setCollisionSound(sound); + auto renderable = entity->getRenderableInterface(); + if (renderable) { + renderable->setCollisionSound(sound); + } } }, Qt::QueuedConnection); connect(getMyAvatar().get(), &MyAvatar::newCollisionSoundURL, this, [this](QUrl newURL) { diff --git a/libraries/animation/src/AnimationCache.cpp b/libraries/animation/src/AnimationCache.cpp index 6594482085..7d4c0f4e92 100644 --- a/libraries/animation/src/AnimationCache.cpp +++ b/libraries/animation/src/AnimationCache.cpp @@ -144,38 +144,3 @@ void Animation::animationParseError(int error, QString str) { finishedLoading(false); } -AnimationDetails::AnimationDetails() : - role(), url(), fps(0.0f), priority(0.0f), loop(false), hold(false), - startAutomatically(false), firstFrame(0.0f), lastFrame(0.0f), running(false), currentFrame(0.0f) -{ -} - -AnimationDetails::AnimationDetails(QString role, QUrl url, float fps, float priority, bool loop, - bool hold, bool startAutomatically, float firstFrame, float lastFrame, bool running, float currentFrame) : - role(role), url(url), fps(fps), priority(priority), loop(loop), hold(hold), - startAutomatically(startAutomatically), firstFrame(firstFrame), lastFrame(lastFrame), - running(running), currentFrame(currentFrame) -{ -} - - -QScriptValue animationDetailsToScriptValue(QScriptEngine* engine, const AnimationDetails& details) { - QScriptValue obj = engine->newObject(); - obj.setProperty("role", details.role); - obj.setProperty("url", details.url.toString()); - obj.setProperty("fps", details.fps); - obj.setProperty("priority", details.priority); - obj.setProperty("loop", details.loop); - obj.setProperty("hold", details.hold); - obj.setProperty("startAutomatically", details.startAutomatically); - obj.setProperty("firstFrame", details.firstFrame); - obj.setProperty("lastFrame", details.lastFrame); - obj.setProperty("running", details.running); - obj.setProperty("currentFrame", details.currentFrame); - return obj; -} - -void animationDetailsFromScriptValue(const QScriptValue& object, AnimationDetails& details) { - // nothing for now... -} - diff --git a/libraries/animation/src/AnimationCache.h b/libraries/animation/src/AnimationCache.h index 0e6a94c1b8..490bb7dcd8 100644 --- a/libraries/animation/src/AnimationCache.h +++ b/libraries/animation/src/AnimationCache.h @@ -107,26 +107,5 @@ private: QByteArray _data; }; -class AnimationDetails { -public: - AnimationDetails(); - AnimationDetails(QString role, QUrl url, float fps, float priority, bool loop, - bool hold, bool startAutomatically, float firstFrame, float lastFrame, bool running, float currentFrame); - - QString role; - QUrl url; - float fps; - float priority; - bool loop; - bool hold; - bool startAutomatically; - float firstFrame; - float lastFrame; - bool running; - float currentFrame; -}; -Q_DECLARE_METATYPE(AnimationDetails); -QScriptValue animationDetailsToScriptValue(QScriptEngine* engine, const AnimationDetails& event); -void animationDetailsFromScriptValue(const QScriptValue& object, AnimationDetails& event); #endif // hifi_AnimationCache_h diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index ffaac3bf3e..c02bde2244 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -163,7 +163,6 @@ void EntityTreeRenderer::reloadEntityScripts() { void EntityTreeRenderer::init() { OctreeProcessor::init(); EntityTreePointer entityTree = std::static_pointer_cast(_tree); - entityTree->setFBXService(this); if (_wantScripts) { resetEntitiesScriptEngine(); @@ -188,7 +187,6 @@ void EntityTreeRenderer::shutdown() { void EntityTreeRenderer::setTree(OctreePointer newTree) { OctreeProcessor::setTree(newTree); - std::static_pointer_cast(_tree)->setFBXService(this); } void EntityTreeRenderer::update() { @@ -373,31 +371,6 @@ bool EntityTreeRenderer::applyLayeredZones() { return true; } -const FBXGeometry* EntityTreeRenderer::getGeometryForEntity(EntityItemPointer entityItem) { - const FBXGeometry* result = NULL; - - if (entityItem->getType() == EntityTypes::Model) { - std::shared_ptr modelEntityItem = - std::dynamic_pointer_cast(entityItem); - assert(modelEntityItem); // we need this!!! - ModelPointer model = modelEntityItem->getModel(getSharedFromThis()); - if (model && model->isLoaded()) { - result = &model->getFBXGeometry(); - } - } - return result; -} - -ModelPointer EntityTreeRenderer::getModelForEntityItem(EntityItemPointer entityItem) { - ModelPointer result = nullptr; - if (entityItem->getType() == EntityTypes::Model) { - std::shared_ptr modelEntityItem = - std::dynamic_pointer_cast(entityItem); - result = modelEntityItem->getModel(getSharedFromThis()); - } - return result; -} - void EntityTreeRenderer::processEraseMessage(ReceivedMessage& message, const SharedNodePointer& sourceNode) { std::static_pointer_cast(_tree)->processEraseMessage(message, sourceNode); } @@ -889,7 +862,12 @@ void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, bool void EntityTreeRenderer::playEntityCollisionSound(EntityItemPointer entity, const Collision& collision) { assert((bool)entity); - SharedSoundPointer collisionSound = entity->getCollisionSound(); + auto renderable = entity->getRenderableInterface(); + if (!renderable) { + return; + } + + SharedSoundPointer collisionSound = renderable->getCollisionSound(); if (!collisionSound) { return; } diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index 5dcbd1aeb9..f4909a2036 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -39,7 +39,7 @@ using ModelWeakPointer = std::weak_ptr; using CalculateEntityLoadingPriority = std::function; // Generic client side Octree renderer class. -class EntityTreeRenderer : public OctreeProcessor, public EntityItemFBXService, public Dependency { +class EntityTreeRenderer : public OctreeProcessor, public Dependency { Q_OBJECT public: EntityTreeRenderer(bool wantScripts, AbstractViewStateInterface* viewState, @@ -68,9 +68,6 @@ public: virtual void init() override; - virtual const FBXGeometry* getGeometryForEntity(EntityItemPointer entityItem) override; - virtual ModelPointer getModelForEntityItem(EntityItemPointer entityItem) override; - /// clears the tree virtual void clear() override; diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index 244a850d67..c848b10f6a 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -14,6 +14,7 @@ #include #include +#include #include "AbstractViewStateInterface.h" #include "EntitiesRendererLogging.h" @@ -40,7 +41,11 @@ public: virtual void render(RenderArgs* args) {}; virtual bool addToScene(const EntityItemPointer& self, const render::ScenePointer& scene, render::Transaction& transaction) = 0; virtual void removeFromScene(const EntityItemPointer& self, const render::ScenePointer& scene, render::Transaction& transaction) = 0; + const SharedSoundPointer& getCollisionSound() { return _collisionSound; } + void setCollisionSound(const SharedSoundPointer& sound) { _collisionSound = sound; } virtual RenderableEntityInterface* getRenderableInterface() { return nullptr; } +private: + SharedSoundPointer _collisionSound; }; class RenderableEntityItemProxy { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index f343fdb155..b8e869e4d9 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -69,11 +69,9 @@ void RenderableModelEntityItem::setModelURL(const QString& url) { void RenderableModelEntityItem::loader() { _needsModelReload = true; - auto renderer = DependencyManager::get(); - assert(renderer); { PerformanceTimer perfTimer("getModel"); - getModel(renderer); + getModel(); } } @@ -390,8 +388,7 @@ void RenderableModelEntityItem::render(RenderArgs* args) { if (!_model || _needsModelReload) { // TODO: this getModel() appears to be about 3% of model render time. We should optimize PerformanceTimer perfTimer("getModel"); - auto renderer = qSharedPointerCast(args->_renderData); - getModel(renderer); + getModel(); // Remap textures immediately after loading to avoid flicker remapTextures(); @@ -483,7 +480,7 @@ void RenderableModelEntityItem::render(RenderArgs* args) { auto& currentURL = getParsedModelURL(); if (currentURL != _model->getURL()) { // Defer setting the url to the render thread - getModel(_myRenderer); + getModel(); } } } @@ -492,16 +489,11 @@ ModelPointer RenderableModelEntityItem::getModelNotSafe() { return _model; } -ModelPointer RenderableModelEntityItem::getModel(QSharedPointer renderer) { - if (!renderer) { - return nullptr; - } - +ModelPointer RenderableModelEntityItem::getModel() { // make sure our renderer is setup if (!_myRenderer) { - _myRenderer = renderer; + _myRenderer = DependencyManager::get(); } - assert(_myRenderer == renderer); // you should only ever render on one renderer if (!_myRenderer || QThread::currentThread() != _myRenderer->thread()) { return _model; @@ -513,7 +505,7 @@ ModelPointer RenderableModelEntityItem::getModel(QSharedPointerallocateModel(getModelURL(), renderer->getEntityLoadingPriority(*this), this); + _model = _myRenderer->allocateModel(getModelURL(), _myRenderer->getEntityLoadingPriority(*this), this); _needsInitialSimulation = true; // If we need to change URLs, update it *after rendering* (to avoid access violations) } else if (QUrl(getModelURL()) != _model->getURL()) { @@ -587,6 +579,22 @@ EntityItemProperties RenderableModelEntityItem::getProperties(EntityPropertyFlag properties.setRenderInfoHasTransparent(_model->getRenderInfoHasTransparent()); } + + const FBXGeometry* geometry = NULL; + const_cast(this)->getModel(); + if (_model && _model->isLoaded()) { + geometry = &_model->getFBXGeometry(); + } + + // TODO: improve naturalDimensions in the future, + // for now we've added this hack for setting natural dimensions of models + if (geometry) { + Extents meshExtents = geometry->getUnscaledMeshExtents(); + properties.setNaturalDimensions(meshExtents.maximum - meshExtents.minimum); + properties.calculateNaturalPosition(meshExtents.minimum, meshExtents.maximum); + } + + return properties; } @@ -1255,3 +1263,27 @@ QStringList RenderableModelEntityItem::getJointNames() const { } return result; } + +void RenderableModelEntityItem::mapJoints(const QStringList& modelJointNames) { + // if we don't have animation, or we're already joint mapped then bail early + if (!hasAnimation() || jointsMapped()) { + return; + } + + if (!_animation || _animation->getURL().toString() != getAnimationURL()) { + _animation = DependencyManager::get()->getAnimation(getAnimationURL()); + } + + if (_animation && _animation->isLoaded()) { + QStringList animationJointNames = _animation->getJointNames(); + + if (modelJointNames.size() > 0 && animationJointNames.size() > 0) { + _jointMapping.resize(modelJointNames.size()); + for (int i = 0; i < modelJointNames.size(); i++) { + _jointMapping[i] = animationJointNames.indexOf(modelJointNames[i]); + } + _jointMappingCompleted = true; + _jointMappingURL = _animationProperties.getURL(); + } + } +} diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 2bbb51b3f0..2d240c01a6 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -16,6 +16,7 @@ #include #include +#include class Model; class EntityTreeRenderer; @@ -53,7 +54,7 @@ public: bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, void** intersectedObject, bool precisionPicking) const override; - ModelPointer getModel(QSharedPointer renderer); + ModelPointer getModel(); ModelPointer getModelNotSafe(); virtual bool needsToCallUpdate() const override; @@ -106,6 +107,15 @@ public: // Transparency is handled in ModelMeshPartPayload bool isTransparent() override { return false; } + void mapJoints(const QStringList& modelJointNames); + bool jointsMapped() const { + return _jointMappingURL == getAnimationURL() && _jointMappingCompleted; + } + + AnimationPointer getAnimation() const { + return _animation; + } + private: QVariantMap parseTexturesToMap(QString textures); void remapTextures(); @@ -131,6 +141,12 @@ private: bool _needsJointSimulation { false }; bool _showCollisionGeometry { false }; const void* _collisionMeshKey { nullptr }; + + // used on client side + bool _jointMappingCompleted { false }; + QVector _jointMapping; // domain is index into model-joints, range is index into animation-joints + QString _jointMappingURL; + AnimationPointer _animation; }; #endif // hifi_RenderableModelEntityItem_h diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 6cda472d96..88a5d2b873 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -9,11 +9,15 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // + +#include "RenderablePolyVoxEntityItem.h" + #include #include #include #include #include +#include #include "ModelScriptingInterface.h" #if defined(__GNUC__) && !defined(__clang__) @@ -52,7 +56,6 @@ #include "EntityTreeRenderer.h" #include "polyvox_vert.h" #include "polyvox_frag.h" -#include "RenderablePolyVoxEntityItem.h" #include "EntityEditPacketSender.h" #include "PhysicalEntitySimulation.h" @@ -1626,6 +1629,7 @@ void RenderablePolyVoxEntityItem::locationChanged(bool tellPhysics) { scene->enqueueTransaction(transaction); } + bool RenderablePolyVoxEntityItem::getMeshes(MeshProxyList& result) { if (!updateDependents()) { return false; @@ -1645,7 +1649,7 @@ bool RenderablePolyVoxEntityItem::getMeshes(MeshProxyList& result) { } else { success = true; // the mesh will be in voxel-space. transform it into object-space - meshProxy = new MeshProxy( + meshProxy = new SimpleMeshProxy( _mesh->map([=](glm::vec3 position){ return glm::vec3(transform * glm::vec4(position, 1.0f)); }, [=](glm::vec3 normal){ return glm::normalize(glm::vec3(transform * glm::vec4(normal, 0.0f))); }, [&](uint32_t index){ return index; })); diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h index 174d6338d3..45625ada6d 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h @@ -12,17 +12,19 @@ #ifndef hifi_RenderablePolyVoxEntityItem_h #define hifi_RenderablePolyVoxEntityItem_h -#include #include +#include + #include #include +#include +#include #include +#include -#include "PolyVoxEntityItem.h" #include "RenderableEntityItem.h" -#include "gpu/Context.h" class PolyVoxPayload { public: diff --git a/libraries/entities/CMakeLists.txt b/libraries/entities/CMakeLists.txt index ddb5fbaf73..19341ec3e2 100644 --- a/libraries/entities/CMakeLists.txt +++ b/libraries/entities/CMakeLists.txt @@ -1,9 +1,3 @@ set(TARGET_NAME entities) setup_hifi_library(Network Script) -link_hifi_libraries(avatars shared audio octree model model-networking fbx networking animation) -include_hifi_library_headers(networking) -include_hifi_library_headers(gpu) - -target_bullet() - -include_hifi_library_headers(render) +link_hifi_libraries(shared networking octree avatars) diff --git a/libraries/entities/src/AnimationPropertyGroup.cpp b/libraries/entities/src/AnimationPropertyGroup.cpp index f6d08ad8b9..848d4352a7 100644 --- a/libraries/entities/src/AnimationPropertyGroup.cpp +++ b/libraries/entities/src/AnimationPropertyGroup.cpp @@ -9,12 +9,12 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "AnimationPropertyGroup.h" + #include #include -#include -#include "AnimationPropertyGroup.h" #include "EntityItemProperties.h" #include "EntityItemPropertiesMacros.h" diff --git a/libraries/entities/src/AnimationPropertyGroup.h b/libraries/entities/src/AnimationPropertyGroup.h index c6d386d2ef..c0086b41b3 100644 --- a/libraries/entities/src/AnimationPropertyGroup.h +++ b/libraries/entities/src/AnimationPropertyGroup.h @@ -19,7 +19,7 @@ #include -#include "AnimationLoop.h" +#include // for Animation, AnimationCache, and AnimationPointer classes #include "EntityItemPropertiesMacros.h" #include "PropertyGroup.h" diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 17de15e32b..23ce097cc2 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -23,8 +23,8 @@ #include #include #include // usecTimestampNow() -#include #include +#include #include "EntityScriptingInterface.h" #include "EntitiesLogging.h" @@ -988,21 +988,6 @@ void EntityItem::setCollisionSoundURL(const QString& value) { } } -SharedSoundPointer EntityItem::getCollisionSound() { - SharedSoundPointer result; - withReadLock([&] { - result = _collisionSound; - }); - - if (!result) { - result = DependencyManager::get()->getSound(_collisionSoundURL); - withWriteLock([&] { - _collisionSound = result; - }); - } - return result; -} - void EntityItem::simulate(const quint64& now) { if (getLastSimulated() == 0) { setLastSimulated(now); @@ -2650,12 +2635,6 @@ QString EntityItem::getCollisionSoundURL() const { return result; } -void EntityItem::setCollisionSound(SharedSoundPointer sound) { - withWriteLock([&] { - _collisionSound = sound; - }); -} - glm::vec3 EntityItem::getRegistrationPoint() const { glm::vec3 result; withReadLock([&] { diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 0318c72991..92c83651aa 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -19,14 +19,13 @@ #include -#include // for Animation, AnimationCache, and AnimationPointer classes +#include // for Animation, AnimationCache, and AnimationPointer classes #include // for EncodeBitstreamParams class #include // for OctreeElement::AppendState #include #include #include #include -#include #include #include @@ -260,9 +259,6 @@ public: QString getCollisionSoundURL() const; void setCollisionSoundURL(const QString& value); - SharedSoundPointer getCollisionSound(); - void setCollisionSound(SharedSoundPointer sound); - glm::vec3 getRegistrationPoint() const; /// registration point as ratio of entity /// registration point as ratio of entity @@ -526,7 +522,6 @@ protected: quint64 _loadedScriptTimestamp { ENTITY_ITEM_DEFAULT_SCRIPT_TIMESTAMP + 1 }; QString _collisionSoundURL; - SharedSoundPointer _collisionSound; glm::vec3 _registrationPoint; float _angularDamping; bool _visible; diff --git a/libraries/entities/src/EntityItemID.cpp b/libraries/entities/src/EntityItemID.cpp index 5f07019db4..3b4ca1cea0 100644 --- a/libraries/entities/src/EntityItemID.cpp +++ b/libraries/entities/src/EntityItemID.cpp @@ -9,6 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "EntityItemID.h" #include #include @@ -17,7 +18,6 @@ #include #include "RegisteredMetaTypes.h" -#include "EntityItemID.h" int entityItemIDTypeID = qRegisterMetaType(); diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 1ed020e592..a207902789 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include "EntitiesLogging.h" #include "EntityItem.h" diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 590298e102..b526ac663c 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -15,6 +15,7 @@ #include #include +#include #include #include diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 90dc6893b4..7351d49dff 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -21,7 +21,6 @@ #include #include #include -#include #include "EntitiesLogging.h" #include "EntityDynamicFactoryInterface.h" @@ -298,18 +297,6 @@ EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identit } results = entity->getProperties(desiredProperties); - - // TODO: improve naturalDimensions in the future, - // for now we've added this hack for setting natural dimensions of models - if (entity->getType() == EntityTypes::Model) { - const FBXGeometry* geometry = _entityTree->getGeometryForEntity(entity); - if (geometry) { - Extents meshExtents = geometry->getUnscaledMeshExtents(); - results.setNaturalDimensions(meshExtents.maximum - meshExtents.minimum); - results.calculateNaturalPosition(meshExtents.minimum, meshExtents.maximum); - } - } - } }); } diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 11694c4cea..4773f45af7 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -9,11 +9,15 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include -#include +#include "EntityTree.h" +#include +#include + #include -#include "EntityTree.h" +#include +#include + #include "EntitySimulation.h" #include "VariantMapToScriptValue.h" @@ -55,9 +59,7 @@ public: EntityTree::EntityTree(bool shouldReaverage) : - Octree(shouldReaverage), - _fbxService(NULL), - _simulation(NULL) + Octree(shouldReaverage) { resetClientEditStats(); } diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 8e3c9f5412..24e6c364b1 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -41,13 +41,6 @@ public: virtual void entityCreated(const EntityItem& newEntity, const SharedNodePointer& senderNode) = 0; }; -class EntityItemFBXService { -public: - virtual const FBXGeometry* getGeometryForEntity(EntityItemPointer entityItem) = 0; - virtual ModelPointer getModelForEntityItem(EntityItemPointer entityItem) = 0; -}; - - class SendEntitiesOperationArgs { public: glm::vec3 root; @@ -189,15 +182,6 @@ public: int processEraseMessage(ReceivedMessage& message, const SharedNodePointer& sourceNode); int processEraseMessageDetails(const QByteArray& buffer, const SharedNodePointer& sourceNode); - EntityItemFBXService* getFBXService() const { return _fbxService; } - void setFBXService(EntityItemFBXService* service) { _fbxService = service; } - const FBXGeometry* getGeometryForEntity(EntityItemPointer entityItem) { - return _fbxService ? _fbxService->getGeometryForEntity(entityItem) : NULL; - } - ModelPointer getModelForEntityItem(EntityItemPointer entityItem) { - return _fbxService ? _fbxService->getModelForEntityItem(entityItem) : NULL; - } - EntityTreeElementPointer getContainingElement(const EntityItemID& entityItemID) /*const*/; void setContainingElement(const EntityItemID& entityItemID, EntityTreeElementPointer element); void debugDumpMap(); @@ -325,8 +309,6 @@ protected: _deletedEntityItemIDs << id; } - EntityItemFBXService* _fbxService; - mutable QReadWriteLock _entityToElementLock; QHash _entityToElementMap; diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 0dc42717f5..cce7ee006f 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -9,17 +9,18 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "EntityTreeElement.h" + #include -#include #include #include +#include #include "EntitiesLogging.h" #include "EntityNodeData.h" #include "EntityItemProperties.h" #include "EntityTree.h" -#include "EntityTreeElement.h" #include "EntityTypes.h" EntityTreeElement::EntityTreeElement(unsigned char* octalCode) : OctreeElement() { diff --git a/libraries/entities/src/KeyLightPropertyGroup.cpp b/libraries/entities/src/KeyLightPropertyGroup.cpp index 1011094266..f0d059af67 100644 --- a/libraries/entities/src/KeyLightPropertyGroup.cpp +++ b/libraries/entities/src/KeyLightPropertyGroup.cpp @@ -9,12 +9,11 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "KeyLightPropertyGroup.h" + #include #include -#include - -#include "KeyLightPropertyGroup.h" #include "EntityItemProperties.h" #include "EntityItemPropertiesMacros.h" diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index 89213459fa..b5e759d2d8 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -37,7 +37,6 @@ ModelEntityItem::ModelEntityItem(const EntityItemID& entityItemID) : EntityItem( _animationLoop.setResetOnRunning(false); _type = EntityTypes::Model; - _jointMappingCompleted = false; _lastKnownCurrentFrame = -1; _color[0] = _color[1] = _color[2] = 0; } @@ -204,30 +203,6 @@ void ModelEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit } -void ModelEntityItem::mapJoints(const QStringList& modelJointNames) { - // if we don't have animation, or we're already joint mapped then bail early - if (!hasAnimation() || jointsMapped()) { - return; - } - - if (!_animation || _animation->getURL().toString() != getAnimationURL()) { - _animation = DependencyManager::get()->getAnimation(getAnimationURL()); - } - - if (_animation && _animation->isLoaded()) { - QStringList animationJointNames = _animation->getJointNames(); - - if (modelJointNames.size() > 0 && animationJointNames.size() > 0) { - _jointMapping.resize(modelJointNames.size()); - for (int i = 0; i < modelJointNames.size(); i++) { - _jointMapping[i] = animationJointNames.indexOf(modelJointNames[i]); - } - _jointMappingCompleted = true; - _jointMappingURL = _animationProperties.getURL(); - } - } -} - bool ModelEntityItem::isAnimatingSomething() const { return getAnimationIsPlaying() && getAnimationFPS() != 0.0f && diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index 5076a43892..0c6132e211 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -12,8 +12,6 @@ #ifndef hifi_ModelEntityItem_h #define hifi_ModelEntityItem_h -#include - #include "EntityItem.h" #include "AnimationPropertyGroup.h" @@ -103,10 +101,7 @@ public: void setAnimationLastFrame(float lastFrame) { _animationLoop.setLastFrame(lastFrame); } float getAnimationLastFrame() const { return _animationLoop.getLastFrame(); } - void mapJoints(const QStringList& modelJointNames); - bool jointsMapped() const { return _jointMappingURL == getAnimationURL() && _jointMappingCompleted; } - AnimationPointer getAnimation() const { return _animation; } bool getAnimationIsPlaying() const { return _animationLoop.getRunning(); } float getAnimationCurrentFrame() const { return _animationLoop.getCurrentFrame(); } float getAnimationFPS() const { return _animationLoop.getFPS(); } @@ -158,7 +153,6 @@ protected: QUrl _parsedModelURL; QString _compoundShapeURL; - AnimationPointer _animation; AnimationPropertyGroup _animationProperties; AnimationLoop _animationLoop; @@ -166,11 +160,6 @@ protected: QString _textures; ShapeType _shapeType = SHAPE_TYPE_NONE; - - // used on client side - bool _jointMappingCompleted; - QVector _jointMapping; // domain is index into model-joints, range is index into animation-joints - QString _jointMappingURL; }; #endif // hifi_ModelEntityItem_h diff --git a/libraries/model-networking/src/model-networking/MeshFace.cpp b/libraries/model-networking/src/model-networking/MeshFace.cpp deleted file mode 100644 index 8092d36aa3..0000000000 --- a/libraries/model-networking/src/model-networking/MeshFace.cpp +++ /dev/null @@ -1,44 +0,0 @@ -// -// MeshFace.cpp -// libraries/model/src/model/ -// -// Created by Seth Alves on 2017-3-23 -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include - -#include "MeshFace.h" - - -QScriptValue meshFaceToScriptValue(QScriptEngine* engine, const MeshFace &meshFace) { - QScriptValue obj = engine->newObject(); - obj.setProperty("vertices", qVectorIntToScriptValue(engine, meshFace.vertexIndices)); - return obj; -} - -void meshFaceFromScriptValue(const QScriptValue &object, MeshFace& meshFaceResult) { - qVectorIntFromScriptValue(object.property("vertices"), meshFaceResult.vertexIndices); -} - -QScriptValue qVectorMeshFaceToScriptValue(QScriptEngine* engine, const QVector& vector) { - QScriptValue array = engine->newArray(); - for (int i = 0; i < vector.size(); i++) { - array.setProperty(i, meshFaceToScriptValue(engine, vector.at(i))); - } - return array; -} - -void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector& result) { - int length = array.property("length").toInteger(); - result.clear(); - - for (int i = 0; i < length; i++) { - MeshFace meshFace = MeshFace(); - meshFaceFromScriptValue(array.property(i), meshFace); - result << meshFace; - } -} diff --git a/libraries/model-networking/src/model-networking/MeshFace.h b/libraries/model-networking/src/model-networking/MeshFace.h deleted file mode 100644 index 3b81b372c3..0000000000 --- a/libraries/model-networking/src/model-networking/MeshFace.h +++ /dev/null @@ -1,43 +0,0 @@ -// -// MeshFace.h -// libraries/model/src/model/ -// -// Created by Seth Alves on 2017-3-23 -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_MeshFace_h -#define hifi_MeshFace_h - -#include -#include -#include - -#include - -using MeshPointer = std::shared_ptr; - -class MeshFace { - -public: - MeshFace() {} - ~MeshFace() {} - - QVector vertexIndices; - // TODO -- material... -}; - -Q_DECLARE_METATYPE(MeshFace) -Q_DECLARE_METATYPE(QVector) - -QScriptValue meshFaceToScriptValue(QScriptEngine* engine, const MeshFace &meshFace); -void meshFaceFromScriptValue(const QScriptValue &object, MeshFace& meshFaceResult); -QScriptValue qVectorMeshFaceToScriptValue(QScriptEngine* engine, const QVector& vector); -void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector& result); - - - -#endif // hifi_MeshFace_h diff --git a/libraries/model-networking/src/model-networking/MeshProxy.cpp b/libraries/model-networking/src/model-networking/MeshProxy.cpp deleted file mode 100644 index 1b6fa43c82..0000000000 --- a/libraries/model-networking/src/model-networking/MeshProxy.cpp +++ /dev/null @@ -1,48 +0,0 @@ -// -// MeshProxy.cpp -// libraries/model/src/model/ -// -// Created by Seth Alves on 2017-3-22. -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "MeshProxy.h" - - -QScriptValue meshToScriptValue(QScriptEngine* engine, MeshProxy* const &in) { - return engine->newQObject(in, QScriptEngine::QtOwnership, - QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects); -} - -void meshFromScriptValue(const QScriptValue& value, MeshProxy* &out) { - out = qobject_cast(value.toQObject()); -} - -QScriptValue meshesToScriptValue(QScriptEngine* engine, const MeshProxyList &in) { - // QScriptValueList result; - QScriptValue result = engine->newArray(); - int i = 0; - foreach (MeshProxy* const meshProxy, in) { - result.setProperty(i++, meshToScriptValue(engine, meshProxy)); - } - return result; -} - -void meshesFromScriptValue(const QScriptValue& value, MeshProxyList &out) { - QScriptValueIterator itr(value); - - qDebug() << "in meshesFromScriptValue, value.length =" << value.property("length").toInt32(); - - while(itr.hasNext()) { - itr.next(); - MeshProxy* meshProxy = qscriptvalue_cast(itr.value()); - if (meshProxy) { - out.append(meshProxy); - } else { - qDebug() << "null meshProxy"; - } - } -} diff --git a/libraries/model-networking/src/model-networking/MeshProxy.h b/libraries/model-networking/src/model-networking/MeshProxy.h deleted file mode 100644 index c5b25b7895..0000000000 --- a/libraries/model-networking/src/model-networking/MeshProxy.h +++ /dev/null @@ -1,52 +0,0 @@ -// -// MeshProxy.h -// libraries/model/src/model/ -// -// Created by Seth Alves on 2017-1-27. -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_MeshProxy_h -#define hifi_MeshProxy_h - -#include -#include -#include - -#include - -using MeshPointer = std::shared_ptr; - -class MeshProxy : public QObject { - Q_OBJECT - -public: - MeshProxy(MeshPointer mesh) : _mesh(mesh) {} - ~MeshProxy() {} - - MeshPointer getMeshPointer() const { return _mesh; } - - Q_INVOKABLE int getNumVertices() const { return (int)_mesh->getNumVertices(); } - Q_INVOKABLE glm::vec3 getPos3(int index) const { return _mesh->getPos3(index); } - - -protected: - MeshPointer _mesh; -}; - -Q_DECLARE_METATYPE(MeshProxy*); - -class MeshProxyList : public QList {}; // typedef and using fight with the Qt macros/templates, do this instead -Q_DECLARE_METATYPE(MeshProxyList); - - -QScriptValue meshToScriptValue(QScriptEngine* engine, MeshProxy* const &in); -void meshFromScriptValue(const QScriptValue& value, MeshProxy* &out); - -QScriptValue meshesToScriptValue(QScriptEngine* engine, const MeshProxyList &in); -void meshesFromScriptValue(const QScriptValue& value, MeshProxyList &out); - -#endif // hifi_MeshProxy_h diff --git a/libraries/model-networking/src/model-networking/SimpleMeshProxy.cpp b/libraries/model-networking/src/model-networking/SimpleMeshProxy.cpp new file mode 100644 index 0000000000..40b30154bf --- /dev/null +++ b/libraries/model-networking/src/model-networking/SimpleMeshProxy.cpp @@ -0,0 +1,27 @@ +// +// MeshProxy.cpp +// libraries/model/src/model/ +// +// Created by Seth Alves on 2017-3-22. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "SimpleMeshProxy.h" + +#include + +MeshPointer SimpleMeshProxy::getMeshPointer() const { + return _mesh; +} + +int SimpleMeshProxy::getNumVertices() const { + return (int)_mesh->getNumVertices(); +} + +glm::vec3 SimpleMeshProxy::getPos3(int index) const { + return _mesh->getPos3(index); +} + diff --git a/libraries/model-networking/src/model-networking/SimpleMeshProxy.h b/libraries/model-networking/src/model-networking/SimpleMeshProxy.h new file mode 100644 index 0000000000..b82e501673 --- /dev/null +++ b/libraries/model-networking/src/model-networking/SimpleMeshProxy.h @@ -0,0 +1,36 @@ +// +// MeshProxy.h +// libraries/model/src/model/ +// +// Created by Seth Alves on 2017-1-27. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_SimpleMeshProxy_h +#define hifi_SimpleMeshProxy_h + +#include +#include +#include + +#include + +class SimpleMeshProxy : public MeshProxy { +public: + SimpleMeshProxy(const MeshPointer& mesh) : _mesh(mesh) { } + + MeshPointer getMeshPointer() const override; + + int getNumVertices() const override; + + glm::vec3 getPos3(int index) const override; + + +protected: + const MeshPointer _mesh; +}; + +#endif // hifi_SimpleMeshProxy_h diff --git a/libraries/model/src/model/Forward.h b/libraries/model/src/model/Forward.h new file mode 100644 index 0000000000..362eabb454 --- /dev/null +++ b/libraries/model/src/model/Forward.h @@ -0,0 +1,19 @@ +// +// Geometry.h +// libraries/model/src/model +// +// Created by Sam Gateau on 12/5/2014. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_model_Forward_h +#define hifi_model_Forward_h + +namespace model { + class Mesh; + using MeshPointer = std::shared_ptr; +} + +#endif diff --git a/libraries/script-engine/src/ModelScriptingInterface.cpp b/libraries/script-engine/src/ModelScriptingInterface.cpp index f56312568e..762d9ffb29 100644 --- a/libraries/script-engine/src/ModelScriptingInterface.cpp +++ b/libraries/script-engine/src/ModelScriptingInterface.cpp @@ -9,17 +9,17 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "ModelScriptingInterface.h" #include #include #include -#include +#include #include "ScriptEngine.h" #include "ScriptEngineLogging.h" -#include "ModelScriptingInterface.h" #include "OBJWriter.h" ModelScriptingInterface::ModelScriptingInterface(QObject* parent) : QObject(parent) { - _modelScriptEngine = qobject_cast(parent); + _modelScriptEngine = qobject_cast(parent); qScriptRegisterSequenceMetaType>(_modelScriptEngine); qScriptRegisterMetaType(_modelScriptEngine, meshFaceToScriptValue, meshFaceFromScriptValue); @@ -118,7 +118,7 @@ QScriptValue ModelScriptingInterface::appendMeshes(MeshProxyList in) { (gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL)); - MeshProxy* resultProxy = new MeshProxy(result); + MeshProxy* resultProxy = new SimpleMeshProxy(result); return meshToScriptValue(_modelScriptEngine, resultProxy); } @@ -134,7 +134,7 @@ QScriptValue ModelScriptingInterface::transformMesh(glm::mat4 transform, MeshPro model::MeshPointer result = mesh->map([&](glm::vec3 position){ return glm::vec3(transform * glm::vec4(position, 1.0f)); }, [&](glm::vec3 normal){ return glm::vec3(transform * glm::vec4(normal, 0.0f)); }, [&](uint32_t index){ return index; }); - MeshProxy* resultProxy = new MeshProxy(result); + MeshProxy* resultProxy = new SimpleMeshProxy(result); return meshToScriptValue(_modelScriptEngine, resultProxy); } @@ -188,6 +188,6 @@ QScriptValue ModelScriptingInterface::newMesh(const QVector& vertices - MeshProxy* meshProxy = new MeshProxy(mesh); + MeshProxy* meshProxy = new SimpleMeshProxy(mesh); return meshToScriptValue(_modelScriptEngine, meshProxy); } diff --git a/libraries/script-engine/src/ModelScriptingInterface.h b/libraries/script-engine/src/ModelScriptingInterface.h index d899f532d8..ba23623acf 100644 --- a/libraries/script-engine/src/ModelScriptingInterface.h +++ b/libraries/script-engine/src/ModelScriptingInterface.h @@ -9,19 +9,13 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // - #ifndef hifi_ModelScriptingInterface_h #define hifi_ModelScriptingInterface_h #include -#include -#include -#include -#include -#include -using MeshPointer = std::shared_ptr; -class ScriptEngine; +#include +class QScriptEngine; class ModelScriptingInterface : public QObject { Q_OBJECT @@ -37,7 +31,7 @@ public: const QVector& faces); private: - ScriptEngine* _modelScriptEngine { nullptr }; + QScriptEngine* _modelScriptEngine { nullptr }; }; #endif // hifi_ModelScriptingInterface_h diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h index ef92552d1f..3386ea2c22 100644 --- a/libraries/shared/src/GLMHelpers.h +++ b/libraries/shared/src/GLMHelpers.h @@ -17,6 +17,7 @@ #include #include #include +#include // Bring the most commonly used GLM types into the default namespace using glm::ivec2; diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp index 70067b93f3..1e5fe23458 100644 --- a/libraries/shared/src/RegisteredMetaTypes.cpp +++ b/libraries/shared/src/RegisteredMetaTypes.cpp @@ -9,6 +9,10 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "RegisteredMetaTypes.h" + +#include + #include #include #include @@ -17,10 +21,9 @@ #include #include #include -#include -#include - -#include "RegisteredMetaTypes.h" +#include +#include +#include int vec4MetaTypeId = qRegisterMetaType(); int vec3MetaTypeId = qRegisterMetaType(); @@ -796,3 +799,101 @@ void qSizeFFromScriptValue(const QScriptValue& object, QSizeF& qSizeF) { qSizeF.setWidth(object.property("width").toVariant().toFloat()); qSizeF.setHeight(object.property("height").toVariant().toFloat()); } + +AnimationDetails::AnimationDetails() : + role(), url(), fps(0.0f), priority(0.0f), loop(false), hold(false), + startAutomatically(false), firstFrame(0.0f), lastFrame(0.0f), running(false), currentFrame(0.0f) { +} + +AnimationDetails::AnimationDetails(QString role, QUrl url, float fps, float priority, bool loop, + bool hold, bool startAutomatically, float firstFrame, float lastFrame, bool running, float currentFrame) : + role(role), url(url), fps(fps), priority(priority), loop(loop), hold(hold), + startAutomatically(startAutomatically), firstFrame(firstFrame), lastFrame(lastFrame), + running(running), currentFrame(currentFrame) { +} + + +QScriptValue animationDetailsToScriptValue(QScriptEngine* engine, const AnimationDetails& details) { + QScriptValue obj = engine->newObject(); + obj.setProperty("role", details.role); + obj.setProperty("url", details.url.toString()); + obj.setProperty("fps", details.fps); + obj.setProperty("priority", details.priority); + obj.setProperty("loop", details.loop); + obj.setProperty("hold", details.hold); + obj.setProperty("startAutomatically", details.startAutomatically); + obj.setProperty("firstFrame", details.firstFrame); + obj.setProperty("lastFrame", details.lastFrame); + obj.setProperty("running", details.running); + obj.setProperty("currentFrame", details.currentFrame); + return obj; +} + +void animationDetailsFromScriptValue(const QScriptValue& object, AnimationDetails& details) { + // nothing for now... +} + +QScriptValue meshToScriptValue(QScriptEngine* engine, MeshProxy* const &in) { + return engine->newQObject(in, QScriptEngine::QtOwnership, + QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects); +} + +void meshFromScriptValue(const QScriptValue& value, MeshProxy* &out) { + out = qobject_cast(value.toQObject()); +} + +QScriptValue meshesToScriptValue(QScriptEngine* engine, const MeshProxyList &in) { + // QScriptValueList result; + QScriptValue result = engine->newArray(); + int i = 0; + foreach(MeshProxy* const meshProxy, in) { + result.setProperty(i++, meshToScriptValue(engine, meshProxy)); + } + return result; +} + +void meshesFromScriptValue(const QScriptValue& value, MeshProxyList &out) { + QScriptValueIterator itr(value); + + qDebug() << "in meshesFromScriptValue, value.length =" << value.property("length").toInt32(); + + while (itr.hasNext()) { + itr.next(); + MeshProxy* meshProxy = qscriptvalue_cast(itr.value()); + if (meshProxy) { + out.append(meshProxy); + } else { + qDebug() << "null meshProxy"; + } + } +} + + +QScriptValue meshFaceToScriptValue(QScriptEngine* engine, const MeshFace &meshFace) { + QScriptValue obj = engine->newObject(); + obj.setProperty("vertices", qVectorIntToScriptValue(engine, meshFace.vertexIndices)); + return obj; +} + +void meshFaceFromScriptValue(const QScriptValue &object, MeshFace& meshFaceResult) { + qVectorIntFromScriptValue(object.property("vertices"), meshFaceResult.vertexIndices); +} + +QScriptValue qVectorMeshFaceToScriptValue(QScriptEngine* engine, const QVector& vector) { + QScriptValue array = engine->newArray(); + for (int i = 0; i < vector.size(); i++) { + array.setProperty(i, meshFaceToScriptValue(engine, vector.at(i))); + } + return array; +} + +void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector& result) { + int length = array.property("length").toInteger(); + result.clear(); + + for (int i = 0; i < length; i++) { + MeshFace meshFace = MeshFace(); + meshFaceFromScriptValue(array.property(i), meshFace); + result << meshFace; + } +} diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index 8a15f62eed..dd37cb3410 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -167,4 +168,73 @@ void quuidFromScriptValue(const QScriptValue& object, QUuid& uuid); QScriptValue qSizeFToScriptValue(QScriptEngine* engine, const QSizeF& qSizeF); void qSizeFFromScriptValue(const QScriptValue& object, QSizeF& qSizeF); +class AnimationDetails { +public: + AnimationDetails(); + AnimationDetails(QString role, QUrl url, float fps, float priority, bool loop, + bool hold, bool startAutomatically, float firstFrame, float lastFrame, bool running, float currentFrame); + + QString role; + QUrl url; + float fps; + float priority; + bool loop; + bool hold; + bool startAutomatically; + float firstFrame; + float lastFrame; + bool running; + float currentFrame; +}; +Q_DECLARE_METATYPE(AnimationDetails); +QScriptValue animationDetailsToScriptValue(QScriptEngine* engine, const AnimationDetails& event); +void animationDetailsFromScriptValue(const QScriptValue& object, AnimationDetails& event); + +namespace model { + class Mesh; +} + +using MeshPointer = std::shared_ptr; + + +class MeshProxy : public QObject { + Q_OBJECT + +public: + virtual MeshPointer getMeshPointer() const = 0; + Q_INVOKABLE virtual int getNumVertices() const = 0; + Q_INVOKABLE virtual glm::vec3 getPos3(int index) const = 0; +}; + +Q_DECLARE_METATYPE(MeshProxy*); + +class MeshProxyList : public QList {}; // typedef and using fight with the Qt macros/templates, do this instead +Q_DECLARE_METATYPE(MeshProxyList); + + +QScriptValue meshToScriptValue(QScriptEngine* engine, MeshProxy* const &in); +void meshFromScriptValue(const QScriptValue& value, MeshProxy* &out); + +QScriptValue meshesToScriptValue(QScriptEngine* engine, const MeshProxyList &in); +void meshesFromScriptValue(const QScriptValue& value, MeshProxyList &out); + +class MeshFace { + +public: + MeshFace() {} + ~MeshFace() {} + + QVector vertexIndices; + // TODO -- material... +}; + +Q_DECLARE_METATYPE(MeshFace) +Q_DECLARE_METATYPE(QVector) + +QScriptValue meshFaceToScriptValue(QScriptEngine* engine, const MeshFace &meshFace); +void meshFaceFromScriptValue(const QScriptValue &object, MeshFace& meshFaceResult); +QScriptValue qVectorMeshFaceToScriptValue(QScriptEngine* engine, const QVector& vector); +void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector& result); + + #endif // hifi_RegisteredMetaTypes_h diff --git a/libraries/animation/src/AnimationLoop.cpp b/libraries/shared/src/shared/types/AnimationLoop.cpp similarity index 95% rename from libraries/animation/src/AnimationLoop.cpp rename to libraries/shared/src/shared/types/AnimationLoop.cpp index 3d7bca863f..f77bd8bb2a 100644 --- a/libraries/animation/src/AnimationLoop.cpp +++ b/libraries/shared/src/shared/types/AnimationLoop.cpp @@ -9,11 +9,13 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include - -#include "AnimationCache.h" #include "AnimationLoop.h" +#include "../../NumericalConstants.h" +#include "../../SharedUtil.h" +#include "../../GLMHelpers.h" +#include "../../RegisteredMetaTypes.h" + const float AnimationLoop::MAXIMUM_POSSIBLE_FRAME = 100000.0f; AnimationLoop::AnimationLoop() : @@ -62,7 +64,7 @@ AnimationLoop::AnimationLoop(float fps, bool loop, bool hold, bool startAutomati { } -void AnimationLoop::simulateAtTime(quint64 now) { +void AnimationLoop::simulateAtTime(uint64_t now) { float deltaTime = (float)(now - _lastSimulated) / (float)USECS_PER_SECOND; _lastSimulated = now; simulate(deltaTime); diff --git a/libraries/animation/src/AnimationLoop.h b/libraries/shared/src/shared/types/AnimationLoop.h similarity index 93% rename from libraries/animation/src/AnimationLoop.h rename to libraries/shared/src/shared/types/AnimationLoop.h index 6adfbf35e2..33434209e7 100644 --- a/libraries/animation/src/AnimationLoop.h +++ b/libraries/shared/src/shared/types/AnimationLoop.h @@ -14,6 +14,9 @@ class AnimationDetails; +#include +#include + class AnimationLoop { public: static const float MAXIMUM_POSSIBLE_FRAME; @@ -58,7 +61,7 @@ public: void stop() { setRunning(false); } void simulate(float deltaTime); /// call this with deltaTime if you as the caller are managing the delta time between calls - void simulateAtTime(quint64 now); /// call this with "now" if you want the animationLoop to handle delta times + void simulateAtTime(uint64_t now); /// call this with "now" if you want the animationLoop to handle delta times private: float _fps; @@ -71,7 +74,7 @@ private: float _currentFrame; float _maxFrameIndexHint; bool _resetOnRunning; - quint64 _lastSimulated; + uint64_t _lastSimulated; }; #endif // hifi_AnimationLoop_h diff --git a/tests/render-perf/src/main.cpp b/tests/render-perf/src/main.cpp index 52592cd202..552dc3d270 100644 --- a/tests/render-perf/src/main.cpp +++ b/tests/render-perf/src/main.cpp @@ -47,6 +47,7 @@ #include #include +#include #include #include #include From 28d727153b4418c277b2d4cbf75584cddf4d233f Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 27 Jun 2017 19:22:07 -0700 Subject: [PATCH 79/95] PR feedback --- .../src/RenderableModelEntityItem.cpp | 13 ++++--------- .../src/model-networking/SimpleMeshProxy.cpp | 4 ++-- .../src/model-networking/SimpleMeshProxy.h | 4 ++-- libraries/model/src/model/Forward.h | 6 +++--- 4 files changed, 11 insertions(+), 16 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index b8e869e4d9..0547c60364 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -580,21 +580,16 @@ EntityItemProperties RenderableModelEntityItem::getProperties(EntityPropertyFlag } - const FBXGeometry* geometry = NULL; - const_cast(this)->getModel(); if (_model && _model->isLoaded()) { - geometry = &_model->getFBXGeometry(); - } - - // TODO: improve naturalDimensions in the future, - // for now we've added this hack for setting natural dimensions of models - if (geometry) { - Extents meshExtents = geometry->getUnscaledMeshExtents(); + // TODO: improve naturalDimensions in the future, + // for now we've added this hack for setting natural dimensions of models + Extents meshExtents = _model->getFBXGeometry().getUnscaledMeshExtents(); properties.setNaturalDimensions(meshExtents.maximum - meshExtents.minimum); properties.calculateNaturalPosition(meshExtents.minimum, meshExtents.maximum); } + return properties; } diff --git a/libraries/model-networking/src/model-networking/SimpleMeshProxy.cpp b/libraries/model-networking/src/model-networking/SimpleMeshProxy.cpp index 40b30154bf..b44ea1ff56 100644 --- a/libraries/model-networking/src/model-networking/SimpleMeshProxy.cpp +++ b/libraries/model-networking/src/model-networking/SimpleMeshProxy.cpp @@ -1,6 +1,6 @@ // -// MeshProxy.cpp -// libraries/model/src/model/ +// SimpleMeshProxy.cpp +// libraries/model-networking/src/model-networking/ // // Created by Seth Alves on 2017-3-22. // Copyright 2017 High Fidelity, Inc. diff --git a/libraries/model-networking/src/model-networking/SimpleMeshProxy.h b/libraries/model-networking/src/model-networking/SimpleMeshProxy.h index b82e501673..24c3fca27e 100644 --- a/libraries/model-networking/src/model-networking/SimpleMeshProxy.h +++ b/libraries/model-networking/src/model-networking/SimpleMeshProxy.h @@ -1,6 +1,6 @@ // -// MeshProxy.h -// libraries/model/src/model/ +// SimpleMeshProxy.h +// libraries/model-networking/src/model-networking/ // // Created by Seth Alves on 2017-1-27. // Copyright 2017 High Fidelity, Inc. diff --git a/libraries/model/src/model/Forward.h b/libraries/model/src/model/Forward.h index 362eabb454..90f55fa64f 100644 --- a/libraries/model/src/model/Forward.h +++ b/libraries/model/src/model/Forward.h @@ -1,9 +1,9 @@ // -// Geometry.h +// Forward.h // libraries/model/src/model // -// Created by Sam Gateau on 12/5/2014. -// Copyright 2014 High Fidelity, Inc. +// Created by Bradley Austin Davis on 2017/06/21 +// Copyright 2013-2017 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html From 93963b32577737dfc469e7bd80f7dfd64f0f03e9 Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Wed, 28 Jun 2017 15:44:17 +0200 Subject: [PATCH 80/95] Apply the projection and deferred transform fixes found while working on spectator camera --- interface/src/SecondaryCamera.cpp | 2 ++ libraries/gpu-gl/src/gpu/gl/GLBackend.cpp | 11 ++++++++++- libraries/gpu-gl/src/gpu/gl/GLBackend.h | 9 +++++++++ libraries/gpu-gl/src/gpu/gl/GLBackendPipeline.cpp | 8 +++++++- libraries/gpu-gl/src/gpu/gl/GLBackendTransform.cpp | 2 +- libraries/gpu/src/gpu/Batch.cpp | 7 +++++++ libraries/gpu/src/gpu/Batch.h | 6 ++++++ libraries/render-utils/src/DeferredFrameTransform.cpp | 2 +- libraries/render-utils/src/RenderDeferredTask.cpp | 2 +- libraries/render-utils/src/SurfaceGeometryPass.cpp | 2 +- libraries/render/src/render/Args.h | 2 ++ libraries/render/src/render/BlurTask.cpp | 4 ++-- 12 files changed, 49 insertions(+), 8 deletions(-) diff --git a/interface/src/SecondaryCamera.cpp b/interface/src/SecondaryCamera.cpp index f6ee8caa61..f59d2fcc7a 100644 --- a/interface/src/SecondaryCamera.cpp +++ b/interface/src/SecondaryCamera.cpp @@ -79,6 +79,7 @@ public: gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { batch.disableContextStereo(); + batch.disableContextViewCorrection(); }); auto srcViewFrustum = args->getViewFrustum(); @@ -112,6 +113,7 @@ public: gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { batch.restoreContextStereo(); + batch.restoreContextViewCorrection(); }); } }; diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp index 3441407f62..11e67811b6 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp @@ -114,7 +114,9 @@ GLBackend::CommandCall GLBackend::_commandCalls[Batch::NUM_COMMANDS] = (&::gpu::gl::GLBackend::do_getQuery), (&::gpu::gl::GLBackend::do_resetStages), - + + (&::gpu::gl::GLBackend::do_disableContextViewCorrection), + (&::gpu::gl::GLBackend::do_restoreContextViewCorrection), (&::gpu::gl::GLBackend::do_disableContextStereo), (&::gpu::gl::GLBackend::do_restoreContextStereo), @@ -374,6 +376,13 @@ void GLBackend::do_resetStages(const Batch& batch, size_t paramOffset) { resetStages(); } +void GLBackend::do_disableContextViewCorrection(const Batch& batch, size_t paramOffset) { + _transform._viewCorrectionEnabled = false; +} + +void GLBackend::do_restoreContextViewCorrection(const Batch& batch, size_t paramOffset) { + _transform._viewCorrectionEnabled = true; +} void GLBackend::do_disableContextStereo(const Batch& batch, size_t paramOffset) { diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackend.h b/libraries/gpu-gl/src/gpu/gl/GLBackend.h index 96217555e1..88aecda617 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackend.h +++ b/libraries/gpu-gl/src/gpu/gl/GLBackend.h @@ -143,6 +143,10 @@ public: // Reset stages virtual void do_resetStages(const Batch& batch, size_t paramOffset) final; + + virtual void do_disableContextViewCorrection(const Batch& batch, size_t paramOffset) final; + virtual void do_restoreContextViewCorrection(const Batch& batch, size_t paramOffset) final; + virtual void do_disableContextStereo(const Batch& batch, size_t paramOffset) final; virtual void do_restoreContextStereo(const Batch& batch, size_t paramOffset) final; @@ -333,6 +337,8 @@ protected: bool _skybox { false }; Transform _view; CameraCorrection _correction; + bool _viewCorrectionEnabled{ true }; + Mat4 _projection; Vec4i _viewport { 0, 0, 1, 1 }; @@ -400,6 +406,7 @@ protected: bool _invalidProgram { false }; BufferView _cameraCorrectionBuffer { gpu::BufferView(std::make_shared(sizeof(CameraCorrection), nullptr )) }; + BufferView _cameraCorrectionBufferIdentity { gpu::BufferView(std::make_shared(sizeof(CameraCorrection), nullptr )) }; State::Data _stateCache{ State::DEFAULT }; State::Signature _stateSignatureCache { 0 }; @@ -409,6 +416,8 @@ protected: PipelineStageState() { _cameraCorrectionBuffer.edit() = CameraCorrection(); + _cameraCorrectionBufferIdentity.edit() = CameraCorrection(); + _cameraCorrectionBufferIdentity._buffer->flush(); } } _pipeline; diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendPipeline.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendPipeline.cpp index 2d71e8ed78..7ef64343ea 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendPipeline.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendPipeline.cpp @@ -77,8 +77,14 @@ void GLBackend::do_setPipeline(const Batch& batch, size_t paramOffset) { if (_pipeline._invalidProgram) { glUseProgram(_pipeline._program); if (_pipeline._cameraCorrectionLocation != -1) { - auto cameraCorrectionBuffer = syncGPUObject(*_pipeline._cameraCorrectionBuffer._buffer); + gl::GLBuffer* cameraCorrectionBuffer = nullptr; + if (_transform._viewCorrectionEnabled) { + cameraCorrectionBuffer = syncGPUObject(*_pipeline._cameraCorrectionBuffer._buffer); + } else { + cameraCorrectionBuffer = syncGPUObject(*_pipeline._cameraCorrectionBufferIdentity._buffer); + } glBindBufferRange(GL_UNIFORM_BUFFER, _pipeline._cameraCorrectionLocation, cameraCorrectionBuffer->_id, 0, sizeof(CameraCorrection)); + } (void) CHECK_GL_ERROR(); _pipeline._invalidProgram = false; diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendTransform.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendTransform.cpp index 01f055e0d9..f286a5cca9 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendTransform.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendTransform.cpp @@ -102,7 +102,7 @@ void GLBackend::TransformStageState::preUpdate(size_t commandIndex, const Stereo if (_invalidView) { // Apply the correction - if (_viewIsCamera && _correction.correction != glm::mat4()) { + if (_viewIsCamera && (_viewCorrectionEnabled && _correction.correction != glm::mat4())) { // FIXME should I switch to using the camera correction buffer in Transform.slf and leave this out? Transform result; _view.mult(result, _view, _correction.correction); diff --git a/libraries/gpu/src/gpu/Batch.cpp b/libraries/gpu/src/gpu/Batch.cpp index 15c0dfce49..c432e19368 100644 --- a/libraries/gpu/src/gpu/Batch.cpp +++ b/libraries/gpu/src/gpu/Batch.cpp @@ -390,6 +390,13 @@ void Batch::resetStages() { ADD_COMMAND(resetStages); } +void Batch::disableContextViewCorrection() { + ADD_COMMAND(disableContextViewCorrection); +} + +void Batch::restoreContextViewCorrection() { + ADD_COMMAND(restoreContextViewCorrection); +} void Batch::disableContextStereo() { ADD_COMMAND(disableContextStereo); diff --git a/libraries/gpu/src/gpu/Batch.h b/libraries/gpu/src/gpu/Batch.h index 27c9402131..77d22258b2 100644 --- a/libraries/gpu/src/gpu/Batch.h +++ b/libraries/gpu/src/gpu/Batch.h @@ -217,6 +217,9 @@ public: // Reset the stage caches and states void resetStages(); + void disableContextViewCorrection(); + void restoreContextViewCorrection(); + void disableContextStereo(); void restoreContextStereo(); @@ -304,6 +307,9 @@ public: COMMAND_resetStages, + COMMAND_disableContextViewCorrection, + COMMAND_restoreContextViewCorrection, + COMMAND_disableContextStereo, COMMAND_restoreContextStereo, diff --git a/libraries/render-utils/src/DeferredFrameTransform.cpp b/libraries/render-utils/src/DeferredFrameTransform.cpp index b1166437db..baf523312c 100644 --- a/libraries/render-utils/src/DeferredFrameTransform.cpp +++ b/libraries/render-utils/src/DeferredFrameTransform.cpp @@ -39,7 +39,7 @@ void DeferredFrameTransform::update(RenderArgs* args) { args->getViewFrustum().evalProjectionMatrix(frameTransformBuffer.projectionMono); // Running in stero ? - bool isStereo = args->_context->isStereo(); + bool isStereo = args->isStereo(); if (!isStereo) { frameTransformBuffer.projection[0] = frameTransformBuffer.projectionMono; frameTransformBuffer.stereoInfo = glm::vec4(0.0f, (float)args->_viewport.z, 0.0f, 0.0f); diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index b49a066bf7..20c999019b 100644 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -406,7 +406,7 @@ void Blit::run(const RenderContextPointer& renderContext, const gpu::Framebuffer batch.setFramebuffer(blitFbo); if (renderArgs->_renderMode == RenderArgs::MIRROR_RENDER_MODE) { - if (renderArgs->_context->isStereo()) { + if (renderArgs->isStereo()) { gpu::Vec4i srcRectLeft; srcRectLeft.z = width / 2; srcRectLeft.w = height; diff --git a/libraries/render-utils/src/SurfaceGeometryPass.cpp b/libraries/render-utils/src/SurfaceGeometryPass.cpp index 1941766353..c4eea7ce7f 100644 --- a/libraries/render-utils/src/SurfaceGeometryPass.cpp +++ b/libraries/render-utils/src/SurfaceGeometryPass.cpp @@ -459,7 +459,7 @@ void SurfaceGeometryPass::run(const render::RenderContextPointer& renderContext, auto diffuseVPipeline = _diffusePass.getBlurVPipeline(); auto diffuseHPipeline = _diffusePass.getBlurHPipeline(); - _diffusePass.getParameters()->setWidthHeight(curvatureViewport.z, curvatureViewport.w, args->_context->isStereo()); + _diffusePass.getParameters()->setWidthHeight(curvatureViewport.z, curvatureViewport.w, args->isStereo()); glm::ivec2 textureSize(curvatureTexture->getDimensions()); _diffusePass.getParameters()->setTexcoordTransform(gpu::Framebuffer::evalSubregionTexcoordTransformCoefficients(textureSize, curvatureViewport)); _diffusePass.getParameters()->setDepthPerspective(args->getViewFrustum().getProjection()[1][1]); diff --git a/libraries/render/src/render/Args.h b/libraries/render/src/render/Args.h index a75488ce7a..c2e03d4f46 100644 --- a/libraries/render/src/render/Args.h +++ b/libraries/render/src/render/Args.h @@ -99,6 +99,8 @@ namespace render { void pushViewFrustum(const ViewFrustum& viewFrustum) { _viewFrustums.push(viewFrustum); } void popViewFrustum() { _viewFrustums.pop(); } + bool isStereo() const { return _displayMode != MONO; } + std::shared_ptr _context; std::shared_ptr _blitFramebuffer; std::shared_ptr _pipeline; diff --git a/libraries/render/src/render/BlurTask.cpp b/libraries/render/src/render/BlurTask.cpp index 0a6b3d16fc..73a8e0a0dd 100644 --- a/libraries/render/src/render/BlurTask.cpp +++ b/libraries/render/src/render/BlurTask.cpp @@ -217,7 +217,7 @@ void BlurGaussian::run(const RenderContextPointer& renderContext, const gpu::Fra auto blurVPipeline = getBlurVPipeline(); auto blurHPipeline = getBlurHPipeline(); - _parameters->setWidthHeight(args->_viewport.z, args->_viewport.w, args->_context->isStereo()); + _parameters->setWidthHeight(args->_viewport.z, args->_viewport.w, args->isStereo()); glm::ivec2 textureSize(blurringResources.sourceTexture->getDimensions()); _parameters->setTexcoordTransform(gpu::Framebuffer::evalSubregionTexcoordTransformCoefficients(textureSize, args->_viewport)); @@ -330,7 +330,7 @@ void BlurGaussianDepthAware::run(const RenderContextPointer& renderContext, cons auto sourceViewport = args->_viewport; - _parameters->setWidthHeight(sourceViewport.z, sourceViewport.w, args->_context->isStereo()); + _parameters->setWidthHeight(sourceViewport.z, sourceViewport.w, args->isStereo()); glm::ivec2 textureSize(blurringResources.sourceTexture->getDimensions()); _parameters->setTexcoordTransform(gpu::Framebuffer::evalSubregionTexcoordTransformCoefficients(textureSize, sourceViewport)); _parameters->setDepthPerspective(args->getViewFrustum().getProjection()[1][1]); From d3282a4ed06d203c74b9f9fb0272f57aa18a929e Mon Sep 17 00:00:00 2001 From: Vladyslav Stelmakhovskyi Date: Wed, 28 Jun 2017 15:56:35 +0200 Subject: [PATCH 81/95] Cleanup --- .../resources/qml/dialogs/TabletFileDialog.qml | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/interface/resources/qml/dialogs/TabletFileDialog.qml b/interface/resources/qml/dialogs/TabletFileDialog.qml index f77cc7c78a..9e1d0a9f5a 100644 --- a/interface/resources/qml/dialogs/TabletFileDialog.qml +++ b/interface/resources/qml/dialogs/TabletFileDialog.qml @@ -293,7 +293,7 @@ TabletModalWindow { } onFolderChanged: { - refreshTimer.start() + fileTableModel.update() } function getItem(index, field) { @@ -333,14 +333,6 @@ TabletModalWindow { } } - Timer { - id: refreshTimer - interval: 100 - repeat: false - running: false - onTriggered: fileTableModel.update() - } - Component { id: filesModelBuilder ListModel { } @@ -377,17 +369,16 @@ TabletModalWindow { } onFolderChanged: { + if (folder === rootFolder) { model = driveListModel; helper.monitorDirectory(""); update(); } else { var needsUpdate = model === driveListModel && folder === folderListModel.folder; - model = folderListModel; folderListModel.folder = folder; helper.monitorDirectory(helper.urlToPath(folder)); - if (needsUpdate) { update(); } @@ -420,7 +411,6 @@ TabletModalWindow { rows = 0, i; - console.log("dialog.qml updating") var newFilesModel = filesModelBuilder.createObject(root); comparisonFunction = sortOrder === Qt.AscendingOrder From 51b44d90213e823280233088f8b5d6c84a0a73a1 Mon Sep 17 00:00:00 2001 From: Vladyslav Stelmakhovskyi Date: Wed, 28 Jun 2017 16:36:25 +0200 Subject: [PATCH 82/95] Implement same for HMD mode --- .../resources/qml/dialogs/FileDialog.qml | 38 +++++++++++++------ 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index 0886a25949..106e067968 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -34,6 +34,8 @@ ModalWindow { HifiConstants { id: hifi } + property var filesModel: ListModel { } + Settings { category: "FileDialog" property alias width: root.width @@ -253,7 +255,9 @@ ModalWindow { } currentSelectionUrl = helper.pathToUrl(fileTableView.model.get(row).filePath); - currentSelectionIsFolder = fileTableView.model.isFolder(row); + currentSelectionIsFolder = fileTableView.model !== filesModel ? + fileTableView.model.isFolder(row) : + fileTableModel.isFolder(row); if (root.selectDirectory || !currentSelectionIsFolder) { currentSelection.text = capitalizeDrive(helper.urlToPath(currentSelectionUrl)); } else { @@ -331,7 +335,12 @@ ModalWindow { } } - ListModel { + Component { + id: filesModelBuilder + ListModel { } + } + + QtObject { id: fileTableModel // FolderListModel has a couple of problems: @@ -383,7 +392,11 @@ ModalWindow { if (row === -1) { return false; } - return get(row).fileIsDir; + return filesModel.get(row).fileIsDir; + } + + function get(row) { + return filesModel.get(row) } function update() { @@ -401,7 +414,7 @@ ModalWindow { rows = 0, i; - clear(); + var newFilesModel = filesModelBuilder.createObject(root); comparisonFunction = sortOrder === Qt.AscendingOrder ? function(a, b) { return a < b; } @@ -423,7 +436,7 @@ ModalWindow { while (lower < upper) { middle = Math.floor((lower + upper) / 2); var lessThan; - if (comparisonFunction(sortValue, get(middle)[sortField])) { + if (comparisonFunction(sortValue, newFilesModel.get(middle)[sortField])) { lessThan = true; upper = middle; } else { @@ -432,7 +445,7 @@ ModalWindow { } } - insert(lower, { + newFilesModel.insert(lower, { fileName: fileName, fileModified: (fileIsDir ? new Date(0) : model.getItem(i, "fileModified")), fileSize: model.getItem(i, "fileSize"), @@ -443,6 +456,7 @@ ModalWindow { rows++; } + filesModel = newFilesModel; d.clearSelection(); } @@ -469,7 +483,7 @@ ModalWindow { sortIndicatorOrder: Qt.AscendingOrder sortIndicatorVisible: true - model: fileTableModel + model: filesModel function updateSort() { model.sortOrder = sortIndicatorOrder; @@ -561,11 +575,12 @@ ModalWindow { } function navigateToCurrentRow() { + var currentModel = fileTableView.model !== filesModel ? fileTableView.model : fileTableModel var row = fileTableView.currentRow - var isFolder = model.isFolder(row); - var file = model.get(row).filePath; + var isFolder = currentModel.isFolder(row); + var file = currentModel.get(row).filePath; if (isFolder) { - fileTableView.model.folder = helper.pathToUrl(file); + currentModel.folder = helper.pathToUrl(file); } else { okAction.trigger(); } @@ -580,7 +595,8 @@ ModalWindow { var newPrefix = prefix + event.text.toLowerCase(); var matchedIndex = -1; for (var i = 0; i < model.count; ++i) { - var name = model.get(i).fileName.toLowerCase(); + var name = model !== filesModel ? model.get(i).fileName.toLowerCase() : + filesModel.get(i).fileName.toLowerCase(); if (0 === name.indexOf(newPrefix)) { matchedIndex = i; break; From f6ba264de67ff3bbc31441f19588b96ce020078b Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Wed, 28 Jun 2017 16:22:28 +0100 Subject: [PATCH 83/95] avatar recording fix --- libraries/avatars/src/AvatarData.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 1147b8a811..5023bd7ae5 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -1978,7 +1978,7 @@ JointData jointDataFromJsonValue(const QJsonValue& json) { result.rotation = quatFromJsonValue(array[0]); result.rotationSet = true; result.translation = vec3FromJsonValue(array[1]); - result.translationSet = false; + result.translationSet = true; } return result; } @@ -2146,12 +2146,9 @@ void AvatarData::fromJson(const QJsonObject& json, bool useFrameSkeleton) { QVector jointArray; QJsonArray jointArrayJson = json[JSON_AVATAR_JOINT_ARRAY].toArray(); jointArray.reserve(jointArrayJson.size()); - int i = 0; for (const auto& jointJson : jointArrayJson) { auto joint = jointDataFromJsonValue(jointJson); jointArray.push_back(joint); - setJointData(i, joint.rotation, joint.translation); - i++; } setRawJointData(jointArray); } From 0acbdc7755e925e7ba498583ca0252e1a04a10db Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Wed, 28 Jun 2017 10:18:54 -0700 Subject: [PATCH 84/95] CR fix: use predefined PI --- libraries/shared/src/AudioHelpers.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/shared/src/AudioHelpers.h b/libraries/shared/src/AudioHelpers.h index d75733e678..1dcc11af0c 100644 --- a/libraries/shared/src/AudioHelpers.h +++ b/libraries/shared/src/AudioHelpers.h @@ -14,6 +14,8 @@ #include +#include + const int IEEE754_MANT_BITS = 23; const int IEEE754_EXPN_BIAS = 127; @@ -105,7 +107,7 @@ static inline float fastAcosf(float x) { xi.f = ((-0.0198439236f * xi.f + 0.0762021306f) * xi.f + -0.212940971f) * xi.f + 1.57079633f; xi.f *= r; - return (sign ? 3.141592654f - xi.f : xi.f); + return (sign ? PI - xi.f : xi.f); } // From 2f3109e4beb86222227fcd89d53211626184407e Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 28 Jun 2017 11:00:05 -0700 Subject: [PATCH 85/95] move the location of Q_DECLARE_METATYPE(std::function) so that the server-script AC can see it --- libraries/shared/src/RegisteredMetaTypes.cpp | 2 ++ libraries/shared/src/RegisteredMetaTypes.h | 2 ++ libraries/ui/src/ui/OffscreenQmlSurface.cpp | 6 ------ 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp index 1e5fe23458..b30637c83f 100644 --- a/libraries/shared/src/RegisteredMetaTypes.cpp +++ b/libraries/shared/src/RegisteredMetaTypes.cpp @@ -37,6 +37,8 @@ int pickRayMetaTypeId = qRegisterMetaType(); int collisionMetaTypeId = qRegisterMetaType(); int qMapURLStringMetaTypeId = qRegisterMetaType>(); int socketErrorMetaTypeId = qRegisterMetaType(); +int voidLambdaType = qRegisterMetaType>(); +int variantLambdaType = qRegisterMetaType>(); void registerMetaTypes(QScriptEngine* engine) { qScriptRegisterMetaType(engine, mat4toScriptValue, mat4FromScriptValue); diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index dd37cb3410..123c769a96 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -34,6 +34,8 @@ Q_DECLARE_METATYPE(xColor) Q_DECLARE_METATYPE(QVector) Q_DECLARE_METATYPE(QVector) Q_DECLARE_METATYPE(AACube) +Q_DECLARE_METATYPE(std::function); +Q_DECLARE_METATYPE(std::function); void registerMetaTypes(QScriptEngine* engine); diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index f0006cb399..648bdad1bf 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -886,12 +886,6 @@ QQmlContext* OffscreenQmlSurface::getSurfaceContext() { return _qmlContext; } -Q_DECLARE_METATYPE(std::function); -auto VoidLambdaType = qRegisterMetaType>(); -Q_DECLARE_METATYPE(std::function); -auto VariantLambdaType = qRegisterMetaType>(); - - void OffscreenQmlSurface::executeOnUiThread(std::function function, bool blocking ) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "executeOnUiThread", blocking ? Qt::BlockingQueuedConnection : Qt::QueuedConnection, From 0dbb7a71f3831b69b86b8960e4e6b07cdd1d9143 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Wed, 28 Jun 2017 11:32:24 -0700 Subject: [PATCH 86/95] CR fix: comments instead of ref code --- libraries/audio-client/src/AudioClient.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 27e70e5f01..43af7afdef 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1702,9 +1702,8 @@ float AudioClient::azimuthForSource(const glm::vec3& relativePosition) { if (rotatedSourcePositionLength2 > SOURCE_DISTANCE_THRESHOLD) { // produce an oriented angle about the y-axis - // return glm::orientedAngle(glm::vec3(0.0f, 0.0f, -1.0f), glm::normalize(rotatedSourcePosition), glm::vec3(0.0f, -1.0f, 0.0f)); glm::vec3 direction = rotatedSourcePosition * (1.0f / fastSqrtf(rotatedSourcePositionLength2)); - float angle = fastAcosf(glm::clamp(-direction.z, -1.0f, 1.0f)); + float angle = fastAcosf(glm::clamp(-direction.z, -1.0f, 1.0f)); // UNIT_NEG_Z is "forward" return (direction.x < 0.0f) ? -angle : angle; } else { From a7a049434d733c3b3a8ed112697ce7a9c4824781 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Wed, 28 Jun 2017 11:32:41 -0700 Subject: [PATCH 87/95] CR fix: comments instead of ref code --- assignment-client/src/audio/AudioMixerSlave.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/assignment-client/src/audio/AudioMixerSlave.cpp b/assignment-client/src/audio/AudioMixerSlave.cpp index 9cfc659c14..ed63bbc298 100644 --- a/assignment-client/src/audio/AudioMixerSlave.cpp +++ b/assignment-client/src/audio/AudioMixerSlave.cpp @@ -498,9 +498,9 @@ float computeGain(const AvatarAudioStream& listeningNodeStream, const Positional } else if (!isEcho && (streamToAdd.getType() == PositionalAudioStream::Microphone)) { glm::vec3 rotatedListenerPosition = glm::inverse(streamToAdd.getOrientation()) * relativePosition; - // float angleOfDelivery = glm::angle(glm::vec3(0.0f, 0.0f, -1.0f), glm::normalize(rotatedListenerPosition)); + // source directivity is based on angle of emission, in local coordinates glm::vec3 direction = glm::normalize(rotatedListenerPosition); - float angleOfDelivery = fastAcosf(glm::clamp(-direction.z, -1.0f, 1.0f)); + float angleOfDelivery = fastAcosf(glm::clamp(-direction.z, -1.0f, 1.0f)); // UNIT_NEG_Z is "forward" const float MAX_OFF_AXIS_ATTENUATION = 0.2f; const float OFF_AXIS_ATTENUATION_STEP = (1 - MAX_OFF_AXIS_ATTENUATION) / 2.0f; @@ -557,9 +557,8 @@ float computeAzimuth(const AvatarAudioStream& listeningNodeStream, const Positio if (rotatedSourcePositionLength2 > SOURCE_DISTANCE_THRESHOLD) { // produce an oriented angle about the y-axis - // return glm::orientedAngle(glm::vec3(0.0f, 0.0f, -1.0f), glm::normalize(rotatedSourcePosition), glm::vec3(0.0f, -1.0f, 0.0f)); glm::vec3 direction = rotatedSourcePosition * (1.0f / fastSqrtf(rotatedSourcePositionLength2)); - float angle = fastAcosf(glm::clamp(-direction.z, -1.0f, 1.0f)); + float angle = fastAcosf(glm::clamp(-direction.z, -1.0f, 1.0f)); // UNIT_NEG_Z is "forward" return (direction.x < 0.0f) ? -angle : angle; } else { From 48de8011b28c8241dd00626640871c62c6b00f98 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Wed, 28 Jun 2017 14:15:29 -0700 Subject: [PATCH 88/95] cr feedback --- interface/src/Application.cpp | 3 ++- interface/src/Application.h | 1 + interface/src/main.cpp | 6 ++++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 72e14656f3..a36b9ace93 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1185,7 +1185,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // we just want to see if --scripts was set, we've already parsed it and done // the change in PathUtils. Rather than pass that in the constructor, lets just // look (this could be debated) - QDir defaultScriptsLocation(getCmdOption(argc, constArgv, "--scripts")); + QString scriptsSwitch = QString("--").append(SCRIPTS_SWITCH); + QDir defaultScriptsLocation(getCmdOption(argc, constArgv, scriptsSwitch.toStdString().c_str())); if (!defaultScriptsLocation.exists()) { scriptEngines->loadDefaultScripts(); scriptEngines->defaultScriptsLocationOverridden(true); diff --git a/interface/src/Application.h b/interface/src/Application.h index a7aded006b..c26b3b215e 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -95,6 +95,7 @@ static const UINT UWM_SHOW_APPLICATION = #endif static const QString RUNNING_MARKER_FILENAME = "Interface.running"; +static const QString SCRIPTS_SWITCH = "scripts"; class Application; #if defined(qApp) diff --git a/interface/src/main.cpp b/interface/src/main.cpp index ac1b6d5010..ebe6ebc7b5 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -73,7 +73,7 @@ int main(int argc, const char* argv[]) { QCommandLineOption serverContentPathOption("serverContentPath", "Where to find server content", "serverContentPath"); QCommandLineOption allowMultipleInstancesOption("allowMultipleInstances", "Allow multiple instances to run"); QCommandLineOption overrideAppLocalDataPathOption("cache", "set test cache ", "dir"); - QCommandLineOption overrideScriptsPathOption("scripts", "set scripts ", "path"); + QCommandLineOption overrideScriptsPathOption(SCRIPTS_SWITCH, "set scripts ", "path"); parser.addOption(urlOption); parser.addOption(noUpdaterOption); parser.addOption(checkMinSpecOption); @@ -101,7 +101,9 @@ int main(int argc, const char* argv[]) { if (allowMultipleInstances) { instanceMightBeRunning = false; } - + // this needs to be done here in main, as the mechanism for setting the + // scripts directory appears not to work. See the bug report + // https://highfidelity.fogbugz.com/f/cases/5759/Issues-changing-scripts-directory-in-ScriptsEngine if (parser.isSet(overrideScriptsPathOption)) { QDir scriptsPath(parser.value(overrideScriptsPathOption)); if (scriptsPath.exists()) { From 794495c197e1e99a380117cc6cef104455819b6d Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 28 Jun 2017 14:18:14 -0700 Subject: [PATCH 89/95] make ResourceManager be owned by DependencyManager --- assignment-client/src/Agent.cpp | 7 ++-- .../src/entities/EntityServer.cpp | 2 +- .../src/scripts/EntityScriptServer.cpp | 7 ++-- interface/src/Application.cpp | 6 ++-- interface/src/assets/ATPAssetMigrator.cpp | 3 +- interface/src/main.cpp | 2 +- .../src/scripting/TestScriptingInterface.cpp | 2 +- .../src/EntityTreeRenderer.cpp | 2 +- libraries/entities/src/EntityEditFilters.cpp | 2 +- libraries/fbx/src/OBJReader.cpp | 4 +-- .../src/model-networking/TextureCache.cpp | 4 +-- libraries/networking/src/AtpReply.cpp | 2 +- libraries/networking/src/ResourceCache.cpp | 2 +- libraries/networking/src/ResourceManager.cpp | 24 ++++++------- libraries/networking/src/ResourceManager.h | 34 ++++++++++++------- .../src/ResourceScriptingInterface.cpp | 2 +- libraries/octree/src/Octree.cpp | 3 +- .../procedural/src/procedural/Procedural.cpp | 2 +- .../src/FileScriptingInterface.cpp | 2 +- libraries/script-engine/src/ScriptCache.cpp | 8 ++--- libraries/script-engine/src/ScriptEngine.cpp | 2 +- tests/render-perf/src/main.cpp | 7 ++-- tests/render-texture-load/src/main.cpp | 4 +-- 23 files changed, 68 insertions(+), 65 deletions(-) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 260a6d6825..f336f1dad5 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -62,8 +62,6 @@ Agent::Agent(ReceivedMessage& message) : _entityEditSender.setPacketsPerSecond(DEFAULT_ENTITY_PPS_PER_SCRIPT); DependencyManager::get()->setPacketSender(&_entityEditSender); - ResourceManager::init(); - DependencyManager::registerInheritance(); DependencyManager::set(); @@ -81,6 +79,7 @@ Agent::Agent(ReceivedMessage& message) : DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); @@ -199,7 +198,7 @@ void Agent::requestScript() { return; } - auto request = ResourceManager::createResourceRequest(this, scriptURL); + auto request = DependencyManager::get()->createResourceRequest(this, scriptURL); if (!request) { qWarning() << "Could not create ResourceRequest for Agent script at" << scriptURL.toString(); @@ -779,7 +778,7 @@ void Agent::aboutToFinish() { // our entity tree is going to go away so tell that to the EntityScriptingInterface DependencyManager::get()->setEntityTree(nullptr); - ResourceManager::cleanup(); + DependencyManager::get()->cleanup(); // cleanup the AudioInjectorManager (and any still running injectors) DependencyManager::destroy(); diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index dc0a2add3a..afcbcf4f1e 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -31,9 +31,9 @@ EntityServer::EntityServer(ReceivedMessage& message) : OctreeServer(message), _entitySimulation(NULL) { - ResourceManager::init(); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); packetReceiver.registerListenerForTypes({ PacketType::EntityAdd, PacketType::EntityEdit, PacketType::EntityErase, PacketType::EntityPhysics }, diff --git a/assignment-client/src/scripts/EntityScriptServer.cpp b/assignment-client/src/scripts/EntityScriptServer.cpp index 1b226ab642..489478ff9a 100644 --- a/assignment-client/src/scripts/EntityScriptServer.cpp +++ b/assignment-client/src/scripts/EntityScriptServer.cpp @@ -54,7 +54,7 @@ EntityScriptServer::EntityScriptServer(ReceivedMessage& message) : ThreadedAssig DependencyManager::get()->setPacketSender(&_entityEditSender); - ResourceManager::init(); + DependencyManager::set(); DependencyManager::registerInheritance(); @@ -67,7 +67,6 @@ EntityScriptServer::EntityScriptServer(ReceivedMessage& message) : ThreadedAssig DependencyManager::set(); DependencyManager::set(ScriptEngine::ENTITY_SERVER_SCRIPT); - auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); packetReceiver.registerListenerForTypes({ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase }, this, "handleOctreePacket"); @@ -493,7 +492,7 @@ void EntityScriptServer::checkAndCallPreload(const EntityItemID& entityID, bool if (entity && (reload || notRunning || details.scriptText != entity->getServerScripts())) { QString scriptUrl = entity->getServerScripts(); if (!scriptUrl.isEmpty()) { - scriptUrl = ResourceManager::normalizeURL(scriptUrl); + scriptUrl = DependencyManager::get()->normalizeURL(scriptUrl); qCDebug(entity_script_server) << "Loading entity server script" << scriptUrl << "for" << entityID; _entitiesScriptEngine->loadEntityScript(entityID, scriptUrl, reload); } @@ -551,7 +550,7 @@ void EntityScriptServer::aboutToFinish() { // our entity tree is going to go away so tell that to the EntityScriptingInterface DependencyManager::get()->setEntityTree(nullptr); - ResourceManager::cleanup(); + DependencyManager::get()->cleanup(); // cleanup the AudioInjectorManager (and any still running injectors) DependencyManager::destroy(); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c6741ee7e4..118d99997a 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -582,6 +582,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); return previousSessionCrashed; } @@ -776,7 +777,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(this, &Application::activeDisplayPluginChanged, reinterpret_cast(audioScriptingInterface.data()), &scripting::Audio::onContextChanged); - ResourceManager::init(); // Make sure we don't time out during slow operations at startup updateHeartbeat(); @@ -1880,7 +1880,7 @@ Application::~Application() { DependencyManager::destroy(); DependencyManager::destroy(); - ResourceManager::cleanup(); + DependencyManager::get()->cleanup(); // remove the NodeList from the DependencyManager DependencyManager::destroy(); @@ -5940,7 +5940,7 @@ void Application::addAssetToWorldFromURL(QString url) { addAssetToWorldInfo(filename, "Downloading model file " + filename + "."); - auto request = ResourceManager::createResourceRequest(nullptr, QUrl(url)); + auto request = DependencyManager::get()->createResourceRequest(nullptr, QUrl(url)); connect(request, &ResourceRequest::finished, this, &Application::addAssetToWorldFromURLRequestFinished); request->send(); } diff --git a/interface/src/assets/ATPAssetMigrator.cpp b/interface/src/assets/ATPAssetMigrator.cpp index e0e9d5a73a..667c2587b0 100644 --- a/interface/src/assets/ATPAssetMigrator.cpp +++ b/interface/src/assets/ATPAssetMigrator.cpp @@ -106,7 +106,8 @@ void ATPAssetMigrator::loadEntityServerFile() { jsonValue = entityObject; } else if (wantsToMigrateResource(migrationURL)) { - auto request = ResourceManager::createResourceRequest(this, migrationURL); + auto request = + DependencyManager::get()->createResourceRequest(this, migrationURL); if (request) { qCDebug(asset_migrator) << "Requesting" << migrationURL << "for ATP asset migration"; diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 67e248506f..88493f9944 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -106,7 +106,7 @@ int main(int argc, const char* argv[]) { // tell everyone to use the right cache location // // this handles data8 and prepared - ResourceManager::setCacheDir(cacheDir); + DependencyManager::get()->setCacheDir(cacheDir); // this does the ktx_cache PathUtils::getAppLocalDataPath(cacheDir); diff --git a/interface/src/scripting/TestScriptingInterface.cpp b/interface/src/scripting/TestScriptingInterface.cpp index b8892fae7e..84c742d0ab 100644 --- a/interface/src/scripting/TestScriptingInterface.cpp +++ b/interface/src/scripting/TestScriptingInterface.cpp @@ -62,7 +62,7 @@ bool TestScriptingInterface::loadTestScene(QString scene) { static const QString TEST_SCRIPTS_ROOT = TEST_ROOT + "scripts/"; static const QString TEST_SCENES_ROOT = TEST_ROOT + "scenes/"; return DependencyManager::get()->returnFromUiThread([scene]()->QVariant { - ResourceManager::setUrlPrefixOverride("atp:/", TEST_BINARY_ROOT + scene + ".atp/"); + DependencyManager::get()->setUrlPrefixOverride("atp:/", TEST_BINARY_ROOT + scene + ".atp/"); auto tree = qApp->getEntities()->getTree(); auto treeIsClient = tree->getIsClient(); // Force the tree to accept the load regardless of permissions diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index c02bde2244..1684c06512 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -853,7 +853,7 @@ void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, bool entity->scriptHasUnloaded(); } if (shouldLoad) { - scriptUrl = ResourceManager::normalizeURL(scriptUrl); + scriptUrl = DependencyManager::get()->normalizeURL(scriptUrl); _entitiesScriptEngine->loadEntityScript(entityID, scriptUrl, reload); entity->scriptHasPreloaded(); } diff --git a/libraries/entities/src/EntityEditFilters.cpp b/libraries/entities/src/EntityEditFilters.cpp index d62495d95e..5359ebd31b 100644 --- a/libraries/entities/src/EntityEditFilters.cpp +++ b/libraries/entities/src/EntityEditFilters.cpp @@ -132,7 +132,7 @@ void EntityEditFilters::addFilter(EntityItemID entityID, QString filterURL) { _filterDataMap.insert(entityID, filterData); _lock.unlock(); - auto scriptRequest = ResourceManager::createResourceRequest(this, scriptURL); + auto scriptRequest = DependencyManager::get()->createResourceRequest(this, scriptURL); if (!scriptRequest) { qWarning() << "Could not create ResourceRequest for Entity Edit filter script at" << scriptURL.toString(); scriptRequestFinished(entityID); diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index 1445d14d84..417901b9ab 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -202,7 +202,7 @@ bool OBJReader::isValidTexture(const QByteArray &filename) { } QUrl candidateUrl = _url.resolved(QUrl(filename)); - return ResourceManager::resourceExists(candidateUrl); + return DependencyManager::get()->resourceExists(candidateUrl); } void OBJReader::parseMaterialLibrary(QIODevice* device) { @@ -267,7 +267,7 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { } std::tuple requestData(QUrl& url) { - auto request = ResourceManager::createResourceRequest(nullptr, url); + auto request = DependencyManager::get()->createResourceRequest(nullptr, url); if (!request) { return std::make_tuple(false, QByteArray()); diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index ed1715219a..5c8f59f20f 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -384,7 +384,7 @@ void NetworkTexture::makeRequest() { // Add a fragment to the base url so we can identify the section of the ktx being requested when debugging // The actual requested url is _activeUrl and will not contain the fragment _url.setFragment("head"); - _ktxHeaderRequest = ResourceManager::createResourceRequest(this, _activeUrl); + _ktxHeaderRequest = DependencyManager::get()->createResourceRequest(this, _activeUrl); if (!_ktxHeaderRequest) { qCDebug(networking).noquote() << "Failed to get request for" << _url.toDisplayString(); @@ -454,7 +454,7 @@ void NetworkTexture::startMipRangeRequest(uint16_t low, uint16_t high) { bool isHighMipRequest = low == NULL_MIP_LEVEL && high == NULL_MIP_LEVEL; - _ktxMipRequest = ResourceManager::createResourceRequest(this, _activeUrl); + _ktxMipRequest = DependencyManager::get()->createResourceRequest(this, _activeUrl); if (!_ktxMipRequest) { qCWarning(networking).noquote() << "Failed to get request for" << _url.toDisplayString(); diff --git a/libraries/networking/src/AtpReply.cpp b/libraries/networking/src/AtpReply.cpp index 4440995ee0..6417478005 100644 --- a/libraries/networking/src/AtpReply.cpp +++ b/libraries/networking/src/AtpReply.cpp @@ -13,7 +13,7 @@ #include "AtpReply.h" AtpReply::AtpReply(const QUrl& url, QObject* parent) : - _resourceRequest(ResourceManager::createResourceRequest(parent, url)) { + _resourceRequest(DependencyManager::get()->createResourceRequest(parent, url)) { setOperation(QNetworkAccessManager::GetOperation); connect(_resourceRequest, &AssetResourceRequest::progress, this, &AtpReply::downloadProgress); diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 88ea68780b..f07514cd85 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -672,7 +672,7 @@ void Resource::makeRequest() { PROFILE_ASYNC_BEGIN(resource, "Resource:" + getType(), QString::number(_requestID), { { "url", _url.toString() }, { "activeURL", _activeUrl.toString() } }); - _request = ResourceManager::createResourceRequest(this, _activeUrl); + _request = DependencyManager::get()->createResourceRequest(this, _activeUrl); if (!_request) { qCDebug(networking).noquote() << "Failed to get request for" << _url.toDisplayString(); diff --git a/libraries/networking/src/ResourceManager.cpp b/libraries/networking/src/ResourceManager.cpp index e4357ca507..e9fe2f1ec1 100644 --- a/libraries/networking/src/ResourceManager.cpp +++ b/libraries/networking/src/ResourceManager.cpp @@ -24,10 +24,16 @@ #include "NetworkAccessManager.h" #include "NetworkLogging.h" -QThread ResourceManager::_thread; -ResourceManager::PrefixMap ResourceManager::_prefixMap; -QMutex ResourceManager::_prefixMapLock; -QString ResourceManager::_cacheDir; + +ResourceManager::ResourceManager() { + _thread.setObjectName("Resource Manager Thread"); + + auto assetClient = DependencyManager::set(_cacheDir); + assetClient->moveToThread(&_thread); + QObject::connect(&_thread, &QThread::started, assetClient.data(), &AssetClient::init); + + _thread.start(); +} void ResourceManager::setUrlPrefixOverride(const QString& prefix, const QString& replacement) { QMutexLocker locker(&_prefixMapLock); @@ -75,16 +81,6 @@ QUrl ResourceManager::normalizeURL(const QUrl& originalUrl) { return url; } -void ResourceManager::init() { - _thread.setObjectName("Resource Manager Thread"); - - auto assetClient = DependencyManager::set(_cacheDir); - assetClient->moveToThread(&_thread); - QObject::connect(&_thread, &QThread::started, assetClient.data(), &AssetClient::init); - - _thread.start(); -} - void ResourceManager::cleanup() { // cleanup the AssetClient thread DependencyManager::destroy(); diff --git a/libraries/networking/src/ResourceManager.h b/libraries/networking/src/ResourceManager.h index 699573ddd6..4e7cd3d92d 100644 --- a/libraries/networking/src/ResourceManager.h +++ b/libraries/networking/src/ResourceManager.h @@ -14,7 +14,11 @@ #include +#include #include +#include + +#include #include "ResourceRequest.h" @@ -24,34 +28,38 @@ const QString URL_SCHEME_HTTPS = "https"; const QString URL_SCHEME_FTP = "ftp"; const QString URL_SCHEME_ATP = "atp"; -class ResourceManager { +class ResourceManager: public QObject, public Dependency { + Q_OBJECT + SINGLETON_DEPENDENCY + public: + ResourceManager(); - static void setUrlPrefixOverride(const QString& prefix, const QString& replacement); - static QString normalizeURL(const QString& urlString); - static QUrl normalizeURL(const QUrl& url); + void setUrlPrefixOverride(const QString& prefix, const QString& replacement); + QString normalizeURL(const QString& urlString); + QUrl normalizeURL(const QUrl& url); - static ResourceRequest* createResourceRequest(QObject* parent, const QUrl& url); + ResourceRequest* createResourceRequest(QObject* parent, const QUrl& url); - static void init(); - static void cleanup(); + void init(); + void cleanup(); // Blocking call to check if a resource exists. This function uses a QEventLoop internally // to return to the calling thread so that events can still be processed. - static bool resourceExists(const QUrl& url); + bool resourceExists(const QUrl& url); // adjust where we persist the cache - static void setCacheDir(const QString& cacheDir); + void setCacheDir(const QString& cacheDir); private: - static QThread _thread; + QThread _thread; using PrefixMap = std::map; - static PrefixMap _prefixMap; - static QMutex _prefixMapLock; + PrefixMap _prefixMap; + QMutex _prefixMapLock; - static QString _cacheDir; + QString _cacheDir; }; #endif diff --git a/libraries/networking/src/ResourceScriptingInterface.cpp b/libraries/networking/src/ResourceScriptingInterface.cpp index 38be49049c..3227d44de1 100644 --- a/libraries/networking/src/ResourceScriptingInterface.cpp +++ b/libraries/networking/src/ResourceScriptingInterface.cpp @@ -11,5 +11,5 @@ #include "ResourceManager.h" void ResourceScriptingInterface::overrideUrlPrefix(const QString& prefix, const QString& replacement) { - ResourceManager::setUrlPrefixOverride(prefix, replacement); + DependencyManager::get()->setUrlPrefixOverride(prefix, replacement); } diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 180f25f106..2e93f3515f 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -1668,7 +1668,8 @@ bool Octree::readJSONFromGzippedFile(QString qFileName) { } bool Octree::readFromURL(const QString& urlString) { - auto request = std::unique_ptr(ResourceManager::createResourceRequest(this, urlString)); + auto request = + std::unique_ptr(DependencyManager::get()->createResourceRequest(this, urlString)); if (!request) { return false; diff --git a/libraries/procedural/src/procedural/Procedural.cpp b/libraries/procedural/src/procedural/Procedural.cpp index e4ce3c691a..c38e562672 100644 --- a/libraries/procedural/src/procedural/Procedural.cpp +++ b/libraries/procedural/src/procedural/Procedural.cpp @@ -103,7 +103,7 @@ bool Procedural::parseVersion(const QJsonValue& version) { } bool Procedural::parseShader(const QUrl& shaderPath) { - auto shaderUrl = ResourceManager::normalizeURL(shaderPath); + auto shaderUrl = DependencyManager::get()->normalizeURL(shaderPath); if (!shaderUrl.isValid()) { if (!shaderUrl.isEmpty()) { diff --git a/libraries/script-engine/src/FileScriptingInterface.cpp b/libraries/script-engine/src/FileScriptingInterface.cpp index 2f5cc2bc88..30d0a3a201 100644 --- a/libraries/script-engine/src/FileScriptingInterface.cpp +++ b/libraries/script-engine/src/FileScriptingInterface.cpp @@ -85,7 +85,7 @@ QString FileScriptingInterface::convertUrlToPath(QUrl url) { // this function is not in use void FileScriptingInterface::downloadZip(QString path, const QString link) { QUrl url = QUrl(link); - auto request = ResourceManager::createResourceRequest(nullptr, url); + auto request = DependencyManager::get()->createResourceRequest(nullptr, url); connect(request, &ResourceRequest::finished, this, [this, path]{ unzipFile(path, ""); // so intellisense isn't mad }); diff --git a/libraries/script-engine/src/ScriptCache.cpp b/libraries/script-engine/src/ScriptCache.cpp index 601ca6bc95..dba2db0458 100644 --- a/libraries/script-engine/src/ScriptCache.cpp +++ b/libraries/script-engine/src/ScriptCache.cpp @@ -57,7 +57,7 @@ void ScriptCache::clearATPScriptsFromCache() { } void ScriptCache::deleteScript(const QUrl& unnormalizedURL) { - QUrl url = ResourceManager::normalizeURL(unnormalizedURL); + QUrl url = DependencyManager::get()->normalizeURL(unnormalizedURL); Lock lock(_containerLock); if (_scriptCache.contains(url)) { qCDebug(scriptengine) << "Delete script from cache:" << url.toString(); @@ -70,7 +70,7 @@ void ScriptCache::getScriptContents(const QString& scriptOrURL, contentAvailable qCDebug(scriptengine) << "ScriptCache::getScriptContents() on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]"; #endif QUrl unnormalizedURL(scriptOrURL); - QUrl url = ResourceManager::normalizeURL(unnormalizedURL); + QUrl url = DependencyManager::get()->normalizeURL(unnormalizedURL); // attempt to determine if this is a URL to a script, or if this is actually a script itself (which is valid in the // entityScript use case) @@ -109,7 +109,7 @@ void ScriptCache::getScriptContents(const QString& scriptOrURL, contentAvailable #ifdef THREAD_DEBUGGING qCDebug(scriptengine) << "about to call: ResourceManager::createResourceRequest(this, url); on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]"; #endif - auto request = ResourceManager::createResourceRequest(nullptr, url); + auto request = DependencyManager::get()->createResourceRequest(nullptr, url); Q_ASSERT(request); request->setCacheEnabled(!forceDownload); connect(request, &ResourceRequest::finished, this, [=]{ scriptContentAvailable(maxRetries); }); @@ -166,7 +166,7 @@ void ScriptCache::scriptContentAvailable(int maxRetries) { qCDebug(scriptengine) << QString("Retrying script request [%1 / %2]: %3") .arg(attempt).arg(maxRetries).arg(url.toString()); - auto request = ResourceManager::createResourceRequest(nullptr, url); + auto request = DependencyManager::get()->createResourceRequest(nullptr, url); Q_ASSERT(request); // We've already made a request, so the cache must be disabled or it wasn't there, so enabling diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 48f8b07a4a..11bb044d72 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -1762,7 +1762,7 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac QList urls; for (QString includeFile : includeFiles) { - QString file = ResourceManager::normalizeURL(includeFile); + QString file = DependencyManager::get()->normalizeURL(includeFile); QUrl thisURL; bool isStandardLibrary = false; if (file.startsWith("/~/")) { diff --git a/tests/render-perf/src/main.cpp b/tests/render-perf/src/main.cpp index 552dc3d270..50f92ec3b9 100644 --- a/tests/render-perf/src/main.cpp +++ b/tests/render-perf/src/main.cpp @@ -494,6 +494,7 @@ public: DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); } QTestWindow() { @@ -520,8 +521,6 @@ public: _entitySimulation = simpleSimulation; } - ResourceManager::init(); - setFlags(Qt::MSWindowsOwnDC | Qt::Window | Qt::Dialog | Qt::WindowMinMaxButtonsHint | Qt::WindowTitleHint); _size = QSize(800, 600); _renderThread._size = _size; @@ -575,7 +574,7 @@ public: DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); - ResourceManager::cleanup(); + DependencyManager::get()->cleanup(); // remove the NodeList from the DependencyManager DependencyManager::destroy(); } @@ -998,7 +997,7 @@ private: QFileInfo atpPathInfo(atpPath); if (atpPathInfo.exists()) { QString atpUrl = QUrl::fromLocalFile(atpPath).toString(); - ResourceManager::setUrlPrefixOverride("atp:/", atpUrl + "/"); + DependencyManager::get()->setUrlPrefixOverride("atp:/", atpUrl + "/"); } _octree->clear(); _octree->getTree()->readFromURL(fileName); diff --git a/tests/render-texture-load/src/main.cpp b/tests/render-texture-load/src/main.cpp index f426cd8024..5920e04558 100644 --- a/tests/render-texture-load/src/main.cpp +++ b/tests/render-texture-load/src/main.cpp @@ -289,6 +289,7 @@ public: DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); } struct TextureLoad { @@ -329,7 +330,6 @@ public: installEventFilter(this); QThreadPool::globalInstance()->setMaxThreadCount(2); QThread::currentThread()->setPriority(QThread::HighestPriority); - ResourceManager::init(); setFlags(Qt::MSWindowsOwnDC | Qt::Window | Qt::Dialog | Qt::WindowMinMaxButtonsHint | Qt::WindowTitleHint); _size = QSize(800, 600); _renderThread._size = _size; @@ -369,7 +369,7 @@ public: DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); - ResourceManager::cleanup(); + DependencyManager::get()->cleanup(); } protected: From e6be838e79101cb3abc2e95fa150fc4b0e841930 Mon Sep 17 00:00:00 2001 From: utkarshgautamnyu Date: Wed, 28 Jun 2017 15:30:49 -0700 Subject: [PATCH 90/95] Update snapshot.js --- scripts/system/snapshot.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index c60e3a67f7..e033ba9f44 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -504,7 +504,14 @@ function takeSnapshot() { Window.takeSnapshot(false, includeAnimated, 1.91); }, SNAPSHOT_DELAY); }, FINISH_SOUND_DELAY); +UserActivityLogger.logAction("snapshot_taken", logDetails()); } + + function logDetails() { + return { + current_domain: location.href + }; + } function isDomainOpen(id, callback) { print("Checking open status of domain with ID:", id); From 93679f7c3be1899fbe5ae534ff5fd50b3ad79bc1 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Thu, 29 Jun 2017 00:52:51 +0200 Subject: [PATCH 91/95] move the ResourceManager init back to where it was, to prevent problems --- assignment-client/src/Agent.cpp | 3 ++- assignment-client/src/entities/EntityServer.cpp | 2 +- tests/render-perf/src/main.cpp | 3 ++- tests/render-texture-load/src/main.cpp | 4 ++-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index f336f1dad5..8aec5adb1f 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -62,6 +62,8 @@ Agent::Agent(ReceivedMessage& message) : _entityEditSender.setPacketsPerSecond(DEFAULT_ENTITY_PPS_PER_SCRIPT); DependencyManager::get()->setPacketSender(&_entityEditSender); + DependencyManager::set(); + DependencyManager::registerInheritance(); DependencyManager::set(); @@ -79,7 +81,6 @@ Agent::Agent(ReceivedMessage& message) : DependencyManager::set(); DependencyManager::set(); - DependencyManager::set(); auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index afcbcf4f1e..ac686e2e0a 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -31,9 +31,9 @@ EntityServer::EntityServer(ReceivedMessage& message) : OctreeServer(message), _entitySimulation(NULL) { + DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); - DependencyManager::set(); auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); packetReceiver.registerListenerForTypes({ PacketType::EntityAdd, PacketType::EntityEdit, PacketType::EntityErase, PacketType::EntityPhysics }, diff --git a/tests/render-perf/src/main.cpp b/tests/render-perf/src/main.cpp index 50f92ec3b9..61608f12aa 100644 --- a/tests/render-perf/src/main.cpp +++ b/tests/render-perf/src/main.cpp @@ -494,7 +494,6 @@ public: DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); - DependencyManager::set(); } QTestWindow() { @@ -520,6 +519,8 @@ public: _octree->getTree()->setSimulation(simpleSimulation); _entitySimulation = simpleSimulation; } + + DependencyManager::set(); setFlags(Qt::MSWindowsOwnDC | Qt::Window | Qt::Dialog | Qt::WindowMinMaxButtonsHint | Qt::WindowTitleHint); _size = QSize(800, 600); diff --git a/tests/render-texture-load/src/main.cpp b/tests/render-texture-load/src/main.cpp index 5920e04558..d85fcd6a32 100644 --- a/tests/render-texture-load/src/main.cpp +++ b/tests/render-texture-load/src/main.cpp @@ -289,7 +289,6 @@ public: DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); - DependencyManager::set(); } struct TextureLoad { @@ -329,7 +328,8 @@ public: installEventFilter(this); QThreadPool::globalInstance()->setMaxThreadCount(2); - QThread::currentThread()->setPriority(QThread::HighestPriority); + QThread::currentThread()->setPriority(QThread::HighestPriority); + DependencyManager::set(); setFlags(Qt::MSWindowsOwnDC | Qt::Window | Qt::Dialog | Qt::WindowMinMaxButtonsHint | Qt::WindowTitleHint); _size = QSize(800, 600); _renderThread._size = _size; From d5289d357b9e67ed53ef884a0d51e35edb1b8c48 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Thu, 29 Jun 2017 01:01:59 +0200 Subject: [PATCH 92/95] I need to setup my code-editor, spaces. --- tests/render-perf/src/main.cpp | 2 +- tests/render-texture-load/src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/render-perf/src/main.cpp b/tests/render-perf/src/main.cpp index 61608f12aa..9c4e3ae870 100644 --- a/tests/render-perf/src/main.cpp +++ b/tests/render-perf/src/main.cpp @@ -519,7 +519,7 @@ public: _octree->getTree()->setSimulation(simpleSimulation); _entitySimulation = simpleSimulation; } - + DependencyManager::set(); setFlags(Qt::MSWindowsOwnDC | Qt::Window | Qt::Dialog | Qt::WindowMinMaxButtonsHint | Qt::WindowTitleHint); diff --git a/tests/render-texture-load/src/main.cpp b/tests/render-texture-load/src/main.cpp index d85fcd6a32..67b80d9ba8 100644 --- a/tests/render-texture-load/src/main.cpp +++ b/tests/render-texture-load/src/main.cpp @@ -328,7 +328,7 @@ public: installEventFilter(this); QThreadPool::globalInstance()->setMaxThreadCount(2); - QThread::currentThread()->setPriority(QThread::HighestPriority); + QThread::currentThread()->setPriority(QThread::HighestPriority); DependencyManager::set(); setFlags(Qt::MSWindowsOwnDC | Qt::Window | Qt::Dialog | Qt::WindowMinMaxButtonsHint | Qt::WindowTitleHint); _size = QSize(800, 600); From 6e5eff9407b9a26226de7729ca1474d0cf2f955d Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 29 Jun 2017 10:01:48 -0700 Subject: [PATCH 93/95] Fix snapshot connection errors --- scripts/system/snapshot.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index c60e3a67f7..7825490b2c 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -384,7 +384,6 @@ function onButtonClicked() { } else { fillImageDataFromPrevious(); tablet.gotoWebScreen(SNAPSHOT_REVIEW_URL); - tablet.webEventReceived.connect(onMessage); HMD.openTablet(); } } @@ -659,10 +658,15 @@ function maybeDeleteSnapshotStories() { storyIDsToMaybeDelete = []; } function onTabletScreenChanged(type, url) { + var wasInSnapshotReview = isInSnapshotReview; isInSnapshotReview = (type === "Web" && url === SNAPSHOT_REVIEW_URL); button.editProperties({ isActive: isInSnapshotReview }); - if (!isInSnapshotReview) { - tablet.webEventReceived.disconnect(onMessage); + if (isInSnapshotReview !== wasInSnapshotReview) { + if (isInSnapshotReview) { + tablet.webEventReceived.connect(onMessage); + } else { + tablet.webEventReceived.disconnect(onMessage); + } } } function onUsernameChanged() { From 5f123018a4109c0cf43458293fdf59bffdeab8bf Mon Sep 17 00:00:00 2001 From: utkarshgautamnyu Date: Thu, 29 Jun 2017 10:38:33 -0700 Subject: [PATCH 94/95] Update DomainMetadata.cpp --- domain-server/src/DomainMetadata.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain-server/src/DomainMetadata.cpp b/domain-server/src/DomainMetadata.cpp index c19cefa397..eee5673af3 100644 --- a/domain-server/src/DomainMetadata.cpp +++ b/domain-server/src/DomainMetadata.cpp @@ -171,7 +171,7 @@ void DomainMetadata::maybeUpdateUsers() { if (linkedData) { auto nodeData = static_cast(linkedData); - if (!nodeData->wasAssigned()) { + if (!nodeData->wasAssigned() && node->getType() == NodeType::Agent) { ++numConnected; if (nodeData->getUsername().isEmpty()) { From 82597ac4243df799eec18c095474f4501f9ef6d0 Mon Sep 17 00:00:00 2001 From: utkarshgautamnyu Date: Fri, 30 Jun 2017 10:22:05 -0700 Subject: [PATCH 95/95] Update snapshot.js --- scripts/system/snapshot.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index e033ba9f44..c2dc682a97 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -504,15 +504,9 @@ function takeSnapshot() { Window.takeSnapshot(false, includeAnimated, 1.91); }, SNAPSHOT_DELAY); }, FINISH_SOUND_DELAY); -UserActivityLogger.logAction("snapshot_taken", logDetails()); + UserActivityLogger.logAction("snaphshot_taken", { location: location.href }); } - function logDetails() { - return { - current_domain: location.href - }; - } - function isDomainOpen(id, callback) { print("Checking open status of domain with ID:", id); var status = false;