From 7996a02bd8270c2321a72f758bacc32d397fc6fe Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 11 Sep 2015 09:48:48 -0700 Subject: [PATCH] Added head target to AnimGraph IK node. * In HMD mode head orientation and position is set. * When not in HMD only orientation is set, position should default to the underlying pose position. --- interface/src/avatar/MyAvatar.cpp | 3 +- interface/src/avatar/SkeletonModel.cpp | 11 +++ .../animation/src/AnimInverseKinematics.cpp | 2 +- libraries/animation/src/AnimVariant.h | 1 + libraries/animation/src/Rig.cpp | 76 ++++++++++++------- libraries/animation/src/Rig.h | 9 ++- libraries/render-utils/src/AnimDebugDraw.cpp | 18 +++++ libraries/render-utils/src/AnimDebugDraw.h | 9 +++ tests/animation/src/data/avatar.json | 5 ++ 9 files changed, 101 insertions(+), 33 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index bafca2844d..85c47ad665 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1276,7 +1276,8 @@ void MyAvatar::initAnimGraph() { // // or run a local web-server // python -m SimpleHTTPServer& - auto graphUrl = QUrl("https://gist.githubusercontent.com/hyperlogic/e58e0a24cc341ad5d060/raw/2a994bef7726ce8e9efcee7622b8b1a1b6b67490/ik-avatar.json"); + // auto graphUrl = QUrl("http://localhost:8000/avatar.json"); + auto graphUrl = QUrl("https://gist.githubusercontent.com/hyperlogic/e58e0a24cc341ad5d060/raw/8f824da2908fd89ad1befadd1d8f5d7b3b6efa66/ik-avatar.json"); _rig->initAnimGraph(graphUrl, _skeletonModel.getGeometry()->getFBXGeometry()); } diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 8a05b15c4c..41ca193a7a 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -116,6 +116,17 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { params.leanForward = head->getFinalLeanForward(); params.torsoTwist = head->getTorsoTwist(); params.localHeadOrientation = head->getFinalOrientationInLocalFrame(); + params.localHeadPitch = head->getFinalPitch(); + params.localHeadYaw = head->getFinalYaw(); + params.localHeadRoll = head->getFinalRoll(); + params.isInHMD = qApp->getAvatarUpdater()->isHMDMode(); + + // get HMD position from sensor space into world space, and back into model space + glm::mat4 worldToModel = glm::inverse(createMatFromQuatAndPos(myAvatar->getOrientation(), myAvatar->getPosition())); + glm::vec3 yAxis(0.0f, 1.0f, 0.0f); + glm::vec3 hmdPosition = glm::angleAxis((float)M_PI, yAxis) * transformPoint(worldToModel * myAvatar->getSensorToWorldMatrix(), myAvatar->getHMDSensorPosition()); + params.localHeadPosition = hmdPosition; + params.worldHeadOrientation = head->getFinalOrientationInWorldFrame(); params.eyeLookAt = head->getLookAtPosition(); params.eyeSaccade = head->getSaccade(); diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 71401b6c60..3aa3f7718f 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -40,7 +40,7 @@ void AnimInverseKinematics::loadPoses(const AnimPoseVec& poses) { void AnimInverseKinematics::computeAbsolutePoses(AnimPoseVec& absolutePoses) const { int numJoints = (int)_relativePoses.size(); absolutePoses.clear(); - absolutePoses.reserve(numJoints); + absolutePoses.resize(numJoints); assert(numJoints <= _skeleton->getNumJoints()); for (int i = 0; i < numJoints; ++i) { int parentIndex = _skeleton->getParentIndex(i); diff --git a/libraries/animation/src/AnimVariant.h b/libraries/animation/src/AnimVariant.h index 1d720ba565..ac84aafc32 100644 --- a/libraries/animation/src/AnimVariant.h +++ b/libraries/animation/src/AnimVariant.h @@ -149,6 +149,7 @@ public: void set(const std::string& key, const glm::quat& value) { _map[key] = AnimVariant(value); } void set(const std::string& key, const glm::mat4& value) { _map[key] = AnimVariant(value); } void set(const std::string& key, const std::string& value) { _map[key] = AnimVariant(value); } + void unset(const std::string& key) { _map.erase(key); } void setTrigger(const std::string& key) { _triggers.insert(key); } void clearTriggers() { _triggers.clear(); } diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index e0a2b31622..528f678b33 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -30,6 +30,9 @@ void Rig::HeadParameters::dump() const { qCDebug(animation, " localHeadOrientation axis = (%.5f, %.5f, %.5f), theta = %0.5f", (double)axis.x, (double)axis.y, (double)axis.z, (double)theta); axis = glm::axis(worldHeadOrientation); theta = glm::angle(worldHeadOrientation); + qCDebug(animation, " localHead pitch = %.5f, yaw = %.5f, roll = %.5f", (double)localHeadPitch, (double)localHeadYaw, (double)localHeadRoll); + qCDebug(animation, " localHeadPosition = (%.5f, %.5f, %.5f)", (double)localHeadPosition.x, (double)localHeadPosition.y, (double)localHeadPosition.z); + qCDebug(animation, " isInHMD = %s", isInHMD ? "true" : "false"); qCDebug(animation, " worldHeadOrientation axis = (%.5f, %.5f, %.5f), theta = %0.5f", (double)axis.x, (double)axis.y, (double)axis.z, (double)theta); axis = glm::axis(modelRotation); theta = glm::angle(modelRotation); @@ -953,56 +956,71 @@ void Rig::updateFromHeadParameters(const HeadParameters& params) { if (params.enableLean) { updateLeanJoint(params.leanJointIndex, params.leanSideways, params.leanForward, params.torsoTwist); } - updateNeckJoint(params.neckJointIndex, params.localHeadOrientation, params.leanSideways, params.leanForward, params.torsoTwist); + updateNeckJoint(params.neckJointIndex, params); updateEyeJoints(params.leftEyeJointIndex, params.rightEyeJointIndex, params.modelTranslation, params.modelRotation, params.worldHeadOrientation, params.eyeLookAt, params.eyeSaccade); } +static const glm::vec3 X_AXIS(1.0f, 0.0f, 0.0f); +static const glm::vec3 Y_AXIS(0.0f, 1.0f, 0.0f); +static const glm::vec3 Z_AXIS(0.0f, 0.0f, 1.0f); + void Rig::updateLeanJoint(int index, float leanSideways, float leanForward, float torsoTwist) { if (index >= 0 && _jointStates[index].getParentIndex() >= 0) { if (_enableAnimGraph && _animSkeleton) { - glm::vec3 xAxis(1.0f, 0.0f, 0.0f); - glm::vec3 yAxis(0.0f, 1.0f, 0.0f); - glm::vec3 zAxis(0.0f, 0.0f, 1.0f); - glm::quat absRot = (glm::angleAxis(-RADIANS_PER_DEGREE * leanSideways, zAxis) * - glm::angleAxis(-RADIANS_PER_DEGREE * leanForward, xAxis) * - glm::angleAxis(RADIANS_PER_DEGREE * torsoTwist, yAxis)); + glm::quat absRot = (glm::angleAxis(-RADIANS_PER_DEGREE * leanSideways, Z_AXIS) * + glm::angleAxis(-RADIANS_PER_DEGREE * leanForward, X_AXIS) * + glm::angleAxis(RADIANS_PER_DEGREE * torsoTwist, Y_AXIS)); _animVars.set("lean", absRot); } else if (!_enableAnimGraph) { auto& parentState = _jointStates[_jointStates[index].getParentIndex()]; // get the rotation axes in joint space and use them to adjust the rotation - glm::vec3 xAxis(1.0f, 0.0f, 0.0f); - glm::vec3 yAxis(0.0f, 1.0f, 0.0f); - glm::vec3 zAxis(0.0f, 0.0f, 1.0f); glm::quat inverse = glm::inverse(parentState.getRotation() * getJointDefaultRotationInParentFrame(index)); setJointRotationInConstrainedFrame(index, - glm::angleAxis(- RADIANS_PER_DEGREE * leanSideways, inverse * zAxis) * - glm::angleAxis(- RADIANS_PER_DEGREE * leanForward, inverse * xAxis) * - glm::angleAxis(RADIANS_PER_DEGREE * torsoTwist, inverse * yAxis) * + glm::angleAxis(- RADIANS_PER_DEGREE * leanSideways, inverse * Z_AXIS) * + glm::angleAxis(- RADIANS_PER_DEGREE * leanForward, inverse * X_AXIS) * + glm::angleAxis(RADIANS_PER_DEGREE * torsoTwist, inverse * Y_AXIS) * getJointState(index).getDefaultRotation(), DEFAULT_PRIORITY); } } } -void Rig::updateNeckJoint(int index, const glm::quat& localHeadOrientation, float leanSideways, float leanForward, float torsoTwist) { +void Rig::updateNeckJoint(int index, const HeadParameters& params) { if (index >= 0 && _jointStates[index].getParentIndex() >= 0) { - auto& state = _jointStates[index]; - auto& parentState = _jointStates[state.getParentIndex()]; + if (_enableAnimGraph && _animSkeleton) { + // the params.localHeadOrientation is composed incorrectly, so re-compose it correctly from pitch, yaw and roll. + glm::quat realLocalHeadOrientation = (glm::angleAxis(glm::radians(-params.localHeadRoll), Z_AXIS) * + glm::angleAxis(glm::radians(params.localHeadYaw), Y_AXIS) * + glm::angleAxis(glm::radians(-params.localHeadPitch), X_AXIS)); + _animVars.set("headRotation", realLocalHeadOrientation); - // get the rotation axes in joint space and use them to adjust the rotation - glm::mat3 axes = glm::mat3_cast(glm::quat()); - glm::mat3 inverse = glm::mat3(glm::inverse(parentState.getTransform() * - glm::translate(getJointDefaultTranslationInConstrainedFrame(index)) * - state.getPreTransform() * glm::mat4_cast(state.getPreRotation()))); - glm::vec3 pitchYawRoll = safeEulerAngles(localHeadOrientation); - glm::vec3 lean = glm::radians(glm::vec3(leanForward, torsoTwist, leanSideways)); - pitchYawRoll -= lean; - setJointRotationInConstrainedFrame(index, - glm::angleAxis(-pitchYawRoll.z, glm::normalize(inverse * axes[2])) * - glm::angleAxis(pitchYawRoll.y, glm::normalize(inverse * axes[1])) * - glm::angleAxis(-pitchYawRoll.x, glm::normalize(inverse * axes[0])) * - state.getDefaultRotation(), DEFAULT_PRIORITY); + auto rootTrans = _animSkeleton->getAbsoluteBindPose(_rootJointIndex).trans; + + if (params.isInHMD) { + _animVars.set("headPosition", params.localHeadPosition + rootTrans); + } else { + _animVars.unset("headPosition"); + } + } else if (!_enableAnimGraph) { + + auto& state = _jointStates[index]; + auto& parentState = _jointStates[state.getParentIndex()]; + + // get the rotation axes in joint space and use them to adjust the rotation + glm::mat3 axes = glm::mat3_cast(glm::quat()); + glm::mat3 inverse = glm::mat3(glm::inverse(parentState.getTransform() * + glm::translate(getJointDefaultTranslationInConstrainedFrame(index)) * + state.getPreTransform() * glm::mat4_cast(state.getPreRotation()))); + glm::vec3 pitchYawRoll = safeEulerAngles(params.localHeadOrientation); + glm::vec3 lean = glm::radians(glm::vec3(params.leanForward, params.torsoTwist, params.leanSideways)); + pitchYawRoll -= lean; + setJointRotationInConstrainedFrame(index, + glm::angleAxis(-pitchYawRoll.z, glm::normalize(inverse * Z_AXIS)) * + glm::angleAxis(pitchYawRoll.y, glm::normalize(inverse * Y_AXIS)) * + glm::angleAxis(-pitchYawRoll.x, glm::normalize(inverse * X_AXIS)) * + state.getDefaultRotation(), DEFAULT_PRIORITY); + } } } diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 0a3ebad5d2..5f04dd550f 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -59,10 +59,15 @@ public: bool enableLean = false; glm::quat modelRotation = glm::quat(); glm::quat localHeadOrientation = glm::quat(); + float localHeadPitch = 0.0f; // degrees + float localHeadYaw = 0.0f; // degrees + float localHeadRoll = 0.0f; // degrees + glm::vec3 localHeadPosition = glm::vec3(0); + bool isInHMD = false; glm::quat worldHeadOrientation = glm::quat(); glm::vec3 eyeLookAt = glm::vec3(); // world space glm::vec3 eyeSaccade = glm::vec3(); // world space - glm::vec3 modelTranslation = glm::vec3(); + glm::vec3 modelTranslation = glm::vec3(0); int leanJointIndex = -1; int neckJointIndex = -1; int leftEyeJointIndex = -1; @@ -177,7 +182,7 @@ public: protected: void updateLeanJoint(int index, float leanSideways, float leanForward, float torsoTwist); - void updateNeckJoint(int index, const glm::quat& localHeadOrientation, float leanSideways, float leanForward, float torsoTwist); + void updateNeckJoint(int index, const HeadParameters& params); void updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::quat& worldHeadOrientation, const glm::vec3& lookAt, const glm::vec3& saccade); QVector _jointStates; diff --git a/libraries/render-utils/src/AnimDebugDraw.cpp b/libraries/render-utils/src/AnimDebugDraw.cpp index b91db94f74..d057bf067f 100644 --- a/libraries/render-utils/src/AnimDebugDraw.cpp +++ b/libraries/render-utils/src/AnimDebugDraw.cpp @@ -176,6 +176,14 @@ void AnimDebugDraw::removePoses(const std::string& key) { _poses.erase(key); } +void AnimDebugDraw::addPose(const std::string& key, const AnimPose& pose, const AnimPose& rootPose) { + _singlePoses[key] = SinglePoseInfo(pose, rootPose); +} + +void AnimDebugDraw::removePose(const std::string& key) { + _singlePoses.erase(key); +} + static const uint32_t red = toRGBA(255, 0, 0, 255); static const uint32_t green = toRGBA(0, 255, 0, 255); static const uint32_t blue = toRGBA(0, 0, 255, 255); @@ -333,6 +341,7 @@ void AnimDebugDraw::update() { const size_t VERTICES_PER_LINK = 8 * 2; const float BONE_RADIUS = 0.01f; // 1 cm + const float POSE_RADIUS = 0.1f; // 10 cm // figure out how many verts we will need. int numVerts = 0; @@ -371,6 +380,8 @@ void AnimDebugDraw::update() { } } + numVerts += _singlePoses.size() * VERTICES_PER_BONE; + data._vertexBuffer->resize(sizeof(Vertex) * numVerts); Vertex* verts = (Vertex*)data._vertexBuffer->editData(); Vertex* v = verts; @@ -475,6 +486,13 @@ void AnimDebugDraw::update() { } } + for (auto& iter : _singlePoses) { + AnimPose pose = std::get<0>(iter.second); + AnimPose rootPose = std::get<1>(iter.second); + const float radius = POSE_RADIUS / (pose.scale.x * rootPose.scale.x); + addBone(rootPose, pose, radius, v); + } + assert(numVerts == (v - verts)); data._indexBuffer->resize(sizeof(uint16_t) * numVerts); diff --git a/libraries/render-utils/src/AnimDebugDraw.h b/libraries/render-utils/src/AnimDebugDraw.h index cd17f62590..5bb19097f2 100644 --- a/libraries/render-utils/src/AnimDebugDraw.h +++ b/libraries/render-utils/src/AnimDebugDraw.h @@ -27,15 +27,22 @@ public: AnimDebugDraw(); ~AnimDebugDraw(); + // draw a skeleton bind pose void addSkeleton(const std::string& key, AnimSkeleton::ConstPointer skeleton, const AnimPose& rootPose, const glm::vec4& color); void removeSkeleton(const std::string& key); + // draw the interal poses for a specific animNode void addAnimNode(const std::string& key, AnimNode::ConstPointer animNode, const AnimPose& rootPose, const glm::vec4& color); void removeAnimNode(const std::string& key); + // draw a set of poses with a skeleton void addPoses(const std::string& key, AnimSkeleton::ConstPointer skeleton, const AnimPoseVec& poses, const AnimPose& rootPose, const glm::vec4& color); void removePoses(const std::string& key); + // draw a single pose + void addPose(const std::string& key, const AnimPose& rootPose, const AnimPose& pose); + void removePose(const std::string& key); + void update(); protected: @@ -48,10 +55,12 @@ protected: typedef std::tuple SkeletonInfo; typedef std::tuple AnimNodeInfo; typedef std::tuple PosesInfo; + typedef std::tuple SinglePoseInfo; std::unordered_map _skeletons; std::unordered_map _animNodes; std::unordered_map _poses; + std::unordered_map _singlePoses; // no copies AnimDebugDraw(const AnimDebugDraw&) = delete; diff --git a/tests/animation/src/data/avatar.json b/tests/animation/src/data/avatar.json index 3122fd433d..08425201b0 100644 --- a/tests/animation/src/data/avatar.json +++ b/tests/animation/src/data/avatar.json @@ -22,6 +22,11 @@ "jointName": "LeftHand", "positionVar": "leftHandPosition", "rotationVar": "leftHandRotation" + }, + { + "jointName": "Head", + "positionVar": "headPosition", + "rotationVar": "headRotation" } ] },