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) {
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> 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>& 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;
}

View file

@ -14,12 +14,8 @@
#include <QSettings>
#include <AnimationCache.h>
#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<AnimationHandlePointer>& 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>& animationData);
const QVector<AnimationData>& 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> _animationData;
class AnimationState {
public:
AnimationPointer animation;
QVector<int> jointMappings;
float frameIndex;
};
QVector<AnimationState> _animationStates;
QList<AnimationHandlePointer> _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

View file

@ -404,6 +404,22 @@ QSharedPointer<NetworkGeometry> NetworkGeometry::getLODOrFallback(float distance
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) {
Resource::setLoadPriority(owner, priority);

View file

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

View file

@ -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<WeakAnimationHandlePointer>::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);
}
}
}

View file

@ -24,8 +24,12 @@
#include "ProgramObject.h"
#include "TextureCache.h"
class AnimationHandle;
class Shape;
typedef QSharedPointer<AnimationHandle> AnimationHandlePointer;
typedef QWeakPointer<AnimationHandle> 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<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();
AnimationHandlePointer createAnimationHandle();
void clearShapes();
void rebuildShapes();
@ -279,8 +268,6 @@ protected:
QVector<MeshState> _meshStates;
QVector<AnimationState> _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<Model*> _attachments;
QSet<WeakAnimationHandlePointer> _animationHandles;
QList<AnimationHandlePointer> _runningAnimations;
static ProgramObject _program;
static ProgramObject _normalMapProgram;
static ProgramObject _specularMapProgram;
@ -378,4 +371,40 @@ Q_DECLARE_METATYPE(QPointer<Model>)
Q_DECLARE_METATYPE(QWeakPointer<NetworkGeometry>)
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

View file

@ -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<AnimationData> data;
for (int i = 0; i < _animations->count() - 1; i++) {
data.append(static_cast<AnimationPanel*>(_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();
}

View file

@ -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;