diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index c13950e520..31dcbe9eee 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -433,11 +433,11 @@ void MyAvatar::removeAnimationHandle(const AnimationHandlePointer& handle) { } void MyAvatar::startAnimation(const QString& url, float fps, float priority, - bool loop, bool hold, int firstFrame, int lastFrame, const QStringList& maskedJoints) { + bool loop, bool hold, float firstFrame, float lastFrame, const QStringList& maskedJoints) { 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), Q_ARG(bool, hold), Q_ARG(int, firstFrame), - Q_ARG(int, lastFrame), Q_ARG(const QStringList&, maskedJoints)); + Q_ARG(float, priority), Q_ARG(bool, loop), Q_ARG(bool, hold), Q_ARG(float, firstFrame), + Q_ARG(float, lastFrame), Q_ARG(const QStringList&, maskedJoints)); return; } AnimationHandlePointer handle = _skeletonModel.createAnimationHandle(); @@ -453,11 +453,11 @@ void MyAvatar::startAnimation(const QString& url, float fps, float priority, } void MyAvatar::startAnimationByRole(const QString& role, const QString& url, float fps, float priority, - bool loop, bool hold, int firstFrame, int lastFrame, const QStringList& maskedJoints) { + bool loop, bool hold, float firstFrame, float lastFrame, const QStringList& maskedJoints) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "startAnimationByRole", Q_ARG(const QString&, role), Q_ARG(const QString&, url), - Q_ARG(float, fps), Q_ARG(float, priority), Q_ARG(bool, loop), Q_ARG(bool, hold), Q_ARG(int, firstFrame), - Q_ARG(int, lastFrame), Q_ARG(const QStringList&, maskedJoints)); + Q_ARG(float, fps), Q_ARG(float, priority), Q_ARG(bool, loop), Q_ARG(bool, hold), Q_ARG(float, firstFrame), + Q_ARG(float, lastFrame), Q_ARG(const QStringList&, maskedJoints)); return; } // check for a configured animation for the role @@ -627,8 +627,8 @@ void MyAvatar::loadData(QSettings* settings) { handle->setLoop(settings->value("loop", true).toBool()); handle->setHold(settings->value("hold", false).toBool()); handle->setStartAutomatically(settings->value("startAutomatically", true).toBool()); - handle->setFirstFrame(settings->value("firstFrame", 0).toInt()); - handle->setLastFrame(settings->value("lastFrame", INT_MAX).toInt()); + handle->setFirstFrame(settings->value("firstFrame", 0.0f).toFloat()); + handle->setLastFrame(settings->value("lastFrame", INT_MAX).toFloat()); handle->setMaskedJoints(settings->value("maskedJoints").toStringList()); } settings->endArray(); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index c0bc12b6b0..d99102c356 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -68,7 +68,7 @@ public: /// Allows scripts to run animations. Q_INVOKABLE void startAnimation(const QString& url, float fps = 30.0f, float priority = 1.0f, bool loop = false, - bool hold = false, int firstFrame = 0, int lastFrame = INT_MAX, const QStringList& maskedJoints = QStringList()); + bool hold = false, float firstFrame = 0.0f, float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList()); /// Stops an animation as identified by a URL. Q_INVOKABLE void stopAnimation(const QString& url); @@ -76,8 +76,8 @@ public: /// Starts an animation by its role, using the provided URL and parameters if the avatar doesn't have a custom /// animation for the role. Q_INVOKABLE void startAnimationByRole(const QString& role, const QString& url = QString(), float fps = 30.0f, - float priority = 1.0f, bool loop = false, bool hold = false, int firstFrame = 0, - int lastFrame = INT_MAX, const QStringList& maskedJoints = QStringList()); + float priority = 1.0f, bool loop = false, bool hold = false, float firstFrame = 0.0f, + float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList()); /// Stops an animation identified by its role. Q_INVOKABLE void stopAnimationByRole(const QString& role); diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 4ed0e2a4f2..93d5a5e823 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -1895,8 +1895,8 @@ AnimationHandle::AnimationHandle(Model* model) : _loop(false), _hold(false), _startAutomatically(false), - _firstFrame(0), - _lastFrame(INT_MAX), + _firstFrame(0.0f), + _lastFrame(FLT_MAX), _running(false) { } @@ -1927,35 +1927,34 @@ void AnimationHandle::simulate(float deltaTime) { stop(); return; } - int lastFrameIndex = qMin(_lastFrame, animationGeometry.animationFrames.size() - 1); - int firstFrameIndex = qMin(_firstFrame, lastFrameIndex); - if ((!_loop && _frameIndex >= lastFrameIndex) || firstFrameIndex == lastFrameIndex) { + float endFrameIndex = qMin(_lastFrame, animationGeometry.animationFrames.size() - (_loop ? 0.0f : 1.0f)); + float startFrameIndex = qMin(_firstFrame, endFrameIndex); + if ((!_loop && (_frameIndex < startFrameIndex || _frameIndex > endFrameIndex)) || startFrameIndex == endFrameIndex) { // passed the end; apply the last frame - const FBXAnimationFrame& frame = animationGeometry.animationFrames.at(lastFrameIndex); - for (int i = 0; i < _jointMappings.size(); i++) { - int mapping = _jointMappings.at(i); - if (mapping != -1) { - JointState& state = _model->_jointStates[mapping]; - if (_priority >= state._animationPriority) { - state._rotation = frame.rotations.at(i); - state._animationPriority = _priority; - } - } - } + applyFrame(glm::clamp(_frameIndex, startFrameIndex, endFrameIndex)); if (!_hold) { stop(); } return; } - int frameCount = lastFrameIndex - firstFrameIndex + 1; - _frameIndex = firstFrameIndex + glm::mod(qMax(_frameIndex - firstFrameIndex, 0.0f), (float)frameCount); + // wrap within the the desired range + if (_frameIndex < startFrameIndex) { + _frameIndex = endFrameIndex - glm::mod(endFrameIndex - _frameIndex, endFrameIndex - startFrameIndex); + + } else if (_frameIndex > endFrameIndex) { + _frameIndex = startFrameIndex + glm::mod(_frameIndex - startFrameIndex, endFrameIndex - startFrameIndex); + } // blend between the closest two frames - const FBXAnimationFrame& ceilFrame = animationGeometry.animationFrames.at( - firstFrameIndex + ((int)glm::ceil(_frameIndex) - firstFrameIndex) % frameCount); - const FBXAnimationFrame& floorFrame = animationGeometry.animationFrames.at( - firstFrameIndex + ((int)glm::floor(_frameIndex) - firstFrameIndex) % frameCount); - float frameFraction = glm::fract(_frameIndex); + applyFrame(_frameIndex); +} + +void AnimationHandle::applyFrame(float frameIndex) { + const FBXGeometry& animationGeometry = _animation->getGeometry(); + int frameCount = animationGeometry.animationFrames.size(); + const FBXAnimationFrame& floorFrame = animationGeometry.animationFrames.at((int)glm::floor(frameIndex) % frameCount); + const FBXAnimationFrame& ceilFrame = animationGeometry.animationFrames.at((int)glm::ceil(frameIndex) % frameCount); + float frameFraction = glm::fract(frameIndex); for (int i = 0; i < _jointMappings.size(); i++) { int mapping = _jointMappings.at(i); if (mapping != -1) { diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index fed83ecaae..63df20d43e 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -395,11 +395,11 @@ public: void setStartAutomatically(bool startAutomatically); bool getStartAutomatically() const { return _startAutomatically; } - void setFirstFrame(int firstFrame) { _firstFrame = firstFrame; } - int getFirstFrame() const { return _firstFrame; } + void setFirstFrame(float firstFrame) { _firstFrame = firstFrame; } + float getFirstFrame() const { return _firstFrame; } - void setLastFrame(int lastFrame) { _lastFrame = lastFrame; } - int getLastFrame() const { return _lastFrame; } + void setLastFrame(float lastFrame) { _lastFrame = lastFrame; } + float getLastFrame() const { return _lastFrame; } void setMaskedJoints(const QStringList& maskedJoints); const QStringList& getMaskedJoints() const { return _maskedJoints; } @@ -423,6 +423,7 @@ private: AnimationHandle(Model* model); void simulate(float deltaTime); + void applyFrame(float frameIndex); void replaceMatchingPriorities(float newPriority); Model* _model; @@ -435,8 +436,8 @@ private: bool _loop; bool _hold; bool _startAutomatically; - int _firstFrame; - int _lastFrame; + float _firstFrame; + float _lastFrame; QStringList _maskedJoints; bool _running; QVector _jointMappings; diff --git a/interface/src/ui/AnimationsDialog.cpp b/interface/src/ui/AnimationsDialog.cpp index 2456b589da..c5ab826ebb 100644 --- a/interface/src/ui/AnimationsDialog.cpp +++ b/interface/src/ui/AnimationsDialog.cpp @@ -98,6 +98,7 @@ AnimationPanel::AnimationPanel(AnimationsDialog* dialog, const AnimationHandlePo layout->addRow("FPS:", _fps = new QDoubleSpinBox()); _fps->setSingleStep(0.01); + _fps->setMinimum(-FLT_MAX); _fps->setMaximum(FLT_MAX); _fps->setValue(handle->getFPS()); connect(_fps, SIGNAL(valueChanged(double)), SLOT(updateHandle())); @@ -128,15 +129,17 @@ AnimationPanel::AnimationPanel(AnimationsDialog* dialog, const AnimationHandlePo _startAutomatically->setChecked(handle->getStartAutomatically()); connect(_startAutomatically, SIGNAL(toggled(bool)), SLOT(updateHandle())); - layout->addRow("First Frame:", _firstFrame = new QSpinBox()); + layout->addRow("First Frame:", _firstFrame = new QDoubleSpinBox()); + _firstFrame->setSingleStep(0.01); _firstFrame->setMaximum(INT_MAX); _firstFrame->setValue(handle->getFirstFrame()); - connect(_firstFrame, SIGNAL(valueChanged(int)), SLOT(updateHandle())); + connect(_firstFrame, SIGNAL(valueChanged(double)), SLOT(updateHandle())); - layout->addRow("Last Frame:", _lastFrame = new QSpinBox()); + layout->addRow("Last Frame:", _lastFrame = new QDoubleSpinBox()); + _lastFrame->setSingleStep(0.01); _lastFrame->setMaximum(INT_MAX); _lastFrame->setValue(handle->getLastFrame()); - connect(_lastFrame, SIGNAL(valueChanged(int)), SLOT(updateHandle())); + connect(_lastFrame, SIGNAL(valueChanged(double)), SLOT(updateHandle())); QHBoxLayout* buttons = new QHBoxLayout(); layout->addRow(buttons); diff --git a/interface/src/ui/AnimationsDialog.h b/interface/src/ui/AnimationsDialog.h index dd3865741e..b9454d94e5 100644 --- a/interface/src/ui/AnimationsDialog.h +++ b/interface/src/ui/AnimationsDialog.h @@ -22,7 +22,6 @@ class QComboBox; class QDoubleSpinner; class QLineEdit; class QPushButton; -class QSpinBox; class QVBoxLayout; /// Allows users to edit the avatar animations. @@ -71,8 +70,8 @@ private: QCheckBox* _loop; QCheckBox* _hold; QCheckBox* _startAutomatically; - QSpinBox* _firstFrame; - QSpinBox* _lastFrame; + QDoubleSpinBox* _firstFrame; + QDoubleSpinBox* _lastFrame; QLineEdit* _maskedJoints; QPushButton* _chooseMaskedJoints; QPushButton* _start;