diff --git a/examples/libraries/entityPropertyDialogBox.js b/examples/libraries/entityPropertyDialogBox.js index ef597549f2..da60e0c370 100644 --- a/examples/libraries/entityPropertyDialogBox.js +++ b/examples/libraries/entityPropertyDialogBox.js @@ -22,6 +22,9 @@ EntityPropertyDialogBox = (function () { var dimensionZ; var rescalePercentage; var editModelID = -1; + var previousAnimationIsPlaying; + var previousAnimationFrameIndex; + var previousAnimationSettings; that.cleanup = function () { }; @@ -47,10 +50,15 @@ EntityPropertyDialogBox = (function () { array.push({ label: "Animation URL:", value: properties.animationURL }); index++; array.push({ label: "Animation is playing:", value: properties.animationIsPlaying }); + previousAnimationIsPlaying = properties.animationIsPlaying; index++; array.push({ label: "Animation FPS:", value: properties.animationFPS }); index++; array.push({ label: "Animation Frame:", value: properties.animationFrameIndex }); + previousAnimationFrameIndex = properties.animationFrameIndex; + index++; + array.push({ label: "Animation Settings:", value: properties.animationSettings }); + previousAnimationSettings = properties.animationSettings; index++; array.push({ label: "Textures:", value: properties.textures }); index++; @@ -237,9 +245,29 @@ EntityPropertyDialogBox = (function () { if (properties.type == "Model") { properties.modelURL = array[index++].value; properties.animationURL = array[index++].value; - properties.animationIsPlaying = array[index++].value; + + var newAnimationIsPlaying = array[index++].value; + if (previousAnimationIsPlaying != newAnimationIsPlaying) { + properties.animationIsPlaying = newAnimationIsPlaying; + } else { + delete properties.animationIsPlaying; + } + properties.animationFPS = array[index++].value; - properties.animationFrameIndex = array[index++].value; + + var newAnimationFrameIndex = array[index++].value; + if (previousAnimationFrameIndex != newAnimationFrameIndex) { + properties.animationFrameIndex = newAnimationFrameIndex; + } else { + delete properties.animationFrameIndex; + } + + var newAnimationSettings = array[index++].value; + if (previousAnimationSettings != newAnimationSettings) { + properties.animationSettings = newAnimationSettings; + } else { + delete properties.animationSettings; + } properties.textures = array[index++].value; index++; // skip textureNames label } diff --git a/interface/src/renderer/AnimationHandle.cpp b/interface/src/renderer/AnimationHandle.cpp index 8ecf5d9699..767f941049 100644 --- a/interface/src/renderer/AnimationHandle.cpp +++ b/interface/src/renderer/AnimationHandle.cpp @@ -33,7 +33,7 @@ void AnimationHandle::setPriority(float priority) { if (_priority == priority) { return; } - if (_running) { + if (isRunning()) { _model->_runningAnimations.removeOne(_self); if (priority < _priority) { replaceMatchingPriorities(priority); @@ -47,7 +47,8 @@ void AnimationHandle::setPriority(float priority) { } void AnimationHandle::setStartAutomatically(bool startAutomatically) { - if ((_startAutomatically = startAutomatically) && !_running) { + _animationLoop.setStartAutomatically(startAutomatically); + if (getStartAutomatically() && !isRunning()) { start(); } } @@ -58,48 +59,59 @@ void AnimationHandle::setMaskedJoints(const QStringList& maskedJoints) { } void AnimationHandle::setRunning(bool running) { - if (_running == running) { + if (isRunning() == running) { + // if we're already running, this is the same as a restart if (running) { // move back to the beginning - _frameIndex = _firstFrame; + setFrameIndex(getFirstFrame()); } return; } - if ((_running = running)) { + _animationLoop.setRunning(running); + if (isRunning()) { if (!_model->_runningAnimations.contains(_self)) { insertSorted(_model->_runningAnimations, _self); } - _frameIndex = _firstFrame; - } else { _model->_runningAnimations.removeOne(_self); replaceMatchingPriorities(0.0f); } - emit runningChanged(_running); + emit runningChanged(isRunning()); } AnimationHandle::AnimationHandle(Model* model) : QObject(model), _model(model), - _fps(30.0f), - _priority(1.0f), - _loop(false), - _hold(false), - _startAutomatically(false), - _firstFrame(0.0f), - _lastFrame(FLT_MAX), - _running(false) { + _priority(1.0f) +{ } AnimationDetails AnimationHandle::getAnimationDetails() const { - AnimationDetails details(_role, _url, _fps, _priority, _loop, _hold, - _startAutomatically, _firstFrame, _lastFrame, _running, _frameIndex); + AnimationDetails details(_role, _url, getFPS(), _priority, getLoop(), getHold(), + getStartAutomatically(), getFirstFrame(), getLastFrame(), isRunning(), getFrameIndex()); return details; } +void AnimationHandle::setAnimationDetails(const AnimationDetails& details) { + setRole(details.role); + setURL(details.url); + setFPS(details.fps); + setPriority(details.priority); + setLoop(details.loop); + setHold(details.hold); + setStartAutomatically(details.startAutomatically); + setFirstFrame(details.firstFrame); + setLastFrame(details.lastFrame); + setRunning(details.running); + setFrameIndex(details.frameIndex); + + // NOTE: AnimationDetails doesn't support maskedJoints + //setMaskedJoints(const QStringList& maskedJoints); +} + void AnimationHandle::simulate(float deltaTime) { - _frameIndex += deltaTime * _fps; + _animationLoop.simulate(deltaTime); // update the joint mappings if necessary/possible if (_jointMappings.isEmpty()) { @@ -125,26 +137,15 @@ void AnimationHandle::simulate(float deltaTime) { stop(); return; } - 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 - applyFrame(glm::clamp(_frameIndex, startFrameIndex, endFrameIndex)); - if (!_hold) { - stop(); - } - return; - } - // 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); - } + // TODO: When moving the loop/frame calculations to AnimationLoop class, we changed this behavior + // see AnimationLoop class for more details. Do we need to support clamping the endFrameIndex to + // the max number of frames in the geometry??? + // + // float endFrameIndex = qMin(_lastFrame, animationGeometry.animationFrames.size() - (_loop ? 0.0f : 1.0f)); + // blend between the closest two frames - applyFrame(_frameIndex); + applyFrame(getFrameIndex()); } void AnimationHandle::applyFrame(float frameIndex) { diff --git a/interface/src/renderer/AnimationHandle.h b/interface/src/renderer/AnimationHandle.h index 3b736698df..3956b01ebf 100644 --- a/interface/src/renderer/AnimationHandle.h +++ b/interface/src/renderer/AnimationHandle.h @@ -19,6 +19,7 @@ #include #include +#include class AnimationHandle; class Model; @@ -38,38 +39,40 @@ 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 setHold(bool hold) { _hold = hold; } - bool getHold() const { return _hold; } - - void setStartAutomatically(bool startAutomatically); - bool getStartAutomatically() const { return _startAutomatically; } - - void setFirstFrame(float firstFrame) { _firstFrame = firstFrame; } - float getFirstFrame() const { return _firstFrame; } - - void setLastFrame(float lastFrame) { _lastFrame = lastFrame; } - float getLastFrame() const { return _lastFrame; } - + void setMaskedJoints(const QStringList& maskedJoints); const QStringList& getMaskedJoints() const { return _maskedJoints; } - void setRunning(bool running); - bool isRunning() const { return _running; } - void setFrameIndex(float frameIndex) { _frameIndex = glm::clamp(_frameIndex, _firstFrame, _lastFrame); } - float getFrameIndex() const { return _frameIndex; } + void setFPS(float fps) { _animationLoop.setFPS(fps); } + float getFPS() const { return _animationLoop.getFPS(); } + + void setLoop(bool loop) { _animationLoop.setLoop(loop); } + bool getLoop() const { return _animationLoop.getLoop(); } + + void setHold(bool hold) { _animationLoop.setHold(hold); } + bool getHold() const { return _animationLoop.getHold(); } + + void setStartAutomatically(bool startAutomatically); + bool getStartAutomatically() const { return _animationLoop.getStartAutomatically(); } + + void setFirstFrame(float firstFrame) { _animationLoop.setFirstFrame(firstFrame); } + float getFirstFrame() const { return _animationLoop.getFirstFrame(); } + + void setLastFrame(float lastFrame) { _animationLoop.setLastFrame(lastFrame); } + float getLastFrame() const { return _animationLoop.getLastFrame(); } + + void setRunning(bool running); + bool isRunning() const { return _animationLoop.isRunning(); } + + void setFrameIndex(float frameIndex) { _animationLoop.setFrameIndex(frameIndex); } + float getFrameIndex() const { return _animationLoop.getFrameIndex(); } AnimationDetails getAnimationDetails() const; + void setAnimationDetails(const AnimationDetails& details); signals: @@ -95,17 +98,12 @@ private: AnimationPointer _animation; QString _role; QUrl _url; - float _fps; float _priority; - bool _loop; - bool _hold; - bool _startAutomatically; - float _firstFrame; - float _lastFrame; + QStringList _maskedJoints; - bool _running; QVector _jointMappings; - float _frameIndex; + + AnimationLoop _animationLoop; }; diff --git a/libraries/animation/src/AnimationCache.h b/libraries/animation/src/AnimationCache.h index 4af9f0a83f..8a9e371cb0 100644 --- a/libraries/animation/src/AnimationCache.h +++ b/libraries/animation/src/AnimationCache.h @@ -94,5 +94,4 @@ Q_DECLARE_METATYPE(AnimationDetails); QScriptValue animationDetailsToScriptValue(QScriptEngine* engine, const AnimationDetails& event); void animationDetailsFromScriptValue(const QScriptValue& object, AnimationDetails& event); - #endif // hifi_AnimationCache_h diff --git a/libraries/animation/src/AnimationLoop.cpp b/libraries/animation/src/AnimationLoop.cpp new file mode 100644 index 0000000000..f81904990f --- /dev/null +++ b/libraries/animation/src/AnimationLoop.cpp @@ -0,0 +1,95 @@ +// +// AnimationLoop.cpp +// libraries/animation +// +// Created by Brad Hefta-Gaub on 11/12/14. +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "AnimationCache.h" +#include "AnimationLoop.h" + +AnimationLoop::AnimationLoop() : + _fps(30.0f), + _loop(false), + _hold(false), + _startAutomatically(false), + _firstFrame(0.0f), + _lastFrame(FLT_MAX), + _running(false), + _frameIndex(0.0f) +{ +} + +AnimationLoop::AnimationLoop(const AnimationDetails& animationDetails) : + _fps(animationDetails.fps), + _loop(animationDetails.loop), + _hold(animationDetails.hold), + _startAutomatically(animationDetails.startAutomatically), + _firstFrame(animationDetails.firstFrame), + _lastFrame(animationDetails.lastFrame), + _running(animationDetails.running), + _frameIndex(animationDetails.frameIndex) +{ +} + +AnimationLoop::AnimationLoop(float fps, bool loop, bool hold, bool startAutomatically, float firstFrame, + float lastFrame, bool running, float frameIndex) : + _fps(fps), + _loop(loop), + _hold(hold), + _startAutomatically(startAutomatically), + _firstFrame(firstFrame), + _lastFrame(lastFrame), + _running(running), + _frameIndex(frameIndex) +{ +} + +void AnimationLoop::simulate(float deltaTime) { + _frameIndex += deltaTime * _fps; + + + // If we knew the number of frames from the animation, we'd consider using it here + // animationGeometry.animationFrames.size() + float maxFrame = _lastFrame; + float endFrameIndex = qMin(_lastFrame, maxFrame - (_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 + _frameIndex = glm::clamp(_frameIndex, startFrameIndex, endFrameIndex); + if (!_hold) { + stop(); + } + } else { + // 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); + } + } +} + +void AnimationLoop::setStartAutomatically(bool startAutomatically) { + if ((_startAutomatically = startAutomatically) && !isRunning()) { + start(); + } +} + +void AnimationLoop::setRunning(bool running) { + if (_running == running) { + if (running) { + // move back to the beginning + _frameIndex = _firstFrame; + } + return; + } + if ((_running = running)) { + _frameIndex = _firstFrame; + } +} diff --git a/libraries/animation/src/AnimationLoop.h b/libraries/animation/src/AnimationLoop.h new file mode 100644 index 0000000000..b56f68f23b --- /dev/null +++ b/libraries/animation/src/AnimationLoop.h @@ -0,0 +1,63 @@ +// +// AnimationLoop.h +// libraries/script-engine/src/ +// +// Created by Brad Hefta-Gaub on 11/12/14. +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// +// 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_AnimationLoop_h +#define hifi_AnimationLoop_h + +class AnimationDetails; + +class AnimationLoop { +public: + AnimationLoop(); + AnimationLoop(const AnimationDetails& animationDetails); + AnimationLoop(float fps, bool loop, bool hold, bool startAutomatically, float firstFrame, + float lastFrame, bool running, float frameIndex); + + void setFPS(float fps) { _fps = fps; } + float getFPS() const { return _fps; } + + void setLoop(bool loop) { _loop = loop; } + bool getLoop() const { return _loop; } + + void setHold(bool hold) { _hold = hold; } + bool getHold() const { return _hold; } + + void setStartAutomatically(bool startAutomatically); + bool getStartAutomatically() const { return _startAutomatically; } + + void setFirstFrame(float firstFrame) { _firstFrame = firstFrame; } + float getFirstFrame() const { return _firstFrame; } + + void setLastFrame(float lastFrame) { _lastFrame = lastFrame; } + float getLastFrame() const { return _lastFrame; } + + void setRunning(bool running); + bool isRunning() const { return _running; } + + void setFrameIndex(float frameIndex) { _frameIndex = glm::clamp(frameIndex, _firstFrame, _lastFrame); } + float getFrameIndex() const { return _frameIndex; } + + void start() { setRunning(true); } + void stop() { setRunning(false); } + void simulate(float deltaTime); + +private: + float _fps; + bool _loop; + bool _hold; + bool _startAutomatically; + float _firstFrame; + float _lastFrame; + bool _running; + float _frameIndex; +}; + +#endif // hifi_AnimationLoop_h diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index cb6d9c7bcd..0c184d5e35 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -66,6 +67,7 @@ EntityItemProperties::EntityItemProperties() : _animationIsPlaying(ModelEntityItem::DEFAULT_ANIMATION_IS_PLAYING), _animationFrameIndex(ModelEntityItem::DEFAULT_ANIMATION_FRAME_INDEX), _animationFPS(ModelEntityItem::DEFAULT_ANIMATION_FPS), + _animationSettings(""), _glowLevel(0.0f), _localRenderAlpha(1.0f), _isSpotlight(false), @@ -76,6 +78,8 @@ EntityItemProperties::EntityItemProperties() : _animationIsPlayingChanged(false), _animationFrameIndexChanged(false), _animationFPSChanged(false), + _animationSettingsChanged(false), + _glowLevelChanged(false), _localRenderAlphaChanged(false), _isSpotlightChanged(false), @@ -117,6 +121,58 @@ void EntityItemProperties::setSittingPoints(const QVector& sitting } } +void EntityItemProperties::setAnimationSettings(const QString& value) { + // the animations setting is a JSON string that may contain various animation settings. + // if it includes fps, frameIndex, or running, those values will be parsed out and + // will over ride the regular animation settings + + QJsonDocument settingsAsJson = QJsonDocument::fromJson(value.toUtf8()); + QJsonObject settingsAsJsonObject = settingsAsJson.object(); + QVariantMap settingsMap = settingsAsJsonObject.toVariantMap(); + if (settingsMap.contains("fps")) { + float fps = settingsMap["fps"].toFloat(); + setAnimationFPS(fps); + } + + if (settingsMap.contains("frameIndex")) { + float frameIndex = settingsMap["frameIndex"].toFloat(); + setAnimationFrameIndex(frameIndex); + } + + if (settingsMap.contains("running")) { + bool running = settingsMap["running"].toBool(); + setAnimationIsPlaying(running); + } + + _animationSettings = value; + _animationSettingsChanged = true; +} + +QString EntityItemProperties::getAnimationSettings() const { + // the animations setting is a JSON string that may contain various animation settings. + // if it includes fps, frameIndex, or running, those values will be parsed out and + // will over ride the regular animation settings + QString value = _animationSettings; + + QJsonDocument settingsAsJson = QJsonDocument::fromJson(value.toUtf8()); + QJsonObject settingsAsJsonObject = settingsAsJson.object(); + QVariantMap settingsMap = settingsAsJsonObject.toVariantMap(); + + QVariant fpsValue(getAnimationFPS()); + settingsMap["fps"] = fpsValue; + + QVariant frameIndexValue(getAnimationFrameIndex()); + settingsMap["frameIndex"] = frameIndexValue; + + QVariant runningValue(getAnimationIsPlaying()); + settingsMap["running"] = runningValue; + + settingsAsJsonObject = QJsonObject::fromVariantMap(settingsMap); + QJsonDocument newDocument(settingsAsJsonObject); + QByteArray jsonByteArray = newDocument.toJson(QJsonDocument::Compact); + QString jsonByteString(jsonByteArray); + return jsonByteString; +} void EntityItemProperties::debugDump() const { qDebug() << "EntityItemProperties..."; @@ -149,6 +205,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_ANIMATION_PLAYING, animationIsPlaying); CHECK_PROPERTY_CHANGE(PROP_ANIMATION_FRAME_INDEX, animationFrameIndex); CHECK_PROPERTY_CHANGE(PROP_ANIMATION_FPS, animationFPS); + CHECK_PROPERTY_CHANGE(PROP_ANIMATION_SETTINGS, animationSettings); CHECK_PROPERTY_CHANGE(PROP_VISIBLE, visible); CHECK_PROPERTY_CHANGE(PROP_REGISTRATION_POINT, registrationPoint); CHECK_PROPERTY_CHANGE(PROP_ANGULAR_VELOCITY, angularVelocity); @@ -201,8 +258,9 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine) cons COPY_PROPERTY_TO_QSCRIPTVALUE(modelURL); COPY_PROPERTY_TO_QSCRIPTVALUE(animationURL); COPY_PROPERTY_TO_QSCRIPTVALUE(animationIsPlaying); - COPY_PROPERTY_TO_QSCRIPTVALUE(animationFrameIndex); COPY_PROPERTY_TO_QSCRIPTVALUE(animationFPS); + COPY_PROPERTY_TO_QSCRIPTVALUE(animationFrameIndex); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(animationSettings,getAnimationSettings()); COPY_PROPERTY_TO_QSCRIPTVALUE(glowLevel); COPY_PROPERTY_TO_QSCRIPTVALUE(localRenderAlpha); COPY_PROPERTY_TO_QSCRIPTVALUE(ignoreForCollisions); @@ -276,6 +334,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object) { COPY_PROPERTY_FROM_QSCRIPTVALUE_BOOL(animationIsPlaying, setAnimationIsPlaying); COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(animationFPS, setAnimationFPS); COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(animationFrameIndex, setAnimationFrameIndex); + COPY_PROPERTY_FROM_QSCRIPTVALUE_STRING(animationSettings, setAnimationSettings); COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(glowLevel, setGlowLevel); COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(localRenderAlpha, setLocalRenderAlpha); COPY_PROPERTY_FROM_QSCRIPTVALUE_BOOL(ignoreForCollisions, setIgnoreForCollisions); @@ -452,6 +511,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem APPEND_ENTITY_PROPERTY(PROP_CUTOFF, appendValue, properties.getCutoff()); APPEND_ENTITY_PROPERTY(PROP_LOCKED, appendValue, properties.getLocked()); APPEND_ENTITY_PROPERTY(PROP_TEXTURES, appendValue, properties.getTextures()); + APPEND_ENTITY_PROPERTY(PROP_ANIMATION_SETTINGS, appendValue, properties.getAnimationSettings()); } if (propertyCount > 0) { int endOfEntityItemData = packetData->getUncompressedByteOffset(); @@ -661,7 +721,8 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CUTOFF, float, setCutoff); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LOCKED, bool, setLocked); READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_TEXTURES, setTextures); - + READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_ANIMATION_SETTINGS, setAnimationSettings); + return valid; } @@ -714,6 +775,7 @@ void EntityItemProperties::markAllChanged() { _animationIsPlayingChanged = true; _animationFrameIndexChanged = true; _animationFPSChanged = true; + _animationSettingsChanged = true; _glowLevelChanged = true; _localRenderAlphaChanged = true; _isSpotlightChanged = true; diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 6b22e8cba9..d6b8181c28 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -80,8 +80,9 @@ enum EntityPropertyList { // used by Model entities PROP_TEXTURES, + PROP_ANIMATION_SETTINGS, - PROP_LAST_ITEM = PROP_CUTOFF + PROP_LAST_ITEM = PROP_ANIMATION_SETTINGS }; typedef PropertyFlags EntityPropertyFlags; @@ -178,6 +179,8 @@ public: float getAnimationFrameIndex() const { return _animationFrameIndex; } bool getAnimationIsPlaying() const { return _animationIsPlaying; } float getAnimationFPS() const { return _animationFPS; } + QString getAnimationSettings() const; + float getGlowLevel() const { return _glowLevel; } float getLocalRenderAlpha() const { return _localRenderAlpha; } const QString& getScript() const { return _script; } @@ -189,6 +192,8 @@ public: void setAnimationFrameIndex(float value) { _animationFrameIndex = value; _animationFrameIndexChanged = true; } void setAnimationIsPlaying(bool value) { _animationIsPlaying = value; _animationIsPlayingChanged = true; } void setAnimationFPS(float value) { _animationFPS = value; _animationFPSChanged = true; } + void setAnimationSettings(const QString& value); + void setGlowLevel(float value) { _glowLevel = value; _glowLevelChanged = true; } void setLocalRenderAlpha(float value) { _localRenderAlpha = value; _localRenderAlphaChanged = true; } void setScript(const QString& value) { _script = value; _scriptChanged = true; } @@ -342,6 +347,7 @@ private: bool _animationIsPlaying; float _animationFrameIndex; float _animationFPS; + QString _animationSettings; float _glowLevel; float _localRenderAlpha; bool _isSpotlight; @@ -352,6 +358,7 @@ private: bool _animationIsPlayingChanged; bool _animationFrameIndexChanged; bool _animationFPSChanged; + bool _animationSettingsChanged; bool _glowLevelChanged; bool _localRenderAlphaChanged; bool _isSpotlightChanged; diff --git a/libraries/entities/src/EntityItemPropertiesMacros.h b/libraries/entities/src/EntityItemPropertiesMacros.h index b5a489f88c..77782cb90f 100644 --- a/libraries/entities/src/EntityItemPropertiesMacros.h +++ b/libraries/entities/src/EntityItemPropertiesMacros.h @@ -40,6 +40,18 @@ } \ } +#define READ_ENTITY_PROPERTY_SETTER(P,T,M) \ + if (propertyFlags.getHasProperty(P)) { \ + T fromBuffer; \ + memcpy(&fromBuffer, dataAt, sizeof(fromBuffer)); \ + dataAt += sizeof(fromBuffer); \ + bytesRead += sizeof(fromBuffer); \ + if (overwriteLocalData) { \ + M(fromBuffer); \ + } \ + } + + #define READ_ENTITY_PROPERTY_QUAT(P,M) \ if (propertyFlags.getHasProperty(P)) { \ glm::quat fromBuffer; \ diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index 827f4f7e39..52b8f7e643 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include + #include #include @@ -33,7 +35,6 @@ ModelEntityItem::ModelEntityItem(const EntityItemID& entityItemID, const EntityI { _type = EntityTypes::Model; setProperties(properties, true); - _animationFrameIndex = 0.0f; _lastAnimated = usecTimestampNow(); _jointMappingCompleted = false; _color[0] = _color[1] = _color[2] = 0; @@ -50,6 +51,7 @@ EntityItemProperties ModelEntityItem::getProperties() const { COPY_ENTITY_PROPERTY_TO_PROPERTIES(animationFPS, getAnimationFPS); COPY_ENTITY_PROPERTY_TO_PROPERTIES(glowLevel, getGlowLevel); COPY_ENTITY_PROPERTY_TO_PROPERTIES(textures, getTextures); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(animationSettings, getAnimationSettings); return properties; } @@ -64,6 +66,7 @@ bool ModelEntityItem::setProperties(const EntityItemProperties& properties, bool SET_ENTITY_PROPERTY_FROM_PROPERTIES(animationFrameIndex, setAnimationFrameIndex); SET_ENTITY_PROPERTY_FROM_PROPERTIES(animationFPS, setAnimationFPS); SET_ENTITY_PROPERTY_FROM_PROPERTIES(textures, setTextures); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(animationSettings, setAnimationSettings); if (somethingChanged) { bool wantDebug = false; @@ -100,10 +103,29 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, READ_ENTITY_PROPERTY_COLOR(PROP_COLOR, _color); READ_ENTITY_PROPERTY_STRING(PROP_MODEL_URL, setModelURL); READ_ENTITY_PROPERTY_STRING(PROP_ANIMATION_URL, setAnimationURL); - READ_ENTITY_PROPERTY(PROP_ANIMATION_FPS, float, _animationFPS); - READ_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, float, _animationFrameIndex); - READ_ENTITY_PROPERTY(PROP_ANIMATION_PLAYING, bool, _animationIsPlaying); + + // Because we're using AnimationLoop which will reset the frame index if you change it's running state + // we want to read these values in the order they appear in the buffer, but call our setters in an + // order that allows AnimationLoop to preserve the correct frame rate. + float animationFPS = getAnimationFPS(); + float animationFrameIndex = getAnimationFrameIndex(); + bool animationIsPlaying = getAnimationIsPlaying(); + READ_ENTITY_PROPERTY(PROP_ANIMATION_FPS, float, animationFPS); + READ_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, float, animationFrameIndex); + READ_ENTITY_PROPERTY(PROP_ANIMATION_PLAYING, bool, animationIsPlaying); + + if (propertyFlags.getHasProperty(PROP_ANIMATION_PLAYING)) { + setAnimationIsPlaying(animationIsPlaying); + } + if (propertyFlags.getHasProperty(PROP_ANIMATION_FPS)) { + setAnimationFPS(animationFPS); + } + if (propertyFlags.getHasProperty(PROP_ANIMATION_FRAME_INDEX)) { + setAnimationFrameIndex(animationFrameIndex); + } + READ_ENTITY_PROPERTY_STRING(PROP_TEXTURES, setTextures); + READ_ENTITY_PROPERTY_STRING(PROP_ANIMATION_SETTINGS, setAnimationSettings); return bytesRead; } @@ -199,19 +221,25 @@ int ModelEntityItem::oldVersionReadEntityDataFromBuffer(const unsigned char* dat bytesRead += animationURLLength; // animationIsPlaying - memcpy(&_animationIsPlaying, dataAt, sizeof(_animationIsPlaying)); - dataAt += sizeof(_animationIsPlaying); - bytesRead += sizeof(_animationIsPlaying); + bool animationIsPlaying; + memcpy(&animationIsPlaying, dataAt, sizeof(animationIsPlaying)); + dataAt += sizeof(animationIsPlaying); + bytesRead += sizeof(animationIsPlaying); + setAnimationIsPlaying(animationIsPlaying); // animationFrameIndex - memcpy(&_animationFrameIndex, dataAt, sizeof(_animationFrameIndex)); - dataAt += sizeof(_animationFrameIndex); - bytesRead += sizeof(_animationFrameIndex); + float animationFrameIndex; + memcpy(&animationFrameIndex, dataAt, sizeof(animationFrameIndex)); + dataAt += sizeof(animationFrameIndex); + bytesRead += sizeof(animationFrameIndex); + setAnimationFrameIndex(animationFrameIndex); // animationFPS - memcpy(&_animationFPS, dataAt, sizeof(_animationFPS)); - dataAt += sizeof(_animationFPS); - bytesRead += sizeof(_animationFPS); + float animationFPS; + memcpy(&animationFPS, dataAt, sizeof(animationFPS)); + dataAt += sizeof(animationFPS); + bytesRead += sizeof(animationFPS); + setAnimationFPS(animationFPS); } } return bytesRead; @@ -227,6 +255,7 @@ EntityPropertyFlags ModelEntityItem::getEntityProperties(EncodeBitstreamParams& requestedProperties += PROP_ANIMATION_FPS; requestedProperties += PROP_ANIMATION_FRAME_INDEX; requestedProperties += PROP_ANIMATION_PLAYING; + requestedProperties += PROP_ANIMATION_SETTINGS; requestedProperties += PROP_TEXTURES; return requestedProperties; @@ -249,6 +278,7 @@ void ModelEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, appendValue, getAnimationFrameIndex()); APPEND_ENTITY_PROPERTY(PROP_ANIMATION_PLAYING, appendValue, getAnimationIsPlaying()); APPEND_ENTITY_PROPERTY(PROP_TEXTURES, appendValue, getTextures()); + APPEND_ENTITY_PROPERTY(PROP_ANIMATION_SETTINGS, appendValue, getAnimationSettings()); } @@ -314,7 +344,7 @@ QVector ModelEntityItem::getAnimationFrame() { int frameCount = frames.size(); if (frameCount > 0) { - int animationFrameIndex = (int)(glm::floor(_animationFrameIndex)) % frameCount; + int animationFrameIndex = (int)(glm::floor(getAnimationFrameIndex())) % frameCount; if (animationFrameIndex < 0 || animationFrameIndex > frameCount) { animationFrameIndex = 0; @@ -363,7 +393,7 @@ void ModelEntityItem::update(const quint64& updateTime) { if (getAnimationIsPlaying()) { float deltaTime = (float)(now - _lastAnimated) / (float)USECS_PER_SECOND; _lastAnimated = now; - _animationFrameIndex += deltaTime * _animationFPS; + _animationLoop.simulate(deltaTime); } else { _lastAnimated = now; } @@ -377,3 +407,94 @@ void ModelEntityItem::debugDump() const { qDebug() << " model URL:" << getModelURL(); } +void ModelEntityItem::setAnimationSettings(const QString& value) { + // the animations setting is a JSON string that may contain various animation settings. + // if it includes fps, frameIndex, or running, those values will be parsed out and + // will over ride the regular animation settings + + QJsonDocument settingsAsJson = QJsonDocument::fromJson(value.toUtf8()); + QJsonObject settingsAsJsonObject = settingsAsJson.object(); + QVariantMap settingsMap = settingsAsJsonObject.toVariantMap(); + if (settingsMap.contains("fps")) { + float fps = settingsMap["fps"].toFloat(); + setAnimationFPS(fps); + } + + if (settingsMap.contains("frameIndex")) { + float frameIndex = settingsMap["frameIndex"].toFloat(); + setAnimationFrameIndex(frameIndex); + } + + if (settingsMap.contains("running")) { + bool running = settingsMap["running"].toBool(); + setAnimationIsPlaying(running); + } + + if (settingsMap.contains("firstFrame")) { + float firstFrame = settingsMap["firstFrame"].toFloat(); + setAnimationFirstFrame(firstFrame); + } + + if (settingsMap.contains("lastFrame")) { + float lastFrame = settingsMap["lastFrame"].toFloat(); + setAnimationLastFrame(lastFrame); + } + + if (settingsMap.contains("loop")) { + bool loop = settingsMap["loop"].toBool(); + setAnimationLoop(loop); + } + + if (settingsMap.contains("hold")) { + bool hold = settingsMap["hold"].toBool(); + setAnimationHold(hold); + } + + if (settingsMap.contains("startAutomatically")) { + bool startAutomatically = settingsMap["startAutomatically"].toBool(); + setAnimationStartAutomatically(startAutomatically); + } + + _animationSettings = value; +} + +QString ModelEntityItem::getAnimationSettings() const { + // the animations setting is a JSON string that may contain various animation settings. + // if it includes fps, frameIndex, or running, those values will be parsed out and + // will over ride the regular animation settings + QString value = _animationSettings; + + QJsonDocument settingsAsJson = QJsonDocument::fromJson(value.toUtf8()); + QJsonObject settingsAsJsonObject = settingsAsJson.object(); + QVariantMap settingsMap = settingsAsJsonObject.toVariantMap(); + + QVariant fpsValue(getAnimationFPS()); + settingsMap["fps"] = fpsValue; + + QVariant frameIndexValue(getAnimationFrameIndex()); + settingsMap["frameIndex"] = frameIndexValue; + + QVariant runningValue(getAnimationIsPlaying()); + settingsMap["running"] = runningValue; + + QVariant firstFrameValue(getAnimationFirstFrame()); + settingsMap["firstFrame"] = firstFrameValue; + + QVariant lastFrameValue(getAnimationLastFrame()); + settingsMap["lastFrame"] = lastFrameValue; + + QVariant loopValue(getAnimationLoop()); + settingsMap["loop"] = loopValue; + + QVariant holdValue(getAnimationHold()); + settingsMap["hold"] = holdValue; + + QVariant startAutomaticallyValue(getAnimationStartAutomatically()); + settingsMap["startAutomatically"] = startAutomaticallyValue; + + settingsAsJsonObject = QJsonObject::fromVariantMap(settingsMap); + QJsonDocument newDocument(settingsAsJsonObject); + QByteArray jsonByteArray = newDocument.toJson(QJsonDocument::Compact); + QString jsonByteString(jsonByteArray); + return jsonByteString; +} diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index 4c79a46c5e..97ffed4076 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -12,6 +12,8 @@ #ifndef hifi_ModelEntityItem_h #define hifi_ModelEntityItem_h +#include + #include "EntityItem.h" class ModelEntityItem : public EntityItem { @@ -73,21 +75,38 @@ public: void setModelURL(const QString& url) { _modelURL = url; } void setAnimationURL(const QString& url) { _animationURL = url; } static const float DEFAULT_ANIMATION_FRAME_INDEX; - void setAnimationFrameIndex(float value) { _animationFrameIndex = value; } + void setAnimationFrameIndex(float value) { _animationLoop.setFrameIndex(value); } + void setAnimationSettings(const QString& value); static const bool DEFAULT_ANIMATION_IS_PLAYING; - void setAnimationIsPlaying(bool value) { _animationIsPlaying = value; } + void setAnimationIsPlaying(bool value) { _animationLoop.setRunning(value); } static const float DEFAULT_ANIMATION_FPS; - void setAnimationFPS(float value) { _animationFPS = value; } + void setAnimationFPS(float value) { _animationLoop.setFPS(value); } + + void setAnimationLoop(bool loop) { _animationLoop.setLoop(loop); } + bool getAnimationLoop() const { return _animationLoop.getLoop(); } + + void setAnimationHold(bool hold) { _animationLoop.setHold(hold); } + bool getAnimationHold() const { return _animationLoop.getHold(); } + + void setAnimationStartAutomatically(bool startAutomatically) { _animationLoop.setStartAutomatically(startAutomatically); } + bool getAnimationStartAutomatically() const { return _animationLoop.getStartAutomatically(); } + + void setAnimationFirstFrame(float firstFrame) { _animationLoop.setFirstFrame(firstFrame); } + float getAnimationFirstFrame() const { return _animationLoop.getFirstFrame(); } + + void setAnimationLastFrame(float lastFrame) { _animationLoop.setLastFrame(lastFrame); } + float getAnimationLastFrame() const { return _animationLoop.getLastFrame(); } void mapJoints(const QStringList& modelJointNames); QVector getAnimationFrame(); bool jointsMapped() const { return _jointMappingCompleted; } - bool getAnimationIsPlaying() const { return _animationIsPlaying; } - float getAnimationFrameIndex() const { return _animationFrameIndex; } - float getAnimationFPS() const { return _animationFPS; } + bool getAnimationIsPlaying() const { return _animationLoop.isRunning(); } + float getAnimationFrameIndex() const { return _animationLoop.getFrameIndex(); } + float getAnimationFPS() const { return _animationLoop.getFPS(); } + QString getAnimationSettings() const; static const QString DEFAULT_TEXTURES; const QString& getTextures() const { return _textures; } @@ -106,9 +125,8 @@ protected: quint64 _lastAnimated; QString _animationURL; - float _animationFrameIndex; // we keep this as a float and round to int only when we need the exact index - bool _animationIsPlaying; - float _animationFPS; + AnimationLoop _animationLoop; + QString _animationSettings; QString _textures; // used on client side diff --git a/libraries/networking/src/PacketHeaders.cpp b/libraries/networking/src/PacketHeaders.cpp index b5cf84ee28..a5fdd86e3d 100644 --- a/libraries/networking/src/PacketHeaders.cpp +++ b/libraries/networking/src/PacketHeaders.cpp @@ -75,7 +75,7 @@ PacketVersion versionForPacketType(PacketType type) { return 1; case PacketTypeEntityAddOrEdit: case PacketTypeEntityData: - return VERSION_ENTITIES_SUPPORT_DIMENSIONS; + return VERSION_ENTITIES_MODELS_HAVE_ANIMATION_SETTINGS; case PacketTypeEntityErase: return 2; case PacketTypeAudioStreamStats: diff --git a/libraries/networking/src/PacketHeaders.h b/libraries/networking/src/PacketHeaders.h index 2e9ce697f0..466aebd36b 100644 --- a/libraries/networking/src/PacketHeaders.h +++ b/libraries/networking/src/PacketHeaders.h @@ -124,6 +124,7 @@ const PacketVersion VERSION_ROOT_ELEMENT_HAS_DATA = 2; const PacketVersion VERSION_ENTITIES_SUPPORT_SPLIT_MTU = 3; const PacketVersion VERSION_ENTITIES_HAS_FILE_BREAKS = VERSION_ENTITIES_SUPPORT_SPLIT_MTU; const PacketVersion VERSION_ENTITIES_SUPPORT_DIMENSIONS = 4; +const PacketVersion VERSION_ENTITIES_MODELS_HAVE_ANIMATION_SETTINGS = 5; const PacketVersion VERSION_VOXELS_HAS_FILE_BREAKS = 1; #endif // hifi_PacketHeaders_h diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 596dd7536c..4450689949 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include #include @@ -71,6 +72,10 @@ void Octree::recurseTreeWithPostOperation(RecurseOctreeOperation operation, void void Octree::recurseElementWithOperation(OctreeElement* element, RecurseOctreeOperation operation, void* extraData, int recursionCount) { if (recursionCount > DANGEROUSLY_DEEP_RECURSION) { + static QString repeatedMessage + = LogHandler::getInstance().addRepeatedMessageRegex( + "Octree::recurseElementWithOperation() reached DANGEROUSLY_DEEP_RECURSION, bailing!"); + qDebug() << "Octree::recurseElementWithOperation() reached DANGEROUSLY_DEEP_RECURSION, bailing!"; return; } @@ -89,7 +94,11 @@ void Octree::recurseElementWithOperation(OctreeElement* element, RecurseOctreeOp void Octree::recurseElementWithPostOperation(OctreeElement* element, RecurseOctreeOperation operation, void* extraData, int recursionCount) { if (recursionCount > DANGEROUSLY_DEEP_RECURSION) { - qDebug() << "Octree::recurseElementWithOperation() reached DANGEROUSLY_DEEP_RECURSION, bailing!\n"; + static QString repeatedMessage + = LogHandler::getInstance().addRepeatedMessageRegex( + "Octree::recurseElementWithPostOperation() reached DANGEROUSLY_DEEP_RECURSION, bailing!"); + + qDebug() << "Octree::recurseElementWithPostOperation() reached DANGEROUSLY_DEEP_RECURSION, bailing!"; return; } @@ -115,6 +124,10 @@ void Octree::recurseElementWithOperationDistanceSorted(OctreeElement* element, R const glm::vec3& point, void* extraData, int recursionCount) { if (recursionCount > DANGEROUSLY_DEEP_RECURSION) { + static QString repeatedMessage + = LogHandler::getInstance().addRepeatedMessageRegex( + "Octree::recurseElementWithOperationDistanceSorted() reached DANGEROUSLY_DEEP_RECURSION, bailing!"); + qDebug() << "Octree::recurseElementWithOperationDistanceSorted() reached DANGEROUSLY_DEEP_RECURSION, bailing!"; return; } @@ -152,7 +165,11 @@ void Octree::recurseTreeWithOperator(RecurseOctreeOperator* operatorObject) { bool Octree::recurseElementWithOperator(OctreeElement* element, RecurseOctreeOperator* operatorObject, int recursionCount) { if (recursionCount > DANGEROUSLY_DEEP_RECURSION) { - qDebug() << "Octree::recurseElementWithOperation() reached DANGEROUSLY_DEEP_RECURSION, bailing!"; + static QString repeatedMessage + = LogHandler::getInstance().addRepeatedMessageRegex( + "Octree::recurseElementWithOperator() reached DANGEROUSLY_DEEP_RECURSION, bailing!"); + + qDebug() << "Octree::recurseElementWithOperator() reached DANGEROUSLY_DEEP_RECURSION, bailing!"; return false; } diff --git a/libraries/octree/src/OctreeSceneStats.cpp b/libraries/octree/src/OctreeSceneStats.cpp index 82a874a680..4b21890a22 100644 --- a/libraries/octree/src/OctreeSceneStats.cpp +++ b/libraries/octree/src/OctreeSceneStats.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -854,8 +855,12 @@ void OctreeSceneStats::trackIncomingOctreePacket(const QByteArray& packet, const int MAX_RESONABLE_FLIGHT_TIME = 200 * USECS_PER_SECOND; // 200 seconds is more than enough time for a packet to arrive const int MIN_RESONABLE_FLIGHT_TIME = 0; if (flightTime > MAX_RESONABLE_FLIGHT_TIME || flightTime < MIN_RESONABLE_FLIGHT_TIME) { + static QString repeatedMessage + = LogHandler::getInstance().addRepeatedMessageRegex( + "ignoring unreasonable packet... flightTime: -?\\d+ nodeClockSkewUsec: -?\\d+ usecs"); + qDebug() << "ignoring unreasonable packet... flightTime:" << flightTime - << " nodeClockSkewUsec:" << nodeClockSkewUsec << " usecs";; + << "nodeClockSkewUsec:" << nodeClockSkewUsec << "usecs";; return; // ignore any packets that are unreasonable } diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index ac1b73e50b..69dfb4db35 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -34,6 +34,15 @@ struct xColor { unsigned char blue; }; +inline QDebug& operator<<(QDebug& dbg, const xColor& c) { + dbg.nospace() << "{type='xColor'" + ", red=" << c.red << + ", green=" << c.green << + ", blue=" << c.blue << + "}"; + return dbg; +} + static const float ZERO = 0.0f; static const float ONE = 1.0f; static const float ONE_HALF = 0.5f;