From 7f4e0c206cd0cce1140863168c8bb48340b89529 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 19 Nov 2014 09:13:03 -0800 Subject: [PATCH 1/3] cache indeces to toe joints --- libraries/fbx/src/FBXReader.cpp | 14 ++++++++++++-- libraries/fbx/src/FBXReader.h | 2 ++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 4ffd3f6286..e0f175d60d 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -1067,6 +1067,8 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) QString jointHeadID; QString jointLeftHandID; QString jointRightHandID; + QString jointLeftToeID; + QString jointRightToeID; QVector humanIKJointNames; for (int i = 0;; i++) { @@ -1166,11 +1168,17 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) } else if (name == jointHeadName) { jointHeadID = getID(object.properties); - } else if (name == jointLeftHandName) { + } else if (name == jointLeftHandName || name == "LeftHand" || name == "joint_L_hand") { jointLeftHandID = getID(object.properties); - } else if (name == jointRightHandName) { + } else if (name == jointRightHandName || name == "RightHand" || name == "joint_R_hand") { jointRightHandID = getID(object.properties); + + } else if (name == "LeftToe" || name == "joint_L_toe" || name == "LeftToe_End") { + jointLeftToeID = getID(object.properties); + + } else if (name == "RightToe" || name == "joint_R_toe" || name == "RightToe_End") { + jointRightToeID = getID(object.properties); } int humanIKJointIndex = humanIKJointNames.indexOf(name); if (humanIKJointIndex != -1) { @@ -1595,6 +1603,8 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) geometry.headJointIndex = modelIDs.indexOf(jointHeadID); geometry.leftHandJointIndex = modelIDs.indexOf(jointLeftHandID); geometry.rightHandJointIndex = modelIDs.indexOf(jointRightHandID); + geometry.leftToeJointIndex = modelIDs.indexOf(jointLeftToeID); + geometry.rightToeJointIndex = modelIDs.indexOf(jointRightToeID); foreach (const QString& id, humanIKJointIDs) { geometry.humanIKJointIndices.append(modelIDs.indexOf(id)); diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index 3c9e918686..4a585c2476 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -201,6 +201,8 @@ public: int headJointIndex; int leftHandJointIndex; int rightHandJointIndex; + int leftToeJointIndex; + int rightToeJointIndex; QVector humanIKJointIndices; From 6673d9d0c22bca16f792c5781f1d457094893c1a Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 19 Nov 2014 09:13:24 -0800 Subject: [PATCH 2/3] avatar stands on lowest toe --- interface/src/avatar/Avatar.cpp | 5 +- interface/src/avatar/MyAvatar.cpp | 15 +++++- interface/src/avatar/MyAvatar.h | 1 + interface/src/avatar/SkeletonModel.cpp | 67 +++++++++++++++++++++++++- interface/src/avatar/SkeletonModel.h | 7 +++ 5 files changed, 91 insertions(+), 4 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index e29f68e585..7c04bab633 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -775,7 +775,10 @@ void Avatar::setSkeletonOffset(const glm::vec3& offset) { } glm::vec3 Avatar::getSkeletonPosition() const { - return _position + _skeletonOffset; + // The avatar is rotated PI about the yAxis, so we have to correct for it + // to get the skeleton offset contribution in the world-frame. + const glm::quat FLIP = glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f)); + return _position + getOrientation() * FLIP * _skeletonOffset; } QVector Avatar::getJointRotations() const { diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 292cc2fb71..af26ac95af 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -89,6 +89,7 @@ MyAvatar::MyAvatar() : _billboardValid(false), _physicsSimulation(), _voxelShapeManager(), + _feetTouchFloor(true), _isLookingAtLeftEye(true) { ShapeCollider::initDispatchTable(); @@ -115,7 +116,7 @@ QByteArray MyAvatar::toByteArray() { if (mode == CAMERA_MODE_THIRD_PERSON || mode == CAMERA_MODE_INDEPENDENT) { // fake the avatar position that is sent up to the AvatarMixer glm::vec3 oldPosition = _position; - _position += _skeletonOffset; + _position = getSkeletonPosition(); QByteArray array = AvatarData::toByteArray(); // copy the correct position back _position = oldPosition; @@ -155,6 +156,9 @@ void MyAvatar::update(float deltaTime) { } simulate(deltaTime); + if (_feetTouchFloor) { + _skeletonModel.updateStandingFoot(); + } } void MyAvatar::simulate(float deltaTime) { @@ -1034,7 +1038,14 @@ void MyAvatar::setAttachmentData(const QVector& attachmentData) glm::vec3 MyAvatar::getSkeletonPosition() const { CameraMode mode = Application::getInstance()->getCamera()->getMode(); if (mode == CAMERA_MODE_THIRD_PERSON || mode == CAMERA_MODE_INDEPENDENT) { - return Avatar::getSkeletonPosition(); + // The avatar is rotated PI about the yAxis, so we have to correct for it + // to get the skeleton offset contribution in the world-frame. + const glm::quat FLIP = glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f)); + glm::vec3 skeletonOffset = _skeletonOffset; + if (_feetTouchFloor) { + skeletonOffset += _skeletonModel.getStandingOffset(); + } + return _position + getOrientation() * FLIP * skeletonOffset; } return Avatar::getPosition(); } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 01bbb556a1..0060c959e4 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -235,6 +235,7 @@ private: PhysicsSimulation _physicsSimulation; VoxelShapeManager _voxelShapeManager; + bool _feetTouchFloor; bool _isLookingAtLeftEye; RecorderPointer _recorder; diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 7ca105f483..834f1c2c08 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -22,13 +22,22 @@ #include "SkeletonModel.h" #include "SkeletonRagdoll.h" +enum StandingFootState { + LEFT_FOOT, + RIGHT_FOOT, + NO_FOOT +}; + SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent) : Model(parent), _owningAvatar(owningAvatar), _boundingShape(), _boundingShapeLocalOffset(0.0f), _ragdoll(NULL), - _defaultEyeModelPosition(glm::vec3(0.f, 0.f, 0.f)) { + _defaultEyeModelPosition(glm::vec3(0.f, 0.f, 0.f)), + _standingFoot(NO_FOOT), + _standingOffset(0.0f), + _clampedFootPosition(0.0f) { } SkeletonModel::~SkeletonModel() { @@ -607,6 +616,62 @@ void SkeletonModel::updateVisibleJointStates() { } } +/// \return offset of hips after foot animation +void SkeletonModel::updateStandingFoot() { + glm::vec3 offset(0.0f); + int leftFootIndex = _geometry->getFBXGeometry().leftToeJointIndex; + int rightFootIndex = _geometry->getFBXGeometry().rightToeJointIndex; + + if (leftFootIndex != -1 && rightFootIndex != -1) { + glm::vec3 leftPosition, rightPosition; + getJointPosition(leftFootIndex, leftPosition); + getJointPosition(rightFootIndex, rightPosition); + + int lowestFoot = (leftPosition.y < rightPosition.y) ? LEFT_FOOT : RIGHT_FOOT; + const float MIN_STEP_HEIGHT_THRESHOLD = 0.05f; + bool oneFoot = fabsf(leftPosition.y - rightPosition.y) > MIN_STEP_HEIGHT_THRESHOLD; + int currentFoot = oneFoot ? lowestFoot : _standingFoot; + + if (_standingFoot == NO_FOOT) { + currentFoot = lowestFoot; + } + if (currentFoot != _standingFoot) { + if (_standingFoot == NO_FOOT) { + // pick the lowest foot + glm::vec3 lowestPosition = (currentFoot == LEFT_FOOT) ? leftPosition : rightPosition; + // we ignore zero length positions which can happen for a few frames until skeleton is fully loaded + if (glm::length(lowestPosition) > 0.0f) { + _standingFoot = currentFoot; + _clampedFootPosition = lowestPosition; + } + } else { + // swap feet + _standingFoot = currentFoot; + glm::vec3 nextPosition = leftPosition; + glm::vec3 prevPosition = rightPosition; + if (_standingFoot == RIGHT_FOOT) { + nextPosition = rightPosition; + prevPosition = leftPosition; + } + glm::vec3 oldOffset = _clampedFootPosition - prevPosition; + _clampedFootPosition = oldOffset + nextPosition; + offset = _clampedFootPosition - nextPosition; + } + } else { + glm::vec3 nextPosition = (_standingFoot == LEFT_FOOT) ? leftPosition : rightPosition; + offset = _clampedFootPosition - nextPosition; + } + + // clamp the offset to not exceed some max distance + const float MAX_STEP_OFFSET = 1.0f; + float stepDistance = glm::length(offset); + if (stepDistance > MAX_STEP_OFFSET) { + offset *= (MAX_STEP_OFFSET / stepDistance); + } + } + _standingOffset = offset; +} + SkeletonRagdoll* SkeletonModel::buildRagdoll() { if (!_ragdoll) { _ragdoll = new SkeletonRagdoll(this); diff --git a/interface/src/avatar/SkeletonModel.h b/interface/src/avatar/SkeletonModel.h index 81e3fcb3b6..ea732acfd5 100644 --- a/interface/src/avatar/SkeletonModel.h +++ b/interface/src/avatar/SkeletonModel.h @@ -101,6 +101,10 @@ public: /// \return whether or not the head was found. glm::vec3 getDefaultEyeModelPosition() const; + /// skeleton offset caused by moving feet + void updateStandingFoot(); + const glm::vec3& getStandingOffset() const { return _standingOffset; } + virtual void updateVisibleJointStates(); SkeletonRagdoll* buildRagdoll(); @@ -154,6 +158,9 @@ private: SkeletonRagdoll* _ragdoll; glm::vec3 _defaultEyeModelPosition; + int _standingFoot; + glm::vec3 _standingOffset; + glm::vec3 _clampedFootPosition; }; #endif // hifi_SkeletonModel_h From 82bf0f62104681327163a1aa9a220acf877aeb49 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 19 Nov 2014 09:38:10 -0800 Subject: [PATCH 3/3] menu item for "idle animations shift hips" hack --- interface/src/Menu.cpp | 2 ++ interface/src/Menu.h | 1 + interface/src/avatar/MyAvatar.cpp | 1 + 3 files changed, 4 insertions(+) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index f7aabc8f06..1eacfa8363 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -297,6 +297,8 @@ Menu::Menu() : avatar, SLOT(updateMotionBehavior())); addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::StandOnNearbyFloors, 0, true, avatar, SLOT(updateMotionBehavior())); + addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::ShiftHipsForIdleAnimations, 0, false, + avatar, SLOT(updateMotionBehavior())); QMenu* collisionsMenu = avatarMenu->addMenu("Collide With..."); addCheckableActionToQMenuAndActionHash(collisionsMenu, MenuOption::CollideAsRagdoll, 0, false, diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 8590d8580e..7ed8811b4f 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -484,6 +484,7 @@ namespace MenuOption { const QString SixenseMouseInput = "Enable Sixense Mouse Input"; const QString SixenseLasers = "Enable Sixense UI Lasers"; const QString StandOnNearbyFloors = "Stand on nearby floors"; + const QString ShiftHipsForIdleAnimations = "Shift hips for idle animations"; const QString Stars = "Stars"; const QString Stats = "Stats"; const QString StereoAudio = "Stereo Audio"; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index af26ac95af..8b5fefc3be 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1950,6 +1950,7 @@ void MyAvatar::updateMotionBehavior() { } else { _motionBehaviors &= ~AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED; } + _feetTouchFloor = menu->isOptionChecked(MenuOption::ShiftHipsForIdleAnimations); } void MyAvatar::onToggleRagdoll() {