diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index fd4b64ee5d..e7638cc56d 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -423,6 +423,19 @@ void MyAvatar::setGravity(const glm::vec3& gravity) { } } +AnimationHandlePointer MyAvatar::addAnimationHandle() { + AnimationHandlePointer handle = _skeletonModel.createAnimationHandle(); + handle->setLoop(true); + handle->start(); + _animationHandles.append(handle); + return handle; +} + +void MyAvatar::removeAnimationHandle(const AnimationHandlePointer& handle) { + handle->stop(); + _animationHandles.removeOne(handle); +} + void MyAvatar::saveData(QSettings* settings) { settings->beginGroup("Avatar"); @@ -461,12 +474,12 @@ void MyAvatar::saveData(QSettings* settings) { } settings->endArray(); - settings->beginWriteArray("animationData"); - for (int i = 0; i < _animationData.size(); i++) { + settings->beginWriteArray("animationHandles"); + for (int i = 0; i < _animationHandles.size(); i++) { settings->setArrayIndex(i); - const AnimationData& animation = _animationData.at(i); - settings->setValue("url", animation.url); - settings->setValue("fps", animation.fps); + const AnimationHandlePointer& pointer = _animationHandles.at(i); + settings->setValue("url", pointer->getURL()); + settings->setValue("fps", pointer->getFPS()); } settings->endArray(); @@ -520,17 +533,20 @@ void MyAvatar::loadData(QSettings* settings) { settings->endArray(); setAttachmentData(attachmentData); - QVector animationData; - int animationCount = settings->beginReadArray("animationData"); + int animationCount = settings->beginReadArray("animationHandles"); + while (_animationHandles.size() > animationCount) { + _animationHandles.takeLast()->stop(); + } + while (_animationHandles.size() < animationCount) { + addAnimationHandle(); + } for (int i = 0; i < animationCount; i++) { settings->setArrayIndex(i); - AnimationData animation; - animation.url = settings->value("url").toUrl(); - animation.fps = loadSetting(settings, "fps", 30.0f); - animationData.append(animation); + const AnimationHandlePointer& handle = _animationHandles.at(i); + handle->setURL(settings->value("url").toUrl()); + handle->setFPS(loadSetting(settings, "fps", 30.0f)); } settings->endArray(); - setAnimationData(animationData); setDisplayName(settings->value("displayName").toString()); @@ -598,13 +614,6 @@ AttachmentData MyAvatar::loadAttachmentData(const QUrl& modelURL, const QString& return attachment; } -void MyAvatar::setAnimationData(const QVector& animationData) { - // exit early if no change - if (_animationData != animationData) { - _animationData = animationData; - } -} - int MyAvatar::parseDataAtOffset(const QByteArray& packet, int offset) { qDebug() << "Error: ignoring update packet for MyAvatar" << " packetLength = " << packet.size() @@ -1570,10 +1579,3 @@ void MyAvatar::applyCollision(const glm::vec3& contactPoint, const glm::vec3& pe } } -AnimationData::AnimationData() : - fps(30.0f) { -} - -bool AnimationData::operator==(const AnimationData& other) const { - return url == other.url && fps == other.fps; -} diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 4e076e22bb..50b8fceca3 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -14,12 +14,8 @@ #include -#include - #include "Avatar.h" -class AnimationData; - enum AvatarHandState { HAND_STATE_NULL = 0, @@ -66,6 +62,10 @@ public: glm::vec3 getUprightHeadPosition() const; bool getShouldRenderLocally() const { return _shouldRender; } + const QList& getAnimationHandles() const { return _animationHandles; } + AnimationHandlePointer addAnimationHandle(); + void removeAnimationHandle(const AnimationHandlePointer& handle); + // get/set avatar data void saveData(QSettings* settings); void loadData(QSettings* settings); @@ -73,9 +73,6 @@ public: void saveAttachmentData(const AttachmentData& attachment) const; AttachmentData loadAttachmentData(const QUrl& modelURL, const QString& jointName = QString()) const; - void setAnimationData(const QVector& animationData); - const QVector& getAnimationData() const { return _animationData; } - // Set what driving keys are being pressed to control thrust levels void setDriveKeys(int key, float val) { _driveKeys[key] = val; }; bool getDriveKeys(int key) { return _driveKeys[key] != 0.f; }; @@ -158,16 +155,7 @@ private: bool _billboardValid; float _oculusYawOffset; - QVector _animationData; - - class AnimationState { - public: - AnimationPointer animation; - QVector jointMappings; - float frameIndex; - }; - - QVector _animationStates; + QList _animationHandles; // private methods void updateOrientation(float deltaTime); @@ -185,15 +173,4 @@ private: void setGravity(const glm::vec3& gravity); }; -/// Describes an animation being run on the avatar. -class AnimationData { -public: - QUrl url; - float fps; - - AnimationData(); - - bool operator==(const AnimationData& other) const; -}; - #endif // hifi_MyAvatar_h diff --git a/interface/src/renderer/GeometryCache.cpp b/interface/src/renderer/GeometryCache.cpp index ec68c87a76..b5bd63ab87 100644 --- a/interface/src/renderer/GeometryCache.cpp +++ b/interface/src/renderer/GeometryCache.cpp @@ -404,6 +404,22 @@ QSharedPointer NetworkGeometry::getLODOrFallback(float distance return lod; } +uint qHash(const QWeakPointer& animation, uint seed = 0) { + return qHash(animation.data(), seed); +} + +QVector NetworkGeometry::getJointMappings(const AnimationPointer& animation) { + QVector mappings = _jointMappings.value(animation); + if (mappings.isEmpty() && isLoaded() && animation && animation->isLoaded()) { + const FBXGeometry& animationGeometry = animation->getGeometry(); + for (int i = 0; i < animationGeometry.joints.size(); i++) { + mappings.append(_geometry.jointIndices.value(animationGeometry.joints.at(i).name) - 1); + } + _jointMappings.insert(animation, mappings); + } + return mappings; +} + void NetworkGeometry::setLoadPriority(const QPointer& owner, float priority) { Resource::setLoadPriority(owner, priority); diff --git a/interface/src/renderer/GeometryCache.h b/interface/src/renderer/GeometryCache.h index deecfd56c5..41bedc5e05 100644 --- a/interface/src/renderer/GeometryCache.h +++ b/interface/src/renderer/GeometryCache.h @@ -22,6 +22,8 @@ #include +#include + class Model; class NetworkGeometry; class NetworkMesh; @@ -90,6 +92,8 @@ public: const FBXGeometry& getFBXGeometry() const { return _geometry; } const QVector& getMeshes() const { return _meshes; } + QVector getJointMappings(const AnimationPointer& animation); + virtual void setLoadPriority(const QPointer& owner, float priority); virtual void setLoadPriorities(const QHash, float>& priorities); virtual void clearLoadPriority(const QPointer& owner); @@ -117,6 +121,8 @@ private: QVector _meshes; QWeakPointer _lodParent; + + QHash, QVector > _jointMappings; }; /// The state associated with a single mesh part. diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 7e1ba69e27..3914f46fe7 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -594,13 +594,15 @@ QStringList Model::getJointNames() const { return isActive() ? _geometry->getFBXGeometry().getJointNames() : QStringList(); } -void Model::startAnimation(const QUrl& url, float fps, bool loop, float offset) { - AnimationState state = { Application::getInstance()->getAnimationCache()->getAnimation(url), fps, loop, offset }; - _animationStates.append(state); +uint qHash(const WeakAnimationHandlePointer& handle, uint seed) { + return qHash(handle.data(), seed); } -void Model::stopAnimation() { - _animationStates.clear(); +AnimationHandlePointer Model::createAnimationHandle() { + AnimationHandlePointer handle(new AnimationHandle(this)); + handle->_self = handle; + _animationHandles.insert(handle); + return handle; } void Model::clearShapes() { @@ -1015,19 +1017,8 @@ void Model::simulate(float deltaTime, bool fullUpdate) { void Model::simulateInternal(float deltaTime) { // update animations - const FBXGeometry& geometry = _geometry->getFBXGeometry(); - for (int i = 0; i < _animationStates.size(); i++) { - AnimationState& state = _animationStates[i]; - if (!(state.animation && state.animation->isLoaded())) { - continue; - } - const FBXGeometry& animationGeometry = state.animation->getGeometry(); - if (state.jointMappings.isEmpty()) { - for (int j = 0; j < geometry.joints.size(); j++) { - state.jointMappings.append(animationGeometry.jointIndices.value(geometry.joints.at(j).name) - 1); - } - } - + foreach (const AnimationHandlePointer& handle, _runningAnimations) { + handle->simulate(deltaTime); } // NOTE: this is a recursive call that walks all attachments, and their attachments @@ -1038,6 +1029,7 @@ void Model::simulateInternal(float deltaTime) { _shapesAreDirty = true; // update the attachment transforms and simulate them + const FBXGeometry& geometry = _geometry->getFBXGeometry(); for (int i = 0; i < _attachments.size(); i++) { const FBXAttachment& attachment = geometry.attachments.at(i); Model* model = _attachments.at(i); @@ -1448,8 +1440,14 @@ void Model::deleteGeometry() { _meshStates.clear(); clearShapes(); - for (int i = 0; i < _animationStates.size(); i++) { - _animationStates[i].jointMappings.clear(); + for (QSet::iterator it = _animationHandles.begin(); it != _animationHandles.end(); ) { + AnimationHandlePointer handle = it->toStrongRef(); + if (handle) { + handle->_jointMappings.clear(); + it++; + } else { + it = _animationHandles.erase(it); + } } if (_geometry) { @@ -1647,3 +1645,71 @@ void Model::renderMeshes(float alpha, RenderMode mode, bool translucent) { activeProgram->release(); } } + +void AnimationHandle::setURL(const QUrl& url) { + _animation = Application::getInstance()->getAnimationCache()->getAnimation(_url = url); + _jointMappings.clear(); +} + +void AnimationHandle::start() { + if (!_model->_runningAnimations.contains(_self)) { + _model->_runningAnimations.append(_self); + } + _frameIndex = 0.0f; +} + +void AnimationHandle::stop() { + _model->_runningAnimations.removeOne(_self); +} + +AnimationHandle::AnimationHandle(Model* model) : + QObject(model), + _model(model), + _fps(30.0f), + _loop(false) { +} + +void AnimationHandle::simulate(float deltaTime) { + _frameIndex += deltaTime * _fps; + + // update the joint mappings if necessary/possible + if (_jointMappings.isEmpty()) { + if (_model->isActive()) { + _jointMappings = _model->getGeometry()->getJointMappings(_animation); + } + if (_jointMappings.isEmpty()) { + return; + } + } + + const FBXGeometry& animationGeometry = _animation->getGeometry(); + if (animationGeometry.animationFrames.isEmpty()) { + stop(); + return; + } + int ceilFrameIndex = (int)glm::ceil(_frameIndex); + if (!_loop && ceilFrameIndex >= animationGeometry.animationFrames.size()) { + const FBXAnimationFrame& frame = animationGeometry.animationFrames.last(); + for (int i = 0; i < _jointMappings.size(); i++) { + int mapping = _jointMappings.at(i); + if (mapping != -1) { + _model->_jointStates[mapping].rotation = frame.rotations.at(i); + } + } + stop(); + return; + } + const FBXAnimationFrame& ceilFrame = animationGeometry.animationFrames.at( + ceilFrameIndex % animationGeometry.animationFrames.size()); + const FBXAnimationFrame& floorFrame = animationGeometry.animationFrames.at( + (int)glm::floor(_frameIndex) % animationGeometry.animationFrames.size()); + float frameFraction = glm::fract(_frameIndex); + for (int i = 0; i < _jointMappings.size(); i++) { + int mapping = _jointMappings.at(i); + if (mapping != -1) { + _model->_jointStates[mapping].rotation = safeMix(floorFrame.rotations.at(i), + ceilFrame.rotations.at(i), frameFraction); + } + } +} + diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 5b8e640401..b8b877ac96 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -24,8 +24,12 @@ #include "ProgramObject.h" #include "TextureCache.h" +class AnimationHandle; class Shape; +typedef QSharedPointer AnimationHandlePointer; +typedef QWeakPointer WeakAnimationHandlePointer; + /// A generic 3D model displaying geometry loaded from a URL. class Model : public QObject { Q_OBJECT @@ -187,22 +191,7 @@ public: QStringList getJointNames() const; - class AnimationState { - public: - AnimationPointer animation; - float fps; - bool loop; - float offset; - QVector jointMappings; - }; - - const QVector& getAnimationStates() const { return _animationStates; } - - /// Starts playing the animation at the specified URL. - void startAnimation(const QUrl& url, float fps = 30.0f, bool loop = true, float offset = 0.0f); - - /// Stops playing all animations. - void stopAnimation(); + AnimationHandlePointer createAnimationHandle(); void clearShapes(); void rebuildShapes(); @@ -279,8 +268,6 @@ protected: QVector _meshStates; - QVector _animationStates; - // returns 'true' if needs fullUpdate after geometry change bool updateGeometry(); @@ -320,6 +307,8 @@ protected: private: + friend class AnimationHandle; + void applyNextGeometry(); void deleteGeometry(); void renderMeshes(float alpha, RenderMode mode, bool translucent); @@ -343,6 +332,10 @@ private: QVector _attachments; + QSet _animationHandles; + + QList _runningAnimations; + static ProgramObject _program; static ProgramObject _normalMapProgram; static ProgramObject _specularMapProgram; @@ -378,4 +371,40 @@ Q_DECLARE_METATYPE(QPointer) Q_DECLARE_METATYPE(QWeakPointer) Q_DECLARE_METATYPE(QVector) +/// Represents a handle to a model animation. +class AnimationHandle : public QObject { + Q_OBJECT + +public: + + void setURL(const QUrl& url); + const QUrl& getURL() const { return _url; } + + void setFPS(float fps) { _fps = fps; } + float getFPS() const { return _fps; } + + void setLoop(bool loop) { _loop = loop; } + bool getLoop() const { return _loop; } + + void start(); + void stop(); + +private: + + friend class Model; + + AnimationHandle(Model* model); + + void simulate(float deltaTime); + + Model* _model; + WeakAnimationHandlePointer _self; + AnimationPointer _animation; + QUrl _url; + float _fps; + bool _loop; + QVector _jointMappings; + float _frameIndex; +}; + #endif // hifi_Model_h diff --git a/interface/src/ui/AnimationsDialog.cpp b/interface/src/ui/AnimationsDialog.cpp index 5285c294ab..2beea24578 100644 --- a/interface/src/ui/AnimationsDialog.cpp +++ b/interface/src/ui/AnimationsDialog.cpp @@ -39,8 +39,8 @@ AnimationsDialog::AnimationsDialog() : area->setWidget(container); _animations->addStretch(1); - foreach (const AnimationData& data, Application::getInstance()->getAvatar()->getAnimationData()) { - addAnimation(data); + foreach (const AnimationHandlePointer& handle, Application::getInstance()->getAvatar()->getAnimationHandles()) { + _animations->insertWidget(_animations->count() - 1, new AnimationPanel(this, handle)); } QPushButton* newAnimation = new QPushButton("New Animation"); @@ -64,20 +64,14 @@ void AnimationsDialog::setVisible(bool visible) { } } -void AnimationsDialog::updateAnimationData() { - QVector data; - for (int i = 0; i < _animations->count() - 1; i++) { - data.append(static_cast(_animations->itemAt(i)->widget())->getAnimationData()); - } - Application::getInstance()->getAvatar()->setAnimationData(data); +void AnimationsDialog::addAnimation() { + _animations->insertWidget(_animations->count() - 1, new AnimationPanel( + this, Application::getInstance()->getAvatar()->addAnimationHandle())); } -void AnimationsDialog::addAnimation(const AnimationData& data) { - _animations->insertWidget(_animations->count() - 1, new AnimationPanel(this, data)); -} - -AnimationPanel::AnimationPanel(AnimationsDialog* dialog, const AnimationData& data) : +AnimationPanel::AnimationPanel(AnimationsDialog* dialog, const AnimationHandlePointer& handle) : _dialog(dialog), + _handle(handle), _applying(false) { setFrameStyle(QFrame::StyledPanel); @@ -87,8 +81,8 @@ AnimationPanel::AnimationPanel(AnimationsDialog* dialog, const AnimationData& da QHBoxLayout* urlBox = new QHBoxLayout(); layout->addRow("URL:", urlBox); - urlBox->addWidget(_url = new QLineEdit(data.url.toString()), 1); - dialog->connect(_url, SIGNAL(returnPressed()), SLOT(updateAnimationData())); + urlBox->addWidget(_url = new QLineEdit(handle->getURL().toString()), 1); + connect(_url, SIGNAL(returnPressed()), SLOT(updateHandle())); QPushButton* chooseURL = new QPushButton("Choose"); urlBox->addWidget(chooseURL); connect(chooseURL, SIGNAL(clicked(bool)), SLOT(chooseURL())); @@ -96,20 +90,12 @@ AnimationPanel::AnimationPanel(AnimationsDialog* dialog, const AnimationData& da layout->addRow("FPS:", _fps = new QDoubleSpinBox()); _fps->setSingleStep(0.01); _fps->setMaximum(FLT_MAX); - _fps->setValue(data.fps); - dialog->connect(_fps, SIGNAL(valueChanged(double)), SLOT(updateAnimationData())); + _fps->setValue(handle->getFPS()); + connect(_fps, SIGNAL(valueChanged(double)), SLOT(updateHandle())); QPushButton* remove = new QPushButton("Delete"); layout->addRow(remove); - connect(remove, SIGNAL(clicked(bool)), SLOT(deleteLater())); - dialog->connect(remove, SIGNAL(clicked(bool)), SLOT(updateAnimationData()), Qt::QueuedConnection); -} - -AnimationData AnimationPanel::getAnimationData() const { - AnimationData data; - data.url = _url->text(); - data.fps = _fps->value(); - return data; + connect(remove, SIGNAL(clicked(bool)), SLOT(removeHandle())); } void AnimationPanel::chooseURL() { @@ -125,3 +111,12 @@ void AnimationPanel::chooseURL() { emit _url->returnPressed(); } +void AnimationPanel::updateHandle() { + _handle->setURL(_url->text()); + _handle->setFPS(_fps->value()); +} + +void AnimationPanel::removeHandle() { + Application::getInstance()->getAvatar()->removeAnimationHandle(_handle); + deleteLater(); +} diff --git a/interface/src/ui/AnimationsDialog.h b/interface/src/ui/AnimationsDialog.h index 3d969aa32a..d53b3dd6a5 100644 --- a/interface/src/ui/AnimationsDialog.h +++ b/interface/src/ui/AnimationsDialog.h @@ -32,14 +32,10 @@ public: virtual void setVisible(bool visible); -public slots: - - void updateAnimationData(); - private slots: - - void addAnimation(const AnimationData& animation = AnimationData()); - + + void addAnimation(); + private: QVBoxLayout* _animations; @@ -52,17 +48,18 @@ class AnimationPanel : public QFrame { public: - AnimationPanel(AnimationsDialog* dialog, const AnimationData& data = AnimationData()); - - AnimationData getAnimationData() const; + AnimationPanel(AnimationsDialog* dialog, const AnimationHandlePointer& handle); private slots: void chooseURL(); + void updateHandle(); + void removeHandle(); private: AnimationsDialog* _dialog; + AnimationHandlePointer _handle; QLineEdit* _url; QDoubleSpinBox* _fps; bool _applying;