From 50dd8eba4586c9494efdd5bfa31ba1793daa246a Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 12 Sep 2015 08:23:50 -0700 Subject: [PATCH] Relay joint translations across network. Apply animation's root-joint translation to avatar. --- .../src/avatars/ScriptableAvatar.cpp | 14 +- interface/src/avatar/Avatar.cpp | 17 +- interface/src/avatar/Avatar.h | 1 + interface/src/avatar/MyAvatar.cpp | 37 ++- interface/src/avatar/MyAvatar.h | 5 +- interface/src/avatar/SkeletonModel.cpp | 1 + libraries/animation/src/AnimClip.cpp | 8 +- .../animation/src/AnimInverseKinematics.cpp | 3 + libraries/animation/src/AnimManipulator.cpp | 2 + libraries/animation/src/AnimationHandle.cpp | 11 + libraries/animation/src/AvatarRig.cpp | 27 +- libraries/animation/src/AvatarRig.h | 4 + libraries/animation/src/EntityRig.cpp | 14 + libraries/animation/src/EntityRig.h | 1 + libraries/animation/src/JointState.cpp | 32 ++- libraries/animation/src/JointState.h | 14 +- libraries/animation/src/Rig.cpp | 37 ++- libraries/animation/src/Rig.h | 12 +- libraries/avatars/src/AvatarData.cpp | 264 ++++++++++++++++-- libraries/avatars/src/AvatarData.h | 15 +- libraries/avatars/src/Player.cpp | 11 +- libraries/avatars/src/Recording.cpp | 14 +- libraries/avatars/src/Recording.h | 5 +- .../src/RenderableModelEntityItem.cpp | 9 +- libraries/entities/src/ModelEntityItem.cpp | 33 ++- libraries/entities/src/ModelEntityItem.h | 5 +- libraries/fbx/src/FBXReader.cpp | 39 ++- libraries/fbx/src/FBXReader.h | 2 +- .../networking/src/udt/PacketHeaders.cpp | 3 + libraries/render-utils/src/Model.cpp | 16 +- libraries/render-utils/src/Model.h | 5 +- libraries/shared/src/GLMHelpers.cpp | 2 + libraries/shared/src/GLMHelpers.h | 6 + 33 files changed, 585 insertions(+), 84 deletions(-) diff --git a/assignment-client/src/avatars/ScriptableAvatar.cpp b/assignment-client/src/avatars/ScriptableAvatar.cpp index 0b7af01c94..36a527009e 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.cpp +++ b/assignment-client/src/avatars/ScriptableAvatar.cpp @@ -77,7 +77,19 @@ void ScriptableAvatar::update(float deltatime) { int mapping = animationJoints.indexOf(modelJoints[i]); if (mapping != -1 && !_maskedJoints.contains(modelJoints[i])) { JointData& data = _jointData[i]; - data.rotation = safeMix(floorFrame.rotations.at(i), ceilFrame.rotations.at(i), frameFraction); + + auto newRotation = safeMix(floorFrame.rotations.at(i), ceilFrame.rotations.at(i), frameFraction); + auto newTranslation = floorFrame.translations.at(i) * (1.0f - frameFraction) + + ceilFrame.translations.at(i) * frameFraction; + + if (data.rotation != newRotation) { + data.rotation = newRotation; + data.rotationSet = true; + } + if (data.translation != newTranslation) { + data.translation = newTranslation; + data.translationSet = true; + } } } } else { diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 0d1c3b8a20..faf4bffeba 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -202,11 +202,14 @@ void Avatar::simulate(float deltaTime) { PerformanceTimer perfTimer("skeleton"); for (int i = 0; i < _jointData.size(); i++) { const JointData& data = _jointData.at(i); - _skeletonModel.setJointState(i, true, data.rotation); + _skeletonModel.setJointRotation(i, data.rotationSet, data.rotation, 1.0f); + _skeletonModel.setJointTranslation(i, data.translationSet, data.translation, 1.0f); } - _skeletonModel.simulate(deltaTime, _hasNewJointRotations); + + _skeletonModel.simulate(deltaTime, _hasNewJointRotations || _hasNewJointTranslations); simulateAttachments(deltaTime); _hasNewJointRotations = false; + _hasNewJointTranslations = false; } { PerformanceTimer perfTimer("head"); @@ -879,6 +882,16 @@ glm::quat Avatar::getJointRotation(int index) const { return rotation; } +glm::vec3 Avatar::getJointTranslation(int index) const { + if (QThread::currentThread() != thread()) { + return AvatarData::getJointTranslation(index); + } + glm::vec3 translation; + _skeletonModel.getJointTranslation(index, translation); + return translation; +} + + int Avatar::getJointIndex(const QString& name) const { if (QThread::currentThread() != thread()) { int result; diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 6cfbf939a2..5ff3f37ef5 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -115,6 +115,7 @@ public: virtual QVector getJointRotations() const; virtual glm::quat getJointRotation(int index) const; + virtual glm::vec3 getJointTranslation(int index) const; virtual int getJointIndex(const QString& name) const; virtual QStringList getJointNames() const; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 4cf1b69ce4..e6013638c8 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -239,9 +239,11 @@ void MyAvatar::simulate(float deltaTime) { PerformanceTimer perfTimer("joints"); // copy out the skeleton joints from the model _jointData.resize(_rig->getJointStateCount()); + for (int i = 0; i < _jointData.size(); i++) { JointData& data = _jointData[i]; - _rig->getJointStateRotation(i, data.rotation); + data.rotationSet |= _rig->getJointStateRotation(i, data.rotation); + data.translationSet |= _rig->getJointStateTranslation(i, data.translation); } } @@ -1083,21 +1085,43 @@ void MyAvatar::setJointRotations(QVector jointRotations) { int numStates = glm::min(_skeletonModel.getJointStateCount(), jointRotations.size()); for (int i = 0; i < numStates; ++i) { // HACK: ATM only Recorder calls setJointRotations() so we hardcode its priority here - _skeletonModel.setJointState(i, true, jointRotations[i], RECORDER_PRIORITY); + _skeletonModel.setJointRotation(i, true, jointRotations[i], RECORDER_PRIORITY); } } -void MyAvatar::setJointData(int index, const glm::quat& rotation) { +void MyAvatar::setJointTranslations(QVector jointTranslations) { + int numStates = glm::min(_skeletonModel.getJointStateCount(), jointTranslations.size()); + for (int i = 0; i < numStates; ++i) { + // HACK: ATM only Recorder calls setJointTranslations() so we hardcode its priority here + _skeletonModel.setJointTranslation(i, true, jointTranslations[i], RECORDER_PRIORITY); + } +} + +void MyAvatar::setJointData(int index, const glm::quat& rotation, const glm::vec3& translation) { if (QThread::currentThread() == thread()) { // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority - _rig->setJointState(index, true, rotation, SCRIPT_PRIORITY); + _rig->setJointState(index, true, rotation, translation, SCRIPT_PRIORITY); + } +} + +void MyAvatar::setJointRotation(int index, const glm::quat& rotation) { + if (QThread::currentThread() == thread()) { + // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority + _rig->setJointRotation(index, true, rotation, SCRIPT_PRIORITY); + } +} + +void MyAvatar::setJointTranslation(int index, const glm::vec3& translation) { + if (QThread::currentThread() == thread()) { + // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority + _rig->setJointTranslation(index, true, translation, SCRIPT_PRIORITY); } } void MyAvatar::clearJointData(int index) { if (QThread::currentThread() == thread()) { // HACK: ATM only JS scripts call clearJointData() on MyAvatar so we hardcode the priority - _rig->setJointState(index, false, glm::quat(), 0.0f); + _rig->setJointState(index, false, glm::quat(), glm::vec3(), 0.0f); _rig->clearJointAnimationPriority(index); } } @@ -1396,7 +1420,10 @@ void MyAvatar::preRender(RenderArgs* renderArgs) { AnimPose pose = _debugDrawSkeleton->getRelativeBindPose(i); glm::quat jointRot; _rig->getJointRotationInConstrainedFrame(i, jointRot); + glm::vec3 jointTrans; + _rig->getJointTranslation(i, jointTrans); pose.rot = pose.rot * jointRot; + pose.trans = jointTrans; poses.push_back(pose); } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index c1a6ada751..6ee42778c8 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -139,7 +139,10 @@ public: void clearLookAtTargetAvatar(); virtual void setJointRotations(QVector jointRotations); - virtual void setJointData(int index, const glm::quat& rotation); + virtual void setJointTranslations(QVector jointTranslations); + virtual void setJointData(int index, const glm::quat& rotation, const glm::vec3& translation); + virtual void setJointRotation(int index, const glm::quat& rotation); + virtual void setJointTranslation(int index, const glm::vec3& translation); virtual void clearJointData(int index); virtual void clearJointsData(); diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 703f3983ee..9cffe5956f 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -648,6 +648,7 @@ void SkeletonModel::computeBoundingShape() { // RECOVER FROM BOUNINDG SHAPE HACK: now that we're all done, restore the default pose for (int i = 0; i < numStates; i++) { _rig->restoreJointRotation(i, 1.0f, 1.0f); + _rig->restoreJointTranslation(i, 1.0f, 1.0f); } } diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index 23aa884933..f33c958fa8 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -147,8 +147,14 @@ void AnimClip::copyFromNetworkAnim() { for (int j = 0; j < animJointCount; j++) { int k = jointMap[j]; if (k >= 0 && k < skeletonJointCount) { - // currently FBX animations only have rotation. _anim[i][k].rot = _skeleton->getRelativeBindPose(k).rot * geom.animationFrames[i].rotations[j]; + + // TODO -- why does applying all the joint translations make a mutant? + if (animJoints[j].parentIndex == -1) { + _anim[i][k].trans = geom.animationFrames[i].translations[j] * extractScale(geom.offset); + } else { + _anim[i][k].trans = _skeleton->getRelativeBindPose(k).trans; + } } } } diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index de226092f1..037db8747c 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -322,9 +322,11 @@ const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVar std::map::iterator constraintItr = _constraints.begin(); while (constraintItr != _constraints.end()) { int index = constraintItr->first; + glm::quat rotation = _relativePoses[index].rot; constraintItr->second->apply(rotation); _relativePoses[index].rot = rotation; + ++constraintItr; } } else { @@ -354,6 +356,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars } else { _relativePoses[i].rot = underPoses[i].rot; } + _relativePoses[i].trans = underPoses[i].trans; } } return evaluate(animVars, dt, triggersOut); diff --git a/libraries/animation/src/AnimManipulator.cpp b/libraries/animation/src/AnimManipulator.cpp index 8b9089f450..6ab4017aa3 100644 --- a/libraries/animation/src/AnimManipulator.cpp +++ b/libraries/animation/src/AnimManipulator.cpp @@ -55,6 +55,7 @@ const AnimPoseVec& AnimManipulator::overlay(const AnimVariantMap& animVars, floa defaultRelPose = underPoses[jointVar.jointIndex]; defaultAbsPose = _skeleton->getAbsolutePose(jointVar.jointIndex, underPoses); defaultAbsPose.rot = animVars.lookup(jointVar.var, defaultAbsPose.rot); + defaultAbsPose.trans = animVars.lookup(jointVar.var, defaultAbsPose.trans); // because jointVar is absolute, we must use an absolute parent frame to convert into a relative pose. int parentIndex = _skeleton->getParentIndex(jointVar.jointIndex); @@ -68,6 +69,7 @@ const AnimPoseVec& AnimManipulator::overlay(const AnimVariantMap& animVars, floa defaultRelPose = AnimPose::identity; defaultAbsPose = _skeleton->getAbsoluteBindPose(jointVar.jointIndex); defaultAbsPose.rot = animVars.lookup(jointVar.var, defaultAbsPose.rot); + defaultAbsPose.trans = animVars.lookup(jointVar.var, defaultAbsPose.trans); // because jointVar is absolute, we must use an absolute parent frame to convert into a relative pose // here we use the bind pose diff --git a/libraries/animation/src/AnimationHandle.cpp b/libraries/animation/src/AnimationHandle.cpp index f2d12b398e..a6ac9c1f97 100644 --- a/libraries/animation/src/AnimationHandle.cpp +++ b/libraries/animation/src/AnimationHandle.cpp @@ -173,6 +173,7 @@ void AnimationHandle::applyFrame(float frameIndex) { const FBXAnimationFrame& floorFrame = animationGeometry.animationFrames.at((int)glm::floor(frameIndex) % frameCount); const FBXAnimationFrame& ceilFrame = animationGeometry.animationFrames.at((int)glm::ceil(frameIndex) % frameCount); float frameFraction = glm::fract(frameIndex); + for (int i = 0; i < _jointMappings.size(); i++) { int mapping = _jointMappings.at(i); if (mapping != -1) { // allow missing bones @@ -183,6 +184,15 @@ void AnimationHandle::applyFrame(float frameIndex) { _priority, false, _mix); + + // This isn't working. + // glm::vec3 floorTranslationPart = floorFrame.translations.at(i) * (1.0f - frameFraction); + // glm::vec3 ceilTranslationPart = ceilFrame.translations.at(i) * frameFraction; + // glm::vec3 floorCeilFraction = floorTranslationPart + ceilTranslationPart; + // glm::vec3 defaultTrans = _rig->getJointDefaultTranslationInConstrainedFrame(i); + // glm::vec3 mixedTranslation = floorCeilFraction * (1.0f - _mix) + defaultTrans * _mix; + // _rig->setJointTranslation(mapping, true, mixedTranslation, _priority); + } } } @@ -203,6 +213,7 @@ void AnimationHandle::restoreJoints() { int mapping = _jointMappings.at(i); if (mapping != -1) { _rig->restoreJointRotation(mapping, 1.0f, _rig->getJointAnimatinoPriority(mapping)); + _rig->restoreJointTranslation(mapping, 1.0f, _rig->getJointAnimatinoPriority(mapping)); } } } diff --git a/libraries/animation/src/AvatarRig.cpp b/libraries/animation/src/AvatarRig.cpp index 0727d0956d..4dbf5207b1 100644 --- a/libraries/animation/src/AvatarRig.cpp +++ b/libraries/animation/src/AvatarRig.cpp @@ -20,9 +20,7 @@ void AvatarRig::updateJointState(int index, glm::mat4 rootTransform) { // compute model transforms if (index == _rootJointIndex) { - // we always zero-out the translation part of an avatar's root join-transform. state.computeTransform(rootTransform); - clearJointTransformTranslation(index); } else { // guard against out-of-bounds access to _jointStates int parentIndex = state.getParentIndex(); @@ -97,3 +95,28 @@ void AvatarRig::setHandPosition(int jointIndex, shoulderRotation, priority); setJointRotationInBindFrame(jointIndex, rotation, priority); } + +void AvatarRig::setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority) { + if (index != -1 && index < _jointStates.size()) { + JointState& state = _jointStates[index]; + if (valid) { + state.setTranslation(translation, priority); + } else { + state.restoreTranslation(1.0f, priority); + } + } +} + + +void AvatarRig::setJointState(int index, bool valid, const glm::quat& rotation, const glm::vec3& translation, float priority) { + if (index != -1 && index < _jointStates.size()) { + JointState& state = _jointStates[index]; + if (valid) { + state.setRotationInConstrainedFrame(rotation, priority); + state.setTranslation(translation, priority); + } else { + state.restoreRotation(1.0f, priority); + state.restoreTranslation(1.0f, priority); + } + } +} diff --git a/libraries/animation/src/AvatarRig.h b/libraries/animation/src/AvatarRig.h index f137e89939..f45c2951e9 100644 --- a/libraries/animation/src/AvatarRig.h +++ b/libraries/animation/src/AvatarRig.h @@ -24,6 +24,10 @@ class AvatarRig : public Rig { virtual void updateJointState(int index, glm::mat4 rootTransform); virtual void setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation, float scale, float priority); + virtual void setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority); + virtual void setJointState(int index, bool valid, const glm::quat& rotation, const glm::vec3& translation, float priority); }; + + #endif // hifi_AvatarRig_h diff --git a/libraries/animation/src/EntityRig.cpp b/libraries/animation/src/EntityRig.cpp index e65407116b..8748214734 100644 --- a/libraries/animation/src/EntityRig.cpp +++ b/libraries/animation/src/EntityRig.cpp @@ -27,3 +27,17 @@ void EntityRig::updateJointState(int index, glm::mat4 rootTransform) { } } } + + +void EntityRig::setJointState(int index, bool valid, const glm::quat& rotation, const glm::vec3& translation, float priority) { + if (index != -1 && index < _jointStates.size()) { + JointState& state = _jointStates[index]; + if (valid) { + state.setRotationInConstrainedFrame(rotation, priority); + // state.setTranslation(translation, priority); + } else { + state.restoreRotation(1.0f, priority); + // state.restoreTranslation(1.0f, priority); + } + } +} diff --git a/libraries/animation/src/EntityRig.h b/libraries/animation/src/EntityRig.h index b36ffb9874..17486bad78 100644 --- a/libraries/animation/src/EntityRig.h +++ b/libraries/animation/src/EntityRig.h @@ -24,6 +24,7 @@ class EntityRig : public Rig { virtual void updateJointState(int index, glm::mat4 rootTransform); virtual void setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation, float scale, float priority) {} + virtual void setJointState(int index, bool valid, const glm::quat& rotation, const glm::vec3& translation, float priority); }; #endif // hifi_EntityRig_h diff --git a/libraries/animation/src/JointState.cpp b/libraries/animation/src/JointState.cpp index 9597a46726..935319b489 100644 --- a/libraries/animation/src/JointState.cpp +++ b/libraries/animation/src/JointState.cpp @@ -43,6 +43,7 @@ void JointState::copyState(const JointState& other) { _isFree = other._isFree; _parentIndex = other._parentIndex; _defaultRotation = other._defaultRotation; + _defaultTranslation = other._defaultTranslation; _inverseDefaultRotation = other._inverseDefaultRotation; _translation = other._translation; _rotationMin = other._rotationMin; @@ -60,6 +61,7 @@ JointState::JointState(const FBXJoint& joint) { _parentIndex = joint.parentIndex; _translation = joint.translation; _defaultRotation = joint.rotation; + _defaultTranslation = _translation; _inverseDefaultRotation = joint.inverseDefaultRotation; _rotationMin = joint.rotationMin; _rotationMax = joint.rotationMax; @@ -92,6 +94,9 @@ glm::quat JointState::getRotation() const { } void JointState::initTransform(const glm::mat4& parentTransform) { + + _unitsScale = extractScale(parentTransform); + computeTransform(parentTransform); _positionInParentFrame = glm::inverse(extractRotation(parentTransform)) * (extractTranslation(_transform) - extractTranslation(parentTransform)); _distanceToParent = glm::length(_positionInParentFrame); @@ -101,11 +106,11 @@ void JointState::computeTransform(const glm::mat4& parentTransform, bool parentT if (!parentTransformChanged && !_transformChanged) { return; } - + glm::quat rotationInParentFrame = _preRotation * _rotationInConstrainedFrame * _postRotation; glm::mat4 transformInParentFrame = _preTransform * glm::mat4_cast(rotationInParentFrame) * _postTransform; glm::mat4 newTransform = parentTransform * glm::translate(_translation) * transformInParentFrame; - + if (newTransform != _transform) { _transform = newTransform; _transformChanged = true; @@ -139,6 +144,13 @@ void JointState::restoreRotation(float fraction, float priority) { } } +void JointState::restoreTranslation(float fraction, float priority) { + if (priority == _animationPriority || _animationPriority == 0.0f) { + _translation = _translation * (1.0f - fraction) + _defaultTranslation * fraction; + _animationPriority = 0.0f; + } +} + void JointState::setRotationInBindFrame(const glm::quat& rotation, float priority, bool constrain) { // rotation is from bind- to model-frame if (priority >= _animationPriority) { @@ -245,6 +257,14 @@ void JointState::setRotationInConstrainedFrame(glm::quat targetRotation, float p } } +void JointState::setTranslation(const glm::vec3& translation, float priority) { + if (priority >= _animationPriority || _animationPriority == 0.0f) { + _translation = translation / _unitsScale; + _transformChanged = true; + _animationPriority = priority; + } +} + void JointState::setRotationInConstrainedFrameInternal(const glm::quat& targetRotation) { if (_rotationInConstrainedFrame != targetRotation) { glm::quat parentRotation = computeParentRotation(); @@ -269,13 +289,17 @@ bool JointState::rotationIsDefault(const glm::quat& rotation, float tolerance) c glm::abs(rotation.w - defaultRotation.w) < tolerance; } +bool JointState::translationIsDefault(const glm::vec3& translation, float tolerance) const { + return glm::distance(_defaultTranslation, translation * _unitsScale) < tolerance; +} + glm::quat JointState::getDefaultRotationInParentFrame() const { // NOTE: the result is constant and could be cached return _preRotation * _defaultRotation * _postRotation; } -const glm::vec3& JointState::getDefaultTranslationInConstrainedFrame() const { - return _translation; +glm::vec3 JointState::getDefaultTranslationInConstrainedFrame() const { + return _defaultTranslation * _unitsScale; } void JointState::slaveVisibleTransform() { diff --git a/libraries/animation/src/JointState.h b/libraries/animation/src/JointState.h index 07ed010104..d11f98cdd6 100644 --- a/libraries/animation/src/JointState.h +++ b/libraries/animation/src/JointState.h @@ -77,6 +77,8 @@ public: /// \param priority priority level of this animation blend void restoreRotation(float fraction, float priority); + void restoreTranslation(float fraction, float priority); + /// \param rotation is from bind- to model-frame /// computes and sets new _rotationInConstrainedFrame /// NOTE: the JointState's model-frame transform/rotation are NOT updated! @@ -88,14 +90,18 @@ public: void setRotationInModelFrame(const glm::quat& rotationInModelFrame, float priority, bool constrain); void setRotationInConstrainedFrame(glm::quat targetRotation, float priority, bool constrain = false, float mix = 1.0f); + + void setTranslation(const glm::vec3& translation, float priority); + void setVisibleRotationInConstrainedFrame(const glm::quat& targetRotation); const glm::quat& getRotationInConstrainedFrame() const { return _rotationInConstrainedFrame; } const glm::quat& getVisibleRotationInConstrainedFrame() const { return _visibleRotationInConstrainedFrame; } bool rotationIsDefault(const glm::quat& rotation, float tolerance = EPSILON) const; + bool translationIsDefault(const glm::vec3& translation, float tolerance = EPSILON) const; glm::quat getDefaultRotationInParentFrame() const; - const glm::vec3& getDefaultTranslationInConstrainedFrame() const; + glm::vec3 getDefaultTranslationInConstrainedFrame() const; void clearTransformTranslation(); @@ -110,12 +116,13 @@ public: void setTransform(const glm::mat4& transform) { _transform = transform; } void setVisibleTransform(const glm::mat4& transform) { _visibleTransform = transform; } - const glm::vec3& getTranslation() const { return _translation; } + glm::vec3 getTranslation() const { return _translation * _unitsScale; } const glm::mat4& getPreTransform() const { return _preTransform; } const glm::mat4& getPostTransform() const { return _postTransform; } const glm::quat& getPreRotation() const { return _preRotation; } const glm::quat& getPostRotation() const { return _postRotation; } const glm::quat& getDefaultRotation() const { return _defaultRotation; } + glm::vec3 getDefaultTranslation() const { return _defaultTranslation * _unitsScale; } const glm::quat& getInverseDefaultRotation() const { return _inverseDefaultRotation; } const QString& getName() const { return _name; } bool getIsFree() const { return _isFree; } @@ -144,6 +151,7 @@ private: glm::quat _defaultRotation; // Not necessarilly bind rotation. See FBXJoint transform/bindTransform glm::quat _inverseDefaultRotation; + glm::vec3 _defaultTranslation; glm::vec3 _translation; QString _name; int _parentIndex; @@ -155,6 +163,8 @@ private: glm::mat4 _preTransform; glm::mat4 _postTransform; glm::quat _inverseBindRotation; + + glm::vec3 _unitsScale{1.0f, 1.0f, 1.0f}; }; #endif // hifi_JointState_h diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 1874939b3d..eaad5e6bb6 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -270,6 +270,7 @@ void Rig::reset(const QVector& fbxJoints) { } for (int i = 0; i < _jointStates.size(); i++) { _jointStates[i].setRotationInConstrainedFrame(fbxJoints.at(i).rotation, 0.0f); + _jointStates[i].setTranslation(fbxJoints.at(i).translation, 0.0f); } } @@ -289,6 +290,16 @@ bool Rig::getJointStateRotation(int index, glm::quat& rotation) const { return !state.rotationIsDefault(rotation); } +bool Rig::getJointStateTranslation(int index, glm::vec3& translation) const { + if (index == -1 || index >= _jointStates.size()) { + return false; + } + const JointState& state = _jointStates.at(index); + translation = state.getTranslation(); + return !state.translationIsDefault(translation); +} + + bool Rig::getVisibleJointState(int index, glm::quat& rotation) const { if (index == -1 || index >= _jointStates.size()) { return false; @@ -302,6 +313,7 @@ void Rig::clearJointState(int index) { if (index != -1 && index < _jointStates.size()) { JointState& state = _jointStates[index]; state.setRotationInConstrainedFrame(glm::quat(), 0.0f); + state.setTranslation(state.getDefaultTranslationInConstrainedFrame(), 0.0f); } } @@ -328,7 +340,7 @@ void Rig::setJointAnimatinoPriority(int index, float newPriority) { } } -void Rig::setJointState(int index, bool valid, const glm::quat& rotation, float priority) { +void Rig::setJointRotation(int index, bool valid, const glm::quat& rotation, float priority) { if (index != -1 && index < _jointStates.size()) { JointState& state = _jointStates[index]; if (valid) { @@ -345,6 +357,12 @@ void Rig::restoreJointRotation(int index, float fraction, float priority) { } } +void Rig::restoreJointTranslation(int index, float fraction, float priority) { + if (index != -1 && index < _jointStates.size()) { + _jointStates[index].restoreTranslation(fraction, priority); + } +} + bool Rig::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position, glm::vec3 translation, glm::quat rotation) const { if (jointIndex == -1 || jointIndex >= _jointStates.size()) { @@ -380,6 +398,14 @@ bool Rig::getJointRotation(int jointIndex, glm::quat& rotation) const { return true; } +bool Rig::getJointTranslation(int jointIndex, glm::vec3& translation) const { + if (jointIndex == -1 || jointIndex >= _jointStates.size()) { + return false; + } + translation = _jointStates[jointIndex].getTranslation(); + return true; +} + bool Rig::getJointCombinedRotation(int jointIndex, glm::quat& result, const glm::quat& rotation) const { if (jointIndex == -1 || jointIndex >= _jointStates.size()) { return false; @@ -590,7 +616,13 @@ void Rig::updateAnimations(float deltaTime, glm::mat4 rootTransform) { // copy poses into jointStates const float PRIORITY = 1.0f; for (size_t i = 0; i < poses.size(); i++) { - setJointRotationInConstrainedFrame((int)i, glm::inverse(_animSkeleton->getRelativeBindPose(i).rot) * poses[i].rot, PRIORITY, false); + setJointRotationInConstrainedFrame((int)i, + glm::inverse(_animSkeleton->getRelativeBindPose(i).rot) * poses[i].rot, + PRIORITY, + false); + + JointState& state = _jointStates[i]; + setJointTranslation((int)i, true, poses[i].trans, PRIORITY); } } else { @@ -866,6 +898,7 @@ bool Rig::restoreJointPosition(int jointIndex, float fraction, float priority, c foreach (int index, freeLineage) { JointState& state = _jointStates[index]; state.restoreRotation(fraction, priority); + state.restoreTranslation(fraction, priority); } return true; } diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 90082ca38b..f31d030910 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -123,12 +123,13 @@ public: int rightShoulderJointIndex); bool jointStatesEmpty() { return _jointStates.isEmpty(); }; int getJointStateCount() const { return _jointStates.size(); } - int indexOfJoint(const QString& jointName) ; + int indexOfJoint(const QString& jointName); void initJointTransforms(glm::mat4 rootTransform); void clearJointTransformTranslation(int jointIndex); void reset(const QVector& fbxJoints); bool getJointStateRotation(int index, glm::quat& rotation) const; + bool getJointStateTranslation(int index, glm::vec3& translation) const; void applyJointRotationDelta(int jointIndex, const glm::quat& delta, bool constrain, float priority); JointState getJointState(int jointIndex) const; // XXX bool getVisibleJointState(int index, glm::quat& rotation) const; @@ -137,14 +138,21 @@ public: void clearJointAnimationPriority(int index); float getJointAnimatinoPriority(int index); void setJointAnimatinoPriority(int index, float newPriority); - void setJointState(int index, bool valid, const glm::quat& rotation, float priority); + + virtual void setJointState(int index, bool valid, const glm::quat& rotation, const glm::vec3& translation, + float priority) = 0; + virtual void setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority) {} + void setJointRotation(int index, bool valid, const glm::quat& rotation, float priority); + void restoreJointRotation(int index, float fraction, float priority); + void restoreJointTranslation(int index, float fraction, float priority); bool getJointPositionInWorldFrame(int jointIndex, glm::vec3& position, glm::vec3 translation, glm::quat rotation) const; bool getJointPosition(int jointIndex, glm::vec3& position) const; bool getJointRotationInWorldFrame(int jointIndex, glm::quat& result, const glm::quat& rotation) const; bool getJointRotation(int jointIndex, glm::quat& rotation) const; + bool getJointTranslation(int jointIndex, glm::vec3& translation) const; bool getJointCombinedRotation(int jointIndex, glm::quat& result, const glm::quat& rotation) const; bool getVisibleJointPositionInWorldFrame(int jointIndex, glm::vec3& position, glm::vec3 translation, glm::quat rotation) const; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 104b592c1a..3ef53e245b 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -49,6 +49,7 @@ AvatarData::AvatarData() : _keyState(NO_KEY_DOWN), _forceFaceTrackerConnected(false), _hasNewJointRotations(true), + _hasNewJointTranslations(true), _headData(NULL), _handData(NULL), _faceModelURL("http://invalid.com"), @@ -278,12 +279,16 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { // pupil dilation destinationBuffer += packFloatToByte(destinationBuffer, _headData->_pupilDilation, 1.0f); - // joint data + // joint rotation data *destinationBuffer++ = _jointData.size(); unsigned char* validityPosition = destinationBuffer; unsigned char validity = 0; int validityBit = 0; + #ifdef WANT_DEBUG + int rotationSentCount = 0; + #endif + _lastSentJointData.resize(_jointData.size()); for (int i=0; i < _jointData.size(); i++) { @@ -292,7 +297,12 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { if (sendAll || !cullSmallChanges || fabsf(glm::dot(data.rotation, _lastSentJointData[i].rotation)) <= AVATAR_MIN_ROTATION_DOT) { - validity |= (1 << validityBit); + if (data.rotationSet) { + validity |= (1 << validityBit); + #ifdef WANT_DEBUG + rotationSentCount++; + #endif + } } } if (++validityBit == BITS_IN_BYTE) { @@ -317,6 +327,73 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { } } + + // joint translation data + validityPosition = destinationBuffer; + validity = 0; + validityBit = 0; + + #ifdef WANT_DEBUG + int translationSentCount = 0; + #endif + + float maxTranslationDimension = 0.0; + for (int i=0; i < _jointData.size(); i++) { + const JointData& data = _jointData.at(i); + if (sendAll || _lastSentJointData[i].translation != data.translation) { + if (sendAll || + !cullSmallChanges || + glm::distance(data.translation, _lastSentJointData[i].translation) > AVATAR_MIN_TRANSLATION) { + if (data.translationSet) { + validity |= (1 << validityBit); + #ifdef WANT_DEBUG + translationSentCount++; + #endif + maxTranslationDimension = glm::max(fabsf(data.translation.x), maxTranslationDimension); + maxTranslationDimension = glm::max(fabsf(data.translation.y), maxTranslationDimension); + maxTranslationDimension = glm::max(fabsf(data.translation.z), maxTranslationDimension); + } + } + } + if (++validityBit == BITS_IN_BYTE) { + *destinationBuffer++ = validity; + validityBit = validity = 0; + } + } + + + if (validityBit != 0) { + *destinationBuffer++ = validity; + } + + // TODO -- automatically pick translationCompressionRadix + int translationCompressionRadix = 12; + + *destinationBuffer++ = translationCompressionRadix; + + validityBit = 0; + validity = *validityPosition++; + for (int i = 0; i < _jointData.size(); i ++) { + const JointData& data = _jointData[ i ]; + if (validity & (1 << validityBit)) { + destinationBuffer += + packFloatVec3ToSignedTwoByteFixed(destinationBuffer, data.translation, translationCompressionRadix); + } + if (++validityBit == BITS_IN_BYTE) { + validityBit = 0; + validity = *validityPosition++; + } + } + + #ifdef WANT_DEBUG + if (sendAll) { + qDebug() << "SENDING -- rotations:" << rotationSentCount << "translations:" << translationSentCount + << "largest:" << maxTranslationDimension + << "radix:" << translationCompressionRadix + << "size:" << (int)(destinationBuffer - startPosition); + } + #endif + return avatarDataByteArray.left(destinationBuffer - startPosition); } @@ -328,7 +405,16 @@ void AvatarData::doneEncoding(bool cullSmallChanges) { if (_lastSentJointData[i].rotation != data.rotation) { if (!cullSmallChanges || fabsf(glm::dot(data.rotation, _lastSentJointData[i].rotation)) <= AVATAR_MIN_ROTATION_DOT) { - _lastSentJointData[i].rotation = data.rotation; + if (data.rotationSet) { + _lastSentJointData[i].rotation = data.rotation; + } + } + + if (!cullSmallChanges || + glm::distance(data.translation, _lastSentJointData[i].translation) > AVATAR_MIN_TRANSLATION) { + if (data.translationSet) { + _lastSentJointData[i].translation = data.translation; + } } } } @@ -374,7 +460,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { // + 1 byte for numJoints (0) // = 39 bytes int minPossibleSize = 39; - + int maxAvailableSize = buffer.size(); if (minPossibleSize > maxAvailableSize) { if (shouldLogError(now)) { @@ -556,7 +642,11 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { sourceBuffer += unpackFloatFromByte(sourceBuffer, _headData->_pupilDilation, 1.0f); } // 1 byte - // joint data + + //----------------------- + // joint rotations + //----------------------- + int numJoints = *sourceBuffer++; int bytesOfValidity = (int)ceil((float)numJoints / (float)BITS_IN_BYTE); minPossibleSize += bytesOfValidity; @@ -569,13 +659,13 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { } return maxAvailableSize; } - int numValidJoints = 0; + int numValidJointRotations = 0; _jointData.resize(numJoints); - QVector valids; - valids.resize(numJoints); + QVector validRotations; + validRotations.resize(numJoints); - { // validity bits + { // rotation validity bits unsigned char validity = 0; int validityBit = 0; for (int i = 0; i < numJoints; i++) { @@ -584,20 +674,19 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { } bool valid = (bool)(validity & (1 << validityBit)); if (valid) { - ++numValidJoints; + ++numValidJointRotations; } - valids[i] = valid; + validRotations[i] = valid; validityBit = (validityBit + 1) % BITS_IN_BYTE; } - } - // 1 + bytesOfValidity bytes + } // 1 + bytesOfValidity bytes // each joint rotation component is stored in two bytes (sizeof(uint16_t)) int COMPONENTS_PER_QUATERNION = 4; - minPossibleSize += numValidJoints * COMPONENTS_PER_QUATERNION * sizeof(uint16_t); + minPossibleSize += numValidJointRotations * COMPONENTS_PER_QUATERNION * sizeof(uint16_t); if (minPossibleSize > maxAvailableSize) { if (shouldLogError(now)) { - qCDebug(avatars) << "Malformed AvatarData packet after JointData;" + qCDebug(avatars) << "Malformed AvatarData packet after JointData rotation validity;" << " displayName = '" << _displayName << "'" << " minPossibleSize = " << minPossibleSize << " maxAvailableSize = " << maxAvailableSize; @@ -608,13 +697,75 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { { // joint data for (int i = 0; i < numJoints; i++) { JointData& data = _jointData[i]; - if (valids[i]) { + if (validRotations[i]) { _hasNewJointRotations = true; + data.rotationSet = true; sourceBuffer += unpackOrientationQuatFromBytes(sourceBuffer, data.rotation); } } } // numJoints * 8 bytes + + //----------------------- + // joint translations + //----------------------- + + // get translation validity bits -- these indicate which translations were packed + int numValidJointTranslations = 0; + QVector validTranslations; + validTranslations.resize(numJoints); + + { // translation validity bits + unsigned char validity = 0; + int validityBit = 0; + for (int i = 0; i < numJoints; i++) { + if (validityBit == 0) { + validity = *sourceBuffer++; + } + bool valid = (bool)(validity & (1 << validityBit)); + if (valid) { + ++numValidJointTranslations; + } + validTranslations[i] = valid; + validityBit = (validityBit + 1) % BITS_IN_BYTE; + } + } // 1 + bytesOfValidity bytes + + // each joint translation component is stored in 6 bytes. 1 byte for translationCompressionRadix + minPossibleSize += numValidJointTranslations * 6 + 1; + if (minPossibleSize > maxAvailableSize) { + if (shouldLogError(now)) { + qCDebug(avatars) << "Malformed AvatarData packet after JointData translation validity;" + << " displayName = '" << _displayName << "'" + << " minPossibleSize = " << minPossibleSize + << " maxAvailableSize = " << maxAvailableSize; + } + return maxAvailableSize; + } + + int translationCompressionRadix = *sourceBuffer++; + + { // joint data + for (int i = 0; i < numJoints; i++) { + JointData& data = _jointData[i]; + if (validTranslations[i]) { + sourceBuffer += + unpackFloatVec3FromSignedTwoByteFixed(sourceBuffer, data.translation, translationCompressionRadix); + _hasNewJointTranslations = true; + data.translationSet = true; + } + } + } // numJoints * 12 bytes + + #ifdef WANT_DEBUG + if (numValidJointRotations > 15) { + qDebug() << "RECEIVING -- rotations:" << numValidJointRotations + << "translations:" << numValidJointTranslations + << "radix:" << translationCompressionRadix + << "size:" << (int)(sourceBuffer - startPosition); + } + #endif + int numBytesRead = sourceBuffer - startPosition; _averageBytesReceived.updateAverage(numBytesRead); return numBytesRead; @@ -800,7 +951,7 @@ void AvatarData::changeReferential(Referential* ref) { _referential = ref; } -void AvatarData::setJointData(int index, const glm::quat& rotation) { +void AvatarData::setJointData(int index, const glm::quat& rotation, const glm::vec3& translation) { if (index == -1) { return; } @@ -813,6 +964,7 @@ void AvatarData::setJointData(int index, const glm::quat& rotation) { } JointData& data = _jointData[index]; data.rotation = rotation; + data.translation = translation; } void AvatarData::clearJointData(int index) { @@ -854,13 +1006,67 @@ glm::quat AvatarData::getJointRotation(int index) const { return index < _jointData.size() ? _jointData.at(index).rotation : glm::quat(); } -void AvatarData::setJointData(const QString& name, const glm::quat& rotation) { + +glm::vec3 AvatarData::getJointTranslation(int index) const { + if (index == -1) { + return glm::vec3(); + } + if (QThread::currentThread() != thread()) { + glm::vec3 result; + QMetaObject::invokeMethod(const_cast(this), "getJointTranslation", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(glm::vec3, result), Q_ARG(int, index)); + return result; + } + return index < _jointData.size() ? _jointData.at(index).translation : glm::vec3(); +} + +glm::vec3 AvatarData::getJointTranslation(const QString& name) const { + if (QThread::currentThread() != thread()) { + glm::vec3 result; + QMetaObject::invokeMethod(const_cast(this), "getJointTranslation", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(glm::vec3, result), Q_ARG(const QString&, name)); + return result; + } + return getJointTranslation(getJointIndex(name)); +} + +void AvatarData::setJointData(const QString& name, const glm::quat& rotation, const glm::vec3& translation) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "setJointData", Q_ARG(const QString&, name), Q_ARG(const glm::quat&, rotation)); return; } - setJointData(getJointIndex(name), rotation); + setJointData(getJointIndex(name), rotation, translation); +} + +void AvatarData::setJointRotation(int index, const glm::quat& rotation) { + if (index == -1) { + return; + } + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setJointRotation", Q_ARG(int, index), Q_ARG(const glm::quat&, rotation)); + return; + } + if (_jointData.size() <= index) { + _jointData.resize(index + 1); + } + JointData& data = _jointData[index]; + data.rotation = rotation; +} + +void AvatarData::setJointTranslation(int index, const glm::vec3& translation) { + if (index == -1) { + return; + } + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setJointTranslation", Q_ARG(int, index), Q_ARG(const glm::vec3&, translation)); + return; + } + if (_jointData.size() <= index) { + _jointData.resize(index + 1); + } + JointData& data = _jointData[index]; + data.translation = translation; } void AvatarData::clearJointData(const QString& name) { @@ -918,7 +1124,25 @@ void AvatarData::setJointRotations(QVector jointRotations) { } for (int i = 0; i < jointRotations.size(); ++i) { if (i < _jointData.size()) { - setJointData(i, jointRotations[i]); + setJointRotation(i, jointRotations[i]); + } + } +} + +void AvatarData::setJointTranslations(QVector jointTranslations) { + if (QThread::currentThread() != thread()) { + QVector result; + QMetaObject::invokeMethod(const_cast(this), + "setJointTranslations", Qt::BlockingQueuedConnection, + Q_ARG(QVector, jointTranslations)); + } + + if (_jointData.size() < jointTranslations.size()) { + _jointData.resize(jointTranslations.size()); + } + for (int i = 0; i < jointTranslations.size(); ++i) { + if (i < _jointData.size()) { + setJointTranslation(i, jointTranslations[i]); } } } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index a934b98037..e4022fd474 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -117,6 +117,7 @@ const QString DEFAULT_FULL_AVATAR_MODEL_NAME = QString("Default"); const float AVATAR_SEND_FULL_UPDATE_RATIO = 0.02f; // this controls how large a change in joint-rotation must be before the interface sends it to the avatar mixer const float AVATAR_MIN_ROTATION_DOT = 0.9999999f; +const float AVATAR_MIN_TRANSLATION = 0.0001f; // Where one's own Avatar begins in the world (will be overwritten if avatar data file is found). @@ -240,20 +241,24 @@ public: Q_INVOKABLE char getHandState() const { return _handState; } const QVector& getJointData() const { return _jointData; } - void setJointData(const QVector& jointData) { _jointData = jointData; } - Q_INVOKABLE virtual void setJointData(int index, const glm::quat& rotation); + Q_INVOKABLE virtual void setJointData(int index, const glm::quat& rotation, const glm::vec3& translation); + Q_INVOKABLE virtual void setJointRotation(int index, const glm::quat& rotation); + Q_INVOKABLE virtual void setJointTranslation(int index, const glm::vec3& translation); Q_INVOKABLE virtual void clearJointData(int index); Q_INVOKABLE bool isJointDataValid(int index) const; Q_INVOKABLE virtual glm::quat getJointRotation(int index) const; + Q_INVOKABLE virtual glm::vec3 getJointTranslation(int index) const; - Q_INVOKABLE void setJointData(const QString& name, const glm::quat& rotation); + Q_INVOKABLE void setJointData(const QString& name, const glm::quat& rotation, const glm::vec3& translation); Q_INVOKABLE void clearJointData(const QString& name); Q_INVOKABLE bool isJointDataValid(const QString& name) const; Q_INVOKABLE glm::quat getJointRotation(const QString& name) const; + Q_INVOKABLE glm::vec3 getJointTranslation(const QString& name) const; Q_INVOKABLE virtual QVector getJointRotations() const; Q_INVOKABLE virtual void setJointRotations(QVector jointRotations); + Q_INVOKABLE virtual void setJointTranslations(QVector jointTranslations); Q_INVOKABLE virtual void clearJointsData(); @@ -387,6 +392,7 @@ protected: bool _forceFaceTrackerConnected; bool _hasNewJointRotations; // set in AvatarData, cleared in Avatar + bool _hasNewJointTranslations; // set in AvatarData, cleared in Avatar HeadData* _headData; HandData* _handData; @@ -435,6 +441,9 @@ Q_DECLARE_METATYPE(AvatarData*) class JointData { public: glm::quat rotation; + bool rotationSet = false; + glm::vec3 translation; + bool translationSet = false; }; class AttachmentData { diff --git a/libraries/avatars/src/Player.cpp b/libraries/avatars/src/Player.cpp index 29544924b2..a425323a41 100644 --- a/libraries/avatars/src/Player.cpp +++ b/libraries/avatars/src/Player.cpp @@ -254,8 +254,17 @@ void Player::play() { nextFrame.getJointRotations()[i], _frameInterpolationFactor); } + + QVector jointTranslations(currentFrame.getJointTranslations().size()); + for (int i = 0; i < currentFrame.getJointTranslations().size(); ++i) { + jointTranslations[i] = + currentFrame.getJointTranslations()[i] * (1.0f - _frameInterpolationFactor) + + nextFrame.getJointTranslations()[i] * _frameInterpolationFactor; + } + _avatar->setJointRotations(jointRotations); - + _avatar->setJointTranslations(jointTranslations); + HeadData* head = const_cast(_avatar->getHeadData()); if (head) { // Make sure fake face tracker connection doesn't get turned off diff --git a/libraries/avatars/src/Recording.cpp b/libraries/avatars/src/Recording.cpp index 76e1d68050..2c87485d01 100644 --- a/libraries/avatars/src/Recording.cpp +++ b/libraries/avatars/src/Recording.cpp @@ -239,6 +239,7 @@ void writeRecordingToFile(RecordingPointer recording, const QString& filename) { if (i == 0 || frame._jointRotations[j] != previousFrame._jointRotations[j]) { writeQuat(stream, frame._jointRotations[j]); + // XXX handle translations mask.setBit(maskIndex); } maskIndex++; @@ -561,7 +562,10 @@ RecordingPointer readRecordingFromFile(RecordingPointer recording, const QString frame._jointRotations[j] = previousFrame._jointRotations[j]; } } - + + // XXX handle translations + + if (!mask[maskIndex++] || !readVec3(stream, frame._translation)) { frame._translation = previousFrame._translation; } @@ -670,7 +674,9 @@ RecordingPointer readRecordingFromRecFile(RecordingPointer recording, const QStr for (int i = 0; i < jointRotationSize; ++i) { fileStream >> baseFrame._jointRotations[i].x >> baseFrame._jointRotations[i].y >> baseFrame._jointRotations[i].z >> baseFrame._jointRotations[i].w; } - + + // XXX handle translations + fileStream >> baseFrame._translation.x >> baseFrame._translation.y >> baseFrame._translation.z; fileStream >> baseFrame._rotation.x >> baseFrame._rotation.y >> baseFrame._rotation.z >> baseFrame._rotation.w; fileStream >> baseFrame._scale; @@ -736,7 +742,9 @@ RecordingPointer readRecordingFromRecFile(RecordingPointer recording, const QStr frame._jointRotations[i] = previousFrame._jointRotations[i]; } } - + + // XXX handle translations + if (mask[maskIndex++]) { stream >> frame._translation.x >> frame._translation.y >> frame._translation.z; frame._translation = context.orientationInv * frame._translation; diff --git a/libraries/avatars/src/Recording.h b/libraries/avatars/src/Recording.h index 49d12ec5b5..3533af6535 100644 --- a/libraries/avatars/src/Recording.h +++ b/libraries/avatars/src/Recording.h @@ -83,6 +83,7 @@ class RecordingFrame { public: QVector getBlendshapeCoefficients() const { return _blendshapeCoefficients; } QVector getJointRotations() const { return _jointRotations; } + QVector getJointTranslations() const { return _jointTranslations; } glm::vec3 getTranslation() const { return _translation; } glm::quat getRotation() const { return _rotation; } float getScale() const { return _scale; } @@ -94,6 +95,7 @@ public: protected: void setBlendshapeCoefficients(QVector blendshapeCoefficients); void setJointRotations(QVector jointRotations) { _jointRotations = jointRotations; } + void setJointTranslations(QVector jointTranslations) { _jointTranslations = jointTranslations; } void setTranslation(const glm::vec3& translation) { _translation = translation; } void setRotation(const glm::quat& rotation) { _rotation = rotation; } void setScale(float scale) { _scale = scale; } @@ -105,6 +107,7 @@ protected: private: QVector _blendshapeCoefficients; QVector _jointRotations; + QVector _jointTranslations; glm::vec3 _translation; glm::quat _rotation; float _scale; @@ -124,4 +127,4 @@ void writeRecordingToFile(RecordingPointer recording, const QString& filename); RecordingPointer readRecordingFromFile(RecordingPointer recording, const QString& filename); RecordingPointer readRecordingFromRecFile(RecordingPointer recording, const QString& filename, const QByteArray& byteArray); -#endif // hifi_Recording_h \ No newline at end of file +#endif // hifi_Recording_h diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 74317b7eff..6d7399b07a 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -276,10 +276,13 @@ void RenderableModelEntityItem::render(RenderArgs* args) { if (jointsMapped()) { bool newFrame; - auto frameData = getAnimationFrame(newFrame); + QVector frameDataRotations; + QVector frameDataTranslations; + getAnimationFrame(newFrame, frameDataRotations, frameDataTranslations); + assert(frameDataRotations.size() == frameDataTranslations.size()); if (newFrame) { - for (int i = 0; i < frameData.size(); i++) { - _model->setJointState(i, true, frameData[i]); + for (int i = 0; i < frameDataRotations.size(); i++) { + _model->setJointState(i, true, frameDataRotations[i], frameDataTranslations[i], 1.0f); } } } diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index 70747937d8..44b4aa99bf 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -218,18 +218,20 @@ void ModelEntityItem::mapJoints(const QStringList& modelJointNames) { } } -const QVector& ModelEntityItem::getAnimationFrame(bool& newFrame) { +void ModelEntityItem::getAnimationFrame(bool& newFrame, + QVector& rotationsResult, QVector& translationsResult) { newFrame = false; if (!hasAnimation() || !_jointMappingCompleted) { - return _lastKnownFrameData; + rotationsResult = _lastKnownFrameDataRotations; + translationsResult = _lastKnownFrameDataTranslations; } - + AnimationPointer myAnimation = getAnimation(_animationURL); // FIXME: this could be optimized if (myAnimation && myAnimation->isLoaded()) { - + const QVector& frames = myAnimation->getFramesReference(); // NOTE: getFrames() is too heavy - + int frameCount = frames.size(); if (frameCount > 0) { int animationFrameIndex = (int)(glm::floor(getAnimationFrameIndex())) % frameCount; @@ -240,20 +242,27 @@ const QVector& ModelEntityItem::getAnimationFrame(bool& newFrame) { if (animationFrameIndex != _lastKnownFrameIndex) { _lastKnownFrameIndex = animationFrameIndex; newFrame = true; - - const QVector& rotations = frames[animationFrameIndex].rotations; - _lastKnownFrameData.resize(_jointMapping.size()); + const QVector& rotations = frames[animationFrameIndex].rotations; + const QVector& translations = frames[animationFrameIndex].translations; + + _lastKnownFrameDataRotations.resize(_jointMapping.size()); + _lastKnownFrameDataTranslations.resize(_jointMapping.size()); for (int j = 0; j < _jointMapping.size(); j++) { - int rotationIndex = _jointMapping[j]; - if (rotationIndex != -1 && rotationIndex < rotations.size()) { - _lastKnownFrameData[j] = rotations[rotationIndex]; + int index = _jointMapping[j]; + if (index != -1 && index < rotations.size()) { + _lastKnownFrameDataRotations[j] = rotations[index]; + } + if (index != -1 && index < translations.size()) { + _lastKnownFrameDataTranslations[j] = translations[index]; } } } } } - return _lastKnownFrameData; + + rotationsResult = _lastKnownFrameDataRotations; + translationsResult = _lastKnownFrameDataTranslations; } bool ModelEntityItem::isAnimatingSomething() const { diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index bf6d7a9785..8bf6658bf3 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -106,7 +106,7 @@ public: float getAnimationLastFrame() const { return _animationLoop.getLastFrame(); } void mapJoints(const QStringList& modelJointNames); - const QVector& getAnimationFrame(bool& newFrame); + void getAnimationFrame(bool& newFrame, QVector& rotationsResult, QVector& translationsResult); bool jointsMapped() const { return _jointMappingCompleted; } bool getAnimationIsPlaying() const { return _animationLoop.isRunning(); } @@ -123,7 +123,8 @@ public: static void cleanupLoadedAnimations(); protected: - QVector _lastKnownFrameData; + QVector _lastKnownFrameDataRotations; + QVector _lastKnownFrameDataTranslations; int _lastKnownFrameIndex; diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index da240e826a..1accbf4238 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -530,6 +530,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS QHash typeFlags; QHash localRotations; + QHash localTranslations; QHash xComponents; QHash yComponents; QHash zComponents; @@ -1104,16 +1105,16 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS normalTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); } else if (type.contains("specular") || type.contains("reflection")) { specularTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); - + } else if (type == "lcl rotation") { localRotations.insert(getID(connection.properties, 2), getID(connection.properties, 1)); - + } else if (type == "lcl translation") { + localTranslations.insert(getID(connection.properties, 2), getID(connection.properties, 1)); + } else if (type == "d|x") { xComponents.insert(getID(connection.properties, 2), getID(connection.properties, 1)); - } else if (type == "d|y") { yComponents.insert(getID(connection.properties, 2), getID(connection.properties, 1)); - } else if (type == "d|z") { zComponents.insert(getID(connection.properties, 2), getID(connection.properties, 1)); @@ -1224,6 +1225,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS for (int i = 0; i < frameCount; i++) { FBXAnimationFrame frame; frame.rotations.resize(modelIDs.size()); + frame.translations.resize(modelIDs.size()); geometry.animationFrames.append(frame); } @@ -1247,7 +1249,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS joint.freeLineage.append(index); } joint.freeLineage.remove(lastFreeIndex + 1, joint.freeLineage.size() - lastFreeIndex - 1); - joint.translation = model.translation; + joint.translation = model.translation; // these are usually in centimeters joint.preTransform = model.preTransform; joint.preRotation = model.preRotation; joint.rotation = model.rotation; @@ -1272,7 +1274,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } joint.inverseBindRotation = joint.inverseDefaultRotation; joint.name = model.name; - + foreach (const QString& childID, _connectionChildMap.values(modelID)) { QString type = typeFlags.value(childID); if (!type.isEmpty()) { @@ -1285,17 +1287,28 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS geometry.joints.append(joint); geometry.jointIndices.insert(model.name, geometry.joints.size()); - + QString rotationID = localRotations.value(modelID); - AnimationCurve xCurve = animationCurves.value(xComponents.value(rotationID)); - AnimationCurve yCurve = animationCurves.value(yComponents.value(rotationID)); - AnimationCurve zCurve = animationCurves.value(zComponents.value(rotationID)); + AnimationCurve xRotCurve = animationCurves.value(xComponents.value(rotationID)); + AnimationCurve yRotCurve = animationCurves.value(yComponents.value(rotationID)); + AnimationCurve zRotCurve = animationCurves.value(zComponents.value(rotationID)); + + QString translationID = localTranslations.value(modelID); + AnimationCurve xPosCurve = animationCurves.value(xComponents.value(translationID)); + AnimationCurve yPosCurve = animationCurves.value(yComponents.value(translationID)); + AnimationCurve zPosCurve = animationCurves.value(zComponents.value(translationID)); + glm::vec3 defaultValues = glm::degrees(safeEulerAngles(joint.rotation)); + for (int i = 0; i < frameCount; i++) { geometry.animationFrames[i].rotations[jointIndex] = glm::quat(glm::radians(glm::vec3( - xCurve.values.isEmpty() ? defaultValues.x : xCurve.values.at(i % xCurve.values.size()), - yCurve.values.isEmpty() ? defaultValues.y : yCurve.values.at(i % yCurve.values.size()), - zCurve.values.isEmpty() ? defaultValues.z : zCurve.values.at(i % zCurve.values.size())))); + xRotCurve.values.isEmpty() ? defaultValues.x : xRotCurve.values.at(i % xRotCurve.values.size()), + yRotCurve.values.isEmpty() ? defaultValues.y : yRotCurve.values.at(i % yRotCurve.values.size()), + zRotCurve.values.isEmpty() ? defaultValues.z : zRotCurve.values.at(i % zRotCurve.values.size())))); + geometry.animationFrames[i].translations[jointIndex] = glm::vec3( + xPosCurve.values.isEmpty() ? defaultValues.x : xPosCurve.values.at(i % xPosCurve.values.size()), + yPosCurve.values.isEmpty() ? defaultValues.y : yPosCurve.values.at(i % yPosCurve.values.size()), + zPosCurve.values.isEmpty() ? defaultValues.z : zPosCurve.values.at(i % zPosCurve.values.size())); } } diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index 5cfff9826f..20f8fffe44 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -208,8 +208,8 @@ public: /// A single animation frame extracted from an FBX document. class FBXAnimationFrame { public: - QVector rotations; + QVector translations; }; /// A light in an FBX document. diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index a8cf743686..ca75a86158 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -39,6 +39,9 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityEdit: case PacketType::EntityData: return VERSION_ENTITIES_PARTICLE_ELLIPSOID_EMITTER; + case PacketType::AvatarData: + case PacketType::BulkAvatarData: + return 15; default: return 14; } diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 7aee46b108..34896d5714 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1001,8 +1001,16 @@ void Model::clearJointState(int index) { _rig->clearJointState(index); } -void Model::setJointState(int index, bool valid, const glm::quat& rotation, float priority) { - _rig->setJointState(index, valid, rotation, priority); +void Model::setJointState(int index, bool valid, const glm::quat& rotation, const glm::vec3& translation, float priority) { + _rig->setJointState(index, valid, rotation, translation, priority); +} + +void Model::setJointRotation(int index, bool valid, const glm::quat& rotation, float priority) { + _rig->setJointRotation(index, valid, rotation, priority); +} + +void Model::setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority) { + _rig->setJointTranslation(index, valid, translation, priority); } int Model::getParentJointIndex(int jointIndex) const { @@ -1074,6 +1082,10 @@ bool Model::getJointRotation(int jointIndex, glm::quat& rotation) const { return _rig->getJointRotation(jointIndex, rotation); } +bool Model::getJointTranslation(int jointIndex, glm::vec3& translation) const { + return _rig->getJointTranslation(jointIndex, translation); +} + bool Model::getJointCombinedRotation(int jointIndex, glm::quat& rotation) const { return _rig->getJointCombinedRotation(jointIndex, rotation, _rotation); } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index de760dc793..460bc4f044 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -126,7 +126,9 @@ public: QStringList getJointNames() const; /// Sets the joint state at the specified index. - void setJointState(int index, bool valid, const glm::quat& rotation = glm::quat(), float priority = 1.0f); + void setJointState(int index, bool valid, const glm::quat& rotation, const glm::vec3& translation, float priority); + void setJointRotation(int index, bool valid, const glm::quat& rotation, float priority); + void setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority); bool findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, QString& extraInfo, bool pickAgainstTriangles = false); @@ -160,6 +162,7 @@ public: /// \param rotation[out] rotation of joint in model-frame /// \return true if joint exists bool getJointRotation(int jointIndex, glm::quat& rotation) const; + bool getJointTranslation(int jointIndex, glm::vec3& translation) const; /// Returns the index of the parent of the indexed joint, or -1 if not found. int getParentJointIndex(int jointIndex) const; diff --git a/libraries/shared/src/GLMHelpers.cpp b/libraries/shared/src/GLMHelpers.cpp index 7d56157e53..31a6096d13 100644 --- a/libraries/shared/src/GLMHelpers.cpp +++ b/libraries/shared/src/GLMHelpers.cpp @@ -33,6 +33,8 @@ const vec3& Vectors::RIGHT = Vectors::UNIT_X; const vec3& Vectors::UP = Vectors::UNIT_Y; const vec3& Vectors::FRONT = Vectors::UNIT_NEG_Z; +const quat Quaternions::ZERO{ 1.0f, 0.0f, 0.0f, 0.0f }; + // Safe version of glm::mix; based on the code in Nick Bobick's article, // http://www.gamasutra.com/features/19980703/quaternions_01.htm (via Clyde, // https://github.com/threerings/clyde/blob/master/src/main/java/com/threerings/math/Quaternion.java) diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h index 6683088306..e2833b46e3 100644 --- a/libraries/shared/src/GLMHelpers.h +++ b/libraries/shared/src/GLMHelpers.h @@ -53,6 +53,12 @@ const glm::vec3 IDENTITY_FRONT = glm::vec3( 0.0f, 0.0f,-1.0f); glm::quat safeMix(const glm::quat& q1, const glm::quat& q2, float alpha); + +class Quaternions { + public: + static const quat ZERO; +}; + class Vectors { public: static const vec3 UNIT_X;