From 5da656e3f56b5681c2b41b9ef5949eb47869d22c Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Mon, 19 May 2014 16:55:33 -0700 Subject: [PATCH 1/8] Working on animation dialog. --- interface/src/Menu.cpp | 11 +++ interface/src/Menu.h | 4 + interface/src/avatar/MyAvatar.cpp | 29 ++++++ interface/src/avatar/MyAvatar.h | 16 ++++ interface/src/ui/AnimationsDialog.cpp | 127 ++++++++++++++++++++++++++ interface/src/ui/AnimationsDialog.h | 71 ++++++++++++++ 6 files changed, 258 insertions(+) create mode 100644 interface/src/ui/AnimationsDialog.cpp create mode 100644 interface/src/ui/AnimationsDialog.h diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index bff08d5221..998f8acf72 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -37,6 +37,7 @@ #include "Menu.h" #include "scripting/MenuScriptingInterface.h" #include "Util.h" +#include "ui/AnimationsDialog.h" #include "ui/AttachmentsDialog.h" #include "ui/InfoView.h" #include "ui/MetavoxelEditor.h" @@ -193,6 +194,7 @@ Menu::Menu() : QAction::PreferencesRole); addActionToQMenuAndActionHash(editMenu, MenuOption::Attachments, 0, this, SLOT(editAttachments())); + addActionToQMenuAndActionHash(editMenu, MenuOption::Animations, 0, this, SLOT(editAnimations())); addDisabledActionAndSeparator(editMenu, "Physics"); QObject* avatar = appInstance->getAvatar(); @@ -862,6 +864,15 @@ void Menu::editAttachments() { } } +void Menu::editAnimations() { + if (!_animationsDialog) { + _animationsDialog = new AnimationsDialog(); + _animationsDialog->show(); + } else { + _animationsDialog->close(); + } +} + void Menu::goToDomain(const QString newDomain) { if (NodeList::getInstance()->getDomainHandler().getHostname() != newDomain) { // send a node kill request, indicating to other clients that they should play the "disappeared" effect diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 0a21a27960..c881ab05b0 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -64,6 +64,7 @@ struct ViewFrustumOffset { class QSettings; +class AnimationsDialog; class AttachmentsDialog; class BandwidthDialog; class LodToolsDialog; @@ -176,6 +177,7 @@ private slots: void aboutApp(); void editPreferences(); void editAttachments(); + void editAnimations(); void goToDomainDialog(); void goToLocation(); void nameLocation(); @@ -260,6 +262,7 @@ private: QAction* _loginAction; QPointer _preferencesDialog; QPointer _attachmentsDialog; + QPointer _animationsDialog; QAction* _chatAction; QString _snapshotsLocation; }; @@ -269,6 +272,7 @@ namespace MenuOption { const QString AlignForearmsWithWrists = "Align Forearms with Wrists"; const QString AlternateIK = "Alternate IK"; const QString AmbientOcclusion = "Ambient Occlusion"; + const QString Animations = "Animations..."; const QString Atmosphere = "Atmosphere"; const QString Attachments = "Attachments..."; const QString AudioNoiseReduction = "Audio Noise Reduction"; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 7c051d8984..a7a5b4e985 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -461,6 +461,15 @@ void MyAvatar::saveData(QSettings* settings) { } settings->endArray(); + settings->beginWriteArray("animationData"); + for (int i = 0; i < _animationData.size(); i++) { + settings->setArrayIndex(i); + const AnimationData& animation = _animationData.at(i); + settings->setValue("url", animation.url); + settings->setValue("fps", animation.fps); + } + settings->endArray(); + settings->setValue("displayName", _displayName); settings->endGroup(); @@ -511,6 +520,18 @@ void MyAvatar::loadData(QSettings* settings) { settings->endArray(); setAttachmentData(attachmentData); + QVector animationData; + int animationCount = settings->beginReadArray("animationData"); + 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); + } + settings->endArray(); + setAnimationData(animationData); + setDisplayName(settings->value("displayName").toString()); settings->endGroup(); @@ -577,6 +598,10 @@ AttachmentData MyAvatar::loadAttachmentData(const QUrl& modelURL, const QString& return attachment; } +void MyAvatar::setAnimationData(const QVector& animationData) { + _animationData = animationData; +} + int MyAvatar::parseDataAtOffset(const QByteArray& packet, int offset) { qDebug() << "Error: ignoring update packet for MyAvatar" << " packetLength = " << packet.size() @@ -1541,3 +1566,7 @@ void MyAvatar::applyCollision(const glm::vec3& contactPoint, const glm::vec3& pe getHead()->addLeanDeltas(sideways, forward); } } + +AnimationData::AnimationData() : + fps(30.0f) { +} diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 2e75ac984d..92dc447e56 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -16,6 +16,8 @@ #include "Avatar.h" +class AnimationData; + enum AvatarHandState { HAND_STATE_NULL = 0, @@ -69,6 +71,9 @@ 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; }; @@ -151,6 +156,8 @@ private: bool _billboardValid; float _oculusYawOffset; + QVector _animationData; + // private methods void updateOrientation(float deltaTime); void updateMotorFromKeyboard(float deltaTime, bool walking); @@ -167,4 +174,13 @@ private: void setGravity(const glm::vec3& gravity); }; +/// Describes an animation being run on the avatar. +class AnimationData { +public: + QUrl url; + float fps; + + AnimationData(); +}; + #endif // hifi_MyAvatar_h diff --git a/interface/src/ui/AnimationsDialog.cpp b/interface/src/ui/AnimationsDialog.cpp new file mode 100644 index 0000000000..5285c294ab --- /dev/null +++ b/interface/src/ui/AnimationsDialog.cpp @@ -0,0 +1,127 @@ +// +// AnimationsDialog.cpp +// interface/src/ui +// +// Created by Andrzej Kapolka on 5/19/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "AnimationsDialog.h" +#include "Application.h" + +AnimationsDialog::AnimationsDialog() : + QDialog(Application::getInstance()->getWindow()) { + + setWindowTitle("Edit Animations"); + setAttribute(Qt::WA_DeleteOnClose); + + QVBoxLayout* layout = new QVBoxLayout(); + setLayout(layout); + + QScrollArea* area = new QScrollArea(); + layout->addWidget(area); + area->setWidgetResizable(true); + QWidget* container = new QWidget(); + container->setLayout(_animations = new QVBoxLayout()); + container->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred); + area->setWidget(container); + _animations->addStretch(1); + + foreach (const AnimationData& data, Application::getInstance()->getAvatar()->getAnimationData()) { + addAnimation(data); + } + + QPushButton* newAnimation = new QPushButton("New Animation"); + connect(newAnimation, SIGNAL(clicked(bool)), SLOT(addAnimation())); + layout->addWidget(newAnimation); + + QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok); + layout->addWidget(buttons); + connect(buttons, SIGNAL(accepted()), SLOT(deleteLater())); + _ok = buttons->button(QDialogButtonBox::Ok); + + setMinimumSize(600, 600); +} + +void AnimationsDialog::setVisible(bool visible) { + QDialog::setVisible(visible); + + // un-default the OK button + if (visible) { + _ok->setDefault(false); + } +} + +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(const AnimationData& data) { + _animations->insertWidget(_animations->count() - 1, new AnimationPanel(this, data)); +} + +AnimationPanel::AnimationPanel(AnimationsDialog* dialog, const AnimationData& data) : + _dialog(dialog), + _applying(false) { + setFrameStyle(QFrame::StyledPanel); + + QFormLayout* layout = new QFormLayout(); + layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); + setLayout(layout); + + QHBoxLayout* urlBox = new QHBoxLayout(); + layout->addRow("URL:", urlBox); + urlBox->addWidget(_url = new QLineEdit(data.url.toString()), 1); + dialog->connect(_url, SIGNAL(returnPressed()), SLOT(updateAnimationData())); + QPushButton* chooseURL = new QPushButton("Choose"); + urlBox->addWidget(chooseURL); + connect(chooseURL, SIGNAL(clicked(bool)), SLOT(chooseURL())); + + 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())); + + 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; +} + +void AnimationPanel::chooseURL() { + QString directory = Application::getInstance()->lockSettings()->value("animation_directory").toString(); + Application::getInstance()->unlockSettings(); + QString filename = QFileDialog::getOpenFileName(this, "Choose Animation", directory, "Animation files (*.fbx)"); + if (filename.isEmpty()) { + return; + } + Application::getInstance()->lockSettings()->setValue("animation_directory", QFileInfo(filename).path()); + Application::getInstance()->unlockSettings(); + _url->setText(QUrl::fromLocalFile(filename).toString()); + emit _url->returnPressed(); +} + diff --git a/interface/src/ui/AnimationsDialog.h b/interface/src/ui/AnimationsDialog.h new file mode 100644 index 0000000000..3d969aa32a --- /dev/null +++ b/interface/src/ui/AnimationsDialog.h @@ -0,0 +1,71 @@ +// +// AnimationsDialog.h +// interface/src/ui +// +// Created by Andrzej Kapolka on 5/19/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_AnimationsDialog_h +#define hifi_AnimationsDialog_h + +#include +#include + +#include "avatar/MyAvatar.h" + +class QDoubleSpinner; +class QLineEdit; +class QPushButton; +class QVBoxLayout; + +/// Allows users to edit the avatar animations. +class AnimationsDialog : public QDialog { + Q_OBJECT + +public: + + AnimationsDialog(); + + virtual void setVisible(bool visible); + +public slots: + + void updateAnimationData(); + +private slots: + + void addAnimation(const AnimationData& animation = AnimationData()); + +private: + + QVBoxLayout* _animations; + QPushButton* _ok; +}; + +/// A panel controlling a single animation. +class AnimationPanel : public QFrame { + Q_OBJECT + +public: + + AnimationPanel(AnimationsDialog* dialog, const AnimationData& data = AnimationData()); + + AnimationData getAnimationData() const; + +private slots: + + void chooseURL(); + +private: + + AnimationsDialog* _dialog; + QLineEdit* _url; + QDoubleSpinBox* _fps; + bool _applying; +}; + +#endif // hifi_AnimationsDialog_h From 62e7a31602448df9713171b9ab90c97e9af355b2 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Mon, 19 May 2014 18:09:33 -0700 Subject: [PATCH 2/8] More work on animation configuration. --- interface/src/Application.h | 1 + interface/src/avatar/MyAvatar.cpp | 9 ++++++++- interface/src/avatar/MyAvatar.h | 13 +++++++++++++ interface/src/renderer/Model.cpp | 30 ++++++++++++++++++++++++++++-- interface/src/renderer/Model.h | 21 +++++++++++++++++++++ 5 files changed, 71 insertions(+), 3 deletions(-) diff --git a/interface/src/Application.h b/interface/src/Application.h index 5460093cbd..1968ef4fee 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -217,6 +217,7 @@ public: QNetworkAccessManager* getNetworkAccessManager() { return _networkAccessManager; } GeometryCache* getGeometryCache() { return &_geometryCache; } + AnimationCache* getAnimationCache() { return &_animationCache; } TextureCache* getTextureCache() { return &_textureCache; } GlowEffect* getGlowEffect() { return &_glowEffect; } ControllerScriptingInterface* getControllerScriptingInterface() { return &_controllerScriptingInterface; } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index a7a5b4e985..fd4b64ee5d 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -599,7 +599,10 @@ AttachmentData MyAvatar::loadAttachmentData(const QUrl& modelURL, const QString& } void MyAvatar::setAnimationData(const QVector& animationData) { - _animationData = animationData; + // exit early if no change + if (_animationData != animationData) { + _animationData = animationData; + } } int MyAvatar::parseDataAtOffset(const QByteArray& packet, int offset) { @@ -1570,3 +1573,7 @@ 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 92dc447e56..4e076e22bb 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -14,6 +14,8 @@ #include +#include + #include "Avatar.h" class AnimationData; @@ -158,6 +160,15 @@ private: QVector _animationData; + class AnimationState { + public: + AnimationPointer animation; + QVector jointMappings; + float frameIndex; + }; + + QVector _animationStates; + // private methods void updateOrientation(float deltaTime); void updateMotorFromKeyboard(float deltaTime, bool walking); @@ -181,6 +192,8 @@ public: float fps; AnimationData(); + + bool operator==(const AnimationData& other) const; }; #endif // hifi_MyAvatar_h diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 90ae9e5e46..7e1ba69e27 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -594,6 +594,14 @@ 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); +} + +void Model::stopAnimation() { + _animationStates.clear(); +} void Model::clearShapes() { for (int i = 0; i < _jointShapes.size(); ++i) { @@ -1006,6 +1014,22 @@ 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); + } + } + + } + // NOTE: this is a recursive call that walks all attachments, and their attachments // update the world space transforms for all joints for (int i = 0; i < _jointStates.size(); i++) { @@ -1013,8 +1037,6 @@ void Model::simulateInternal(float deltaTime) { } _shapesAreDirty = true; - const FBXGeometry& geometry = _geometry->getFBXGeometry(); - // update the attachment transforms and simulate them for (int i = 0; i < _attachments.size(); i++) { const FBXAttachment& attachment = geometry.attachments.at(i); @@ -1426,6 +1448,10 @@ void Model::deleteGeometry() { _meshStates.clear(); clearShapes(); + for (int i = 0; i < _animationStates.size(); i++) { + _animationStates[i].jointMappings.clear(); + } + if (_geometry) { _geometry->clearLoadPriority(this); } diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 5b2839baa2..5b8e640401 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -17,6 +17,8 @@ #include +#include + #include "GeometryCache.h" #include "InterfaceConfig.h" #include "ProgramObject.h" @@ -185,6 +187,23 @@ 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(); + void clearShapes(); void rebuildShapes(); void resetShapePositions(); @@ -260,6 +279,8 @@ protected: QVector _meshStates; + QVector _animationStates; + // returns 'true' if needs fullUpdate after geometry change bool updateGeometry(); From 49a06456774b9c147ee3564ba3d976b1d5159fa7 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 20 May 2014 15:23:15 -0700 Subject: [PATCH 3/8] Animation bits. --- interface/src/avatar/MyAvatar.cpp | 54 ++++++------ interface/src/avatar/MyAvatar.h | 33 ++----- interface/src/renderer/GeometryCache.cpp | 16 ++++ interface/src/renderer/GeometryCache.h | 6 ++ interface/src/renderer/Model.cpp | 106 ++++++++++++++++++----- interface/src/renderer/Model.h | 65 ++++++++++---- interface/src/ui/AnimationsDialog.cpp | 47 +++++----- interface/src/ui/AnimationsDialog.h | 17 ++-- 8 files changed, 216 insertions(+), 128 deletions(-) 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; From d3e5e3ccf180189e9ffdf3a6021a4a2d441584ea Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 20 May 2014 15:41:11 -0700 Subject: [PATCH 4/8] A couple explanatory comments. --- interface/src/renderer/Model.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 3914f46fe7..439c5738f3 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -1689,6 +1689,7 @@ void AnimationHandle::simulate(float deltaTime) { } int ceilFrameIndex = (int)glm::ceil(_frameIndex); if (!_loop && ceilFrameIndex >= animationGeometry.animationFrames.size()) { + // passed the end; apply the last frame const FBXAnimationFrame& frame = animationGeometry.animationFrames.last(); for (int i = 0; i < _jointMappings.size(); i++) { int mapping = _jointMappings.at(i); @@ -1699,6 +1700,7 @@ void AnimationHandle::simulate(float deltaTime) { stop(); return; } + // blend between the closest two frames const FBXAnimationFrame& ceilFrame = animationGeometry.animationFrames.at( ceilFrameIndex % animationGeometry.animationFrames.size()); const FBXAnimationFrame& floorFrame = animationGeometry.animationFrames.at( From 7124b4a196c245bd60b1695d06aafdc91ad5cf61 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 20 May 2014 17:10:53 -0700 Subject: [PATCH 5/8] Mask joints from animation when explicitly set. --- interface/src/renderer/Model.cpp | 14 +++++++++++--- interface/src/renderer/Model.h | 2 ++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 439c5738f3..6349d7f9ee 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -1200,6 +1200,7 @@ bool Model::setJointRotation(int jointIndex, const glm::quat& rotation, bool fro state.rotation = state.rotation * glm::inverse(state.combinedRotation) * rotation * glm::inverse(fromBind ? _geometry->getFBXGeometry().joints.at(jointIndex).inverseBindRotation : _geometry->getFBXGeometry().joints.at(jointIndex).inverseDefaultRotation); + state.animationDisabled = true; return true; } @@ -1232,6 +1233,7 @@ bool Model::restoreJointPosition(int jointIndex, float percent) { const FBXJoint& joint = geometry.joints.at(index); state.rotation = safeMix(state.rotation, joint.rotation, percent); state.translation = glm::mix(state.translation, joint.translation, percent); + state.animationDisabled = false; } return true; } @@ -1265,6 +1267,7 @@ void Model::applyRotationDelta(int jointIndex, const glm::quat& delta, bool cons glm::quat newRotation = glm::quat(glm::clamp(eulers, joint.rotationMin, joint.rotationMax)); state.combinedRotation = state.combinedRotation * glm::inverse(state.rotation) * newRotation; state.rotation = newRotation; + state.animationDisabled = true; } const int BALL_SUBDIVISIONS = 10; @@ -1694,7 +1697,10 @@ void AnimationHandle::simulate(float deltaTime) { for (int i = 0; i < _jointMappings.size(); i++) { int mapping = _jointMappings.at(i); if (mapping != -1) { - _model->_jointStates[mapping].rotation = frame.rotations.at(i); + Model::JointState& state = _model->_jointStates[mapping]; + if (!state.animationDisabled) { + state.rotation = frame.rotations.at(i); + } } } stop(); @@ -1709,8 +1715,10 @@ void AnimationHandle::simulate(float deltaTime) { 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); + Model::JointState& state = _model->_jointStates[mapping]; + if (!state.animationDisabled) { + state.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 b8b877ac96..4d9d8d5010 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -12,6 +12,7 @@ #ifndef hifi_Model_h #define hifi_Model_h +#include #include #include @@ -251,6 +252,7 @@ protected: glm::quat rotation; // rotation relative to parent glm::mat4 transform; // rotation to world frame + translation in model frame glm::quat combinedRotation; // rotation from joint local to world frame + bool animationDisabled; // if true, animations do not affect this joint }; bool _shapesAreDirty; From f709ea61b5aa2220b6657af3e51480bc3c32b28e Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 20 May 2014 17:55:48 -0700 Subject: [PATCH 6/8] Need to initialize this to false. --- interface/src/renderer/Model.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 6349d7f9ee..199932af80 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -118,6 +118,7 @@ QVector Model::createJointStates(const FBXGeometry& geometry) JointState state; state.translation = joint.translation; state.rotation = joint.rotation; + state.animationDisabled = false; jointStates.append(state); } From 1022f1bec44a14ab43bfa47140c652c1d0bc82b2 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Wed, 21 May 2014 09:40:49 -0700 Subject: [PATCH 7/8] Added a priority setting so that we can control the order in which animations are applied. --- interface/src/avatar/MyAvatar.cpp | 2 ++ interface/src/renderer/Model.cpp | 27 ++++++++++++++++++++++++--- interface/src/renderer/Model.h | 4 ++++ interface/src/ui/AnimationsDialog.cpp | 8 ++++++++ interface/src/ui/AnimationsDialog.h | 1 + 5 files changed, 39 insertions(+), 3 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index e7638cc56d..052b742e89 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -480,6 +480,7 @@ void MyAvatar::saveData(QSettings* settings) { const AnimationHandlePointer& pointer = _animationHandles.at(i); settings->setValue("url", pointer->getURL()); settings->setValue("fps", pointer->getFPS()); + settings->setValue("priority", pointer->getPriority()); } settings->endArray(); @@ -545,6 +546,7 @@ void MyAvatar::loadData(QSettings* settings) { const AnimationHandlePointer& handle = _animationHandles.at(i); handle->setURL(settings->value("url").toUrl()); handle->setFPS(loadSetting(settings, "fps", 30.0f)); + handle->setPriority(loadSetting(settings, "priority", 1.0f)); } settings->endArray(); diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 199932af80..caf3b92a07 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -1651,13 +1651,33 @@ void Model::renderMeshes(float alpha, RenderMode mode, bool translucent) { } void AnimationHandle::setURL(const QUrl& url) { - _animation = Application::getInstance()->getAnimationCache()->getAnimation(_url = url); - _jointMappings.clear(); + if (_url != url) { + _animation = Application::getInstance()->getAnimationCache()->getAnimation(_url = url); + _jointMappings.clear(); + } +} + +static void insertSorted(QList& handles, const AnimationHandlePointer& handle) { + for (QList::iterator it = handles.begin(); it != handles.end(); it++) { + if (handle->getPriority() < (*it)->getPriority()) { + handles.insert(it, handle); + return; + } + } + handles.append(handle); +} + +void AnimationHandle::setPriority(float priority) { + if (_priority != priority) { + _priority = priority; + _model->_runningAnimations.removeOne(_self); + insertSorted(_model->_runningAnimations, _self); + } } void AnimationHandle::start() { if (!_model->_runningAnimations.contains(_self)) { - _model->_runningAnimations.append(_self); + insertSorted(_model->_runningAnimations, _self); } _frameIndex = 0.0f; } @@ -1670,6 +1690,7 @@ AnimationHandle::AnimationHandle(Model* model) : QObject(model), _model(model), _fps(30.0f), + _priority(1.0f), _loop(false) { } diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 4d9d8d5010..63a1e5e435 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -384,6 +384,9 @@ public: void setFPS(float fps) { _fps = fps; } float getFPS() const { return _fps; } + + void setPriority(float priority); + float getPriority() const { return _priority; } void setLoop(bool loop) { _loop = loop; } bool getLoop() const { return _loop; } @@ -404,6 +407,7 @@ private: AnimationPointer _animation; QUrl _url; float _fps; + float _priority; bool _loop; QVector _jointMappings; float _frameIndex; diff --git a/interface/src/ui/AnimationsDialog.cpp b/interface/src/ui/AnimationsDialog.cpp index 2beea24578..a9341fa283 100644 --- a/interface/src/ui/AnimationsDialog.cpp +++ b/interface/src/ui/AnimationsDialog.cpp @@ -93,6 +93,13 @@ AnimationPanel::AnimationPanel(AnimationsDialog* dialog, const AnimationHandlePo _fps->setValue(handle->getFPS()); connect(_fps, SIGNAL(valueChanged(double)), SLOT(updateHandle())); + layout->addRow("Priority:", _priority = new QDoubleSpinBox()); + _priority->setSingleStep(0.01); + _priority->setMinimum(-FLT_MAX); + _priority->setMaximum(FLT_MAX); + _priority->setValue(handle->getPriority()); + connect(_priority, SIGNAL(valueChanged(double)), SLOT(updateHandle())); + QPushButton* remove = new QPushButton("Delete"); layout->addRow(remove); connect(remove, SIGNAL(clicked(bool)), SLOT(removeHandle())); @@ -114,6 +121,7 @@ void AnimationPanel::chooseURL() { void AnimationPanel::updateHandle() { _handle->setURL(_url->text()); _handle->setFPS(_fps->value()); + _handle->setPriority(_priority->value()); } void AnimationPanel::removeHandle() { diff --git a/interface/src/ui/AnimationsDialog.h b/interface/src/ui/AnimationsDialog.h index d53b3dd6a5..5491418f6d 100644 --- a/interface/src/ui/AnimationsDialog.h +++ b/interface/src/ui/AnimationsDialog.h @@ -62,6 +62,7 @@ private: AnimationHandlePointer _handle; QLineEdit* _url; QDoubleSpinBox* _fps; + QDoubleSpinBox* _priority; bool _applying; }; From 0dfd787034a8b812c7f1ce118e76acd8b528a1a4 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Wed, 21 May 2014 10:44:51 -0700 Subject: [PATCH 8/8] Added some basic method to allow scripts to run/stop animations on the avatar. --- interface/src/avatar/MyAvatar.cpp | 27 +++++++++++++++++++++++++++ interface/src/avatar/MyAvatar.h | 7 +++++++ interface/src/renderer/Model.cpp | 30 +++++++++++++++++------------- interface/src/renderer/Model.h | 12 +++++++++--- 4 files changed, 60 insertions(+), 16 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 052b742e89..3aec78cc82 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -436,6 +436,33 @@ void MyAvatar::removeAnimationHandle(const AnimationHandlePointer& handle) { _animationHandles.removeOne(handle); } +void MyAvatar::startAnimation(const QString& url, float fps, float priority, bool loop) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "startAnimation", Q_ARG(const QString&, url), + Q_ARG(float, fps), Q_ARG(float, priority), Q_ARG(bool, loop)); + return; + } + AnimationHandlePointer handle = _skeletonModel.createAnimationHandle(); + handle->setURL(url); + handle->setFPS(fps); + handle->setPriority(priority); + handle->setLoop(loop); + handle->start(); +} + +void MyAvatar::stopAnimation(const QString& url) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "stopAnimation", Q_ARG(const QString&, url)); + return; + } + foreach (const AnimationHandlePointer& handle, _skeletonModel.getRunningAnimations()) { + if (handle->getURL() == url) { + handle->stop(); + return; + } + } +} + void MyAvatar::saveData(QSettings* settings) { settings->beginGroup("Avatar"); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 50b8fceca3..11493524ca 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -66,6 +66,13 @@ public: AnimationHandlePointer addAnimationHandle(); void removeAnimationHandle(const AnimationHandlePointer& handle); + /// Allows scripts to run animations. + Q_INVOKABLE void startAnimation(const QString& url, float fps = 30.0f, + float priority = 1.0f, bool loop = false); + + /// Stops an animation as identified by a URL. + Q_INVOKABLE void stopAnimation(const QString& url); + // get/set avatar data void saveData(QSettings* settings); void loadData(QSettings* settings); diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index caf3b92a07..f251d98e3e 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -1670,28 +1670,32 @@ static void insertSorted(QList& handles, const Animation void AnimationHandle::setPriority(float priority) { if (_priority != priority) { _priority = priority; + if (_running) { + _model->_runningAnimations.removeOne(_self); + insertSorted(_model->_runningAnimations, _self); + } + } +} + +void AnimationHandle::setRunning(bool running) { + if ((_running = running)) { + if (!_model->_runningAnimations.contains(_self)) { + insertSorted(_model->_runningAnimations, _self); + } + _frameIndex = 0.0f; + + } else { _model->_runningAnimations.removeOne(_self); - insertSorted(_model->_runningAnimations, _self); } } -void AnimationHandle::start() { - if (!_model->_runningAnimations.contains(_self)) { - insertSorted(_model->_runningAnimations, _self); - } - _frameIndex = 0.0f; -} - -void AnimationHandle::stop() { - _model->_runningAnimations.removeOne(_self); -} - AnimationHandle::AnimationHandle(Model* model) : QObject(model), _model(model), _fps(30.0f), _priority(1.0f), - _loop(false) { + _loop(false), + _running(false) { } void AnimationHandle::simulate(float deltaTime) { diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 63a1e5e435..feef91f703 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -194,6 +194,8 @@ public: AnimationHandlePointer createAnimationHandle(); + const QList& getRunningAnimations() const { return _runningAnimations; } + void clearShapes(); void rebuildShapes(); void resetShapePositions(); @@ -376,7 +378,7 @@ Q_DECLARE_METATYPE(QVector) /// Represents a handle to a model animation. class AnimationHandle : public QObject { Q_OBJECT - + public: void setURL(const QUrl& url); @@ -391,8 +393,11 @@ public: void setLoop(bool loop) { _loop = loop; } bool getLoop() const { return _loop; } - void start(); - void stop(); + void setRunning(bool running); + bool isRunning() const { return _running; } + + void start() { setRunning(true); } + void stop() { setRunning(false); } private: @@ -409,6 +414,7 @@ private: float _fps; float _priority; bool _loop; + bool _running; QVector _jointMappings; float _frameIndex; };