// // SkeletonModel.cpp // interface/src/avatar // // Created by Andrzej Kapolka on 10/17/13. // Copyright 2013 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include #include #include #include #include "Application.h" #include "Avatar.h" #include "Hand.h" #include "Menu.h" #include "SkeletonModel.h" #include "Util.h" #include "InterfaceLogging.h" #include "AnimDebugDraw.h" SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent, RigPointer rig) : Model(rig, parent), _owningAvatar(owningAvatar), _boundingCapsuleLocalOffset(0.0f), _boundingCapsuleRadius(0.0f), _boundingCapsuleHeight(0.0f), _defaultEyeModelPosition(glm::vec3(0.0f, 0.0f, 0.0f)), _headClipDistance(DEFAULT_NEAR_CLIP) { assert(_rig); assert(_owningAvatar); } SkeletonModel::~SkeletonModel() { } void SkeletonModel::initJointStates() { const FBXGeometry& geometry = _geometry->getFBXGeometry(); glm::mat4 modelOffset = glm::scale(_scale) * glm::translate(_offset); _rig->initJointStates(geometry, modelOffset); // Determine the default eye position for avatar scale = 1.0 int headJointIndex = _geometry->getFBXGeometry().headJointIndex; if (0 <= headJointIndex && headJointIndex < _rig->getJointStateCount()) { glm::vec3 leftEyePosition, rightEyePosition; getEyeModelPositions(leftEyePosition, rightEyePosition); glm::vec3 midEyePosition = (leftEyePosition + rightEyePosition) / 2.0f; int rootJointIndex = _geometry->getFBXGeometry().rootJointIndex; glm::vec3 rootModelPosition; getJointPosition(rootJointIndex, rootModelPosition); _defaultEyeModelPosition = midEyePosition - rootModelPosition; // Skeleton may have already been scaled so unscale it _defaultEyeModelPosition = _defaultEyeModelPosition / _scale; } computeBoundingShape(); Extents meshExtents = getMeshExtents(); _headClipDistance = -(meshExtents.minimum.z / _scale.z - _defaultEyeModelPosition.z); _headClipDistance = std::max(_headClipDistance, DEFAULT_NEAR_CLIP); _owningAvatar->rebuildCollisionShape(); emit skeletonLoaded(); } // Called within Model::simulate call, below. void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { Head* head = _owningAvatar->getHead(); if (_owningAvatar->isMyAvatar()) { MyAvatar* myAvatar = static_cast(_owningAvatar); const FBXGeometry& geometry = _geometry->getFBXGeometry(); Rig::HeadParameters headParams; headParams.enableLean = qApp->getAvatarUpdater()->isHMDMode(); headParams.leanSideways = head->getFinalLeanSideways(); headParams.leanForward = head->getFinalLeanForward(); headParams.torsoTwist = head->getTorsoTwist(); if (qApp->getAvatarUpdater()->isHMDMode()) { headParams.isInHMD = true; // get HMD position from sensor space into world space, and back into rig space glm::mat4 worldHMDMat = myAvatar->getSensorToWorldMatrix() * myAvatar->getHMDSensorMatrix(); glm::mat4 rigToWorld = createMatFromQuatAndPos(getRotation(), getTranslation()); glm::mat4 worldToRig = glm::inverse(rigToWorld); glm::mat4 rigHMDMat = worldToRig * worldHMDMat; headParams.rigHeadPosition = extractTranslation(rigHMDMat); headParams.rigHeadOrientation = extractRotation(rigHMDMat); headParams.worldHeadOrientation = extractRotation(worldHMDMat); } else { headParams.isInHMD = false; // We don't have a valid localHeadPosition. headParams.rigHeadOrientation = Quaternions::Y_180 * head->getFinalOrientationInLocalFrame(); headParams.worldHeadOrientation = head->getFinalOrientationInWorldFrame(); } headParams.leanJointIndex = geometry.leanJointIndex; headParams.neckJointIndex = geometry.neckJointIndex; headParams.isTalking = head->getTimeWithoutTalking() <= 1.5f; _rig->updateFromHeadParameters(headParams, deltaTime); Rig::HandParameters handParams; auto leftPalm = myAvatar->getHand()->getCopyOfPalmData(HandData::LeftHand); if (leftPalm.isValid() && leftPalm.isActive()) { handParams.isLeftEnabled = true; handParams.leftPosition = Quaternions::Y_180 * leftPalm.getRawPosition(); handParams.leftOrientation = Quaternions::Y_180 * leftPalm.getRawRotation(); handParams.leftTrigger = leftPalm.getTrigger(); } else { handParams.isLeftEnabled = false; } auto rightPalm = myAvatar->getHand()->getCopyOfPalmData(HandData::RightHand); if (rightPalm.isValid() && rightPalm.isActive()) { handParams.isRightEnabled = true; handParams.rightPosition = Quaternions::Y_180 * rightPalm.getRawPosition(); handParams.rightOrientation = Quaternions::Y_180 * rightPalm.getRawRotation(); handParams.rightTrigger = rightPalm.getTrigger(); } else { handParams.isRightEnabled = false; } _rig->updateFromHandParameters(handParams, deltaTime); _rig->computeMotionAnimationState(deltaTime, _owningAvatar->getPosition(), _owningAvatar->getVelocity(), _owningAvatar->getOrientation()); // evaluate AnimGraph animation and update jointStates. Model::updateRig(deltaTime, parentTransform); Rig::EyeParameters eyeParams; eyeParams.worldHeadOrientation = headParams.worldHeadOrientation; eyeParams.eyeLookAt = head->getLookAtPosition(); eyeParams.eyeSaccade = head->getSaccade(); eyeParams.modelRotation = getRotation(); eyeParams.modelTranslation = getTranslation(); eyeParams.leftEyeJointIndex = geometry.leftEyeJointIndex; eyeParams.rightEyeJointIndex = geometry.rightEyeJointIndex; _rig->updateFromEyeParameters(eyeParams); } else { Model::updateRig(deltaTime, parentTransform); // This is a little more work than we really want. // // Other avatars joint, including their eyes, should already be set just like any other joints // from the wire data. But when looking at me, we want the eyes to use the corrected lookAt. // // Thus this should really only be ... else if (_owningAvatar->getHead()->isLookingAtMe()) {... // However, in the !isLookingAtMe case, the eyes aren't rotating the way they should right now. // We will revisit that as priorities allow, and particularly after the new rig/animation/joints. const FBXGeometry& geometry = _geometry->getFBXGeometry(); // If the head is not positioned, updateEyeJoints won't get the math right glm::quat headOrientation; _rig->getJointRotation(geometry.headJointIndex, headOrientation); glm::vec3 eulers = safeEulerAngles(headOrientation); head->setBasePitch(glm::degrees(-eulers.x)); head->setBaseYaw(glm::degrees(eulers.y)); head->setBaseRoll(glm::degrees(-eulers.z)); Rig::EyeParameters eyeParams; eyeParams.worldHeadOrientation = head->getFinalOrientationInWorldFrame(); eyeParams.eyeLookAt = head->getCorrectedLookAtPosition(); eyeParams.eyeSaccade = glm::vec3(); eyeParams.modelRotation = getRotation(); eyeParams.modelTranslation = getTranslation(); eyeParams.leftEyeJointIndex = geometry.leftEyeJointIndex; eyeParams.rightEyeJointIndex = geometry.rightEyeJointIndex; _rig->updateFromEyeParameters(eyeParams); } } void SkeletonModel::updateAttitude() { setTranslation(_owningAvatar->getSkeletonPosition()); setRotation(_owningAvatar->getOrientation() * Quaternions::Y_180); setScale(glm::vec3(1.0f, 1.0f, 1.0f) * _owningAvatar->getScale()); } // Called by Avatar::simulate after it has set the joint states (fullUpdate true if changed), // but just before head has been simulated. void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { updateAttitude(); setBlendshapeCoefficients(_owningAvatar->getHead()->getBlendshapeCoefficients()); Model::simulate(deltaTime, fullUpdate); // let rig compute the model offset glm::vec3 registrationPoint; if (_rig->getModelRegistrationPoint(registrationPoint)) { setOffset(registrationPoint); } if (!isActive() || !_owningAvatar->isMyAvatar()) { return; // only simulate for own avatar } auto player = DependencyManager::get(); if (player->isPlaying()) { return; } } class IndexValue { public: int index; float value; }; bool operator<(const IndexValue& firstIndex, const IndexValue& secondIndex) { return firstIndex.value < secondIndex.value; } void SkeletonModel::applyPalmData(int jointIndex, const PalmData& palm) { if (jointIndex == -1 || jointIndex >= _rig->getJointStateCount()) { return; } const FBXGeometry& geometry = _geometry->getFBXGeometry(); int parentJointIndex = geometry.joints.at(jointIndex).parentIndex; if (parentJointIndex == -1) { return; } } bool SkeletonModel::getLeftHandPosition(glm::vec3& position) const { return getJointPositionInWorldFrame(getLeftHandJointIndex(), position); } bool SkeletonModel::getRightHandPosition(glm::vec3& position) const { return getJointPositionInWorldFrame(getRightHandJointIndex(), position); } bool SkeletonModel::restoreLeftHandPosition(float fraction, float priority) { return restoreJointPosition(getLeftHandJointIndex(), fraction, priority); } bool SkeletonModel::getLeftShoulderPosition(glm::vec3& position) const { return getJointPositionInWorldFrame(getLastFreeJointIndex(getLeftHandJointIndex()), position); } float SkeletonModel::getLeftArmLength() const { return getLimbLength(getLeftHandJointIndex()); } bool SkeletonModel::restoreRightHandPosition(float fraction, float priority) { return restoreJointPosition(getRightHandJointIndex(), fraction, priority); } bool SkeletonModel::getRightShoulderPosition(glm::vec3& position) const { return getJointPositionInWorldFrame(getLastFreeJointIndex(getRightHandJointIndex()), position); } float SkeletonModel::getRightArmLength() const { return getLimbLength(getRightHandJointIndex()); } bool SkeletonModel::getHeadPosition(glm::vec3& headPosition) const { return isActive() && getJointPositionInWorldFrame(_geometry->getFBXGeometry().headJointIndex, headPosition); } bool SkeletonModel::getNeckPosition(glm::vec3& neckPosition) const { return isActive() && getJointPositionInWorldFrame(_geometry->getFBXGeometry().neckJointIndex, neckPosition); } bool SkeletonModel::getLocalNeckPosition(glm::vec3& neckPosition) const { return isActive() && getJointPosition(_geometry->getFBXGeometry().neckJointIndex, neckPosition); } bool SkeletonModel::getEyeModelPositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const { if (!isActive()) { return false; } const FBXGeometry& geometry = _geometry->getFBXGeometry(); if (getJointPosition(geometry.leftEyeJointIndex, firstEyePosition) && getJointPosition(geometry.rightEyeJointIndex, secondEyePosition)) { return true; } // no eye joints; try to estimate based on head/neck joints glm::vec3 neckPosition, headPosition; if (getJointPosition(geometry.neckJointIndex, neckPosition) && getJointPosition(geometry.headJointIndex, headPosition)) { const float EYE_PROPORTION = 0.6f; glm::vec3 baseEyePosition = glm::mix(neckPosition, headPosition, EYE_PROPORTION); glm::quat headRotation; getJointRotation(geometry.headJointIndex, headRotation); const float EYES_FORWARD = 0.25f; const float EYE_SEPARATION = 0.1f; float headHeight = glm::distance(neckPosition, headPosition); firstEyePosition = baseEyePosition + headRotation * glm::vec3(EYE_SEPARATION, 0.0f, EYES_FORWARD) * headHeight; secondEyePosition = baseEyePosition + headRotation * glm::vec3(-EYE_SEPARATION, 0.0f, EYES_FORWARD) * headHeight; return true; } return false; } bool SkeletonModel::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const { if (getEyeModelPositions(firstEyePosition, secondEyePosition)) { firstEyePosition = _translation + _rotation * firstEyePosition; secondEyePosition = _translation + _rotation * secondEyePosition; return true; } return false; } glm::vec3 SkeletonModel::getDefaultEyeModelPosition() const { return _owningAvatar->getScale() * _defaultEyeModelPosition; } float DENSITY_OF_WATER = 1000.0f; // kg/m^3 float MIN_JOINT_MASS = 1.0f; float VERY_BIG_MASS = 1.0e6f; // virtual void SkeletonModel::computeBoundingShape() { if (_geometry == NULL || _rig->jointStatesEmpty()) { return; } const FBXGeometry& geometry = _geometry->getFBXGeometry(); if (geometry.joints.isEmpty() || geometry.rootJointIndex == -1) { // rootJointIndex == -1 if the avatar model has no skeleton return; } float radius, height; glm::vec3 offset; _rig->computeAvatarBoundingCapsule(geometry, radius, height, offset); float invScale = 1.0f / _owningAvatar->getUniformScale(); _boundingCapsuleRadius = invScale * radius; _boundingCapsuleHeight = invScale * height; _boundingCapsuleLocalOffset = invScale * offset; } void SkeletonModel::renderBoundingCollisionShapes(gpu::Batch& batch, float scale, float alpha) { auto geometryCache = DependencyManager::get(); auto deferredLighting = DependencyManager::get(); // draw a blue sphere at the capsule top point glm::vec3 topPoint = _translation + getRotation() * (scale * (_boundingCapsuleLocalOffset + (0.5f * _boundingCapsuleHeight) * Vectors::UNIT_Y)); deferredLighting->renderSolidSphereInstance(batch, Transform().setTranslation(topPoint).postScale(scale * _boundingCapsuleRadius), glm::vec4(0.6f, 0.6f, 0.8f, alpha)); // draw a yellow sphere at the capsule bottom point glm::vec3 bottomPoint = topPoint - glm::vec3(0.0f, scale * _boundingCapsuleHeight, 0.0f); glm::vec3 axis = topPoint - bottomPoint; deferredLighting->renderSolidSphereInstance(batch, Transform().setTranslation(bottomPoint).postScale(scale * _boundingCapsuleRadius), glm::vec4(0.8f, 0.8f, 0.6f, alpha)); // draw a green cylinder between the two points glm::vec3 origin(0.0f); batch.setModelTransform(Transform().setTranslation(bottomPoint)); deferredLighting->bindSimpleProgram(batch); Avatar::renderJointConnectingCone(batch, origin, axis, scale * _boundingCapsuleRadius, scale * _boundingCapsuleRadius, glm::vec4(0.6f, 0.8f, 0.6f, alpha)); } bool SkeletonModel::hasSkeleton() { return isActive() ? _geometry->getFBXGeometry().rootJointIndex != -1 : false; } void SkeletonModel::onInvalidate() { }