Merge pull request #11481 from ZappoMan/animatedOverlays

support animation of model overlays
This commit is contained in:
Seth Alves 2017-09-29 10:26:24 -07:00 committed by GitHub
commit e7705175a3
2 changed files with 237 additions and 0 deletions

View file

@ -9,6 +9,9 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <glm/gtx/quaternion.hpp>
#include <glm/gtx/transform.hpp>
#include "ModelOverlay.h"
#include <Rig.h>
@ -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 <typename vectorType, typename itemType>
@ -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,134 @@ QString ModelOverlay::getName() const {
}
return QString("Overlay:") + getType() + ":" + _url.toString();
}
void ModelOverlay::animate() {
if (!_animation || !_animation->isLoaded() || !_model || !_model->isLoaded()) {
return;
}
QVector<JointData> jointsData;
const QVector<FBXAnimationFrame>& 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<glm::quat>& rotations = frames[_lastKnownCurrentFrame].rotations;
const QVector<glm::vec3>& 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 its 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<AnimationCache>()->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<JointData> 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;
}

View file

@ -13,6 +13,7 @@
#define hifi_ModelOverlay_h
#include <Model.h>
#include <AnimationCache.h>
#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 <typename vectorType, typename itemType>
vectorType mapJoints(mapFunction<itemType> function) const;
void animate();
void mapAnimationJoints(const QStringList& modelJointNames);
bool isAnimatingSomething() const {
return !_animationURL.isEmpty() && _animationRunning && _animationFPS != 0.0f;
}
void copyAnimationJointDataToModel(QVector<JointData> 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<int> _jointMapping; // domain is index into model-joints, range is index into animation-joints
};
#endif // hifi_ModelOverlay_h