diff --git a/assignment-client/CMakeLists.txt b/assignment-client/CMakeLists.txt index e783001228..6436ffbd75 100644 --- a/assignment-client/CMakeLists.txt +++ b/assignment-client/CMakeLists.txt @@ -33,6 +33,7 @@ link_hifi_library(particles ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(models ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(metavoxels ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}") +link_hifi_library(animation ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(script-engine ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(embedded-webserver ${TARGET_NAME} "${ROOT_DIR}") diff --git a/examples/placeModelsWithHands.js b/examples/placeModelsWithHands.js index e1ac151fe4..f16945472d 100644 --- a/examples/placeModelsWithHands.js +++ b/examples/placeModelsWithHands.js @@ -37,6 +37,7 @@ var radiusMinimum = 0.05; var radiusMaximum = 0.5; var modelURLs = [ + "http://www.fungibleinsight.com/faces/beta.fst", "https://s3-us-west-1.amazonaws.com/highfidelity-public/models/attachments/topHat.fst", "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Feisar_Ship.FBX", "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/birarda/birarda_head.fbx", @@ -48,6 +49,19 @@ var modelURLs = [ "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/slimer.fbx", ]; +var animationURLs = [ + "http://www.fungibleinsight.com/faces/gangnam_style_2.fbx", + "", + "", + "", + "", + "", + "", + "", + "", + "", +]; + var currentModelURL = 1; var numModels = modelURLs.length; @@ -214,10 +228,18 @@ function checkControllerSide(whichSide) { modelRotation: palmRotation, modelURL: modelURLs[currentModelURL] }; + + if (animationURLs[currentModelURL] !== "") { + properties.animationURL = animationURLs[currentModelURL]; + } debugPrint("modelRadius=" +modelRadius); newModel = Models.addModel(properties); + + print("just added model... newModel=" + newModel.creatorTokenID); + print("properties.animationURL=" + properties.animationURL); + if (whichSide == LEFT_PALM) { leftModelAlreadyInHand = true; leftHandModel = newModel; diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 0a56109260..4d02415df2 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -127,6 +127,7 @@ link_hifi_library(particles ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(models ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(avatars ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(audio ${TARGET_NAME} "${ROOT_DIR}") +link_hifi_library(animation ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(script-engine ${TARGET_NAME} "${ROOT_DIR}") # find any optional libraries diff --git a/interface/src/models/ModelTreeRenderer.cpp b/interface/src/models/ModelTreeRenderer.cpp index c762182290..e5c55ef066 100644 --- a/interface/src/models/ModelTreeRenderer.cpp +++ b/interface/src/models/ModelTreeRenderer.cpp @@ -20,11 +20,16 @@ ModelTreeRenderer::ModelTreeRenderer() : } ModelTreeRenderer::~ModelTreeRenderer() { - // delete the models in _modelsItemModels - foreach(Model* model, _modelsItemModels) { + // delete the models in _knownModelsItemModels + foreach(Model* model, _knownModelsItemModels) { delete model; } - _modelsItemModels.clear(); + _knownModelsItemModels.clear(); + + foreach(Model* model, _unknownModelsItemModels) { + delete model; + } + _unknownModelsItemModels.clear(); } void ModelTreeRenderer::init() { @@ -43,17 +48,27 @@ void ModelTreeRenderer::render(RenderMode renderMode) { OctreeRenderer::render(renderMode); } -Model* ModelTreeRenderer::getModel(const QString& url) { +Model* ModelTreeRenderer::getModel(const ModelItem& modelItem) { Model* model = NULL; - // if we don't already have this model then create it and initialize it - if (_modelsItemModels.find(url) == _modelsItemModels.end()) { - model = new Model(); - model->init(); - model->setURL(QUrl(url)); - _modelsItemModels[url] = model; + if (modelItem.isKnownID()) { + if (_knownModelsItemModels.find(modelItem.getID()) != _knownModelsItemModels.end()) { + model = _knownModelsItemModels[modelItem.getID()]; + } else { + model = new Model(); + model->init(); + model->setURL(QUrl(modelItem.getModelURL())); + _knownModelsItemModels[modelItem.getID()] = model; + } } else { - model = _modelsItemModels[url]; + if (_unknownModelsItemModels.find(modelItem.getCreatorTokenID()) != _unknownModelsItemModels.end()) { + model = _unknownModelsItemModels[modelItem.getCreatorTokenID()]; + } else { + model = new Model(); + model->init(); + model->setURL(QUrl(modelItem.getModelURL())); + _unknownModelsItemModels[modelItem.getCreatorTokenID()] = model; + } } return model; } @@ -63,7 +78,7 @@ void ModelTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args) // we need to iterate the actual modelItems of the element ModelTreeElement* modelTreeElement = (ModelTreeElement*)element; - const QList& modelItems = modelTreeElement->getModels(); + QList& modelItems = modelTreeElement->getModels(); uint16_t numberOfModels = modelItems.size(); @@ -139,7 +154,7 @@ void ModelTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args) } for (uint16_t i = 0; i < numberOfModels; i++) { - const ModelItem& modelItem = modelItems[i]; + ModelItem& modelItem = modelItems[i]; // render modelItem aspoints AABox modelBox = modelItem.getAABox(); modelBox.scale(TREE_SCALE); @@ -156,7 +171,7 @@ void ModelTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args) glPushMatrix(); const float alpha = 1.0f; - Model* model = getModel(modelItem.getModelURL()); + Model* model = getModel(modelItem); model->setScaleToFit(true, radius * 2.0f); model->setSnapModelToCenter(true); @@ -167,6 +182,25 @@ void ModelTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args) // set the position model->setTranslation(position); + + qDebug() << "modelItem.getModelURL()=" << modelItem.getModelURL(); + qDebug() << "modelItem.getAnimationURL()=" << modelItem.getAnimationURL(); + qDebug() << "modelItem.hasAnimation()=" << modelItem.hasAnimation(); + + // handle animations.. + if (modelItem.hasAnimation()) { + if (!modelItem.jointsMapped()) { + QStringList modelJointNames = model->getJointNames(); + modelItem.mapJoints(modelJointNames); + } + + QVector frameData = modelItem.getAnimationFrame(); + for (int i = 0; i < frameData.size(); i++) { + model->setJointState(i, true, frameData[i]); + } + } + + // make sure to simulate so everything gets set up correctly for rendering model->simulate(0.0f); // TODO: should we allow modelItems to have alpha on their models? diff --git a/interface/src/models/ModelTreeRenderer.h b/interface/src/models/ModelTreeRenderer.h index 7af5bbf317..e0b8d7d0a2 100644 --- a/interface/src/models/ModelTreeRenderer.h +++ b/interface/src/models/ModelTreeRenderer.h @@ -49,9 +49,9 @@ public: virtual void render(RenderMode renderMode = DEFAULT_RENDER_MODE); protected: - Model* getModel(const QString& url); - - QMap _modelsItemModels; + Model* getModel(const ModelItem& modelItem); + QMap _knownModelsItemModels; + QMap _unknownModelsItemModels; }; #endif // hifi_ModelTreeRenderer_h diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index f46fd48beb..b2c1665c2f 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -430,7 +430,8 @@ Extents Model::getMeshExtents() const { return Extents(); } const Extents& extents = _geometry->getFBXGeometry().meshExtents; - Extents scaledExtents = { extents.minimum * _scale, extents.maximum * _scale }; + glm::vec3 scale = _scale * _geometry->getFBXGeometry().fstScaled; + Extents scaledExtents = { extents.minimum * scale, extents.maximum * scale }; return scaledExtents; } @@ -438,8 +439,13 @@ Extents Model::getUnscaledMeshExtents() const { if (!isActive()) { return Extents(); } + const Extents& extents = _geometry->getFBXGeometry().meshExtents; - return extents; + + // even though our caller asked for "unscaled" we need to include any fst scaling + float scale = _geometry->getFBXGeometry().fstScaled; + Extents scaledExtents = { extents.minimum * scale, extents.maximum * scale }; + return scaledExtents; } bool Model::getJointState(int index, glm::quat& rotation) const { @@ -573,6 +579,17 @@ bool Model::getJointRotation(int jointIndex, glm::quat& rotation, bool fromBind) return true; } +QStringList Model::getJointNames() const { + if (QThread::currentThread() != thread()) { + QStringList result; + QMetaObject::invokeMethod(const_cast(this), "getJointNames", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(QStringList, result)); + return result; + } + return isActive() ? _geometry->getFBXGeometry().getJointNames() : QStringList(); +} + + void Model::clearShapes() { for (int i = 0; i < _jointShapes.size(); ++i) { delete _jointShapes[i]; diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 1a469c8122..b7a42930dc 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -182,6 +182,8 @@ public: bool getJointPosition(int jointIndex, glm::vec3& position) const; bool getJointRotation(int jointIndex, glm::quat& rotation, bool fromBind = false) const; + + QStringList getJointNames() const; void clearShapes(); void rebuildShapes(); diff --git a/libraries/script-engine/src/AnimationCache.cpp b/libraries/animation/src/AnimationCache.cpp similarity index 98% rename from libraries/script-engine/src/AnimationCache.cpp rename to libraries/animation/src/AnimationCache.cpp index 8e1493f075..ce7e4cdf36 100644 --- a/libraries/script-engine/src/AnimationCache.cpp +++ b/libraries/animation/src/AnimationCache.cpp @@ -36,7 +36,8 @@ QSharedPointer AnimationCache::createResource(const QUrl& url, const Q } Animation::Animation(const QUrl& url) : - Resource(url) { + Resource(url), + _isValid(false) { } class AnimationReader : public QRunnable { @@ -93,6 +94,7 @@ QVector Animation::getFrames() const { void Animation::setGeometry(const FBXGeometry& geometry) { _geometry = geometry; finishedLoading(true); + _isValid = true; } void Animation::downloadFinished(QNetworkReply* reply) { diff --git a/libraries/script-engine/src/AnimationCache.h b/libraries/animation/src/AnimationCache.h similarity index 96% rename from libraries/script-engine/src/AnimationCache.h rename to libraries/animation/src/AnimationCache.h index 23183adf10..392443e7b5 100644 --- a/libraries/script-engine/src/AnimationCache.h +++ b/libraries/animation/src/AnimationCache.h @@ -53,6 +53,8 @@ public: Q_INVOKABLE QStringList getJointNames() const; Q_INVOKABLE QVector getFrames() const; + + bool isValid() const { return _isValid; } protected: @@ -63,6 +65,7 @@ protected: private: FBXGeometry _geometry; + bool _isValid; }; #endif // hifi_AnimationCache_h diff --git a/libraries/script-engine/src/AnimationObject.cpp b/libraries/animation/src/AnimationObject.cpp similarity index 100% rename from libraries/script-engine/src/AnimationObject.cpp rename to libraries/animation/src/AnimationObject.cpp diff --git a/libraries/script-engine/src/AnimationObject.h b/libraries/animation/src/AnimationObject.h similarity index 100% rename from libraries/script-engine/src/AnimationObject.h rename to libraries/animation/src/AnimationObject.h diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 1fc03ceb66..f743a383d1 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -1385,6 +1385,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) // get offset transform from mapping float offsetScale = mapping.value("scale", 1.0f).toFloat(); + geometry.fstScaled = offsetScale; glm::quat offsetRotation = glm::quat(glm::radians(glm::vec3(mapping.value("rx").toFloat(), mapping.value("ry").toFloat(), mapping.value("rz").toFloat()))); geometry.offset = glm::translate(glm::vec3(mapping.value("tx").toFloat(), mapping.value("ty").toFloat(), diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index 51e7380181..567a32c1cb 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -212,6 +212,8 @@ public: Extents bindExtents; Extents meshExtents; + float fstScaled; + QVector animationFrames; QVector attachments; diff --git a/libraries/models/CMakeLists.txt b/libraries/models/CMakeLists.txt index 062352e50c..1e70942872 100644 --- a/libraries/models/CMakeLists.txt +++ b/libraries/models/CMakeLists.txt @@ -28,6 +28,7 @@ link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(fbx ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}") +link_hifi_library(animation ${TARGET_NAME} "${ROOT_DIR}") # for streamable link_hifi_library(metavoxels ${TARGET_NAME} "${ROOT_DIR}") diff --git a/libraries/models/src/ModelItem.cpp b/libraries/models/src/ModelItem.cpp index af8500bf25..785183da56 100644 --- a/libraries/models/src/ModelItem.cpp +++ b/libraries/models/src/ModelItem.cpp @@ -85,6 +85,12 @@ ModelItem::ModelItem(const ModelItemID& modelItemID, const ModelItemProperties& _shouldDie = false; _modelURL = MODEL_DEFAULT_MODEL_URL; _modelRotation = MODEL_DEFAULT_MODEL_ROTATION; + + // animation related + _animationURL = MODEL_DEFAULT_ANIMATION_URL; + _frameIndex = 0.0f; + _jointMappingCompleted = false; + _lastAnimated = now; setProperties(properties); } @@ -110,6 +116,12 @@ void ModelItem::init(glm::vec3 position, float radius, rgbColor color, uint32_t _shouldDie = false; _modelURL = MODEL_DEFAULT_MODEL_URL; _modelRotation = MODEL_DEFAULT_MODEL_ROTATION; + + // animation related + _animationURL = MODEL_DEFAULT_ANIMATION_URL; + _frameIndex = 0.0f; + _jointMappingCompleted = false; + _lastAnimated = now; } bool ModelItem::appendModelData(OctreePacketData* packetData) const { @@ -150,6 +162,16 @@ bool ModelItem::appendModelData(OctreePacketData* packetData) const { if (success) { success = packetData->appendValue(getModelRotation()); } + + // animationURL + if (success) { + uint16_t animationURLLength = _animationURL.size() + 1; // include NULL + success = packetData->appendValue(animationURLLength); + if (success) { + success = packetData->appendRawData((const unsigned char*)qPrintable(_animationURL), animationURLLength); + } + } + return success; } @@ -215,7 +237,7 @@ int ModelItem::readModelDataFromBuffer(const unsigned char* data, int bytesLeftT dataAt += sizeof(modelURLLength); bytesRead += sizeof(modelURLLength); QString modelURLString((const char*)dataAt); - _modelURL = modelURLString; + setModelURL(modelURLString); dataAt += modelURLLength; bytesRead += modelURLLength; @@ -224,6 +246,19 @@ int ModelItem::readModelDataFromBuffer(const unsigned char* data, int bytesLeftT dataAt += bytes; bytesRead += bytes; + // animationURL + uint16_t animationURLLength; + memcpy(&animationURLLength, dataAt, sizeof(animationURLLength)); + dataAt += sizeof(animationURLLength); + bytesRead += sizeof(animationURLLength); + QString animationURLString((const char*)dataAt); + setAnimationURL(animationURLString); + dataAt += animationURLLength; + bytesRead += animationURLLength; + +qDebug() << "readModelDataFromBuffer()... animationURL=" << qPrintable(animationURLString); + + //printf("ModelItem::readModelDataFromBuffer()... "); debugDump(); } return bytesRead; @@ -346,6 +381,21 @@ ModelItem ModelItem::fromEditPacket(const unsigned char* data, int length, int& processedBytes += bytes; } + // animationURL + if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_ANIMATION_URL) == MODEL_PACKET_CONTAINS_ANIMATION_URL)) { + uint16_t animationURLLength; + memcpy(&animationURLLength, dataAt, sizeof(animationURLLength)); + dataAt += sizeof(animationURLLength); + processedBytes += sizeof(animationURLLength); + QString tempString((const char*)dataAt); + newModelItem._animationURL = tempString; + dataAt += animationURLLength; + processedBytes += animationURLLength; + +qDebug() << "fromEditPacket()... animationURL=" << qPrintable(tempString); + + } + const bool wantDebugging = false; if (wantDebugging) { qDebug("ModelItem::fromEditPacket()..."); @@ -476,6 +526,21 @@ bool ModelItem::encodeModelEditMessageDetails(PacketType command, ModelItemID id sizeOut += bytes; } + // animationURL + if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_ANIMATION_URL) == MODEL_PACKET_CONTAINS_ANIMATION_URL)) { + uint16_t urlLength = properties.getAnimationURL().size() + 1; + memcpy(copyAt, &urlLength, sizeof(urlLength)); + copyAt += sizeof(urlLength); + sizeOut += sizeof(urlLength); + memcpy(copyAt, qPrintable(properties.getAnimationURL()), urlLength); + copyAt += urlLength; + sizeOut += urlLength; + +qDebug() << "encodeModelItemEditMessageDetails()... animationURL=" << qPrintable(properties.getAnimationURL()); + + } + + bool wantDebugging = false; if (wantDebugging) { qDebug("encodeModelItemEditMessageDetails()...."); @@ -521,6 +586,66 @@ void ModelItem::adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, ssi } } + +QMap ModelItem::_loadedAnimations; // TODO: cleanup?? +AnimationCache ModelItem::_animationCache; + +Animation* ModelItem::getAnimation(const QString& url) { + AnimationPointer animation; + + // if we don't already have this model then create it and initialize it + if (_loadedAnimations.find(url) == _loadedAnimations.end()) { + animation = _animationCache.getAnimation(url); + _loadedAnimations[url] = animation; + } else { + animation = _loadedAnimations[url]; + } + return animation.data(); +} + +void ModelItem::mapJoints(const QStringList& modelJointNames) { + // if we don't have animation, or we're already joint mapped then bail early + if (!hasAnimation() || _jointMappingCompleted) { + return; + } + + Animation* myAnimation = getAnimation(_animationURL); + + if (!_jointMappingCompleted) { + QStringList animationJointNames = myAnimation->getJointNames(); + if (modelJointNames.size() > 0 && animationJointNames.size() > 0) { + _jointMapping.resize(modelJointNames.size()); + for (int i = 0; i < modelJointNames.size(); i++) { + _jointMapping[i] = animationJointNames.indexOf(modelJointNames[i]); + } + _jointMappingCompleted = true; + } + } +} + +QVector ModelItem::getAnimationFrame() { + QVector frameData; + if (hasAnimation() && _jointMappingCompleted) { + quint64 now = usecTimestampNow(); + float deltaTime = (float)(now - _lastAnimated) / (float)USECS_PER_SECOND; + _lastAnimated = now; + const float FRAME_RATE = 10.0f; + _frameIndex += deltaTime * FRAME_RATE; + Animation* myAnimation = getAnimation(_animationURL); + QVector frames = myAnimation->getFrames(); + int frameIndex = (int)std::floor(_frameIndex) % frames.size(); + QVector rotations = frames[frameIndex].rotations; + frameData.resize(_jointMapping.size()); + for (int j = 0; j < _jointMapping.size(); j++) { + int rotationIndex = _jointMapping[j]; + if (rotationIndex != -1 && rotationIndex < rotations.size()) { + frameData[j] = rotations[rotationIndex]; + } + } + } + return frameData; +} + void ModelItem::update(const quint64& now) { _lastUpdated = now; setShouldDie(getShouldDie()); @@ -547,6 +672,7 @@ ModelItemProperties::ModelItemProperties() : _shouldDie(false), _modelURL(""), _modelRotation(MODEL_DEFAULT_MODEL_ROTATION), + _animationURL(""), _id(UNKNOWN_MODEL_ID), _idSet(false), @@ -558,6 +684,7 @@ ModelItemProperties::ModelItemProperties() : _shouldDieChanged(false), _modelURLChanged(false), _modelRotationChanged(false), + _animationURLChanged(false), _defaultSettings(true) { } @@ -589,6 +716,11 @@ uint16_t ModelItemProperties::getChangedBits() const { changedBits += MODEL_PACKET_CONTAINS_MODEL_ROTATION; } + if (_animationURLChanged) { + changedBits += MODEL_PACKET_CONTAINS_ANIMATION_URL; + } + + return changedBits; } @@ -611,6 +743,7 @@ QScriptValue ModelItemProperties::copyToScriptValue(QScriptEngine* engine) const QScriptValue modelRotation = quatToScriptValue(engine, _modelRotation); properties.setProperty("modelRotation", modelRotation); + properties.setProperty("animationURL", _animationURL); if (_idSet) { properties.setProperty("id", _id); @@ -707,6 +840,16 @@ void ModelItemProperties::copyFromScriptValue(const QScriptValue &object) { } } + QScriptValue animationURL = object.property("animationURL"); + if (animationURL.isValid()) { + QString newAnimationURL; + newAnimationURL = animationURL.toVariant().toString(); + if (_defaultSettings || newAnimationURL != _animationURL) { + _animationURL = newAnimationURL; + _animationURLChanged = true; + } + } + _lastEdited = usecTimestampNow(); } @@ -741,7 +884,14 @@ void ModelItemProperties::copyToModelItem(ModelItem& modelItem) const { modelItem.setModelRotation(_modelRotation); somethingChanged = true; } - + + if (_animationURLChanged) { + modelItem.setAnimationURL(_animationURL); + somethingChanged = true; + +qDebug() << "ModelItemProperties::copyToModelItem()... modelItem.setAnimationURL(_animationURL)=" << _animationURL; + } + if (somethingChanged) { bool wantDebug = false; if (wantDebug) { @@ -761,6 +911,7 @@ void ModelItemProperties::copyFromModelItem(const ModelItem& modelItem) { _shouldDie = modelItem.getShouldDie(); _modelURL = modelItem.getModelURL(); _modelRotation = modelItem.getModelRotation(); + _animationURL = modelItem.getAnimationURL(); _id = modelItem.getID(); _idSet = true; @@ -772,6 +923,7 @@ void ModelItemProperties::copyFromModelItem(const ModelItem& modelItem) { _shouldDieChanged = false; _modelURLChanged = false; _modelRotationChanged = false; + _animationURLChanged = false; _defaultSettings = false; } diff --git a/libraries/models/src/ModelItem.h b/libraries/models/src/ModelItem.h index 9edcf482c0..72a12c9b2c 100644 --- a/libraries/models/src/ModelItem.h +++ b/libraries/models/src/ModelItem.h @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -39,14 +40,16 @@ const uint32_t UNKNOWN_MODEL_ID = 0xFFFFFFFF; const uint16_t MODEL_PACKET_CONTAINS_RADIUS = 1; const uint16_t MODEL_PACKET_CONTAINS_POSITION = 2; const uint16_t MODEL_PACKET_CONTAINS_COLOR = 4; -const uint16_t MODEL_PACKET_CONTAINS_SHOULDDIE = 512; -const uint16_t MODEL_PACKET_CONTAINS_MODEL_URL = 1024; -const uint16_t MODEL_PACKET_CONTAINS_MODEL_ROTATION = 2048; +const uint16_t MODEL_PACKET_CONTAINS_SHOULDDIE = 8; +const uint16_t MODEL_PACKET_CONTAINS_MODEL_URL = 16; +const uint16_t MODEL_PACKET_CONTAINS_MODEL_ROTATION = 32; +const uint16_t MODEL_PACKET_CONTAINS_ANIMATION_URL = 64; const float MODEL_DEFAULT_RADIUS = 0.1f / TREE_SCALE; const float MINIMUM_MODEL_ELEMENT_SIZE = (1.0f / 100000.0f) / TREE_SCALE; // smallest size container const QString MODEL_DEFAULT_MODEL_URL(""); const glm::quat MODEL_DEFAULT_MODEL_ROTATION; +const QString MODEL_DEFAULT_ANIMATION_URL(""); /// A collection of properties of a model item used in the scripting API. Translates between the actual properties of a model /// and a JavaScript style hash/QScriptValue storing a set of properties. Used in scripting to set/get the complete set of @@ -69,6 +72,7 @@ public: const QString& getModelURL() const { return _modelURL; } const glm::quat& getModelRotation() const { return _modelRotation; } + const QString& getAnimationURL() const { return _animationURL; } quint64 getLastEdited() const { return _lastEdited; } uint16_t getChangedBits() const; @@ -82,6 +86,7 @@ public: // model related properties void setModelURL(const QString& url) { _modelURL = url; _modelURLChanged = true; } void setModelRotation(const glm::quat& rotation) { _modelRotation = rotation; _modelRotationChanged = true; } + void setAnimationURL(const QString& url) { _animationURL = url; _animationURLChanged = true; } /// used by ModelScriptingInterface to return ModelItemProperties for unknown models void setIsUnknownID() { _id = UNKNOWN_MODEL_ID; _idSet = true; } @@ -97,6 +102,7 @@ private: QString _modelURL; glm::quat _modelRotation; + QString _animationURL; uint32_t _id; bool _idSet; @@ -109,6 +115,7 @@ private: bool _modelURLChanged; bool _modelRotationChanged; + bool _animationURLChanged; bool _defaultSettings; }; Q_DECLARE_METATYPE(ModelItemProperties); @@ -178,6 +185,8 @@ public: bool hasModel() const { return !_modelURL.isEmpty(); } const QString& getModelURL() const { return _modelURL; } const glm::quat& getModelRotation() const { return _modelRotation; } + bool hasAnimation() const { return !_animationURL.isEmpty(); } + const QString& getAnimationURL() const { return _animationURL; } ModelItemID getModelItemID() const { return ModelItemID(getID(), getCreatorTokenID(), getID() != UNKNOWN_MODEL_ID); } ModelItemProperties getProperties() const; @@ -196,6 +205,7 @@ public: bool getShouldDie() const { return _shouldDie; } uint32_t getCreatorTokenID() const { return _creatorTokenID; } bool isNewlyCreated() const { return _newlyCreated; } + bool isKnownID() const { return getID() != UNKNOWN_MODEL_ID; } /// set position in domain scale units (0.0 - 1.0) void setPosition(const glm::vec3& value) { _position = value; } @@ -215,6 +225,7 @@ public: // model related properties void setModelURL(const QString& url) { _modelURL = url; } void setModelRotation(const glm::quat& rotation) { _modelRotation = rotation; } + void setAnimationURL(const QString& url) { _animationURL = url; } void setProperties(const ModelItemProperties& properties); @@ -239,6 +250,10 @@ public: static uint32_t getNextCreatorTokenID(); static void handleAddModelResponse(const QByteArray& packet); + void mapJoints(const QStringList& modelJointNames); + QVector getAnimationFrame(); + bool jointsMapped() const { return _jointMappingCompleted; }; + protected: glm::vec3 _position; rgbColor _color; @@ -256,10 +271,23 @@ protected: quint64 _lastUpdated; quint64 _lastEdited; + quint64 _lastAnimated; + + QString _animationURL; + float _frameIndex; // we keep this as a float and round to int only when we need the exact index + bool _jointMappingCompleted; + QVector _jointMapping; + // used by the static interfaces for creator token ids static uint32_t _nextCreatorTokenID; static std::map _tokenIDsToIDs; + + + static Animation* getAnimation(const QString& url); + static QMap _loadedAnimations; + static AnimationCache _animationCache; + }; #endif // hifi_ModelItem_h diff --git a/libraries/networking/src/PacketHeaders.cpp b/libraries/networking/src/PacketHeaders.cpp index 0785b81581..b9eee6e0c9 100644 --- a/libraries/networking/src/PacketHeaders.cpp +++ b/libraries/networking/src/PacketHeaders.cpp @@ -66,6 +66,8 @@ PacketVersion versionForPacketType(PacketType type) { return 1; case PacketTypeOctreeStats: return 1; + case PacketTypeModelData: + return 1; default: return 0; } diff --git a/libraries/particles/CMakeLists.txt b/libraries/particles/CMakeLists.txt index 1cb60756a2..8cd2f30012 100644 --- a/libraries/particles/CMakeLists.txt +++ b/libraries/particles/CMakeLists.txt @@ -25,6 +25,7 @@ link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(fbx ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}") +link_hifi_library(animation ${TARGET_NAME} "${ROOT_DIR}") # link ZLIB and GnuTLS find_package(ZLIB) diff --git a/libraries/script-engine/CMakeLists.txt b/libraries/script-engine/CMakeLists.txt index ee918ff864..0374ad570c 100644 --- a/libraries/script-engine/CMakeLists.txt +++ b/libraries/script-engine/CMakeLists.txt @@ -27,6 +27,7 @@ link_hifi_library(voxels ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(fbx ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(particles ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(models ${TARGET_NAME} "${ROOT_DIR}") +link_hifi_library(animation ${TARGET_NAME} "${ROOT_DIR}") # link ZLIB find_package(ZLIB) diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 09d41e3e2e..96cc874453 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -18,13 +18,12 @@ #include #include +#include #include -#include - #include #include +#include -#include "AnimationCache.h" #include "AbstractControllerScriptingInterface.h" #include "Quat.h" #include "ScriptUUID.h"