Animation bits.

This commit is contained in:
Andrzej Kapolka 2014-05-20 15:23:15 -07:00
parent 62e7a31602
commit 49a0645677
8 changed files with 216 additions and 128 deletions

View file

@ -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) { void MyAvatar::saveData(QSettings* settings) {
settings->beginGroup("Avatar"); settings->beginGroup("Avatar");
@ -461,12 +474,12 @@ void MyAvatar::saveData(QSettings* settings) {
} }
settings->endArray(); settings->endArray();
settings->beginWriteArray("animationData"); settings->beginWriteArray("animationHandles");
for (int i = 0; i < _animationData.size(); i++) { for (int i = 0; i < _animationHandles.size(); i++) {
settings->setArrayIndex(i); settings->setArrayIndex(i);
const AnimationData& animation = _animationData.at(i); const AnimationHandlePointer& pointer = _animationHandles.at(i);
settings->setValue("url", animation.url); settings->setValue("url", pointer->getURL());
settings->setValue("fps", animation.fps); settings->setValue("fps", pointer->getFPS());
} }
settings->endArray(); settings->endArray();
@ -520,17 +533,20 @@ void MyAvatar::loadData(QSettings* settings) {
settings->endArray(); settings->endArray();
setAttachmentData(attachmentData); setAttachmentData(attachmentData);
QVector<AnimationData> animationData; int animationCount = settings->beginReadArray("animationHandles");
int animationCount = settings->beginReadArray("animationData"); while (_animationHandles.size() > animationCount) {
_animationHandles.takeLast()->stop();
}
while (_animationHandles.size() < animationCount) {
addAnimationHandle();
}
for (int i = 0; i < animationCount; i++) { for (int i = 0; i < animationCount; i++) {
settings->setArrayIndex(i); settings->setArrayIndex(i);
AnimationData animation; const AnimationHandlePointer& handle = _animationHandles.at(i);
animation.url = settings->value("url").toUrl(); handle->setURL(settings->value("url").toUrl());
animation.fps = loadSetting(settings, "fps", 30.0f); handle->setFPS(loadSetting(settings, "fps", 30.0f));
animationData.append(animation);
} }
settings->endArray(); settings->endArray();
setAnimationData(animationData);
setDisplayName(settings->value("displayName").toString()); setDisplayName(settings->value("displayName").toString());
@ -598,13 +614,6 @@ AttachmentData MyAvatar::loadAttachmentData(const QUrl& modelURL, const QString&
return attachment; return attachment;
} }
void MyAvatar::setAnimationData(const QVector<AnimationData>& animationData) {
// exit early if no change
if (_animationData != animationData) {
_animationData = animationData;
}
}
int MyAvatar::parseDataAtOffset(const QByteArray& packet, int offset) { int MyAvatar::parseDataAtOffset(const QByteArray& packet, int offset) {
qDebug() << "Error: ignoring update packet for MyAvatar" qDebug() << "Error: ignoring update packet for MyAvatar"
<< " packetLength = " << packet.size() << " 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;
}

View file

@ -14,12 +14,8 @@
#include <QSettings> #include <QSettings>
#include <AnimationCache.h>
#include "Avatar.h" #include "Avatar.h"
class AnimationData;
enum AvatarHandState enum AvatarHandState
{ {
HAND_STATE_NULL = 0, HAND_STATE_NULL = 0,
@ -66,6 +62,10 @@ public:
glm::vec3 getUprightHeadPosition() const; glm::vec3 getUprightHeadPosition() const;
bool getShouldRenderLocally() const { return _shouldRender; } bool getShouldRenderLocally() const { return _shouldRender; }
const QList<AnimationHandlePointer>& getAnimationHandles() const { return _animationHandles; }
AnimationHandlePointer addAnimationHandle();
void removeAnimationHandle(const AnimationHandlePointer& handle);
// get/set avatar data // get/set avatar data
void saveData(QSettings* settings); void saveData(QSettings* settings);
void loadData(QSettings* settings); void loadData(QSettings* settings);
@ -73,9 +73,6 @@ public:
void saveAttachmentData(const AttachmentData& attachment) const; void saveAttachmentData(const AttachmentData& attachment) const;
AttachmentData loadAttachmentData(const QUrl& modelURL, const QString& jointName = QString()) const; AttachmentData loadAttachmentData(const QUrl& modelURL, const QString& jointName = QString()) const;
void setAnimationData(const QVector<AnimationData>& animationData);
const QVector<AnimationData>& getAnimationData() const { return _animationData; }
// Set what driving keys are being pressed to control thrust levels // Set what driving keys are being pressed to control thrust levels
void setDriveKeys(int key, float val) { _driveKeys[key] = val; }; void setDriveKeys(int key, float val) { _driveKeys[key] = val; };
bool getDriveKeys(int key) { return _driveKeys[key] != 0.f; }; bool getDriveKeys(int key) { return _driveKeys[key] != 0.f; };
@ -158,16 +155,7 @@ private:
bool _billboardValid; bool _billboardValid;
float _oculusYawOffset; float _oculusYawOffset;
QVector<AnimationData> _animationData; QList<AnimationHandlePointer> _animationHandles;
class AnimationState {
public:
AnimationPointer animation;
QVector<int> jointMappings;
float frameIndex;
};
QVector<AnimationState> _animationStates;
// private methods // private methods
void updateOrientation(float deltaTime); void updateOrientation(float deltaTime);
@ -185,15 +173,4 @@ private:
void setGravity(const glm::vec3& gravity); 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 #endif // hifi_MyAvatar_h

View file

@ -404,6 +404,22 @@ QSharedPointer<NetworkGeometry> NetworkGeometry::getLODOrFallback(float distance
return lod; return lod;
} }
uint qHash(const QWeakPointer<Animation>& animation, uint seed = 0) {
return qHash(animation.data(), seed);
}
QVector<int> NetworkGeometry::getJointMappings(const AnimationPointer& animation) {
QVector<int> 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<QObject>& owner, float priority) { void NetworkGeometry::setLoadPriority(const QPointer<QObject>& owner, float priority) {
Resource::setLoadPriority(owner, priority); Resource::setLoadPriority(owner, priority);

View file

@ -22,6 +22,8 @@
#include <FBXReader.h> #include <FBXReader.h>
#include <AnimationCache.h>
class Model; class Model;
class NetworkGeometry; class NetworkGeometry;
class NetworkMesh; class NetworkMesh;
@ -90,6 +92,8 @@ public:
const FBXGeometry& getFBXGeometry() const { return _geometry; } const FBXGeometry& getFBXGeometry() const { return _geometry; }
const QVector<NetworkMesh>& getMeshes() const { return _meshes; } const QVector<NetworkMesh>& getMeshes() const { return _meshes; }
QVector<int> getJointMappings(const AnimationPointer& animation);
virtual void setLoadPriority(const QPointer<QObject>& owner, float priority); virtual void setLoadPriority(const QPointer<QObject>& owner, float priority);
virtual void setLoadPriorities(const QHash<QPointer<QObject>, float>& priorities); virtual void setLoadPriorities(const QHash<QPointer<QObject>, float>& priorities);
virtual void clearLoadPriority(const QPointer<QObject>& owner); virtual void clearLoadPriority(const QPointer<QObject>& owner);
@ -117,6 +121,8 @@ private:
QVector<NetworkMesh> _meshes; QVector<NetworkMesh> _meshes;
QWeakPointer<NetworkGeometry> _lodParent; QWeakPointer<NetworkGeometry> _lodParent;
QHash<QWeakPointer<Animation>, QVector<int> > _jointMappings;
}; };
/// The state associated with a single mesh part. /// The state associated with a single mesh part.

View file

@ -594,13 +594,15 @@ QStringList Model::getJointNames() const {
return isActive() ? _geometry->getFBXGeometry().getJointNames() : QStringList(); return isActive() ? _geometry->getFBXGeometry().getJointNames() : QStringList();
} }
void Model::startAnimation(const QUrl& url, float fps, bool loop, float offset) { uint qHash(const WeakAnimationHandlePointer& handle, uint seed) {
AnimationState state = { Application::getInstance()->getAnimationCache()->getAnimation(url), fps, loop, offset }; return qHash(handle.data(), seed);
_animationStates.append(state);
} }
void Model::stopAnimation() { AnimationHandlePointer Model::createAnimationHandle() {
_animationStates.clear(); AnimationHandlePointer handle(new AnimationHandle(this));
handle->_self = handle;
_animationHandles.insert(handle);
return handle;
} }
void Model::clearShapes() { void Model::clearShapes() {
@ -1015,19 +1017,8 @@ void Model::simulate(float deltaTime, bool fullUpdate) {
void Model::simulateInternal(float deltaTime) { void Model::simulateInternal(float deltaTime) {
// update animations // update animations
const FBXGeometry& geometry = _geometry->getFBXGeometry(); foreach (const AnimationHandlePointer& handle, _runningAnimations) {
for (int i = 0; i < _animationStates.size(); i++) { handle->simulate(deltaTime);
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 // NOTE: this is a recursive call that walks all attachments, and their attachments
@ -1038,6 +1029,7 @@ void Model::simulateInternal(float deltaTime) {
_shapesAreDirty = true; _shapesAreDirty = true;
// update the attachment transforms and simulate them // update the attachment transforms and simulate them
const FBXGeometry& geometry = _geometry->getFBXGeometry();
for (int i = 0; i < _attachments.size(); i++) { for (int i = 0; i < _attachments.size(); i++) {
const FBXAttachment& attachment = geometry.attachments.at(i); const FBXAttachment& attachment = geometry.attachments.at(i);
Model* model = _attachments.at(i); Model* model = _attachments.at(i);
@ -1448,8 +1440,14 @@ void Model::deleteGeometry() {
_meshStates.clear(); _meshStates.clear();
clearShapes(); clearShapes();
for (int i = 0; i < _animationStates.size(); i++) { for (QSet<WeakAnimationHandlePointer>::iterator it = _animationHandles.begin(); it != _animationHandles.end(); ) {
_animationStates[i].jointMappings.clear(); AnimationHandlePointer handle = it->toStrongRef();
if (handle) {
handle->_jointMappings.clear();
it++;
} else {
it = _animationHandles.erase(it);
}
} }
if (_geometry) { if (_geometry) {
@ -1647,3 +1645,71 @@ void Model::renderMeshes(float alpha, RenderMode mode, bool translucent) {
activeProgram->release(); 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);
}
}
}

View file

@ -24,8 +24,12 @@
#include "ProgramObject.h" #include "ProgramObject.h"
#include "TextureCache.h" #include "TextureCache.h"
class AnimationHandle;
class Shape; class Shape;
typedef QSharedPointer<AnimationHandle> AnimationHandlePointer;
typedef QWeakPointer<AnimationHandle> WeakAnimationHandlePointer;
/// A generic 3D model displaying geometry loaded from a URL. /// A generic 3D model displaying geometry loaded from a URL.
class Model : public QObject { class Model : public QObject {
Q_OBJECT Q_OBJECT
@ -187,22 +191,7 @@ public:
QStringList getJointNames() const; QStringList getJointNames() const;
class AnimationState { AnimationHandlePointer createAnimationHandle();
public:
AnimationPointer animation;
float fps;
bool loop;
float offset;
QVector<int> jointMappings;
};
const QVector<AnimationState>& 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 clearShapes();
void rebuildShapes(); void rebuildShapes();
@ -279,8 +268,6 @@ protected:
QVector<MeshState> _meshStates; QVector<MeshState> _meshStates;
QVector<AnimationState> _animationStates;
// returns 'true' if needs fullUpdate after geometry change // returns 'true' if needs fullUpdate after geometry change
bool updateGeometry(); bool updateGeometry();
@ -320,6 +307,8 @@ protected:
private: private:
friend class AnimationHandle;
void applyNextGeometry(); void applyNextGeometry();
void deleteGeometry(); void deleteGeometry();
void renderMeshes(float alpha, RenderMode mode, bool translucent); void renderMeshes(float alpha, RenderMode mode, bool translucent);
@ -343,6 +332,10 @@ private:
QVector<Model*> _attachments; QVector<Model*> _attachments;
QSet<WeakAnimationHandlePointer> _animationHandles;
QList<AnimationHandlePointer> _runningAnimations;
static ProgramObject _program; static ProgramObject _program;
static ProgramObject _normalMapProgram; static ProgramObject _normalMapProgram;
static ProgramObject _specularMapProgram; static ProgramObject _specularMapProgram;
@ -378,4 +371,40 @@ Q_DECLARE_METATYPE(QPointer<Model>)
Q_DECLARE_METATYPE(QWeakPointer<NetworkGeometry>) Q_DECLARE_METATYPE(QWeakPointer<NetworkGeometry>)
Q_DECLARE_METATYPE(QVector<glm::vec3>) Q_DECLARE_METATYPE(QVector<glm::vec3>)
/// 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<int> _jointMappings;
float _frameIndex;
};
#endif // hifi_Model_h #endif // hifi_Model_h

View file

@ -39,8 +39,8 @@ AnimationsDialog::AnimationsDialog() :
area->setWidget(container); area->setWidget(container);
_animations->addStretch(1); _animations->addStretch(1);
foreach (const AnimationData& data, Application::getInstance()->getAvatar()->getAnimationData()) { foreach (const AnimationHandlePointer& handle, Application::getInstance()->getAvatar()->getAnimationHandles()) {
addAnimation(data); _animations->insertWidget(_animations->count() - 1, new AnimationPanel(this, handle));
} }
QPushButton* newAnimation = new QPushButton("New Animation"); QPushButton* newAnimation = new QPushButton("New Animation");
@ -64,20 +64,14 @@ void AnimationsDialog::setVisible(bool visible) {
} }
} }
void AnimationsDialog::updateAnimationData() { void AnimationsDialog::addAnimation() {
QVector<AnimationData> data; _animations->insertWidget(_animations->count() - 1, new AnimationPanel(
for (int i = 0; i < _animations->count() - 1; i++) { this, Application::getInstance()->getAvatar()->addAnimationHandle()));
data.append(static_cast<AnimationPanel*>(_animations->itemAt(i)->widget())->getAnimationData());
}
Application::getInstance()->getAvatar()->setAnimationData(data);
} }
void AnimationsDialog::addAnimation(const AnimationData& data) { AnimationPanel::AnimationPanel(AnimationsDialog* dialog, const AnimationHandlePointer& handle) :
_animations->insertWidget(_animations->count() - 1, new AnimationPanel(this, data));
}
AnimationPanel::AnimationPanel(AnimationsDialog* dialog, const AnimationData& data) :
_dialog(dialog), _dialog(dialog),
_handle(handle),
_applying(false) { _applying(false) {
setFrameStyle(QFrame::StyledPanel); setFrameStyle(QFrame::StyledPanel);
@ -87,8 +81,8 @@ AnimationPanel::AnimationPanel(AnimationsDialog* dialog, const AnimationData& da
QHBoxLayout* urlBox = new QHBoxLayout(); QHBoxLayout* urlBox = new QHBoxLayout();
layout->addRow("URL:", urlBox); layout->addRow("URL:", urlBox);
urlBox->addWidget(_url = new QLineEdit(data.url.toString()), 1); urlBox->addWidget(_url = new QLineEdit(handle->getURL().toString()), 1);
dialog->connect(_url, SIGNAL(returnPressed()), SLOT(updateAnimationData())); connect(_url, SIGNAL(returnPressed()), SLOT(updateHandle()));
QPushButton* chooseURL = new QPushButton("Choose"); QPushButton* chooseURL = new QPushButton("Choose");
urlBox->addWidget(chooseURL); urlBox->addWidget(chooseURL);
connect(chooseURL, SIGNAL(clicked(bool)), SLOT(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()); layout->addRow("FPS:", _fps = new QDoubleSpinBox());
_fps->setSingleStep(0.01); _fps->setSingleStep(0.01);
_fps->setMaximum(FLT_MAX); _fps->setMaximum(FLT_MAX);
_fps->setValue(data.fps); _fps->setValue(handle->getFPS());
dialog->connect(_fps, SIGNAL(valueChanged(double)), SLOT(updateAnimationData())); connect(_fps, SIGNAL(valueChanged(double)), SLOT(updateHandle()));
QPushButton* remove = new QPushButton("Delete"); QPushButton* remove = new QPushButton("Delete");
layout->addRow(remove); layout->addRow(remove);
connect(remove, SIGNAL(clicked(bool)), SLOT(deleteLater())); connect(remove, SIGNAL(clicked(bool)), SLOT(removeHandle()));
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() { void AnimationPanel::chooseURL() {
@ -125,3 +111,12 @@ void AnimationPanel::chooseURL() {
emit _url->returnPressed(); emit _url->returnPressed();
} }
void AnimationPanel::updateHandle() {
_handle->setURL(_url->text());
_handle->setFPS(_fps->value());
}
void AnimationPanel::removeHandle() {
Application::getInstance()->getAvatar()->removeAnimationHandle(_handle);
deleteLater();
}

View file

@ -32,14 +32,10 @@ public:
virtual void setVisible(bool visible); virtual void setVisible(bool visible);
public slots:
void updateAnimationData();
private slots: private slots:
void addAnimation(const AnimationData& animation = AnimationData()); void addAnimation();
private: private:
QVBoxLayout* _animations; QVBoxLayout* _animations;
@ -52,17 +48,18 @@ class AnimationPanel : public QFrame {
public: public:
AnimationPanel(AnimationsDialog* dialog, const AnimationData& data = AnimationData()); AnimationPanel(AnimationsDialog* dialog, const AnimationHandlePointer& handle);
AnimationData getAnimationData() const;
private slots: private slots:
void chooseURL(); void chooseURL();
void updateHandle();
void removeHandle();
private: private:
AnimationsDialog* _dialog; AnimationsDialog* _dialog;
AnimationHandlePointer _handle;
QLineEdit* _url; QLineEdit* _url;
QDoubleSpinBox* _fps; QDoubleSpinBox* _fps;
bool _applying; bool _applying;