diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index 73a0891fbe..91ca2359b4 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -23,11 +23,39 @@ AnimSkeleton::AnimSkeleton(const HFMModel& hfmModel) { for (auto& joint : hfmModel.joints) { joints.push_back(joint); } - buildSkeletonFromJoints(joints); + buildSkeletonFromJoints(joints, hfmModel.jointRotationOffsets); + + // we make a copy of the inverseBindMatrices in order to prevent mutating the model bind pose + // when we are dealing with a joint offset in the model + for (int i = 0; i < (int)hfmModel.meshes.size(); i++) { + const HFMMesh& mesh = hfmModel.meshes.at(i); + std::vector dummyClustersList; + + for (int j = 0; j < mesh.clusters.size(); j++) { + std::vector bindMatrices; + // cast into a non-const reference, so we can mutate the FBXCluster + HFMCluster& cluster = const_cast(mesh.clusters.at(j)); + + HFMCluster localCluster; + localCluster.jointIndex = cluster.jointIndex; + localCluster.inverseBindMatrix = cluster.inverseBindMatrix; + localCluster.inverseBindTransform.evalFromRawMatrix(localCluster.inverseBindMatrix); + + // if we have a joint offset in the fst file then multiply its inverse by the + // model cluster inverse bind matrix + if (hfmModel.jointRotationOffsets.contains(cluster.jointIndex)) { + AnimPose localOffset(hfmModel.jointRotationOffsets[cluster.jointIndex], glm::vec3()); + localCluster.inverseBindMatrix = (glm::mat4)localOffset.inverse() * cluster.inverseBindMatrix; + localCluster.inverseBindTransform.evalFromRawMatrix(localCluster.inverseBindMatrix); + } + dummyClustersList.push_back(localCluster); + } + _clusterBindMatrixOriginalValues.push_back(dummyClustersList); + } } -AnimSkeleton::AnimSkeleton(const std::vector& joints) { - buildSkeletonFromJoints(joints); +AnimSkeleton::AnimSkeleton(const std::vector& joints, const QMap jointOffsets) { + buildSkeletonFromJoints(joints, jointOffsets); } int AnimSkeleton::nameToJointIndex(const QString& jointName) const { @@ -166,7 +194,8 @@ void AnimSkeleton::mirrorAbsolutePoses(AnimPoseVec& poses) const { } } -void AnimSkeleton::buildSkeletonFromJoints(const std::vector& joints) { +void AnimSkeleton::buildSkeletonFromJoints(const std::vector& joints, const QMap jointOffsets) { + _joints = joints; _jointsSize = (int)joints.size(); // build a cache of bind poses @@ -189,7 +218,7 @@ void AnimSkeleton::buildSkeletonFromJoints(const std::vector& joints) // build relative and absolute default poses glm::mat4 relDefaultMat = glm::translate(_joints[i].translation) * preRotationTransform * glm::mat4_cast(_joints[i].rotation) * postRotationTransform; AnimPose relDefaultPose(relDefaultMat); - _relativeDefaultPoses.push_back(relDefaultPose); + int parentIndex = getParentIndex(i); if (parentIndex >= 0) { _absoluteDefaultPoses.push_back(_absoluteDefaultPoses[parentIndex] * relDefaultPose); @@ -198,6 +227,16 @@ void AnimSkeleton::buildSkeletonFromJoints(const std::vector& joints) } } + for (int k = 0; k < _jointsSize; k++) { + if (jointOffsets.contains(k)) { + AnimPose localOffset(jointOffsets[k], glm::vec3()); + _absoluteDefaultPoses[k] = _absoluteDefaultPoses[k] * localOffset; + } + } + // re-compute relative poses + _relativeDefaultPoses = _absoluteDefaultPoses; + convertAbsolutePosesToRelative(_relativeDefaultPoses); + for (int i = 0; i < _jointsSize; i++) { _jointIndicesByName[_joints[i].name] = i; } diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index 3a384388d0..ab89eb643d 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -24,7 +24,8 @@ public: using ConstPointer = std::shared_ptr; explicit AnimSkeleton(const HFMModel& hfmModel); - explicit AnimSkeleton(const std::vector& joints); + explicit AnimSkeleton(const std::vector& joints, const QMap jointOffsets); + int nameToJointIndex(const QString& jointName) const; const QString& getJointName(int jointIndex) const; int getNumJoints() const; @@ -62,9 +63,10 @@ public: void dump(const AnimPoseVec& poses) const; std::vector lookUpJointIndices(const std::vector& jointNames) const; + const HFMCluster getClusterBindMatricesOriginalValues(const int meshIndex, const int clusterIndex) const { return _clusterBindMatrixOriginalValues[meshIndex][clusterIndex]; } protected: - void buildSkeletonFromJoints(const std::vector& joints); + void buildSkeletonFromJoints(const std::vector& joints, const QMap jointOffsets); std::vector _joints; int _jointsSize { 0 }; @@ -76,6 +78,7 @@ protected: std::vector _nonMirroredIndices; std::vector _mirrorMap; QHash _jointIndicesByName; + std::vector> _clusterBindMatrixOriginalValues; // no copies AnimSkeleton(const AnimSkeleton&) = delete; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 8b7b0ba916..128ac05b81 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -360,8 +360,10 @@ void Rig::initJointStates(const HFMModel& hfmModel, const glm::mat4& modelOffset void Rig::reset(const HFMModel& hfmModel) { _geometryOffset = AnimPose(hfmModel.offset); _invGeometryOffset = _geometryOffset.inverse(); + _animSkeleton = std::make_shared(hfmModel); + _internalPoseSet._relativePoses.clear(); _internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses(); diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index d5a372e093..dd10cd30b3 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -417,6 +417,30 @@ QByteArray fileOnUrl(const QByteArray& filepath, const QString& url) { return filepath.mid(filepath.lastIndexOf('/') + 1); } +QMap getJointRotationOffsets(const QVariantHash& mapping) { + QMap jointRotationOffsets; + static const QString JOINT_ROTATION_OFFSET_FIELD = "jointRotationOffset"; + if (!mapping.isEmpty() && mapping.contains(JOINT_ROTATION_OFFSET_FIELD) && mapping[JOINT_ROTATION_OFFSET_FIELD].type() == QVariant::Hash) { + auto offsets = mapping[JOINT_ROTATION_OFFSET_FIELD].toHash(); + for (auto itr = offsets.begin(); itr != offsets.end(); itr++) { + QString jointName = itr.key(); + QString line = itr.value().toString(); + auto quatCoords = line.split(','); + if (quatCoords.size() == 4) { + float quatX = quatCoords[0].mid(1).toFloat(); + float quatY = quatCoords[1].toFloat(); + float quatZ = quatCoords[2].toFloat(); + float quatW = quatCoords[3].mid(0, quatCoords[3].size() - 1).toFloat(); + if (!isNaN(quatX) && !isNaN(quatY) && !isNaN(quatZ) && !isNaN(quatW)) { + glm::quat rotationOffset = glm::quat(quatW, quatX, quatY, quatZ); + jointRotationOffsets.insert(jointName, rotationOffset); + } + } + } + } + return jointRotationOffsets; +} + HFMModel* FBXReader::extractHFMModel(const QVariantHash& mapping, const QString& url) { const FBXNode& node = _rootNode; QMap meshes; @@ -1793,6 +1817,19 @@ HFMModel* FBXReader::extractHFMModel(const QVariantHash& mapping, const QString& } } } + + auto offsets = getJointRotationOffsets(mapping); + hfmModel.jointRotationOffsets.clear(); + for (auto itr = offsets.begin(); itr != offsets.end(); itr++) { + QString jointName = itr.key(); + glm::quat rotationOffset = itr.value(); + int jointIndex = hfmModel.getJointIndex(jointName); + if (jointIndex != -1) { + hfmModel.jointRotationOffsets.insert(jointIndex, rotationOffset); + } + qCDebug(modelformat) << "Joint Rotation Offset added to Rig._jointRotationOffsets : " << " jointName: " << jointName << " jointIndex: " << jointIndex << " rotation offset: " << rotationOffset; + } + return hfmModelPtr; } diff --git a/libraries/hfm/src/hfm/HFM.h b/libraries/hfm/src/hfm/HFM.h index 7d4479e681..05e48b6534 100644 --- a/libraries/hfm/src/hfm/HFM.h +++ b/libraries/hfm/src/hfm/HFM.h @@ -311,6 +311,8 @@ public: QString getModelNameOfMesh(int meshIndex) const; QList blendshapeChannelNames; + + QMap jointRotationOffsets; }; }; diff --git a/libraries/render-utils/src/CauterizedModel.cpp b/libraries/render-utils/src/CauterizedModel.cpp index c31345bc55..86d4793aa5 100644 --- a/libraries/render-utils/src/CauterizedModel.cpp +++ b/libraries/render-utils/src/CauterizedModel.cpp @@ -114,18 +114,22 @@ void CauterizedModel::updateClusterMatrices() { for (int i = 0; i < (int)_meshStates.size(); i++) { Model::MeshState& state = _meshStates[i]; const HFMMesh& mesh = hfmModel.meshes.at(i); + int meshIndex = i; + for (int j = 0; j < mesh.clusters.size(); j++) { const HFMCluster& cluster = mesh.clusters.at(j); + int clusterIndex = j; + if (_useDualQuaternionSkinning) { auto jointPose = _rig.getJointPose(cluster.jointIndex); Transform jointTransform(jointPose.rot(), jointPose.scale(), jointPose.trans()); Transform clusterTransform; - Transform::mult(clusterTransform, jointTransform, cluster.inverseBindTransform); + Transform::mult(clusterTransform, jointTransform, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindTransform); state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(clusterTransform); state.clusterDualQuaternions[j].setCauterizationParameters(0.0f, jointPose.trans()); } else { auto jointMatrix = _rig.getJointTransform(cluster.jointIndex); - glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]); + glm_mat4u_mul(jointMatrix, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindMatrix, state.clusterMatrices[j]); } } } @@ -146,9 +150,11 @@ void CauterizedModel::updateClusterMatrices() { for (int i = 0; i < _cauterizeMeshStates.size(); i++) { Model::MeshState& state = _cauterizeMeshStates[i]; const HFMMesh& mesh = hfmModel.meshes.at(i); + int meshIndex = i; for (int j = 0; j < mesh.clusters.size(); j++) { const HFMCluster& cluster = mesh.clusters.at(j); + int clusterIndex = j; if (_useDualQuaternionSkinning) { if (_cauterizeBoneSet.find(cluster.jointIndex) == _cauterizeBoneSet.end()) { @@ -157,7 +163,7 @@ void CauterizedModel::updateClusterMatrices() { } else { Transform jointTransform(cauterizePose.rot(), cauterizePose.scale(), cauterizePose.trans()); Transform clusterTransform; - Transform::mult(clusterTransform, jointTransform, cluster.inverseBindTransform); + Transform::mult(clusterTransform, jointTransform, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindTransform); state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(clusterTransform); state.clusterDualQuaternions[j].setCauterizationParameters(1.0f, cauterizePose.trans()); } @@ -166,7 +172,7 @@ void CauterizedModel::updateClusterMatrices() { // not cauterized so just copy the value from the non-cauterized version. state.clusterMatrices[j] = _meshStates[i].clusterMatrices[j]; } else { - glm_mat4u_mul(cauterizeMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]); + glm_mat4u_mul(cauterizeMatrix, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindMatrix, state.clusterMatrices[j]); } } } diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 7da7a45e83..9cefbf65a8 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1418,18 +1418,21 @@ void Model::updateClusterMatrices() { const HFMModel& hfmModel = getHFMModel(); for (int i = 0; i < (int) _meshStates.size(); i++) { MeshState& state = _meshStates[i]; + int meshIndex = i; const HFMMesh& mesh = hfmModel.meshes.at(i); for (int j = 0; j < mesh.clusters.size(); j++) { const HFMCluster& cluster = mesh.clusters.at(j); + int clusterIndex = j; + if (_useDualQuaternionSkinning) { auto jointPose = _rig.getJointPose(cluster.jointIndex); Transform jointTransform(jointPose.rot(), jointPose.scale(), jointPose.trans()); Transform clusterTransform; - Transform::mult(clusterTransform, jointTransform, cluster.inverseBindTransform); + Transform::mult(clusterTransform, jointTransform, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindTransform); state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(clusterTransform); } else { auto jointMatrix = _rig.getJointTransform(cluster.jointIndex); - glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]); + glm_mat4u_mul(jointMatrix, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindMatrix, state.clusterMatrices[j]); } } } diff --git a/libraries/render-utils/src/SoftAttachmentModel.cpp b/libraries/render-utils/src/SoftAttachmentModel.cpp index f26bad86b0..a6f82e1417 100644 --- a/libraries/render-utils/src/SoftAttachmentModel.cpp +++ b/libraries/render-utils/src/SoftAttachmentModel.cpp @@ -46,32 +46,25 @@ void SoftAttachmentModel::updateClusterMatrices() { for (int i = 0; i < (int) _meshStates.size(); i++) { MeshState& state = _meshStates[i]; const HFMMesh& mesh = hfmModel.meshes.at(i); - + int meshIndex = i; for (int j = 0; j < mesh.clusters.size(); j++) { const HFMCluster& cluster = mesh.clusters.at(j); + int clusterIndex = j; // TODO: cache these look-ups as an optimization int jointIndexOverride = getJointIndexOverride(cluster.jointIndex); + glm::mat4 jointMatrix; + if (jointIndexOverride >= 0 && jointIndexOverride < _rigOverride.getJointStateCount()) { + jointMatrix = _rigOverride.getJointTransform(jointIndexOverride); + } else { + jointMatrix = _rig.getJointTransform(cluster.jointIndex); + } if (_useDualQuaternionSkinning) { - glm::mat4 jointMatrix; - if (jointIndexOverride >= 0 && jointIndexOverride < _rigOverride.getJointStateCount()) { - jointMatrix = _rigOverride.getJointTransform(jointIndexOverride); - } else { - jointMatrix = _rig.getJointTransform(cluster.jointIndex); - } - glm::mat4 m; - glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, m); + glm_mat4u_mul(jointMatrix, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindMatrix, m); state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(m); } else { - glm::mat4 jointMatrix; - if (jointIndexOverride >= 0 && jointIndexOverride < _rigOverride.getJointStateCount()) { - jointMatrix = _rigOverride.getJointTransform(jointIndexOverride); - } else { - jointMatrix = _rig.getJointTransform(cluster.jointIndex); - } - - glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]); + glm_mat4u_mul(jointMatrix, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindMatrix, state.clusterMatrices[j]); } } }