diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index f38bb2fc8e..4e18a99259 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1876,19 +1876,6 @@ void Application::updateAvatars(float deltaTime, glm::vec3 mouseRayOrigin, glm:: void Application::update(float deltaTime) { bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::update()"); - - // Use Transmitter Hand to move hand if connected, else use mouse - if (_myTransmitter.isConnected()) { - const float HAND_FORCE_SCALING = 0.01f; - glm::vec3 estimatedRotation = _myTransmitter.getEstimatedRotation(); - glm::vec3 handForce(-estimatedRotation.z, -estimatedRotation.x, estimatedRotation.y); - _myAvatar.setMovedHandOffset(handForce * HAND_FORCE_SCALING); - } else { - // update behaviors for avatar hand movement: handControl takes mouse values as input, - // and gives back 3D values modulated for smooth transitioning between interaction modes. - _handControl.update(_mouseX, _mouseY); - _myAvatar.setMovedHandOffset(_handControl.getValues()); - } // tell my avatar if the mouse is being pressed... _myAvatar.setMousePressed(_mousePressed); @@ -1898,6 +1885,15 @@ void Application::update(float deltaTime) { _viewFrustum.computePickRay(_mouseX / (float)_glWidget->width(), _mouseY / (float)_glWidget->height(), mouseRayOrigin, mouseRayDirection); + // adjust for mirroring + if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { + glm::vec3 mouseRayOffset = mouseRayOrigin - _viewFrustum.getPosition(); + mouseRayOrigin -= 2.0f * (_viewFrustum.getDirection() * glm::dot(_viewFrustum.getDirection(), mouseRayOffset) + + _viewFrustum.getRight() * glm::dot(_viewFrustum.getRight(), mouseRayOffset)); + mouseRayDirection -= 2.0f * (_viewFrustum.getDirection() * glm::dot(_viewFrustum.getDirection(), mouseRayDirection) + + _viewFrustum.getRight() * glm::dot(_viewFrustum.getRight(), mouseRayDirection)); + } + // tell my avatar the posiion and direction of the ray projected ino the world based on the mouse position _myAvatar.setMouseRay(mouseRayOrigin, mouseRayDirection); diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index ed8ed2a103..1fc2157e09 100755 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -82,7 +82,6 @@ Avatar::Avatar(Node* owningNode) : _skeletonModel(this), _ballSpringsInitialized(false), _bodyYawDelta(0.0f), - _movedHandOffset(0.0f, 0.0f, 0.0f), _mode(AVATAR_MODE_STANDING), _velocity(0.0f, 0.0f, 0.0f), _thrust(0.0f, 0.0f, 0.0f), @@ -444,23 +443,7 @@ void Avatar::setMouseRay(const glm::vec3 &origin, const glm::vec3 &direction) { } void Avatar::updateHandMovementAndTouching(float deltaTime, bool enableHandMovement) { - - glm::quat orientation = getOrientation(); - // reset hand and arm positions according to hand movement - glm::vec3 right = orientation * IDENTITY_RIGHT; - glm::vec3 up = orientation * IDENTITY_UP; - glm::vec3 front = orientation * IDENTITY_FRONT; - - if (enableHandMovement) { - glm::vec3 transformedHandMovement = - right * _movedHandOffset.x * 2.0f + - up * -_movedHandOffset.y * 2.0f + - front * -_movedHandOffset.y * 2.0f; - - _skeleton.joint[AVATAR_JOINT_RIGHT_FINGERTIPS].position += transformedHandMovement; - } - enableHandMovement |= updateLeapHandPositions(); //constrain right arm length and re-adjust elbow position as it bends diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index f1b25ea640..7eddb77a14 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -205,7 +205,6 @@ protected: SkeletonModel _skeletonModel; bool _ballSpringsInitialized; float _bodyYawDelta; - glm::vec3 _movedHandOffset; AvatarBall _bodyBall[ NUM_AVATAR_BODY_BALLS ]; AvatarMode _mode; glm::vec3 _velocity; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index b9312de49a..32e244da29 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -828,17 +828,19 @@ void MyAvatar::updateHandMovementAndTouching(float deltaTime, bool enableHandMov glm::quat orientation = getOrientation(); // reset hand and arm positions according to hand movement - glm::vec3 right = orientation * IDENTITY_RIGHT; glm::vec3 up = orientation * IDENTITY_UP; - glm::vec3 front = orientation * IDENTITY_FRONT; if (enableHandMovement) { - glm::vec3 transformedHandMovement = - right * _movedHandOffset.x * 2.0f + - up * -_movedHandOffset.y * 2.0f + - front * -_movedHandOffset.y * 2.0f; - - _skeleton.joint[ AVATAR_JOINT_RIGHT_FINGERTIPS ].position += transformedHandMovement; + // confine to the approximate shoulder plane + glm::vec3 pointDirection = _mouseRayDirection; + if (glm::dot(_mouseRayDirection, up) > 0.0f) { + glm::vec3 projectedVector = glm::cross(up, glm::cross(_mouseRayDirection, up)); + if (glm::length(projectedVector) > EPSILON) { + pointDirection = glm::normalize(projectedVector); + } + } + const float FAR_AWAY_POINT = TREE_SCALE; + _skeleton.joint[AVATAR_JOINT_RIGHT_FINGERTIPS].position = _mouseRayOrigin + pointDirection * FAR_AWAY_POINT; } _avatarTouch.setMyBodyPosition(_position); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 5e25a17fb8..790510536f 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -25,7 +25,6 @@ public: // setters void setMousePressed(bool mousePressed) { _mousePressed = mousePressed; } - void setMovedHandOffset(glm::vec3 movedHandOffset) { _movedHandOffset = movedHandOffset; } void setThrust(glm::vec3 newThrust) { _thrust = newThrust; } void setVelocity(const glm::vec3 velocity) { _velocity = velocity; } void setLeanScale(float scale) { _leanScale = scale; } diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index c76100b316..c6381eea08 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -26,6 +26,8 @@ void SkeletonModel::simulate(float deltaTime) { setScale(glm::vec3(1.0f, 1.0f, 1.0f) * _owningAvatar->getScale() * MODEL_SCALE); Model::simulate(deltaTime); + + setRightHandPosition(_owningAvatar->getHandPosition()); } bool SkeletonModel::render(float alpha) { diff --git a/interface/src/renderer/FBXReader.cpp b/interface/src/renderer/FBXReader.cpp index ab308faea9..9fae95bf53 100644 --- a/interface/src/renderer/FBXReader.cpp +++ b/interface/src/renderer/FBXReader.cpp @@ -326,11 +326,11 @@ QVariantHash parseMapping(QIODevice* device) { } QByteArray name = sections.at(0).trimmed(); if (sections.size() == 2) { - properties.insert(name, sections.at(1).trimmed()); + properties.insertMulti(name, sections.at(1).trimmed()); } else if (sections.size() == 3) { QVariantHash heading = properties.value(name).toHash(); - heading.insert(sections.at(1).trimmed(), sections.at(2).trimmed()); + heading.insertMulti(sections.at(1).trimmed(), sections.at(2).trimmed()); properties.insert(name, heading); } else if (sections.size() >= 4) { @@ -696,12 +696,16 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) QString jointRootName = processID(joints.value("jointRoot", "jointRoot").toString()); QString jointLeanName = processID(joints.value("jointLean", "jointLean").toString()); QString jointHeadName = processID(joints.value("jointHead", "jointHead").toString()); + QString jointLeftHandName = processID(joints.value("jointLeftHand", "jointLeftHand").toString()); + QString jointRightHandName = processID(joints.value("jointRightHand", "jointRightHand").toString()); QString jointEyeLeftID; QString jointEyeRightID; QString jointNeckID; QString jointRootID; QString jointLeanID; QString jointHeadID; + QString jointLeftHandID; + QString jointRightHandID; QVariantHash blendshapeMappings = mapping.value("bs").toHash(); QHash > blendshapeIndices; @@ -775,6 +779,12 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) } else if (name == jointHeadName) { jointHeadID = getID(object.properties); + + } else if (name == jointLeftHandName) { + jointLeftHandID = getID(object.properties); + + } else if (name == jointRightHandName) { + jointRightHandID = getID(object.properties); } glm::vec3 translation; glm::vec3 rotationOffset; @@ -978,10 +988,24 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) } // convert the models to joints + QVariantList freeJoints = mapping.values("freeJoint"); foreach (const QString& modelID, modelIDs) { const FBXModel& model = models[modelID]; FBXJoint joint; + joint.isFree = freeJoints.contains(model.name); joint.parentIndex = model.parentIndex; + + // get the indices of all ancestors starting with the first free one (if any) + joint.freeLineage.append(geometry.joints.size()); + int lastFreeIndex = joint.isFree ? 0 : -1; + for (int index = joint.parentIndex; index != -1; index = geometry.joints.at(index).parentIndex) { + if (geometry.joints.at(index).isFree) { + lastFreeIndex = joint.freeLineage.size(); + } + joint.freeLineage.append(index); + } + joint.freeLineage.remove(lastFreeIndex + 1, joint.freeLineage.size() - lastFreeIndex - 1); + joint.preTransform = model.preTransform; joint.preRotation = model.preRotation; joint.rotation = model.rotation; @@ -1009,6 +1033,8 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) geometry.rootJointIndex = modelIDs.indexOf(jointRootID); geometry.leanJointIndex = modelIDs.indexOf(jointLeanID); geometry.headJointIndex = modelIDs.indexOf(jointHeadID); + geometry.leftHandJointIndex = modelIDs.indexOf(jointLeftHandID); + geometry.rightHandJointIndex = modelIDs.indexOf(jointRightHandID); // extract the translation component of the neck transform if (geometry.neckJointIndex != -1) { diff --git a/interface/src/renderer/FBXReader.h b/interface/src/renderer/FBXReader.h index 557574316e..c48da019ae 100644 --- a/interface/src/renderer/FBXReader.h +++ b/interface/src/renderer/FBXReader.h @@ -43,6 +43,8 @@ public: class FBXJoint { public: + bool isFree; + QVector freeLineage; int parentIndex; glm::mat4 preTransform; glm::quat preRotation; @@ -128,6 +130,8 @@ public: int rootJointIndex; int leanJointIndex; int headJointIndex; + int leftHandJointIndex; + int rightHandJointIndex; glm::vec3 neckPivot; diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 5bcc0520b9..7ba8d24104 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -413,6 +413,22 @@ bool Model::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePos getJointPosition(geometry.rightEyeJointIndex, secondEyePosition); } +bool Model::setLeftHandPosition(const glm::vec3& position) { + return isActive() && setJointPosition(_geometry->getFBXGeometry().leftHandJointIndex, position); +} + +bool Model::setLeftHandRotation(const glm::quat& rotation) { + return isActive() && setJointRotation(_geometry->getFBXGeometry().leftHandJointIndex, rotation); +} + +bool Model::setRightHandPosition(const glm::vec3& position) { + return isActive() && setJointPosition(_geometry->getFBXGeometry().rightHandJointIndex, position); +} + +bool Model::setRightHandRotation(const glm::quat& rotation) { + return isActive() && setJointRotation(_geometry->getFBXGeometry().rightHandJointIndex, rotation); +} + void Model::setURL(const QUrl& url) { // don't recreate the geometry if it's the same URL if (_url == url) { @@ -492,6 +508,55 @@ bool Model::getJointRotation(int jointIndex, glm::quat& rotation) const { return true; } +bool Model::setJointPosition(int jointIndex, const glm::vec3& position) { + if (jointIndex == -1 || _jointStates.isEmpty()) { + return false; + } + const FBXGeometry& geometry = _geometry->getFBXGeometry(); + const QVector& freeLineage = geometry.joints.at(jointIndex).freeLineage; + + // this is a cyclic coordinate descent algorithm: see + // http://www.ryanjuckett.com/programming/animation/21-cyclic-coordinate-descent-in-2d + const int ITERATION_COUNT = 1; + for (int i = 0; i < ITERATION_COUNT; i++) { + // first, we go from the joint upwards, rotating the end as close as possible to the target + glm::vec3 endPosition = extractTranslation(_jointStates[jointIndex].transform); + for (int j = 1; j < freeLineage.size(); j++) { + int index = freeLineage.at(j); + if (glm::distance(endPosition, position) < EPSILON) { + return true; // close enough to target position + } + const FBXJoint& joint = geometry.joints.at(index); + if (!joint.isFree) { + continue; + } + JointState& state = _jointStates[index]; + glm::vec3 jointPosition = extractTranslation(state.transform); + glm::vec3 jointVector = endPosition - jointPosition; + glm::quat deltaRotation = rotationBetween(jointVector, position - jointPosition); + state.rotation = state.rotation * glm::inverse(state.combinedRotation) * deltaRotation * state.combinedRotation; + endPosition = deltaRotation * jointVector + jointPosition; + } + + // then, from the first free joint downwards, update the transforms again + for (int j = freeLineage.size() - 1; j >= 0; j--) { + updateJointState(freeLineage.at(j)); + } + } + + return true; +} + +bool Model::setJointRotation(int jointIndex, const glm::quat& rotation) { + if (jointIndex == -1 || _jointStates.isEmpty()) { + return false; + } + JointState& state = _jointStates[jointIndex]; + state.rotation = state.rotation * glm::inverse(state.combinedRotation) * rotation * + glm::inverse(_geometry->getFBXGeometry().joints.at(jointIndex).inverseBindRotation); + return true; +} + void Model::deleteGeometry() { foreach (Model* attachment, _attachments) { delete attachment; diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index be1e49a8be..4b54fcaaf4 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -70,6 +70,22 @@ public: /// \return whether or not both eye meshes were found bool getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const; + /// Sets the position of the left hand using inverse kinematics. + /// \return whether or not the left hand joint was found + bool setLeftHandPosition(const glm::vec3& position); + + /// Sets the rotation of the left hand. + /// \return whether or not the left hand joint was found + bool setLeftHandRotation(const glm::quat& rotation); + + /// Sets the position of the right hand using inverse kinematics. + /// \return whether or not the right hand joint was found + bool setRightHandPosition(const glm::vec3& position); + + /// Sets the rotation of the right hand. + /// \return whether or not the right hand joint was found + bool setRightHandRotation(const glm::quat& rotation); + /// Returns the average color of all meshes in the geometry. glm::vec4 computeAverageColor() const; @@ -101,6 +117,9 @@ protected: bool getJointPosition(int jointIndex, glm::vec3& position) const; bool getJointRotation(int jointIndex, glm::quat& rotation) const; + bool setJointPosition(int jointIndex, const glm::vec3& position); + bool setJointRotation(int jointIndex, const glm::quat& rotation); + private: void deleteGeometry();