mirror of
https://github.com/overte-org/overte.git
synced 2025-04-20 12:04:18 +02:00
Merge pull request #2887 from ey6es/animenu
First pass at configurable animations for own avatar.
This commit is contained in:
commit
67206af840
11 changed files with 509 additions and 2 deletions
|
@ -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; }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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> _preferencesDialog;
|
||||
QPointer<AttachmentsDialog> _attachmentsDialog;
|
||||
QPointer<AnimationsDialog> _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";
|
||||
|
|
|
@ -428,6 +428,46 @@ 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::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");
|
||||
|
||||
|
@ -466,6 +506,16 @@ void MyAvatar::saveData(QSettings* settings) {
|
|||
}
|
||||
settings->endArray();
|
||||
|
||||
settings->beginWriteArray("animationHandles");
|
||||
for (int i = 0; i < _animationHandles.size(); i++) {
|
||||
settings->setArrayIndex(i);
|
||||
const AnimationHandlePointer& pointer = _animationHandles.at(i);
|
||||
settings->setValue("url", pointer->getURL());
|
||||
settings->setValue("fps", pointer->getFPS());
|
||||
settings->setValue("priority", pointer->getPriority());
|
||||
}
|
||||
settings->endArray();
|
||||
|
||||
settings->setValue("displayName", _displayName);
|
||||
|
||||
settings->endGroup();
|
||||
|
@ -516,6 +566,22 @@ void MyAvatar::loadData(QSettings* settings) {
|
|||
settings->endArray();
|
||||
setAttachmentData(attachmentData);
|
||||
|
||||
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);
|
||||
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();
|
||||
|
||||
setDisplayName(settings->value("displayName").toString());
|
||||
|
||||
settings->endGroup();
|
||||
|
@ -1546,3 +1612,4 @@ void MyAvatar::applyCollision(const glm::vec3& contactPoint, const glm::vec3& pe
|
|||
getHead()->addLeanDeltas(sideways, forward);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -62,6 +62,17 @@ public:
|
|||
glm::vec3 getUprightHeadPosition() const;
|
||||
bool getShouldRenderLocally() const { return _shouldRender; }
|
||||
|
||||
const QList<AnimationHandlePointer>& getAnimationHandles() const { return _animationHandles; }
|
||||
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);
|
||||
|
@ -151,6 +162,8 @@ private:
|
|||
bool _billboardValid;
|
||||
float _oculusYawOffset;
|
||||
|
||||
QList<AnimationHandlePointer> _animationHandles;
|
||||
|
||||
// private methods
|
||||
void updateOrientation(float deltaTime);
|
||||
void updateMotorFromKeyboard(float deltaTime, bool walking);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -118,6 +118,7 @@ QVector<Model::JointState> Model::createJointStates(const FBXGeometry& geometry)
|
|||
JointState state;
|
||||
state.translation = joint.translation;
|
||||
state.rotation = joint.rotation;
|
||||
state.animationDisabled = false;
|
||||
jointStates.append(state);
|
||||
}
|
||||
|
||||
|
@ -601,6 +602,16 @@ QStringList Model::getJointNames() const {
|
|||
return isActive() ? _geometry->getFBXGeometry().getJointNames() : QStringList();
|
||||
}
|
||||
|
||||
uint qHash(const WeakAnimationHandlePointer& handle, uint seed) {
|
||||
return qHash(handle.data(), seed);
|
||||
}
|
||||
|
||||
AnimationHandlePointer Model::createAnimationHandle() {
|
||||
AnimationHandlePointer handle(new AnimationHandle(this));
|
||||
handle->_self = handle;
|
||||
_animationHandles.insert(handle);
|
||||
return handle;
|
||||
}
|
||||
|
||||
void Model::clearShapes() {
|
||||
for (int i = 0; i < _jointShapes.size(); ++i) {
|
||||
|
@ -1013,6 +1024,11 @@ void Model::simulate(float deltaTime, bool fullUpdate) {
|
|||
}
|
||||
|
||||
void Model::simulateInternal(float deltaTime) {
|
||||
// update animations
|
||||
foreach (const AnimationHandlePointer& handle, _runningAnimations) {
|
||||
handle->simulate(deltaTime);
|
||||
}
|
||||
|
||||
// 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++) {
|
||||
|
@ -1020,9 +1036,8 @@ void Model::simulateInternal(float deltaTime) {
|
|||
}
|
||||
_shapesAreDirty = true;
|
||||
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
|
||||
// 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);
|
||||
|
@ -1193,6 +1208,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;
|
||||
}
|
||||
|
||||
|
@ -1225,6 +1241,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;
|
||||
}
|
||||
|
@ -1258,6 +1275,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;
|
||||
|
@ -1433,6 +1451,16 @@ void Model::deleteGeometry() {
|
|||
_meshStates.clear();
|
||||
clearShapes();
|
||||
|
||||
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) {
|
||||
_geometry->clearLoadPriority(this);
|
||||
}
|
||||
|
@ -1628,3 +1656,103 @@ void Model::renderMeshes(float alpha, RenderMode mode, bool translucent) {
|
|||
activeProgram->release();
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationHandle::setURL(const QUrl& url) {
|
||||
if (_url != url) {
|
||||
_animation = Application::getInstance()->getAnimationCache()->getAnimation(_url = url);
|
||||
_jointMappings.clear();
|
||||
}
|
||||
}
|
||||
|
||||
static void insertSorted(QList<AnimationHandlePointer>& handles, const AnimationHandlePointer& handle) {
|
||||
for (QList<AnimationHandlePointer>::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;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
AnimationHandle::AnimationHandle(Model* model) :
|
||||
QObject(model),
|
||||
_model(model),
|
||||
_fps(30.0f),
|
||||
_priority(1.0f),
|
||||
_loop(false),
|
||||
_running(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()) {
|
||||
// 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);
|
||||
if (mapping != -1) {
|
||||
Model::JointState& state = _model->_jointStates[mapping];
|
||||
if (!state.animationDisabled) {
|
||||
state.rotation = frame.rotations.at(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
// blend between the closest two frames
|
||||
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::JointState& state = _model->_jointStates[mapping];
|
||||
if (!state.animationDisabled) {
|
||||
state.rotation = safeMix(floorFrame.rotations.at(i), ceilFrame.rotations.at(i), frameFraction);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,18 +12,25 @@
|
|||
#ifndef hifi_Model_h
|
||||
#define hifi_Model_h
|
||||
|
||||
#include <QBitArray>
|
||||
#include <QObject>
|
||||
#include <QUrl>
|
||||
|
||||
#include <CapsuleShape.h>
|
||||
|
||||
#include <AnimationCache.h>
|
||||
|
||||
#include "GeometryCache.h"
|
||||
#include "InterfaceConfig.h"
|
||||
#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
|
||||
|
@ -185,6 +192,10 @@ public:
|
|||
|
||||
QStringList getJointNames() const;
|
||||
|
||||
AnimationHandlePointer createAnimationHandle();
|
||||
|
||||
const QList<AnimationHandlePointer>& getRunningAnimations() const { return _runningAnimations; }
|
||||
|
||||
void clearShapes();
|
||||
void rebuildShapes();
|
||||
void resetShapePositions();
|
||||
|
@ -243,6 +254,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;
|
||||
|
@ -299,6 +311,8 @@ protected:
|
|||
|
||||
private:
|
||||
|
||||
friend class AnimationHandle;
|
||||
|
||||
void applyNextGeometry();
|
||||
void deleteGeometry();
|
||||
void renderMeshes(float alpha, RenderMode mode, bool translucent);
|
||||
|
@ -322,6 +336,10 @@ private:
|
|||
|
||||
QVector<Model*> _attachments;
|
||||
|
||||
QSet<WeakAnimationHandlePointer> _animationHandles;
|
||||
|
||||
QList<AnimationHandlePointer> _runningAnimations;
|
||||
|
||||
static ProgramObject _program;
|
||||
static ProgramObject _normalMapProgram;
|
||||
static ProgramObject _specularMapProgram;
|
||||
|
@ -357,4 +375,48 @@ 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 setPriority(float priority);
|
||||
float getPriority() const { return _priority; }
|
||||
|
||||
void setLoop(bool loop) { _loop = loop; }
|
||||
bool getLoop() const { return _loop; }
|
||||
|
||||
void setRunning(bool running);
|
||||
bool isRunning() const { return _running; }
|
||||
|
||||
void start() { setRunning(true); }
|
||||
void stop() { setRunning(false); }
|
||||
|
||||
private:
|
||||
|
||||
friend class Model;
|
||||
|
||||
AnimationHandle(Model* model);
|
||||
|
||||
void simulate(float deltaTime);
|
||||
|
||||
Model* _model;
|
||||
WeakAnimationHandlePointer _self;
|
||||
AnimationPointer _animation;
|
||||
QUrl _url;
|
||||
float _fps;
|
||||
float _priority;
|
||||
bool _loop;
|
||||
bool _running;
|
||||
QVector<int> _jointMappings;
|
||||
float _frameIndex;
|
||||
};
|
||||
|
||||
#endif // hifi_Model_h
|
||||
|
|
130
interface/src/ui/AnimationsDialog.cpp
Normal file
130
interface/src/ui/AnimationsDialog.cpp
Normal file
|
@ -0,0 +1,130 @@
|
|||
//
|
||||
// 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 <QDialogButtonBox>
|
||||
#include <QDoubleSpinBox>
|
||||
#include <QFileDialog>
|
||||
#include <QFormLayout>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QScrollArea>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#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 AnimationHandlePointer& handle, Application::getInstance()->getAvatar()->getAnimationHandles()) {
|
||||
_animations->insertWidget(_animations->count() - 1, new AnimationPanel(this, handle));
|
||||
}
|
||||
|
||||
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::addAnimation() {
|
||||
_animations->insertWidget(_animations->count() - 1, new AnimationPanel(
|
||||
this, Application::getInstance()->getAvatar()->addAnimationHandle()));
|
||||
}
|
||||
|
||||
AnimationPanel::AnimationPanel(AnimationsDialog* dialog, const AnimationHandlePointer& handle) :
|
||||
_dialog(dialog),
|
||||
_handle(handle),
|
||||
_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(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()));
|
||||
|
||||
layout->addRow("FPS:", _fps = new QDoubleSpinBox());
|
||||
_fps->setSingleStep(0.01);
|
||||
_fps->setMaximum(FLT_MAX);
|
||||
_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()));
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
void AnimationPanel::updateHandle() {
|
||||
_handle->setURL(_url->text());
|
||||
_handle->setFPS(_fps->value());
|
||||
_handle->setPriority(_priority->value());
|
||||
}
|
||||
|
||||
void AnimationPanel::removeHandle() {
|
||||
Application::getInstance()->getAvatar()->removeAnimationHandle(_handle);
|
||||
deleteLater();
|
||||
}
|
69
interface/src/ui/AnimationsDialog.h
Normal file
69
interface/src/ui/AnimationsDialog.h
Normal file
|
@ -0,0 +1,69 @@
|
|||
//
|
||||
// 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 <QDialog>
|
||||
#include <QFrame>
|
||||
|
||||
#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);
|
||||
|
||||
private slots:
|
||||
|
||||
void addAnimation();
|
||||
|
||||
private:
|
||||
|
||||
QVBoxLayout* _animations;
|
||||
QPushButton* _ok;
|
||||
};
|
||||
|
||||
/// A panel controlling a single animation.
|
||||
class AnimationPanel : public QFrame {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
AnimationPanel(AnimationsDialog* dialog, const AnimationHandlePointer& handle);
|
||||
|
||||
private slots:
|
||||
|
||||
void chooseURL();
|
||||
void updateHandle();
|
||||
void removeHandle();
|
||||
|
||||
private:
|
||||
|
||||
AnimationsDialog* _dialog;
|
||||
AnimationHandlePointer _handle;
|
||||
QLineEdit* _url;
|
||||
QDoubleSpinBox* _fps;
|
||||
QDoubleSpinBox* _priority;
|
||||
bool _applying;
|
||||
};
|
||||
|
||||
#endif // hifi_AnimationsDialog_h
|
Loading…
Reference in a new issue