diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index ca5ca54144..713115bffc 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -9,6 +9,9 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include +#include + #include "ModelOverlay.h" #include @@ -60,6 +63,15 @@ void ModelOverlay::update(float deltatime) { _model->simulate(deltatime); } _isLoaded = _model->isActive(); + + + if (isAnimatingSomething()) { + if (!jointsMapped()) { + mapAnimationJoints(_model->getJointNames()); + } + animate(); + } + } bool ModelOverlay::addToScene(Overlay::Pointer overlay, const render::ScenePointer& scene, render::Transaction& transaction) { @@ -172,6 +184,51 @@ void ModelOverlay::setProperties(const QVariantMap& properties) { } _updateModel = true; } + + auto animationSettings = properties["animationSettings"]; + if (animationSettings.canConvert(QVariant::Map)) { + QVariantMap animationSettingsMap = animationSettings.toMap(); + + auto animationURL = animationSettingsMap["url"]; + auto animationFPS = animationSettingsMap["fps"]; + auto animationCurrentFrame = animationSettingsMap["currentFrame"]; + auto animationFirstFrame = animationSettingsMap["firstFrame"]; + auto animationLastFrame = animationSettingsMap["lastFrame"]; + auto animationRunning = animationSettingsMap["running"]; + auto animationLoop = animationSettingsMap["loop"]; + auto animationHold = animationSettingsMap["hold"]; + auto animationAllowTranslation = animationSettingsMap["allowTranslation"]; + + if (animationURL.canConvert(QVariant::Url)) { + _animationURL = animationURL.toUrl(); + } + if (animationFPS.isValid()) { + _animationFPS = animationFPS.toFloat(); + } + if (animationCurrentFrame.isValid()) { + _animationCurrentFrame = animationCurrentFrame.toFloat(); + } + if (animationFirstFrame.isValid()) { + _animationFirstFrame = animationFirstFrame.toFloat(); + } + if (animationLastFrame.isValid()) { + _animationLastFrame = animationLastFrame.toFloat(); + } + + if (animationRunning.canConvert(QVariant::Bool)) { + _animationRunning = animationRunning.toBool(); + } + if (animationLoop.canConvert(QVariant::Bool)) { + _animationLoop = animationLoop.toBool(); + } + if (animationHold.canConvert(QVariant::Bool)) { + _animationHold = animationHold.toBool(); + } + if (animationAllowTranslation.canConvert(QVariant::Bool)) { + _animationAllowTranslation = animationAllowTranslation.toBool(); + } + + } } template @@ -259,6 +316,24 @@ QVariant ModelOverlay::getProperty(const QString& property) { }); } + // animation properties + if (property == "animationSettings") { + QVariantMap animationSettingsMap; + + animationSettingsMap["url"] = _animationURL; + animationSettingsMap["fps"] = _animationFPS; + animationSettingsMap["currentFrame"] = _animationCurrentFrame; + animationSettingsMap["firstFrame"] = _animationFirstFrame; + animationSettingsMap["lastFrame"] = _animationLastFrame; + animationSettingsMap["running"] = _animationRunning; + animationSettingsMap["loop"] = _animationLoop; + animationSettingsMap["hold"]= _animationHold; + animationSettingsMap["allowTranslation"] = _animationAllowTranslation; + + return animationSettingsMap; + } + + return Volume3DOverlay::getProperty(property); } @@ -301,3 +376,138 @@ QString ModelOverlay::getName() const { } return QString("Overlay:") + getType() + ":" + _url.toString(); } + + +void ModelOverlay::animate() { + + if (!_animation || !_animation->isLoaded() || !_model || !_model->isLoaded()) { + return; + } + + + QVector jointsData; + + const QVector& frames = _animation->getFramesReference(); // NOTE: getFrames() is too heavy + int frameCount = frames.size(); + if (frameCount <= 0) { + return; + } + + if (!_lastAnimated) { + _lastAnimated = usecTimestampNow(); + return; + } + + auto now = usecTimestampNow(); + auto interval = now - _lastAnimated; + _lastAnimated = now; + float deltaTime = (float)interval / (float)USECS_PER_SECOND; + _animationCurrentFrame += (deltaTime * _animationFPS); + + { + int animationCurrentFrame = (int)(glm::floor(_animationCurrentFrame)) % frameCount; + if (animationCurrentFrame < 0 || animationCurrentFrame > frameCount) { + animationCurrentFrame = 0; + } + + if (animationCurrentFrame == _lastKnownCurrentFrame) { + return; + } + _lastKnownCurrentFrame = animationCurrentFrame; + } + + if (_jointMapping.size() != _model->getJointStateCount()) { + return; + } + + QStringList animationJointNames = _animation->getGeometry().getJointNames(); + auto& fbxJoints = _animation->getGeometry().joints; + + auto& originalFbxJoints = _model->getFBXGeometry().joints; + auto& originalFbxIndices = _model->getFBXGeometry().jointIndices; + + const QVector& rotations = frames[_lastKnownCurrentFrame].rotations; + const QVector& translations = frames[_lastKnownCurrentFrame].translations; + + jointsData.resize(_jointMapping.size()); + for (int j = 0; j < _jointMapping.size(); j++) { + int index = _jointMapping[j]; + + if (index >= 0) { + glm::mat4 translationMat; + + if (_animationAllowTranslation) { + if (index < translations.size()) { + translationMat = glm::translate(translations[index]); + } + } + else if (index < animationJointNames.size()) { + QString jointName = fbxJoints[index].name; + + if (originalFbxIndices.contains(jointName)) { + // Making sure the joint names exist in the original model the animation is trying to apply onto. If they do, then remap and get it's translation. + int remappedIndex = originalFbxIndices[jointName] - 1; // JointIndeces seem to always start from 1 and the found index is always 1 higher than actual. + translationMat = glm::translate(originalFbxJoints[remappedIndex].translation); + } + } + glm::mat4 rotationMat; + if (index < rotations.size()) { + rotationMat = glm::mat4_cast(fbxJoints[index].preRotation * rotations[index] * fbxJoints[index].postRotation); + } + else { + rotationMat = glm::mat4_cast(fbxJoints[index].preRotation * fbxJoints[index].postRotation); + } + + glm::mat4 finalMat = (translationMat * fbxJoints[index].preTransform * + rotationMat * fbxJoints[index].postTransform); + auto& jointData = jointsData[j]; + jointData.translation = extractTranslation(finalMat); + jointData.translationSet = true; + jointData.rotation = glmExtractRotation(finalMat); + jointData.rotationSet = true; + } + } + // Set the data in the model + copyAnimationJointDataToModel(jointsData); +} + + +void ModelOverlay::mapAnimationJoints(const QStringList& modelJointNames) { + + // if we don't have animation, or we're already joint mapped then bail early + if (!hasAnimation() || jointsMapped()) { + return; + } + + if (!_animation || _animation->getURL() != _animationURL) { + _animation = DependencyManager::get()->getAnimation(_animationURL); + } + + if (_animation && _animation->isLoaded()) { + QStringList animationJointNames = _animation->getJointNames(); + + if (modelJointNames.size() > 0 && animationJointNames.size() > 0) { + _jointMapping.resize(modelJointNames.size()); + for (int i = 0; i < modelJointNames.size(); i++) { + _jointMapping[i] = animationJointNames.indexOf(modelJointNames[i]); + } + _jointMappingCompleted = true; + _jointMappingURL = _animationURL; + } + } +} + +void ModelOverlay::copyAnimationJointDataToModel(QVector jointsData) { + if (!_model || !_model->isLoaded()) { + return; + } + + // relay any inbound joint changes from scripts/animation/network to the model/rig + for (int index = 0; index < jointsData.size(); ++index) { + auto& jointData = jointsData[index]; + _model->setJointRotation(index, true, jointData.rotation, 1.0f); + _model->setJointTranslation(index, true, jointData.translation, 1.0f); + } + _updateModel = true; +} + diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h index 8d8429b29e..edee4f7ac6 100644 --- a/interface/src/ui/overlays/ModelOverlay.h +++ b/interface/src/ui/overlays/ModelOverlay.h @@ -13,6 +13,7 @@ #define hifi_ModelOverlay_h #include +#include #include "Volume3DOverlay.h" @@ -45,6 +46,9 @@ public: float getLoadPriority() const { return _loadPriority; } + bool hasAnimation() const { return !_animationURL.isEmpty(); } + bool jointsMapped() const { return _jointMappingURL == _animationURL && _jointMappingCompleted; } + protected: Transform evalRenderTransform() override; @@ -53,6 +57,14 @@ protected: template vectorType mapJoints(mapFunction function) const; + void animate(); + void mapAnimationJoints(const QStringList& modelJointNames); + bool isAnimatingSomething() const { + return !_animationURL.isEmpty() && _animationRunning && _animationFPS != 0.0f; + } + void copyAnimationJointDataToModel(QVector jointsData); + + private: ModelPointer _model; @@ -62,6 +74,25 @@ private: bool _updateModel = { false }; bool _scaleToFit = { false }; float _loadPriority { 0.0f }; + + AnimationPointer _animation; + + QUrl _animationURL; + float _animationFPS { 0.0f }; + float _animationCurrentFrame { 0.0f }; + bool _animationRunning { false }; + bool _animationLoop { false }; + float _animationFirstFrame { 0.0f }; + float _animationLastFrame = { 0.0f }; + bool _animationHold { false }; + bool _animationAllowTranslation { false }; + uint64_t _lastAnimated { 0 }; + int _lastKnownCurrentFrame { -1 }; + + QUrl _jointMappingURL; + bool _jointMappingCompleted { false }; + QVector _jointMapping; // domain is index into model-joints, range is index into animation-joints + }; #endif // hifi_ModelOverlay_h