// // Rig.cpp // libraries/animation/src/ // // Created by Howard Stearns, Seth Alves, Anthony Thibault, Andrew Meadows on 7/15/15. // Copyright (c) 2015 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 "Rig.h" #include #include #include #include #include #include #include #include #include #include #include #include "AnimationLogging.h" #include "AnimClip.h" #include "AnimInverseKinematics.h" #include "AnimOverlay.h" #include "AnimSkeleton.h" #include "AnimUtil.h" #include "IKTarget.h" static int nextRigId = 1; static std::map rigRegistry; static std::mutex rigRegistryMutex; static bool isEqual(const glm::vec3& u, const glm::vec3& v) { const float EPSILON = 0.0001f; float uLen = glm::length(u); if (uLen == 0.0f) { return glm::length(v) <= EPSILON; } else { return (glm::length(u - v) / uLen) <= EPSILON; } } static bool isEqual(const glm::quat& p, const glm::quat& q) { const float EPSILON = 0.00001f; return 1.0f - fabsf(glm::dot(p, q)) <= EPSILON; } #define ASSERT(cond) assert(cond) // 2 meter tall dude const glm::vec3 DEFAULT_RIGHT_EYE_POS(-0.3f, 0.9f, 0.0f); const glm::vec3 DEFAULT_LEFT_EYE_POS(0.3f, 0.9f, 0.0f); const glm::vec3 DEFAULT_HEAD_POS(0.0f, 0.75f, 0.0f); static const QString LEFT_FOOT_POSITION("leftFootPosition"); static const QString LEFT_FOOT_ROTATION("leftFootRotation"); static const QString LEFT_FOOT_IK_POSITION_VAR("leftFootIKPositionVar"); static const QString LEFT_FOOT_IK_ROTATION_VAR("leftFootIKRotationVar"); static const QString MAIN_STATE_MACHINE_LEFT_FOOT_POSITION("mainStateMachineLeftFootPosition"); static const QString MAIN_STATE_MACHINE_LEFT_FOOT_ROTATION("mainStateMachineLeftFootRotation"); static const QString RIGHT_FOOT_POSITION("rightFootPosition"); static const QString RIGHT_FOOT_ROTATION("rightFootRotation"); static const QString RIGHT_FOOT_IK_POSITION_VAR("rightFootIKPositionVar"); static const QString RIGHT_FOOT_IK_ROTATION_VAR("rightFootIKRotationVar"); static const QString MAIN_STATE_MACHINE_RIGHT_FOOT_ROTATION("mainStateMachineRightFootRotation"); static const QString MAIN_STATE_MACHINE_RIGHT_FOOT_POSITION("mainStateMachineRightFootPosition"); Rig::Rig() { // Ensure thread-safe access to the rigRegistry. std::lock_guard guard(rigRegistryMutex); // Insert this newly allocated rig into the rig registry _rigId = nextRigId; rigRegistry[_rigId] = this; nextRigId++; } Rig::~Rig() { // Ensure thread-safe access to the rigRegstry, but also prevent the rig from being deleted // while Rig::animationStateHandlerResult is being invoked on a script thread. std::lock_guard guard(rigRegistryMutex); auto iter = rigRegistry.find(_rigId); if (iter != rigRegistry.end()) { rigRegistry.erase(iter); } } void Rig::overrideAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame) { UserAnimState::ClipNodeEnum clipNodeEnum; if (_userAnimState.clipNodeEnum == UserAnimState::None || _userAnimState.clipNodeEnum == UserAnimState::B) { clipNodeEnum = UserAnimState::A; } else { clipNodeEnum = UserAnimState::B; } if (_animNode) { // find an unused AnimClip clipNode std::shared_ptr clip; if (clipNodeEnum == UserAnimState::A) { clip = std::dynamic_pointer_cast(_animNode->findByName("userAnimA")); } else { clip = std::dynamic_pointer_cast(_animNode->findByName("userAnimB")); } if (clip) { // set parameters clip->setLoopFlag(loop); clip->setStartFrame(firstFrame); clip->setEndFrame(lastFrame); const float REFERENCE_FRAMES_PER_SECOND = 30.0f; float timeScale = fps / REFERENCE_FRAMES_PER_SECOND; clip->setTimeScale(timeScale); clip->loadURL(url); } } // store current user anim state. _userAnimState = { clipNodeEnum, url, fps, loop, firstFrame, lastFrame }; // notify the userAnimStateMachine the desired state. _animVars.set("userAnimNone", false); _animVars.set("userAnimA", clipNodeEnum == UserAnimState::A); _animVars.set("userAnimB", clipNodeEnum == UserAnimState::B); } void Rig::restoreAnimation() { if (_userAnimState.clipNodeEnum != UserAnimState::None) { _userAnimState.clipNodeEnum = UserAnimState::None; // notify the userAnimStateMachine the desired state. _animVars.set("userAnimNone", true); _animVars.set("userAnimA", false); _animVars.set("userAnimB", false); } } QStringList Rig::getAnimationRoles() const { if (_animNode) { QStringList list; _animNode->traverse([&](AnimNode::Pointer node) { // only report clip nodes as valid roles. auto clipNode = std::dynamic_pointer_cast(node); if (clipNode) { // filter out the userAnims, they are for internal use only. if (!clipNode->getID().startsWith("userAnim")) { list.append(node->getID()); } } return true; }); return list; } else { return QStringList(); } } void Rig::overrideRoleAnimation(const QString& role, const QString& url, float fps, bool loop, float firstFrame, float lastFrame) { if (_animNode) { AnimNode::Pointer node = _animNode->findByName(role); if (node) { _origRoleAnimations[role] = node; const float REFERENCE_FRAMES_PER_SECOND = 30.0f; float timeScale = fps / REFERENCE_FRAMES_PER_SECOND; auto clipNode = std::make_shared(role, url, firstFrame, lastFrame, timeScale, loop, false); _roleAnimStates[role] = { role, url, fps, loop, firstFrame, lastFrame }; AnimNode::Pointer parent = node->getParent(); parent->replaceChild(node, clipNode); } else { qCWarning(animation) << "Rig::overrideRoleAnimation could not find role " << role; } } else { qCWarning(animation) << "Rig::overrideRoleAnimation avatar not ready yet"; } } void Rig::restoreRoleAnimation(const QString& role) { if (_animNode) { AnimNode::Pointer node = _animNode->findByName(role); if (node) { auto iter = _origRoleAnimations.find(role); if (iter != _origRoleAnimations.end()) { node->getParent()->replaceChild(node, iter->second); _origRoleAnimations.erase(iter); } else { qCWarning(animation) << "Rig::restoreRoleAnimation could not find role " << role; } auto statesIter = _roleAnimStates.find(role); if (statesIter != _roleAnimStates.end()) { _roleAnimStates.erase(statesIter); } } } else { qCWarning(animation) << "Rig::overrideRoleAnimation avatar not ready yet"; } } void Rig::destroyAnimGraph() { _animSkeleton.reset(); _animLoader.reset(); _animNode.reset(); _internalPoseSet._relativePoses.clear(); _internalPoseSet._absolutePoses.clear(); _internalPoseSet._overridePoses.clear(); _internalPoseSet._overrideFlags.clear(); _numOverrides = 0; _leftEyeJointChildren.clear(); _rightEyeJointChildren.clear(); } void Rig::initJointStates(const FBXGeometry& geometry, const glm::mat4& modelOffset) { _geometryOffset = AnimPose(geometry.offset); _invGeometryOffset = _geometryOffset.inverse(); _geometryToRigTransform = modelOffset * geometry.offset; _rigToGeometryTransform = glm::inverse(_geometryToRigTransform); setModelOffset(modelOffset); _animSkeleton = std::make_shared(geometry); _internalPoseSet._relativePoses.clear(); _internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses(); buildAbsoluteRigPoses(_internalPoseSet._relativePoses, _internalPoseSet._absolutePoses); _internalPoseSet._overridePoses.clear(); _internalPoseSet._overridePoses = _animSkeleton->getRelativeDefaultPoses(); _internalPoseSet._overrideFlags.clear(); _internalPoseSet._overrideFlags.resize(_animSkeleton->getNumJoints(), false); _numOverrides = 0; buildAbsoluteRigPoses(_animSkeleton->getRelativeDefaultPoses(), _absoluteDefaultPoses); _rootJointIndex = geometry.rootJointIndex; _leftEyeJointIndex = geometry.leftEyeJointIndex; _rightEyeJointIndex = geometry.rightEyeJointIndex; _leftHandJointIndex = geometry.leftHandJointIndex; _leftElbowJointIndex = _leftHandJointIndex >= 0 ? geometry.joints.at(_leftHandJointIndex).parentIndex : -1; _leftShoulderJointIndex = _leftElbowJointIndex >= 0 ? geometry.joints.at(_leftElbowJointIndex).parentIndex : -1; _rightHandJointIndex = geometry.rightHandJointIndex; _rightElbowJointIndex = _rightHandJointIndex >= 0 ? geometry.joints.at(_rightHandJointIndex).parentIndex : -1; _rightShoulderJointIndex = _rightElbowJointIndex >= 0 ? geometry.joints.at(_rightElbowJointIndex).parentIndex : -1; _leftEyeJointChildren = _animSkeleton->getChildrenOfJoint(geometry.leftEyeJointIndex); _rightEyeJointChildren = _animSkeleton->getChildrenOfJoint(geometry.rightEyeJointIndex); } void Rig::reset(const FBXGeometry& geometry) { _geometryOffset = AnimPose(geometry.offset); _invGeometryOffset = _geometryOffset.inverse(); _animSkeleton = std::make_shared(geometry); _internalPoseSet._relativePoses.clear(); _internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses(); buildAbsoluteRigPoses(_internalPoseSet._relativePoses, _internalPoseSet._absolutePoses); _internalPoseSet._overridePoses.clear(); _internalPoseSet._overridePoses = _animSkeleton->getRelativeDefaultPoses(); _internalPoseSet._overrideFlags.clear(); _internalPoseSet._overrideFlags.resize(_animSkeleton->getNumJoints(), false); _numOverrides = 0; buildAbsoluteRigPoses(_animSkeleton->getRelativeDefaultPoses(), _absoluteDefaultPoses); _rootJointIndex = geometry.rootJointIndex; _leftEyeJointIndex = geometry.leftEyeJointIndex; _rightEyeJointIndex = geometry.rightEyeJointIndex; _leftHandJointIndex = geometry.leftHandJointIndex; _leftElbowJointIndex = _leftHandJointIndex >= 0 ? geometry.joints.at(_leftHandJointIndex).parentIndex : -1; _leftShoulderJointIndex = _leftElbowJointIndex >= 0 ? geometry.joints.at(_leftElbowJointIndex).parentIndex : -1; _rightHandJointIndex = geometry.rightHandJointIndex; _rightElbowJointIndex = _rightHandJointIndex >= 0 ? geometry.joints.at(_rightHandJointIndex).parentIndex : -1; _rightShoulderJointIndex = _rightElbowJointIndex >= 0 ? geometry.joints.at(_rightElbowJointIndex).parentIndex : -1; _leftEyeJointChildren = _animSkeleton->getChildrenOfJoint(geometry.leftEyeJointIndex); _rightEyeJointChildren = _animSkeleton->getChildrenOfJoint(geometry.rightEyeJointIndex); if (!_animGraphURL.isEmpty()) { _animNode.reset(); initAnimGraph(_animGraphURL); } } bool Rig::jointStatesEmpty() { return _internalPoseSet._relativePoses.empty(); } int Rig::getJointStateCount() const { return (int)_internalPoseSet._relativePoses.size(); } static const uint32_t MAX_JOINT_NAME_WARNING_COUNT = 100; int Rig::indexOfJoint(const QString& jointName) const { if (_animSkeleton) { int result = _animSkeleton->nameToJointIndex(jointName); // This is a content error, so we should issue a warning. if (result < 0 && _jointNameWarningCount < MAX_JOINT_NAME_WARNING_COUNT) { qCWarning(animation) << "Rig: Missing joint" << jointName << "in avatar model"; _jointNameWarningCount++; } return result; } else { // This is normal and can happen when the avatar model has not been dowloaded/loaded yet. return -1; } } QString Rig::nameOfJoint(int jointIndex) const { if (_animSkeleton) { return _animSkeleton->getJointName(jointIndex); } else { return ""; } } void Rig::setModelOffset(const glm::mat4& modelOffsetMat) { AnimPose newModelOffset = AnimPose(modelOffsetMat); if (!isEqual(_modelOffset.trans(), newModelOffset.trans()) || !isEqual(_modelOffset.rot(), newModelOffset.rot()) || !isEqual(_modelOffset.scale(), newModelOffset.scale())) { _modelOffset = newModelOffset; // compute geometryToAvatarTransforms _geometryToRigTransform = _modelOffset * _geometryOffset; _rigToGeometryTransform = glm::inverse(_geometryToRigTransform); // rebuild cached default poses if (_animSkeleton) { buildAbsoluteRigPoses(_animSkeleton->getRelativeDefaultPoses(), _absoluteDefaultPoses); } } } void Rig::clearJointState(int index) { if (isIndexValid(index)) { if (_internalPoseSet._overrideFlags[index]) { _internalPoseSet._overrideFlags[index] = false; --_numOverrides; } _internalPoseSet._overridePoses[index] = _animSkeleton->getRelativeDefaultPose(index); } } void Rig::clearJointStates() { _internalPoseSet._overrideFlags.clear(); _numOverrides = 0; if (_animSkeleton) { _internalPoseSet._overrideFlags.resize(_animSkeleton->getNumJoints()); _internalPoseSet._overridePoses = _animSkeleton->getRelativeDefaultPoses(); } } void Rig::clearJointAnimationPriority(int index) { if (isIndexValid(index)) { if (_internalPoseSet._overrideFlags[index]) { _internalPoseSet._overrideFlags[index] = false; --_numOverrides; } _internalPoseSet._overridePoses[index] = _animSkeleton->getRelativeDefaultPose(index); } } std::shared_ptr Rig::getAnimInverseKinematicsNode() const { std::shared_ptr result; if (_animNode) { _animNode->traverse([&](AnimNode::Pointer node) { // only report clip nodes as valid roles. auto ikNode = std::dynamic_pointer_cast(node); if (ikNode) { result = ikNode; return false; } else { return true; } }); } return result; } void Rig::clearIKJointLimitHistory() { auto ikNode = getAnimInverseKinematicsNode(); if (ikNode) { ikNode->clearIKJointLimitHistory(); } } float Rig::getIKErrorOnLastSolve() const { float result = 0.0f; if (_animNode) { _animNode->traverse([&](AnimNode::Pointer node) { auto ikNode = std::dynamic_pointer_cast(node); if (ikNode) { result = ikNode->getMaxErrorOnLastSolve(); } return true; }); } return result; } int Rig::getJointParentIndex(int childIndex) const { if (_animSkeleton && isIndexValid(childIndex)) { return _animSkeleton->getParentIndex(childIndex); } return -1; } void Rig::setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority) { if (isIndexValid(index)) { if (valid) { assert(_internalPoseSet._overrideFlags.size() == _internalPoseSet._overridePoses.size()); if (!_internalPoseSet._overrideFlags[index]) { _internalPoseSet._overrideFlags[index] = true; ++_numOverrides; } _internalPoseSet._overridePoses[index].trans() = translation; } } } void Rig::setJointState(int index, bool valid, const glm::quat& rotation, const glm::vec3& translation, float priority) { if (isIndexValid(index)) { assert(_internalPoseSet._overrideFlags.size() == _internalPoseSet._overridePoses.size()); if (!_internalPoseSet._overrideFlags[index]) { _internalPoseSet._overrideFlags[index] = true; ++_numOverrides; } _internalPoseSet._overridePoses[index].rot() = rotation; _internalPoseSet._overridePoses[index].trans() = translation; } } void Rig::setJointRotation(int index, bool valid, const glm::quat& rotation, float priority) { if (isIndexValid(index)) { if (valid) { ASSERT(_internalPoseSet._overrideFlags.size() == _internalPoseSet._overridePoses.size()); if (!_internalPoseSet._overrideFlags[index]) { _internalPoseSet._overrideFlags[index] = true; ++_numOverrides; } _internalPoseSet._overridePoses[index].rot() = rotation; } } } bool Rig::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position, glm::vec3 translation, glm::quat rotation) const { bool success { false }; glm::vec3 originalPosition = position; bool onOwnerThread = (QThread::currentThread() == thread()); glm::vec3 poseSetTrans; if (onOwnerThread) { if (isIndexValid(jointIndex)) { poseSetTrans = _internalPoseSet._absolutePoses[jointIndex].trans(); position = (rotation * poseSetTrans) + translation; success = true; } else { success = false; } } else { QReadLocker readLock(&_externalPoseSetLock); if (jointIndex >= 0 && jointIndex < (int)_externalPoseSet._absolutePoses.size()) { poseSetTrans = _externalPoseSet._absolutePoses[jointIndex].trans(); position = (rotation * poseSetTrans) + translation; success = true; } else { success = false; } } if (isNaN(position)) { qCWarning(animation) << "Rig::getJointPositionInWorldFrame produced NaN." << " is owner thread = " << onOwnerThread << " position = " << originalPosition << " translation = " << translation << " rotation = " << rotation << " poseSetTrans = " << poseSetTrans << " success = " << success << " jointIndex = " << jointIndex; success = false; position = glm::vec3(0.0f); } return success; } bool Rig::getJointPosition(int jointIndex, glm::vec3& position) const { if (QThread::currentThread() == thread()) { if (isIndexValid(jointIndex)) { position = _internalPoseSet._absolutePoses[jointIndex].trans(); return true; } else { return false; } } else { return getAbsoluteJointTranslationInRigFrame(jointIndex, position); } } bool Rig::getJointRotationInWorldFrame(int jointIndex, glm::quat& result, const glm::quat& rotation) const { if (QThread::currentThread() == thread()) { if (isIndexValid(jointIndex)) { result = rotation * _internalPoseSet._absolutePoses[jointIndex].rot(); return true; } else { return false; } } QReadLocker readLock(&_externalPoseSetLock); if (jointIndex >= 0 && jointIndex < (int)_externalPoseSet._absolutePoses.size()) { result = rotation * _externalPoseSet._absolutePoses[jointIndex].rot(); return true; } else { return false; } } bool Rig::getJointRotation(int jointIndex, glm::quat& rotation) const { if (QThread::currentThread() == thread()) { if (isIndexValid(jointIndex)) { rotation = _internalPoseSet._relativePoses[jointIndex].rot(); return true; } else { return false; } } QReadLocker readLock(&_externalPoseSetLock); if (jointIndex >= 0 && jointIndex < (int)_externalPoseSet._relativePoses.size()) { rotation = _externalPoseSet._relativePoses[jointIndex].rot(); return true; } else { return false; } } bool Rig::getAbsoluteJointRotationInRigFrame(int jointIndex, glm::quat& rotation) const { QReadLocker readLock(&_externalPoseSetLock); if (jointIndex >= 0 && jointIndex < (int)_externalPoseSet._absolutePoses.size()) { rotation = _externalPoseSet._absolutePoses[jointIndex].rot(); return true; } else { return false; } } bool Rig::getJointTranslation(int jointIndex, glm::vec3& translation) const { QReadLocker readLock(&_externalPoseSetLock); if (jointIndex >= 0 && jointIndex < (int)_externalPoseSet._relativePoses.size()) { translation = _externalPoseSet._relativePoses[jointIndex].trans(); return true; } else { return false; } } bool Rig::getAbsoluteJointTranslationInRigFrame(int jointIndex, glm::vec3& translation) const { QReadLocker readLock(&_externalPoseSetLock); if (jointIndex >= 0 && jointIndex < (int)_externalPoseSet._absolutePoses.size()) { translation = _externalPoseSet._absolutePoses[jointIndex].trans(); return true; } else { return false; } } bool Rig::getAbsoluteJointPoseInRigFrame(int jointIndex, AnimPose& returnPose) const { QReadLocker readLock(&_externalPoseSetLock); if (jointIndex >= 0 && jointIndex < (int)_externalPoseSet._absolutePoses.size()) { returnPose = _externalPoseSet._absolutePoses[jointIndex]; return true; } else { return false; } } void Rig::calcAnimAlpha(float speed, const std::vector& referenceSpeeds, float* alphaOut) const { ASSERT(referenceSpeeds.size() > 0); // calculate alpha from linear combination of referenceSpeeds. float alpha = 0.0f; if (speed <= referenceSpeeds.front()) { alpha = 0.0f; } else if (speed > referenceSpeeds.back()) { alpha = (float)(referenceSpeeds.size() - 1); } else { for (size_t i = 0; i < referenceSpeeds.size() - 1; i++) { if (referenceSpeeds[i] < speed && speed < referenceSpeeds[i + 1]) { alpha = (float)i + ((speed - referenceSpeeds[i]) / (referenceSpeeds[i + 1] - referenceSpeeds[i])); break; } } } *alphaOut = alpha; } void Rig::setEnableInverseKinematics(bool enable) { _enableInverseKinematics = enable; } void Rig::setEnableAnimations(bool enable) { _enabledAnimations = enable; } AnimPose Rig::getAbsoluteDefaultPose(int index) const { if (_animSkeleton && index >= 0 && index < _animSkeleton->getNumJoints()) { return _absoluteDefaultPoses[index]; } else { return AnimPose::identity; } } const AnimPoseVec& Rig::getAbsoluteDefaultPoses() const { return _absoluteDefaultPoses; } bool Rig::getRelativeDefaultJointRotation(int index, glm::quat& rotationOut) const { if (_animSkeleton && index >= 0 && index < _animSkeleton->getNumJoints()) { rotationOut = _animSkeleton->getRelativeDefaultPose(index).rot(); return true; } else { return false; } } bool Rig::getRelativeDefaultJointTranslation(int index, glm::vec3& translationOut) const { if (_animSkeleton && index >= 0 && index < _animSkeleton->getNumJoints()) { translationOut = _animSkeleton->getRelativeDefaultPose(index).trans(); return true; } else { return false; } } // animation reference speeds. static const std::vector FORWARD_SPEEDS = { 0.4f, 1.4f, 4.5f }; // m/s static const std::vector BACKWARD_SPEEDS = { 0.6f, 1.45f }; // m/s static const std::vector LATERAL_SPEEDS = { 0.2f, 0.65f }; // m/s void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, const glm::quat& worldRotation, CharacterControllerState ccState) { glm::vec3 forward = worldRotation * IDENTITY_FORWARD; glm::vec3 workingVelocity = worldVelocity; { glm::vec3 localVel = glm::inverse(worldRotation) * workingVelocity; float forwardSpeed = glm::dot(localVel, IDENTITY_FORWARD); float lateralSpeed = glm::dot(localVel, IDENTITY_RIGHT); float turningSpeed = glm::orientedAngle(forward, _lastForward, IDENTITY_UP) / deltaTime; // filter speeds using a simple moving average. _averageForwardSpeed.updateAverage(forwardSpeed); _averageLateralSpeed.updateAverage(lateralSpeed); // sine wave LFO var for testing. static float t = 0.0f; _animVars.set("sine", 2.0f * 0.5f * sinf(t) + 0.5f); float moveForwardAlpha = 0.0f; float moveBackwardAlpha = 0.0f; float moveLateralAlpha = 0.0f; // calcuate the animation alpha and timeScale values based on current speeds and animation reference speeds. calcAnimAlpha(_averageForwardSpeed.getAverage(), FORWARD_SPEEDS, &moveForwardAlpha); calcAnimAlpha(-_averageForwardSpeed.getAverage(), BACKWARD_SPEEDS, &moveBackwardAlpha); calcAnimAlpha(fabsf(_averageLateralSpeed.getAverage()), LATERAL_SPEEDS, &moveLateralAlpha); _animVars.set("moveForwardSpeed", _averageForwardSpeed.getAverage()); _animVars.set("moveForwardAlpha", moveForwardAlpha); _animVars.set("moveBackwardSpeed", -_averageForwardSpeed.getAverage()); _animVars.set("moveBackwardAlpha", moveBackwardAlpha); _animVars.set("moveLateralSpeed", fabsf(_averageLateralSpeed.getAverage())); _animVars.set("moveLateralAlpha", moveLateralAlpha); const float MOVE_ENTER_SPEED_THRESHOLD = 0.2f; // m/sec const float MOVE_EXIT_SPEED_THRESHOLD = 0.07f; // m/sec const float TURN_ENTER_SPEED_THRESHOLD = 0.5f; // rad/sec const float TURN_EXIT_SPEED_THRESHOLD = 0.2f; // rad/sec if (ccState == CharacterControllerState::Hover) { if (_desiredState != RigRole::Hover) { _desiredStateAge = 0.0f; } _desiredState = RigRole::Hover; } else if (ccState == CharacterControllerState::InAir) { if (_desiredState != RigRole::InAir) { _desiredStateAge = 0.0f; } _desiredState = RigRole::InAir; } else if (ccState == CharacterControllerState::Takeoff) { if (_desiredState != RigRole::Takeoff) { _desiredStateAge = 0.0f; } _desiredState = RigRole::Takeoff; } else { float moveThresh; if (_state != RigRole::Move) { moveThresh = MOVE_ENTER_SPEED_THRESHOLD; } else { moveThresh = MOVE_EXIT_SPEED_THRESHOLD; } float turnThresh; if (_state != RigRole::Turn) { turnThresh = TURN_ENTER_SPEED_THRESHOLD; } else { turnThresh = TURN_EXIT_SPEED_THRESHOLD; } if (glm::length(localVel) > moveThresh) { if (_desiredState != RigRole::Move) { _desiredStateAge = 0.0f; } _desiredState = RigRole::Move; } else { if (fabsf(turningSpeed) > turnThresh) { if (_desiredState != RigRole::Turn) { _desiredStateAge = 0.0f; } _desiredState = RigRole::Turn; } else { // idle if (_desiredState != RigRole::Idle) { _desiredStateAge = 0.0f; } _desiredState = RigRole::Idle; } } } const float STATE_CHANGE_HYSTERESIS_TIMER = 0.1f; // Skip hystersis timer for jump transitions. if (_desiredState == RigRole::Takeoff) { _desiredStateAge = STATE_CHANGE_HYSTERESIS_TIMER; } else if (_state == RigRole::Takeoff && _desiredState == RigRole::InAir) { _desiredStateAge = STATE_CHANGE_HYSTERESIS_TIMER; } else if (_state == RigRole::InAir && _desiredState != RigRole::InAir) { _desiredStateAge = STATE_CHANGE_HYSTERESIS_TIMER; } if ((_desiredStateAge >= STATE_CHANGE_HYSTERESIS_TIMER) && _desiredState != _state) { _state = _desiredState; _desiredStateAge = 0.0f; } _desiredStateAge += deltaTime; if (_state == RigRole::Move) { glm::vec3 horizontalVel = localVel - glm::vec3(0.0f, localVel.y, 0.0f); if (glm::length(horizontalVel) > MOVE_ENTER_SPEED_THRESHOLD) { if (fabsf(forwardSpeed) > 0.5f * fabsf(lateralSpeed)) { if (forwardSpeed > 0.0f) { // forward _animVars.set("isMovingForward", true); _animVars.set("isMovingBackward", false); _animVars.set("isMovingRight", false); _animVars.set("isMovingLeft", false); _animVars.set("isNotMoving", false); } else { // backward _animVars.set("isMovingBackward", true); _animVars.set("isMovingForward", false); _animVars.set("isMovingRight", false); _animVars.set("isMovingLeft", false); _animVars.set("isNotMoving", false); } } else { if (lateralSpeed > 0.0f) { // right _animVars.set("isMovingRight", true); _animVars.set("isMovingLeft", false); _animVars.set("isMovingForward", false); _animVars.set("isMovingBackward", false); _animVars.set("isNotMoving", false); } else { // left _animVars.set("isMovingLeft", true); _animVars.set("isMovingRight", false); _animVars.set("isMovingForward", false); _animVars.set("isMovingBackward", false); _animVars.set("isNotMoving", false); } } } _animVars.set("isTurningLeft", false); _animVars.set("isTurningRight", false); _animVars.set("isNotTurning", true); _animVars.set("isFlying", false); _animVars.set("isNotFlying", true); _animVars.set("isTakeoffStand", false); _animVars.set("isTakeoffRun", false); _animVars.set("isNotTakeoff", true); _animVars.set("isInAirStand", false); _animVars.set("isInAirRun", false); _animVars.set("isNotInAir", true); } else if (_state == RigRole::Turn) { if (turningSpeed > 0.0f) { // turning right _animVars.set("isTurningRight", true); _animVars.set("isTurningLeft", false); _animVars.set("isNotTurning", false); } else { // turning left _animVars.set("isTurningLeft", true); _animVars.set("isTurningRight", false); _animVars.set("isNotTurning", false); } _animVars.set("isMovingForward", false); _animVars.set("isMovingBackward", false); _animVars.set("isMovingRight", false); _animVars.set("isMovingLeft", false); _animVars.set("isNotMoving", true); _animVars.set("isFlying", false); _animVars.set("isNotFlying", true); _animVars.set("isTakeoffStand", false); _animVars.set("isTakeoffRun", false); _animVars.set("isNotTakeoff", true); _animVars.set("isInAirStand", false); _animVars.set("isInAirRun", false); _animVars.set("isNotInAir", true); } else if (_state == RigRole::Idle ) { // default anim vars to notMoving and notTurning _animVars.set("isMovingForward", false); _animVars.set("isMovingBackward", false); _animVars.set("isMovingLeft", false); _animVars.set("isMovingRight", false); _animVars.set("isNotMoving", true); _animVars.set("isTurningLeft", false); _animVars.set("isTurningRight", false); _animVars.set("isNotTurning", true); _animVars.set("isFlying", false); _animVars.set("isNotFlying", true); _animVars.set("isTakeoffStand", false); _animVars.set("isTakeoffRun", false); _animVars.set("isNotTakeoff", true); _animVars.set("isInAirStand", false); _animVars.set("isInAirRun", false); _animVars.set("isNotInAir", true); } else if (_state == RigRole::Hover) { // flying. _animVars.set("isMovingForward", false); _animVars.set("isMovingBackward", false); _animVars.set("isMovingLeft", false); _animVars.set("isMovingRight", false); _animVars.set("isNotMoving", true); _animVars.set("isTurningLeft", false); _animVars.set("isTurningRight", false); _animVars.set("isNotTurning", true); _animVars.set("isFlying", true); _animVars.set("isNotFlying", false); _animVars.set("isTakeoffStand", false); _animVars.set("isTakeoffRun", false); _animVars.set("isNotTakeoff", true); _animVars.set("isInAirStand", false); _animVars.set("isInAirRun", false); _animVars.set("isNotInAir", true); } else if (_state == RigRole::Takeoff) { // jumping in-air _animVars.set("isMovingForward", false); _animVars.set("isMovingBackward", false); _animVars.set("isMovingLeft", false); _animVars.set("isMovingRight", false); _animVars.set("isNotMoving", true); _animVars.set("isTurningLeft", false); _animVars.set("isTurningRight", false); _animVars.set("isNotTurning", true); _animVars.set("isFlying", false); _animVars.set("isNotFlying", true); bool takeOffRun = forwardSpeed > 0.1f; if (takeOffRun) { _animVars.set("isTakeoffStand", false); _animVars.set("isTakeoffRun", true); } else { _animVars.set("isTakeoffStand", true); _animVars.set("isTakeoffRun", false); } _animVars.set("isNotTakeoff", false); _animVars.set("isInAirStand", false); _animVars.set("isInAirRun", false); _animVars.set("isNotInAir", false); } else if (_state == RigRole::InAir) { // jumping in-air _animVars.set("isMovingForward", false); _animVars.set("isMovingBackward", false); _animVars.set("isMovingLeft", false); _animVars.set("isMovingRight", false); _animVars.set("isNotMoving", true); _animVars.set("isTurningLeft", false); _animVars.set("isTurningRight", false); _animVars.set("isNotTurning", true); _animVars.set("isFlying", false); _animVars.set("isNotFlying", true); _animVars.set("isTakeoffStand", false); _animVars.set("isTakeoffRun", false); _animVars.set("isNotTakeoff", true); bool inAirRun = forwardSpeed > 0.1f; if (inAirRun) { _animVars.set("isInAirStand", false); _animVars.set("isInAirRun", true); } else { _animVars.set("isInAirStand", true); _animVars.set("isInAirRun", false); } _animVars.set("isNotInAir", false); // compute blend based on velocity const float JUMP_SPEED = 3.5f; float alpha = glm::clamp(-_lastVelocity.y / JUMP_SPEED, -1.0f, 1.0f) + 1.0f; _animVars.set("inAirAlpha", alpha); } t += deltaTime; if (_enableInverseKinematics != _lastEnableInverseKinematics) { if (_enableInverseKinematics) { _animVars.set("ikOverlayAlpha", 1.0f); } else { _animVars.set("ikOverlayAlpha", 0.0f); } } _lastEnableInverseKinematics = _enableInverseKinematics; } _lastForward = forward; _lastPosition = worldPosition; _lastVelocity = workingVelocity; } // Allow script to add/remove handlers and report results, from within their thread. QScriptValue Rig::addAnimationStateHandler(QScriptValue handler, QScriptValue propertiesList) { // called in script thread // validate argument types if (handler.isFunction() && (isListOfStrings(propertiesList) || propertiesList.isUndefined() || propertiesList.isNull())) { QMutexLocker locker(&_stateMutex); // Find a safe id, even if there are lots of many scripts add and remove handlers repeatedly. while (!_nextStateHandlerId || _stateHandlers.contains(_nextStateHandlerId)) { // 0 is unused, and don't reuse existing after wrap. _nextStateHandlerId++; } StateHandler& data = _stateHandlers[_nextStateHandlerId]; data.function = handler; data.useNames = propertiesList.isArray(); if (data.useNames) { data.propertyNames = propertiesList.toVariant().toStringList(); } return QScriptValue(_nextStateHandlerId); // suitable for giving to removeAnimationStateHandler } else { qCWarning(animation) << "Rig::addAnimationStateHandler invalid arguments, expected (function, string[])"; return QScriptValue(QScriptValue::UndefinedValue); } } void Rig::removeAnimationStateHandler(QScriptValue identifier) { // called in script thread // validate arguments if (identifier.isNumber()) { QMutexLocker locker(&_stateMutex); _stateHandlers.remove(identifier.toInt32()); // silently continues if handler not present. 0 is unused } else { qCWarning(animation) << "Rig::removeAnimationStateHandler invalid argument, expected a number"; } } void Rig::animationStateHandlerResult(int identifier, QScriptValue result) { // called synchronously from script QMutexLocker locker(&_stateMutex); auto found = _stateHandlers.find(identifier); if (found == _stateHandlers.end()) { return; // Don't use late-breaking results that got reported after the handler was removed. } found.value().results.animVariantMapFromScriptValue(result); // Into our own copy. } void Rig::updateAnimationStateHandlers() { // called on avatar update thread (which may be main thread) QMutexLocker locker(&_stateMutex); // It might pay to produce just one AnimVariantMap copy here, with a union of all the requested propertyNames, // rather than having each callAnimationStateHandler invocation make its own copy. // However, that copying is done on the script's own time rather than ours, so even if it's less cpu, it would be more // work on the avatar update thread (which is possibly the main thread). for (auto data = _stateHandlers.begin(); data != _stateHandlers.end(); data++) { // call out: int identifier = data.key(); StateHandler& value = data.value(); QScriptValue& function = value.function; int rigId = _rigId; auto handleResult = [rigId, identifier](QScriptValue result) { // called in script thread to get the result back to us. // Hold the rigRegistryMutex to ensure thread-safe access to the rigRegistry, but // also to prevent the rig from being deleted while this lambda is being executed. std::lock_guard guard(rigRegistryMutex); // if the rig pointer is in the registry, it has not been deleted yet. auto iter = rigRegistry.find(rigId); if (iter != rigRegistry.end()) { Rig* rig = iter->second; assert(rig); rig->animationStateHandlerResult(identifier, result); } }; // invokeMethod makes a copy of the args, and copies of AnimVariantMap do copy the underlying map, so this will correctly capture // the state of _animVars and allow continued changes to _animVars in this thread without conflict. QMetaObject::invokeMethod(function.engine(), "callAnimationStateHandler", Qt::QueuedConnection, Q_ARG(QScriptValue, function), Q_ARG(AnimVariantMap, _animVars), Q_ARG(QStringList, value.propertyNames), Q_ARG(bool, value.useNames), Q_ARG(AnimVariantResultHandler, handleResult)); // It turns out that, for thread-safety reasons, ScriptEngine::callAnimationStateHandler will invoke itself if called from other // than the script thread. Thus the above _could_ be replaced with an ordinary call, which will then trigger the same // invokeMethod as is done explicitly above. However, the script-engine library depends on this animation library, not vice versa. // We could create an AnimVariantCallingMixin class in shared, with an abstract virtual slot // AnimVariantCallingMixin::callAnimationStateHandler (and move AnimVariantMap/AnimVaraintResultHandler to shared), but the // call site here would look like this instead of the above: // dynamic_cast(function.engine())->callAnimationStateHandler(function, ..., handleResult); // This works (I tried it), but the result would be that we would still have same runtime type checks as the invokeMethod above // (occuring within the ScriptEngine::callAnimationStateHandler invokeMethod trampoline), _plus_ another runtime check for the dynamic_cast. // Gather results in (likely from an earlier update). // Note: the behavior is undefined if a handler (re-)sets a trigger. Scripts should not be doing that. _animVars.copyVariantsFrom(value.results); // If multiple handlers write the same anim var, the last registgered wins. (_map preserves order). } } void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, const glm::mat4& rigToWorldTransform) { DETAILED_PROFILE_RANGE_EX(simulation_animation_detail, __FUNCTION__, 0xffff00ff, 0); DETAILED_PERFORMANCE_TIMER("updateAnimations"); setModelOffset(rootTransform); if (_animNode && _enabledAnimations) { DETAILED_PERFORMANCE_TIMER("handleTriggers"); updateAnimationStateHandlers(); _animVars.setRigToGeometryTransform(_rigToGeometryTransform); AnimContext context(_enableDebugDrawIKTargets, _enableDebugDrawIKConstraints, _enableDebugDrawIKChains, getGeometryToRigTransform(), rigToWorldTransform); // evaluate the animation AnimVariantMap triggersOut; _internalPoseSet._relativePoses = _animNode->evaluate(_animVars, context, deltaTime, triggersOut); if ((int)_internalPoseSet._relativePoses.size() != _animSkeleton->getNumJoints()) { // animations haven't fully loaded yet. _internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses(); } _animVars.clearTriggers(); _animVars = triggersOut; } applyOverridePoses(); buildAbsoluteRigPoses(_internalPoseSet._relativePoses, _internalPoseSet._absolutePoses); // copy internal poses to external poses { QWriteLocker writeLock(&_externalPoseSetLock); _externalPoseSet = _internalPoseSet; } } void Rig::updateFromEyeParameters(const EyeParameters& params) { updateEyeJoint(params.leftEyeJointIndex, params.modelTranslation, params.modelRotation, params.eyeLookAt, params.eyeSaccade); updateEyeJoint(params.rightEyeJointIndex, params.modelTranslation, params.modelRotation, params.eyeLookAt, params.eyeSaccade); } void Rig::computeHeadFromHMD(const AnimPose& hmdPose, glm::vec3& headPositionOut, glm::quat& headOrientationOut) const { // the input hmd values are in avatar/rig space const glm::vec3& hmdPosition = hmdPose.trans(); // the HMD looks down the negative z axis, but the head bone looks down the z axis, so apply a 180 degree rotation. const glm::quat& hmdOrientation = hmdPose.rot() * Quaternions::Y_180; // TODO: cache jointIndices int rightEyeIndex = indexOfJoint("RightEye"); int leftEyeIndex = indexOfJoint("LeftEye"); int headIndex = indexOfJoint("Head"); glm::vec3 absRightEyePos = rightEyeIndex != -1 ? getAbsoluteDefaultPose(rightEyeIndex).trans() : DEFAULT_RIGHT_EYE_POS; glm::vec3 absLeftEyePos = leftEyeIndex != -1 ? getAbsoluteDefaultPose(leftEyeIndex).trans() : DEFAULT_LEFT_EYE_POS; glm::vec3 absHeadPos = headIndex != -1 ? getAbsoluteDefaultPose(headIndex).trans() : DEFAULT_HEAD_POS; glm::vec3 absCenterEyePos = (absRightEyePos + absLeftEyePos) / 2.0f; glm::vec3 eyeOffset = absCenterEyePos - absHeadPos; headPositionOut = hmdPosition - hmdOrientation * eyeOffset; headOrientationOut = hmdOrientation; } void Rig::updateHead(bool headEnabled, bool hipsEnabled, const AnimPose& headPose) { if (_animSkeleton) { 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); _animVars.unset("headWeight"); // use the default weight for this target. } else { // When there is no hips IK target, use the HmdHead IK chain type. This will make the spine very stiff, // but because the IK _hipsOffset is enabled, the hips will naturally follow underneath the head. _animVars.set("headType", (int)IKTarget::Type::HmdHead); _animVars.set("headWeight", 8.0f); } } else { _animVars.unset("headPosition"); _animVars.set("headRotation", headPose.rot()); _animVars.set("headType", (int)IKTarget::Type::RotationOnly); } } } const float INV_SQRT_3 = 1.0f / sqrtf(3.0f); const int DOP14_COUNT = 14; const glm::vec3 DOP14_NORMALS[DOP14_COUNT] = { Vectors::UNIT_X, -Vectors::UNIT_X, Vectors::UNIT_Y, -Vectors::UNIT_Y, Vectors::UNIT_Z, -Vectors::UNIT_Z, glm::vec3(INV_SQRT_3, INV_SQRT_3, INV_SQRT_3), -glm::vec3(INV_SQRT_3, INV_SQRT_3, INV_SQRT_3), glm::vec3(INV_SQRT_3, -INV_SQRT_3, INV_SQRT_3), -glm::vec3(INV_SQRT_3, -INV_SQRT_3, INV_SQRT_3), glm::vec3(INV_SQRT_3, INV_SQRT_3, -INV_SQRT_3), -glm::vec3(INV_SQRT_3, INV_SQRT_3, -INV_SQRT_3), glm::vec3(INV_SQRT_3, -INV_SQRT_3, -INV_SQRT_3), -glm::vec3(INV_SQRT_3, -INV_SQRT_3, -INV_SQRT_3) }; // returns true if the given point lies inside of the k-dop, specified by shapeInfo & shapePose. // if the given point does lie within the k-dop, it also returns the amount of displacement necessary to push that point outward // such that it lies on the surface of the kdop. static bool findPointKDopDisplacement(const glm::vec3& point, const AnimPose& shapePose, const FBXJointShapeInfo& shapeInfo, glm::vec3& displacementOut) { // transform point into local space of jointShape. glm::vec3 localPoint = shapePose.inverse().xformPoint(point); // Only works for 14-dop shape infos. if (shapeInfo.dots.size() != DOP14_COUNT) { return false; } glm::vec3 minDisplacement(FLT_MAX); float minDisplacementLen = FLT_MAX; glm::vec3 p = localPoint - shapeInfo.avgPoint; float pLen = glm::length(p); if (pLen > 0.0f) { int slabCount = 0; for (int i = 0; i < DOP14_COUNT; i++) { float dot = glm::dot(p, DOP14_NORMALS[i]); if (dot > 0.0f && dot < shapeInfo.dots[i]) { slabCount++; float distToPlane = pLen * (shapeInfo.dots[i] / dot); float displacementLen = distToPlane - pLen; // keep track of the smallest displacement if (displacementLen < minDisplacementLen) { minDisplacementLen = displacementLen; minDisplacement = (p / pLen) * displacementLen; } } } if (slabCount == (DOP14_COUNT / 2) && minDisplacementLen != FLT_MAX) { // we are within the k-dop so push the point along the minimum displacement found displacementOut = shapePose.xformVectorFast(minDisplacement); return true; } else { // point is outside of kdop return false; } } else { // point is directly on top of shapeInfo.avgPoint. // push the point out along the x axis. displacementOut = shapePose.xformVectorFast(shapeInfo.points[0]); return true; } } glm::vec3 Rig::deflectHandFromTorso(const glm::vec3& handPosition, const FBXJointShapeInfo& hipsShapeInfo, const FBXJointShapeInfo& spineShapeInfo, const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo) const { glm::vec3 position = handPosition; glm::vec3 displacement; int hipsJoint = indexOfJoint("Hips"); if (hipsJoint >= 0) { AnimPose hipsPose; if (getAbsoluteJointPoseInRigFrame(hipsJoint, hipsPose)) { if (findPointKDopDisplacement(position, hipsPose, hipsShapeInfo, displacement)) { position += displacement; } } } int spineJoint = indexOfJoint("Spine"); if (spineJoint >= 0) { AnimPose spinePose; if (getAbsoluteJointPoseInRigFrame(spineJoint, spinePose)) { if (findPointKDopDisplacement(position, spinePose, spineShapeInfo, displacement)) { position += displacement; } } } int spine1Joint = indexOfJoint("Spine1"); if (spine1Joint >= 0) { AnimPose spine1Pose; if (getAbsoluteJointPoseInRigFrame(spine1Joint, spine1Pose)) { if (findPointKDopDisplacement(position, spine1Pose, spine1ShapeInfo, displacement)) { position += displacement; } } } int spine2Joint = indexOfJoint("Spine2"); if (spine2Joint >= 0) { AnimPose spine2Pose; if (getAbsoluteJointPoseInRigFrame(spine2Joint, spine2Pose)) { if (findPointKDopDisplacement(position, spine2Pose, spine2ShapeInfo, displacement)) { position += displacement; } } } return position; } void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnabled, bool hipsEstimated, bool leftArmEnabled, bool rightArmEnabled, bool headEnabled, float dt, const AnimPose& leftHandPose, const AnimPose& rightHandPose, const FBXJointShapeInfo& hipsShapeInfo, const FBXJointShapeInfo& spineShapeInfo, const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo, const glm::mat4& rigToSensorMatrix, const glm::mat4& sensorToRigMatrix) { const bool ENABLE_POLE_VECTORS = true; const float ELBOW_POLE_VECTOR_BLEND_FACTOR = 0.95f; if (leftHandEnabled) { glm::vec3 handPosition = leftHandPose.trans(); glm::quat handRotation = leftHandPose.rot(); if (!hipsEnabled || hipsEstimated) { // prevent the hand IK targets from intersecting the torso handPosition = deflectHandFromTorso(handPosition, hipsShapeInfo, spineShapeInfo, spine1ShapeInfo, spine2ShapeInfo); } _animVars.set("leftHandPosition", handPosition); _animVars.set("leftHandRotation", handRotation); _animVars.set("leftHandType", (int)IKTarget::Type::RotationAndPosition); // compute pole vector int handJointIndex = _animSkeleton->nameToJointIndex("LeftHand"); int armJointIndex = _animSkeleton->nameToJointIndex("LeftArm"); int elbowJointIndex = _animSkeleton->nameToJointIndex("LeftForeArm"); int oppositeArmJointIndex = _animSkeleton->nameToJointIndex("RightArm"); if (ENABLE_POLE_VECTORS && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0 && oppositeArmJointIndex >= 0) { glm::vec3 poleVector; bool usePoleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, oppositeArmJointIndex, poleVector); if (usePoleVector) { glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector); if (_smoothPoleVectors) { // smooth toward desired pole vector from previous pole vector... to reduce jitter if (!_prevLeftHandPoleVectorValid) { _prevLeftHandPoleVectorValid = true; _prevLeftHandPoleVector = sensorPoleVector; } glm::quat deltaRot = rotationBetween(_prevLeftHandPoleVector, sensorPoleVector); glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, ELBOW_POLE_VECTOR_BLEND_FACTOR); _prevLeftHandPoleVector = smoothDeltaRot * _prevLeftHandPoleVector; } else { _prevLeftHandPoleVector = sensorPoleVector; } _animVars.set("leftHandPoleVectorEnabled", true); _animVars.set("leftHandPoleReferenceVector", Vectors::UNIT_X); _animVars.set("leftHandPoleVector", transformVectorFast(sensorToRigMatrix, _prevLeftHandPoleVector)); } else { _prevLeftHandPoleVectorValid = false; _animVars.set("leftHandPoleVectorEnabled", false); } } else { _prevLeftHandPoleVectorValid = false; _animVars.set("leftHandPoleVectorEnabled", false); } } else { _prevLeftHandPoleVectorValid = false; _animVars.set("leftHandPoleVectorEnabled", false); _animVars.unset("leftHandPosition"); _animVars.unset("leftHandRotation"); if (headEnabled) { _animVars.set("leftHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition); } else { // disable hand IK for desktop mode _animVars.set("leftHandType", (int)IKTarget::Type::Unknown); } } if (rightHandEnabled) { glm::vec3 handPosition = rightHandPose.trans(); glm::quat handRotation = rightHandPose.rot(); if (!hipsEnabled || hipsEstimated) { // prevent the hand IK targets from intersecting the torso handPosition = deflectHandFromTorso(handPosition, hipsShapeInfo, spineShapeInfo, spine1ShapeInfo, spine2ShapeInfo); } _animVars.set("rightHandPosition", handPosition); _animVars.set("rightHandRotation", handRotation); _animVars.set("rightHandType", (int)IKTarget::Type::RotationAndPosition); // compute pole vector int handJointIndex = _animSkeleton->nameToJointIndex("RightHand"); int armJointIndex = _animSkeleton->nameToJointIndex("RightArm"); int elbowJointIndex = _animSkeleton->nameToJointIndex("RightForeArm"); int oppositeArmJointIndex = _animSkeleton->nameToJointIndex("LeftArm"); if (ENABLE_POLE_VECTORS && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0 && oppositeArmJointIndex >= 0) { glm::vec3 poleVector; bool usePoleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, oppositeArmJointIndex, poleVector); if (usePoleVector) { glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector); if (_smoothPoleVectors) { // smooth toward desired pole vector from previous pole vector... to reduce jitter if (!_prevRightHandPoleVectorValid) { _prevRightHandPoleVectorValid = true; _prevRightHandPoleVector = sensorPoleVector; } glm::quat deltaRot = rotationBetween(_prevRightHandPoleVector, sensorPoleVector); glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, ELBOW_POLE_VECTOR_BLEND_FACTOR); _prevRightHandPoleVector = smoothDeltaRot * _prevRightHandPoleVector; } else { _prevRightHandPoleVector = sensorPoleVector; } _animVars.set("rightHandPoleVectorEnabled", true); _animVars.set("rightHandPoleReferenceVector", -Vectors::UNIT_X); _animVars.set("rightHandPoleVector", transformVectorFast(sensorToRigMatrix, _prevRightHandPoleVector)); } else { _prevRightHandPoleVectorValid = false; _animVars.set("rightHandPoleVectorEnabled", false); } } else { _prevRightHandPoleVectorValid = false; _animVars.set("rightHandPoleVectorEnabled", false); } } else { _prevRightHandPoleVectorValid = false; _animVars.set("rightHandPoleVectorEnabled", false); _animVars.unset("rightHandPosition"); _animVars.unset("rightHandRotation"); if (headEnabled) { _animVars.set("rightHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition); } else { // disable hand IK for desktop mode _animVars.set("rightHandType", (int)IKTarget::Type::Unknown); } } } void Rig::updateFeet(bool leftFootEnabled, bool rightFootEnabled, bool headEnabled, const AnimPose& leftFootPose, const AnimPose& rightFootPose, const glm::mat4& rigToSensorMatrix, const glm::mat4& sensorToRigMatrix) { int hipsIndex = indexOfJoint("Hips"); const float KNEE_POLE_VECTOR_BLEND_FACTOR = 0.85f; if (headEnabled) { // always do IK if head is enabled _animVars.set("leftFootIKEnabled", true); _animVars.set("rightFootIKEnabled", true); } else { // only do IK if we have a valid foot. _animVars.set("leftFootIKEnabled", leftFootEnabled); _animVars.set("rightFootIKEnabled", rightFootEnabled); } if (leftFootEnabled) { _animVars.set(LEFT_FOOT_POSITION, leftFootPose.trans()); _animVars.set(LEFT_FOOT_ROTATION, leftFootPose.rot()); // We want to drive the IK directly from the trackers. _animVars.set(LEFT_FOOT_IK_POSITION_VAR, LEFT_FOOT_POSITION); _animVars.set(LEFT_FOOT_IK_ROTATION_VAR, LEFT_FOOT_ROTATION); int footJointIndex = _animSkeleton->nameToJointIndex("LeftFoot"); int kneeJointIndex = _animSkeleton->nameToJointIndex("LeftLeg"); int upLegJointIndex = _animSkeleton->nameToJointIndex("LeftUpLeg"); glm::vec3 poleVector = calculateKneePoleVector(footJointIndex, kneeJointIndex, upLegJointIndex, hipsIndex, leftFootPose); glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector); // smooth toward desired pole vector from previous pole vector... to reduce jitter, but in sensor space. if (!_prevLeftFootPoleVectorValid) { _prevLeftFootPoleVectorValid = true; _prevLeftFootPoleVector = sensorPoleVector; } glm::quat deltaRot = rotationBetween(_prevLeftFootPoleVector, sensorPoleVector); glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, KNEE_POLE_VECTOR_BLEND_FACTOR); _prevLeftFootPoleVector = smoothDeltaRot * _prevLeftFootPoleVector; _animVars.set("leftFootPoleVectorEnabled", true); _animVars.set("leftFootPoleVector", transformVectorFast(sensorToRigMatrix, _prevLeftFootPoleVector)); } else { // We want to drive the IK from the underlying animation. // This gives us the ability to squat while in the HMD, without the feet from dipping under the floor. _animVars.set(LEFT_FOOT_IK_POSITION_VAR, MAIN_STATE_MACHINE_LEFT_FOOT_POSITION); _animVars.set(LEFT_FOOT_IK_ROTATION_VAR, MAIN_STATE_MACHINE_LEFT_FOOT_ROTATION); // We want to match the animated knee pose as close as possible, so don't use poleVectors _animVars.set("leftFootPoleVectorEnabled", false); _prevLeftFootPoleVectorValid = false; } if (rightFootEnabled) { _animVars.set(RIGHT_FOOT_POSITION, rightFootPose.trans()); _animVars.set(RIGHT_FOOT_ROTATION, rightFootPose.rot()); // We want to drive the IK directly from the trackers. _animVars.set(RIGHT_FOOT_IK_POSITION_VAR, RIGHT_FOOT_POSITION); _animVars.set(RIGHT_FOOT_IK_ROTATION_VAR, RIGHT_FOOT_ROTATION); int footJointIndex = _animSkeleton->nameToJointIndex("RightFoot"); int kneeJointIndex = _animSkeleton->nameToJointIndex("RightLeg"); int upLegJointIndex = _animSkeleton->nameToJointIndex("RightUpLeg"); glm::vec3 poleVector = calculateKneePoleVector(footJointIndex, kneeJointIndex, upLegJointIndex, hipsIndex, rightFootPose); glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector); // smooth toward desired pole vector from previous pole vector... to reduce jitter if (!_prevRightFootPoleVectorValid) { _prevRightFootPoleVectorValid = true; _prevRightFootPoleVector = sensorPoleVector; } glm::quat deltaRot = rotationBetween(_prevRightFootPoleVector, sensorPoleVector); glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, KNEE_POLE_VECTOR_BLEND_FACTOR); _prevRightFootPoleVector = smoothDeltaRot * _prevRightFootPoleVector; _animVars.set("rightFootPoleVectorEnabled", true); _animVars.set("rightFootPoleVector", transformVectorFast(sensorToRigMatrix, _prevRightFootPoleVector)); } else { // We want to drive the IK from the underlying animation. // This gives us the ability to squat while in the HMD, without the feet from dipping under the floor. _animVars.set(RIGHT_FOOT_IK_POSITION_VAR, MAIN_STATE_MACHINE_RIGHT_FOOT_POSITION); _animVars.set(RIGHT_FOOT_IK_ROTATION_VAR, MAIN_STATE_MACHINE_RIGHT_FOOT_ROTATION); // We want to match the animated knee pose as close as possible, so don't use poleVectors _animVars.set("rightFootPoleVectorEnabled", false); _prevRightFootPoleVectorValid = false; } } 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. if (isIndexValid(index)) { const glm::mat4 rigToWorld = createMatFromQuatAndPos(modelRotation, modelTranslation); const glm::mat4 worldToRig = glm::inverse(rigToWorld); const glm::vec3 lookAtVector = glm::normalize(transformPoint(worldToRig, lookAtSpot) - _internalPoseSet._absolutePoses[index].trans()); int headIndex = indexOfJoint("Head"); glm::quat headQuat; if (headIndex >= 0) { headQuat = _internalPoseSet._absolutePoses[headIndex].rot(); } glm::vec3 headUp = headQuat * Vectors::UNIT_Y; glm::vec3 z, y, zCrossY; generateBasisVectors(lookAtVector, headUp, z, y, zCrossY); glm::mat3 m(-zCrossY, y, z); glm::quat desiredQuat = glm::normalize(glm::quat_cast(m)); glm::quat deltaQuat = desiredQuat * glm::inverse(headQuat); // limit swing rotation of the deltaQuat by a 25 degree cone. // TODO: use swing twist decomposition constraint instead, for off axis rotation clamping. const float MAX_ANGLE = 25.0f * RADIANS_PER_DEGREE; if (fabsf(glm::angle(deltaQuat)) > MAX_ANGLE) { deltaQuat = glm::angleAxis(glm::clamp(glm::angle(deltaQuat), -MAX_ANGLE, MAX_ANGLE), glm::axis(deltaQuat)); } // directly set absolutePose rotation _internalPoseSet._absolutePoses[index].rot() = deltaQuat * headQuat; // Update eye joint's children. auto children = index == _leftEyeJointIndex ? _leftEyeJointChildren : _rightEyeJointChildren; for (int i = 0; i < (int)children.size(); i++) { int jointIndex = children[i]; int parentIndex = _animSkeleton->getParentIndex(jointIndex); _internalPoseSet._absolutePoses[jointIndex] = _internalPoseSet._absolutePoses[parentIndex] * _internalPoseSet._relativePoses[jointIndex]; } } } bool Rig::calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex, int oppositeArmIndex, glm::vec3& poleVector) const { // The resulting Pole Vector is calculated as the sum of a three vectors. // The first is the vector with direction shoulder-hand. The module of this vector is inversely proportional to the strength of the resulting Pole Vector. // The second vector is always perpendicular to previous vector and is part of the plane that contains a point located on the horizontal line, // pointing forward and with height aprox to the avatar head. The position of the horizontal point will be determined by the hands Y component. // The third vector apply a weighted correction to the resulting pole vector to avoid interpenetration and force a more natural pose. AnimPose oppositeArmPose = _externalPoseSet._absolutePoses[oppositeArmIndex]; AnimPose handPose = _externalPoseSet._absolutePoses[handIndex]; AnimPose armPose = _externalPoseSet._absolutePoses[armIndex]; AnimPose elbowPose = _externalPoseSet._absolutePoses[elbowIndex]; glm::vec3 armToHand = handPose.trans() - armPose.trans(); glm::vec3 armToElbow = elbowPose.trans() - armPose.trans(); glm::vec3 elbowToHand = handPose.trans() - elbowPose.trans(); glm::vec3 backVector = oppositeArmPose.trans() - armPose.trans(); glm::vec3 backCenter = armPose.trans() + 0.5f * backVector; const float OVER_BACK_HEAD_PERCENTAGE = 0.2f; glm::vec3 headCenter = backCenter + glm::vec3(0, OVER_BACK_HEAD_PERCENTAGE * backVector.length(), 0); glm::vec3 frontVector = glm::normalize(glm::cross(backVector, glm::vec3(0, 1, 0))); // Make sure is pointing forward frontVector = frontVector.z < 0 ? -frontVector : frontVector; float horizontalModule = glm::dot(armToHand, glm::vec3(0, -1, 0)); glm::vec3 headForward = headCenter + horizontalModule * frontVector; glm::vec3 armToHead = headForward - armPose.trans(); float armToHandDistance = glm::length(armToHand); float armToElbowDistance = glm::length(armToElbow); float elbowToHandDistance = glm::length(elbowToHand); float armTotalDistance = armToElbowDistance + elbowToHandDistance; glm::vec3 armToHandDir = armToHand / armToHandDistance; glm::vec3 armToHeadPlaneNormal = glm::cross(armToHead, armToHandDir); // How much the hand is reaching for the opposite side float oppositeProjection = glm::dot(armToHandDir, glm::normalize(backVector)); // Don't use pole vector when the hands are behind if (glm::dot(frontVector, armToHand) < 0 && oppositeProjection < 0.5f * armTotalDistance) { return false; } // The strenght of the resulting pole determined by the arm flex. float armFlexCoeficient = armToHandDistance / armTotalDistance; glm::vec3 attenuationVector = armFlexCoeficient * armToHandDir; // Pole vector is perpendicular to the shoulder-hand direction and located on the plane that contains the head-forward line glm::vec3 fullPoleVector = glm::normalize(glm::cross(armToHeadPlaneNormal, armToHandDir)); // Push elbow forward when hand reaches opposite side glm::vec3 correctionVector = glm::vec3(0, 0, 0); const float FORWARD_TRIGGER_PERCENTAGE = 0.2f; const float FORWARD_CORRECTOR_WEIGHT = 3.0f; float elbowForwardTrigger = FORWARD_TRIGGER_PERCENTAGE * armToHandDistance; if (oppositeProjection > -elbowForwardTrigger) { float forwardAmount = FORWARD_CORRECTOR_WEIGHT * (elbowForwardTrigger + oppositeProjection); correctionVector = forwardAmount * frontVector; } poleVector = glm::normalize(attenuationVector + fullPoleVector + correctionVector); return true; } // returns a poleVector for the knees that is a blend of the foot and the hips. // targetFootPose is in rig space // result poleVector is also in rig space. glm::vec3 Rig::calculateKneePoleVector(int footJointIndex, int kneeIndex, int upLegIndex, int hipsIndex, const AnimPose& targetFootPose) const { const float FOOT_THETA = 0.8969f; // 51.39 degrees const glm::vec3 localFootForward(0.0f, cosf(FOOT_THETA), sinf(FOOT_THETA)); glm::vec3 footForward = targetFootPose.rot() * localFootForward; AnimPose hipsPose = _externalPoseSet._absolutePoses[hipsIndex]; glm::vec3 hipsForward = hipsPose.rot() * Vectors::UNIT_Z; return glm::normalize(lerp(hipsForward, footForward, 0.75f)); } void Rig::updateFromControllerParameters(const ControllerParameters& params, float dt) { if (!_animSkeleton || !_animNode) { return; } _animVars.set("isTalking", params.isTalking); _animVars.set("notIsTalking", !params.isTalking); bool headEnabled = params.primaryControllerFlags[PrimaryControllerType_Head] & (uint8_t)ControllerFlags::Enabled; bool leftHandEnabled = params.primaryControllerFlags[PrimaryControllerType_LeftHand] & (uint8_t)ControllerFlags::Enabled; bool rightHandEnabled = params.primaryControllerFlags[PrimaryControllerType_RightHand] & (uint8_t)ControllerFlags::Enabled; bool hipsEnabled = params.primaryControllerFlags[PrimaryControllerType_Hips] & (uint8_t)ControllerFlags::Enabled; bool hipsEstimated = params.primaryControllerFlags[PrimaryControllerType_Hips] & (uint8_t)ControllerFlags::Estimated; bool leftFootEnabled = params.primaryControllerFlags[PrimaryControllerType_LeftFoot] & (uint8_t)ControllerFlags::Enabled; bool rightFootEnabled = params.primaryControllerFlags[PrimaryControllerType_RightFoot] & (uint8_t)ControllerFlags::Enabled; bool spine2Enabled = params.primaryControllerFlags[PrimaryControllerType_Spine2] & (uint8_t)ControllerFlags::Enabled; bool leftArmEnabled = params.secondaryControllerFlags[SecondaryControllerType_LeftArm] & (uint8_t)ControllerFlags::Enabled; bool rightArmEnabled = params.secondaryControllerFlags[SecondaryControllerType_RightArm] & (uint8_t)ControllerFlags::Enabled; glm::mat4 sensorToRigMatrix = glm::inverse(params.rigToSensorMatrix); updateHead(headEnabled, hipsEnabled, params.primaryControllerPoses[PrimaryControllerType_Head]); updateHands(leftHandEnabled, rightHandEnabled, hipsEnabled, hipsEstimated, leftArmEnabled, rightArmEnabled, headEnabled, dt, params.primaryControllerPoses[PrimaryControllerType_LeftHand], params.primaryControllerPoses[PrimaryControllerType_RightHand], params.hipsShapeInfo, params.spineShapeInfo, params.spine1ShapeInfo, params.spine2ShapeInfo, params.rigToSensorMatrix, sensorToRigMatrix); updateFeet(leftFootEnabled, rightFootEnabled, headEnabled, params.primaryControllerPoses[PrimaryControllerType_LeftFoot], params.primaryControllerPoses[PrimaryControllerType_RightFoot], params.rigToSensorMatrix, sensorToRigMatrix); if (headEnabled) { // Blend IK chains toward the joint limit centers, this should stablize head and hand ik. _animVars.set("solutionSource", (int)AnimInverseKinematics::SolutionSource::RelaxToLimitCenterPoses); } else { // Blend IK chains toward the UnderPoses, so some of the animaton motion is present in the IK solution. _animVars.set("solutionSource", (int)AnimInverseKinematics::SolutionSource::RelaxToUnderPoses); } // if the hips or the feet are being controlled. if (hipsEnabled || rightFootEnabled || leftFootEnabled) { // 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 { // feet should follow source animation _animVars.unset("defaultPoseOverlayAlpha"); _animVars.unset("defaultPoseOverlayBoneSet"); } if (hipsEnabled) { _animVars.set("hipsType", (int)IKTarget::Type::RotationAndPosition); _animVars.set("hipsPosition", params.primaryControllerPoses[PrimaryControllerType_Hips].trans()); _animVars.set("hipsRotation", params.primaryControllerPoses[PrimaryControllerType_Hips].rot()); } else { _animVars.set("hipsType", (int)IKTarget::Type::Unknown); } if (hipsEnabled && spine2Enabled) { _animVars.set("spine2Type", (int)IKTarget::Type::Spline); _animVars.set("spine2Position", params.primaryControllerPoses[PrimaryControllerType_Spine2].trans()); _animVars.set("spine2Rotation", params.primaryControllerPoses[PrimaryControllerType_Spine2].rot()); } else { _animVars.set("spine2Type", (int)IKTarget::Type::Unknown); } // set secondary targets static const std::vector secondaryControllerJointNames = { "LeftShoulder", "RightShoulder", "LeftArm", "RightArm", "LeftForeArm", "RightForeArm", "LeftUpLeg", "RightUpLeg", "LeftLeg", "RightLeg", "LeftToeBase", "RightToeBase" }; std::shared_ptr ikNode = getAnimInverseKinematicsNode(); for (int i = 0; i < (int)NumSecondaryControllerTypes; i++) { int index = indexOfJoint(secondaryControllerJointNames[i]); if (index >= 0) { if (params.secondaryControllerFlags[i] & (uint8_t)ControllerFlags::Enabled) { ikNode->setSecondaryTargetInRigFrame(index, params.secondaryControllerPoses[i]); } else { ikNode->clearSecondaryTarget(index); } } } } void Rig::initAnimGraph(const QUrl& url) { if (_animGraphURL != url || !_animNode) { _animGraphURL = url; _animNode.reset(); // load the anim graph _animLoader.reset(new AnimNodeLoader(url)); std::weak_ptr weakSkeletonPtr = _animSkeleton; connect(_animLoader.get(), &AnimNodeLoader::success, [this, weakSkeletonPtr](AnimNode::Pointer nodeIn) { _animNode = nodeIn; // abort load if the previous skeleton was deleted. auto sharedSkeletonPtr = weakSkeletonPtr.lock(); if (!sharedSkeletonPtr) { return; } _animNode->setSkeleton(sharedSkeletonPtr); if (_userAnimState.clipNodeEnum != UserAnimState::None) { // restore the user animation we had before reset. UserAnimState origState = _userAnimState; _userAnimState = { UserAnimState::None, "", 30.0f, false, 0.0f, 0.0f }; overrideAnimation(origState.url, origState.fps, origState.loop, origState.firstFrame, origState.lastFrame); } // restore the role animations we had before reset. for (auto& roleAnimState : _roleAnimStates) { auto roleState = roleAnimState.second; overrideRoleAnimation(roleState.role, roleState.url, roleState.fps, roleState.loop, roleState.firstFrame, roleState.lastFrame); } emit onLoadComplete(); }); connect(_animLoader.get(), &AnimNodeLoader::error, [url](int error, QString str) { qCCritical(animation) << "Error loading" << url.toDisplayString() << "code = " << error << "str =" << str; }); } } bool Rig::getModelRegistrationPoint(glm::vec3& modelRegistrationPointOut) const { if (_animSkeleton && _rootJointIndex >= 0) { modelRegistrationPointOut = _geometryOffset * -_animSkeleton->getAbsoluteDefaultPose(_rootJointIndex).trans(); return true; } else { return false; } } void Rig::applyOverridePoses() { DETAILED_PERFORMANCE_TIMER("override"); if (_numOverrides == 0 || !_animSkeleton) { return; } ASSERT(_animSkeleton->getNumJoints() == (int)_internalPoseSet._relativePoses.size()); ASSERT(_animSkeleton->getNumJoints() == (int)_internalPoseSet._overrideFlags.size()); ASSERT(_animSkeleton->getNumJoints() == (int)_internalPoseSet._overridePoses.size()); for (size_t i = 0; i < _internalPoseSet._overrideFlags.size(); i++) { if (_internalPoseSet._overrideFlags[i]) { _internalPoseSet._relativePoses[i] = _internalPoseSet._overridePoses[i]; } } } void Rig::buildAbsoluteRigPoses(const AnimPoseVec& relativePoses, AnimPoseVec& absolutePosesOut) { DETAILED_PERFORMANCE_TIMER("buildAbsolute"); if (!_animSkeleton) { return; } ASSERT(_animSkeleton->getNumJoints() == (int)relativePoses.size()); absolutePosesOut.resize(relativePoses.size()); AnimPose geometryToRigTransform(_geometryToRigTransform); for (int i = 0; i < (int)relativePoses.size(); i++) { int parentIndex = _animSkeleton->getParentIndex(i); if (parentIndex == -1) { // transform all root absolute poses into rig space absolutePosesOut[i] = geometryToRigTransform * relativePoses[i]; } else { absolutePosesOut[i] = absolutePosesOut[parentIndex] * relativePoses[i]; } } } glm::mat4 Rig::getJointTransform(int jointIndex) const { static const glm::mat4 IDENTITY; if (isIndexValid(jointIndex)) { return _internalPoseSet._absolutePoses[jointIndex]; } else { return IDENTITY; } } AnimPose Rig::getJointPose(int jointIndex) const { if (isIndexValid(jointIndex)) { return _internalPoseSet._absolutePoses[jointIndex]; } else { return AnimPose::identity; } } void Rig::copyJointsIntoJointData(QVector& jointDataVec) const { const AnimPose geometryToRigPose(_geometryToRigTransform); jointDataVec.resize((int)getJointStateCount()); for (auto i = 0; i < jointDataVec.size(); i++) { JointData& data = jointDataVec[i]; if (isIndexValid(i)) { // rotations are in absolute rig frame. glm::quat defaultAbsRot = geometryToRigPose.rot() * _animSkeleton->getAbsoluteDefaultPose(i).rot(); data.rotation = _internalPoseSet._absolutePoses[i].rot(); data.rotationIsDefaultPose = isEqual(data.rotation, defaultAbsRot); // translations are in relative frame but scaled so that they are in meters, // instead of geometry units. glm::vec3 defaultRelTrans = _geometryOffset.scale() * _animSkeleton->getRelativeDefaultPose(i).trans(); data.translation = _geometryOffset.scale() * _internalPoseSet._relativePoses[i].trans(); data.translationIsDefaultPose = isEqual(data.translation, defaultRelTrans); } else { data.translationIsDefaultPose = true; data.rotationIsDefaultPose = true; } } } void Rig::copyJointsFromJointData(const QVector& jointDataVec) { DETAILED_PROFILE_RANGE(simulation_animation_detail, "copyJoints"); DETAILED_PERFORMANCE_TIMER("copyJoints"); if (!_animSkeleton) { return; } int numJoints = jointDataVec.size(); const AnimPoseVec& absoluteDefaultPoses = _animSkeleton->getAbsoluteDefaultPoses(); if (numJoints != (int)absoluteDefaultPoses.size()) { // jointData is incompatible return; } // make a vector of rotations in absolute-geometry-frame std::vector rotations; rotations.reserve(numJoints); const glm::quat rigToGeometryRot(glmExtractRotation(_rigToGeometryTransform)); for (int i = 0; i < numJoints; i++) { const JointData& data = jointDataVec.at(i); if (data.rotationIsDefaultPose) { rotations.push_back(absoluteDefaultPoses[i].rot()); } else { // JointData rotations are in absolute rig-frame so we rotate them to absolute geometry-frame rotations.push_back(rigToGeometryRot * data.rotation); } } // convert rotations from absolute to parent relative. _animSkeleton->convertAbsoluteRotationsToRelative(rotations); // store new relative poses if (numJoints != (int)_internalPoseSet._relativePoses.size()) { _internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses(); } const AnimPoseVec& relativeDefaultPoses = _animSkeleton->getRelativeDefaultPoses(); for (int i = 0; i < numJoints; i++) { const JointData& data = jointDataVec.at(i); _internalPoseSet._relativePoses[i].rot() = rotations[i]; if (data.translationIsDefaultPose) { _internalPoseSet._relativePoses[i].trans() = relativeDefaultPoses[i].trans(); } else { // JointData translations are in scaled relative-frame so we scale back to regular relative-frame _internalPoseSet._relativePoses[i].trans() = _invGeometryOffset.scale() * data.translation; } } } void Rig::computeExternalPoses(const glm::mat4& modelOffsetMat) { _modelOffset = AnimPose(modelOffsetMat); _geometryToRigTransform = _modelOffset * _geometryOffset; _rigToGeometryTransform = glm::inverse(_geometryToRigTransform); buildAbsoluteRigPoses(_internalPoseSet._relativePoses, _internalPoseSet._absolutePoses); QWriteLocker writeLock(&_externalPoseSetLock); _externalPoseSet = _internalPoseSet; } void Rig::computeAvatarBoundingCapsule( const FBXGeometry& geometry, float& radiusOut, float& heightOut, glm::vec3& localOffsetOut) const { if (! _animSkeleton) { const float DEFAULT_AVATAR_CAPSULE_RADIUS = 0.3f; const float DEFAULT_AVATAR_CAPSULE_HEIGHT = 1.3f; const glm::vec3 DEFAULT_AVATAR_CAPSULE_LOCAL_OFFSET = glm::vec3(0.0f, -0.25f, 0.0f); radiusOut = DEFAULT_AVATAR_CAPSULE_RADIUS; heightOut = DEFAULT_AVATAR_CAPSULE_HEIGHT; localOffsetOut = DEFAULT_AVATAR_CAPSULE_LOCAL_OFFSET; return; } glm::vec3 hipsPosition(0.0f); int hipsIndex = indexOfJoint("Hips"); if (hipsIndex >= 0) { hipsPosition = transformPoint(_geometryToRigTransform, _animSkeleton->getAbsoluteDefaultPose(hipsIndex).trans()); } // compute bounding box that encloses all points Extents totalExtents; totalExtents.reset(); // HACK by convention our Avatars are always modeled such that y=0 is the ground plane. // add the zero point so that our avatars will always have bounding volumes that are flush with the ground // even if they do not have legs (default robot) totalExtents.addPoint(glm::vec3(0.0f)); // To reduce the radius of the bounding capsule to be tight with the torso, we only consider joints // from the head to the hips when computing the rest of the bounding capsule. int index = indexOfJoint("Head"); while (index != -1) { const FBXJointShapeInfo& shapeInfo = geometry.joints.at(index).shapeInfo; AnimPose pose = _animSkeleton->getAbsoluteDefaultPose(index); if (shapeInfo.points.size() > 0) { for (auto& point : shapeInfo.points) { totalExtents.addPoint((pose * point)); } } index = _animSkeleton->getParentIndex(index); } // compute bounding shape parameters // NOTE: we assume that the longest side of totalExtents is the yAxis... glm::vec3 diagonal = (transformPoint(_geometryToRigTransform, totalExtents.maximum) - transformPoint(_geometryToRigTransform, totalExtents.minimum)); // ... and assume the radiusOut is half the RMS of the X and Z sides: radiusOut = 0.5f * sqrtf(0.5f * (diagonal.x * diagonal.x + diagonal.z * diagonal.z)); heightOut = diagonal.y - 2.0f * radiusOut; glm::vec3 capsuleCenter = transformPoint(_geometryToRigTransform, (0.5f * (totalExtents.maximum + totalExtents.minimum))); localOffsetOut = capsuleCenter - hipsPosition; }