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/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 9b2134ba44..e6c14d06da 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -250,6 +250,12 @@ void Agent::run() { _particleViewer.init(); _scriptEngine.getParticlesScriptingInterface()->setParticleTree(_particleViewer.getTree()); + _scriptEngine.registerGlobalObject("ModelViewer", &_modelViewer); + JurisdictionListener* modelJL = _scriptEngine.getModelsScriptingInterface()->getJurisdictionListener(); + _modelViewer.setJurisdictionListener(modelJL); + _modelViewer.init(); + _scriptEngine.getModelsScriptingInterface()->setModelTree(_modelViewer.getTree()); + _scriptEngine.setScriptContents(scriptContents); _scriptEngine.run(); setFinished(true); diff --git a/examples/animatedModelExample.js b/examples/animatedModelExample.js new file mode 100644 index 0000000000..5199eb419f --- /dev/null +++ b/examples/animatedModelExample.js @@ -0,0 +1,124 @@ +// +// animatedModelExample.js +// examples +// +// Created by Brad Hefta-Gaub on 12/31/13. +// Copyright 2014 High Fidelity, Inc. +// +// This is an example script that demonstrates creating and editing a model +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var count = 0; +var moveUntil = 6000; +var stopAfter = moveUntil + 100; + +var pitch = 0.0; +var yaw = 0.0; +var roll = 0.0; +var rotation = Quat.fromPitchYawRollDegrees(pitch, yaw, roll) + +var originalProperties = { + position: { x: 10, + y: 0, + z: 0 }, + + radius : 1, + + color: { red: 0, + green: 255, + blue: 0 }, + + modelURL: "http://www.fungibleinsight.com/faces/beta.fst", + modelRotation: rotation, + animationURL: "http://www.fungibleinsight.com/faces/gangnam_style_2.fbx", + animationIsPlaying: true, +}; + +var modelID = Models.addModel(originalProperties); +print("Models.addModel()... modelID.creatorTokenID = " + modelID.creatorTokenID); + +var isPlaying = true; +var playPauseEveryWhile = 360; +var animationFPS = 30; +var adjustFPSEveryWhile = 120; +var resetFrameEveryWhile = 600; + +function moveModel(deltaTime) { + var somethingChanged = false; + if (count % playPauseEveryWhile == 0) { + isPlaying = !isPlaying; + print("isPlaying=" + isPlaying); + somethingChanged = true; + } + + if (count % adjustFPSEveryWhile == 0) { + if (animationFPS == 30) { + animationFPS = 10; + } else if (animationFPS == 10) { + animationFPS = 60; + } else if (animationFPS == 60) { + animationFPS = 30; + } + print("animationFPS=" + animationFPS); + isPlaying = true; + print("always start playing if we change the FPS -- isPlaying=" + isPlaying); + somethingChanged = true; + } + + if (count % resetFrameEveryWhile == 0) { + resetFrame = true; + somethingChanged = true; + } + + if (count >= moveUntil) { + + // delete it... + if (count == moveUntil) { + print("calling Models.deleteModel()"); + Models.deleteModel(modelID); + } + + // stop it... + if (count >= stopAfter) { + print("calling Script.stop()"); + Script.stop(); + } + + count++; + return; // break early + } + + count++; + + //print("modelID.creatorTokenID = " + modelID.creatorTokenID); + + if (somethingChanged) { + var newProperties = { + animationIsPlaying: isPlaying, + animationFPS: animationFPS, + }; + + if (resetFrame) { + print("resetting the frame!"); + newProperties.animationFrameIndex = 0; + resetFrame = false; + } + + Models.editModel(modelID, newProperties); + } +} + + +// register the call back so it fires before each data send +Script.update.connect(moveModel); + + +Script.scriptEnding.connect(function () { + print("cleaning up..."); + print("modelID="+ modelID.creatorTokenID + ", id:" + modelID.id); + Models.deleteModel(modelID); +}); + diff --git a/examples/placeModelsWithHands.js b/examples/placeModelsWithHands.js index e1ac151fe4..47b4615924 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,19 @@ function checkControllerSide(whichSide) { modelRotation: palmRotation, modelURL: modelURLs[currentModelURL] }; + + if (animationURLs[currentModelURL] !== "") { + properties.animationURL = animationURLs[currentModelURL]; + properties.animationIsPlaying = true; + } 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 d075fbe9d0..96c212add6 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -128,6 +128,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..ae60683745 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,21 @@ void ModelTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args) // set the position model->setTranslation(position); + + // 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 68eccc8f49..3d89080bb0 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -433,7 +433,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; } @@ -441,8 +442,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 { @@ -576,6 +582,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/animation/CMakeLists.txt b/libraries/animation/CMakeLists.txt new file mode 100644 index 0000000000..36088ba4bd --- /dev/null +++ b/libraries/animation/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 2.8) + +if (WIN32) + cmake_policy (SET CMP0020 NEW) +endif (WIN32) + +set(ROOT_DIR ../..) +set(MACRO_DIR "${ROOT_DIR}/cmake/macros") + +# setup for find modules +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/modules/") + +set(TARGET_NAME animation) + +find_package(Qt5Widgets REQUIRED) + +include(${MACRO_DIR}/SetupHifiLibrary.cmake) +setup_hifi_library(${TARGET_NAME}) + +include(${MACRO_DIR}/IncludeGLM.cmake) +include_glm(${TARGET_NAME} "${ROOT_DIR}") + +include(${MACRO_DIR}/LinkHifiLibrary.cmake) +link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}") +link_hifi_library(fbx ${TARGET_NAME} "${ROOT_DIR}") + +# link ZLIB +find_package(ZLIB) +find_package(GnuTLS REQUIRED) + +# add a definition for ssize_t so that windows doesn't bail on gnutls.h +if (WIN32) + add_definitions(-Dssize_t=long) +endif () + +include_directories(SYSTEM "${ZLIB_INCLUDE_DIRS}" "${GNUTLS_INCLUDE_DIR}") +target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" "${GNUTLS_LIBRARY}" Qt5::Widgets) 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 ce71e90114..264f58f0d4 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -1398,6 +1398,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 38251e4065..2aabdab6fa 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -211,6 +211,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..c04f9a76ae 100644 --- a/libraries/models/src/ModelItem.cpp +++ b/libraries/models/src/ModelItem.cpp @@ -85,6 +85,15 @@ 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; + _animationIsPlaying = false; + _animationFrameIndex = 0.0f; + _animationFPS = MODEL_DEFAULT_ANIMATION_FPS; + + _jointMappingCompleted = false; + _lastAnimated = now; setProperties(properties); } @@ -110,6 +119,14 @@ 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; + _animationIsPlaying = false; + _animationFrameIndex = 0.0f; + _animationFPS = MODEL_DEFAULT_ANIMATION_FPS; + _jointMappingCompleted = false; + _lastAnimated = now; } bool ModelItem::appendModelData(OctreePacketData* packetData) const { @@ -150,6 +167,31 @@ 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); + } + } + + // animationIsPlaying + if (success) { + success = packetData->appendValue(getAnimationIsPlaying()); + } + + // animationFrameIndex + if (success) { + success = packetData->appendValue(getAnimationFrameIndex()); + } + + // animationFPS + if (success) { + success = packetData->appendValue(getAnimationFPS()); + } + return success; } @@ -166,6 +208,7 @@ int ModelItem::expectedBytes() { } int ModelItem::readModelDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) { + int bytesRead = 0; if (bytesLeftToRead >= expectedBytes()) { int clockSkew = args.sourceNode ? args.sourceNode->getClockSkewUsec() : 0; @@ -215,7 +258,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,13 +267,37 @@ int ModelItem::readModelDataFromBuffer(const unsigned char* data, int bytesLeftT dataAt += bytes; bytesRead += bytes; - //printf("ModelItem::readModelDataFromBuffer()... "); debugDump(); + if (args.bitstreamVersion >= VERSION_MODELS_HAVE_ANIMATION) { + // 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; + + // animationIsPlaying + memcpy(&_animationIsPlaying, dataAt, sizeof(_animationIsPlaying)); + dataAt += sizeof(_animationIsPlaying); + bytesRead += sizeof(_animationIsPlaying); + + // animationFrameIndex + memcpy(&_animationFrameIndex, dataAt, sizeof(_animationFrameIndex)); + dataAt += sizeof(_animationFrameIndex); + bytesRead += sizeof(_animationFrameIndex); + + // animationFPS + memcpy(&_animationFPS, dataAt, sizeof(_animationFPS)); + dataAt += sizeof(_animationFPS); + bytesRead += sizeof(_animationFPS); + } } return bytesRead; } ModelItem ModelItem::fromEditPacket(const unsigned char* data, int length, int& processedBytes, ModelTree* tree, bool& valid) { - ModelItem newModelItem; // id and _lastUpdated will get set here... const unsigned char* dataAt = data; processedBytes = 0; @@ -340,12 +407,52 @@ ModelItem ModelItem::fromEditPacket(const unsigned char* data, int length, int& } // modelRotation - if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_MODEL_ROTATION) == MODEL_PACKET_CONTAINS_MODEL_ROTATION)) { + if (isNewModelItem || ((packetContainsBits & + MODEL_PACKET_CONTAINS_MODEL_ROTATION) == MODEL_PACKET_CONTAINS_MODEL_ROTATION)) { int bytes = unpackOrientationQuatFromBytes(dataAt, newModelItem._modelRotation); dataAt += bytes; 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; + } + + // animationIsPlaying + if (isNewModelItem || ((packetContainsBits & + MODEL_PACKET_CONTAINS_ANIMATION_PLAYING) == MODEL_PACKET_CONTAINS_ANIMATION_PLAYING)) { + + memcpy(&newModelItem._animationIsPlaying, dataAt, sizeof(newModelItem._animationIsPlaying)); + dataAt += sizeof(newModelItem._animationIsPlaying); + processedBytes += sizeof(newModelItem._animationIsPlaying); + } + + // animationFrameIndex + if (isNewModelItem || ((packetContainsBits & + MODEL_PACKET_CONTAINS_ANIMATION_FRAME) == MODEL_PACKET_CONTAINS_ANIMATION_FRAME)) { + + memcpy(&newModelItem._animationFrameIndex, dataAt, sizeof(newModelItem._animationFrameIndex)); + dataAt += sizeof(newModelItem._animationFrameIndex); + processedBytes += sizeof(newModelItem._animationFrameIndex); + } + + // animationFPS + if (isNewModelItem || ((packetContainsBits & + MODEL_PACKET_CONTAINS_ANIMATION_FPS) == MODEL_PACKET_CONTAINS_ANIMATION_FPS)) { + + memcpy(&newModelItem._animationFPS, dataAt, sizeof(newModelItem._animationFPS)); + dataAt += sizeof(newModelItem._animationFPS); + processedBytes += sizeof(newModelItem._animationFPS); + } + const bool wantDebugging = false; if (wantDebugging) { qDebug("ModelItem::fromEditPacket()..."); @@ -363,6 +470,7 @@ void ModelItem::debugDump() const { qDebug(" position:%f,%f,%f", _position.x, _position.y, _position.z); qDebug(" radius:%f", getRadius()); qDebug(" color:%d,%d,%d", _color[0], _color[1], _color[2]); + qDebug() << " modelURL:" << qPrintable(getModelURL()); } bool ModelItem::encodeModelEditMessageDetails(PacketType command, ModelItemID id, const ModelItemProperties& properties, @@ -476,6 +584,47 @@ 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; + } + + // animationIsPlaying + if (isNewModelItem || ((packetContainsBits & + MODEL_PACKET_CONTAINS_ANIMATION_PLAYING) == MODEL_PACKET_CONTAINS_ANIMATION_PLAYING)) { + + bool animationIsPlaying = properties.getAnimationIsPlaying(); + memcpy(copyAt, &animationIsPlaying, sizeof(animationIsPlaying)); + copyAt += sizeof(animationIsPlaying); + sizeOut += sizeof(animationIsPlaying); + } + + // animationFrameIndex + if (isNewModelItem || ((packetContainsBits & + MODEL_PACKET_CONTAINS_ANIMATION_FRAME) == MODEL_PACKET_CONTAINS_ANIMATION_FRAME)) { + + float animationFrameIndex = properties.getAnimationFrameIndex(); + memcpy(copyAt, &animationFrameIndex, sizeof(animationFrameIndex)); + copyAt += sizeof(animationFrameIndex); + sizeOut += sizeof(animationFrameIndex); + } + + // animationFPS + if (isNewModelItem || ((packetContainsBits & + MODEL_PACKET_CONTAINS_ANIMATION_FPS) == MODEL_PACKET_CONTAINS_ANIMATION_FPS)) { + + float animationFPS = properties.getAnimationFPS(); + memcpy(copyAt, &animationFPS, sizeof(animationFPS)); + copyAt += sizeof(animationFPS); + sizeOut += sizeof(animationFPS); + } + bool wantDebugging = false; if (wantDebugging) { qDebug("encodeModelItemEditMessageDetails()...."); @@ -521,9 +670,106 @@ void ModelItem::adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, ssi } } -void ModelItem::update(const quint64& now) { - _lastUpdated = now; + +QMap ModelItem::_loadedAnimations; // TODO: improve cleanup by leveraging the AnimationPointer(s) +AnimationCache ModelItem::_animationCache; + +// This class/instance will cleanup the animations once unloaded. +class ModelAnimationsBookkeeper { +public: + ~ModelAnimationsBookkeeper() { + ModelItem::cleanupLoadedAnimations(); + } +}; + +ModelAnimationsBookkeeper modelAnimationsBookkeeperInstance; + +void ModelItem::cleanupLoadedAnimations() { + foreach(AnimationPointer animation, _loadedAnimations) { + animation.clear(); + } + _loadedAnimations.clear(); +} + +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) { + Animation* myAnimation = getAnimation(_animationURL); + QVector frames = myAnimation->getFrames(); + int animationFrameIndex = (int)std::floor(_animationFrameIndex) % frames.size(); + QVector rotations = frames[animationFrameIndex].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& updateTime) { + _lastUpdated = updateTime; setShouldDie(getShouldDie()); + + quint64 now = usecTimestampNow(); + + // only advance the frame index if we're playing + if (getAnimationIsPlaying()) { + + float deltaTime = (float)(now - _lastAnimated) / (float)USECS_PER_SECOND; + + const bool wantDebugging = false; + if (wantDebugging) { + qDebug() << "ModelItem::update() now=" << now; + qDebug() << " updateTime=" << updateTime; + qDebug() << " _lastAnimated=" << _lastAnimated; + qDebug() << " deltaTime=" << deltaTime; + } + _lastAnimated = now; + _animationFrameIndex += deltaTime * _animationFPS; + + if (wantDebugging) { + qDebug() << " _animationFrameIndex=" << _animationFrameIndex; + } + + } else { + _lastAnimated = now; + } } void ModelItem::copyChangedProperties(const ModelItem& other) { @@ -547,6 +793,10 @@ ModelItemProperties::ModelItemProperties() : _shouldDie(false), _modelURL(""), _modelRotation(MODEL_DEFAULT_MODEL_ROTATION), + _animationURL(""), + _animationIsPlaying(false), + _animationFrameIndex(0.0), + _animationFPS(MODEL_DEFAULT_ANIMATION_FPS), _id(UNKNOWN_MODEL_ID), _idSet(false), @@ -558,6 +808,10 @@ ModelItemProperties::ModelItemProperties() : _shouldDieChanged(false), _modelURLChanged(false), _modelRotationChanged(false), + _animationURLChanged(false), + _animationIsPlayingChanged(false), + _animationFrameIndexChanged(false), + _animationFPSChanged(false), _defaultSettings(true) { } @@ -589,6 +843,22 @@ uint16_t ModelItemProperties::getChangedBits() const { changedBits += MODEL_PACKET_CONTAINS_MODEL_ROTATION; } + if (_animationURLChanged) { + changedBits += MODEL_PACKET_CONTAINS_ANIMATION_URL; + } + + if (_animationIsPlayingChanged) { + changedBits += MODEL_PACKET_CONTAINS_ANIMATION_PLAYING; + } + + if (_animationFrameIndexChanged) { + changedBits += MODEL_PACKET_CONTAINS_ANIMATION_FRAME; + } + + if (_animationFPSChanged) { + changedBits += MODEL_PACKET_CONTAINS_ANIMATION_FPS; + } + return changedBits; } @@ -611,6 +881,10 @@ QScriptValue ModelItemProperties::copyToScriptValue(QScriptEngine* engine) const QScriptValue modelRotation = quatToScriptValue(engine, _modelRotation); properties.setProperty("modelRotation", modelRotation); + properties.setProperty("animationURL", _animationURL); + properties.setProperty("animationIsPlaying", _animationIsPlaying); + properties.setProperty("animationFrameIndex", _animationFrameIndex); + properties.setProperty("animationFPS", _animationFPS); if (_idSet) { properties.setProperty("id", _id); @@ -707,6 +981,46 @@ 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; + } + } + + QScriptValue animationIsPlaying = object.property("animationIsPlaying"); + if (animationIsPlaying.isValid()) { + bool newIsAnimationPlaying; + newIsAnimationPlaying = animationIsPlaying.toVariant().toBool(); + if (_defaultSettings || newIsAnimationPlaying != _animationIsPlaying) { + _animationIsPlaying = newIsAnimationPlaying; + _animationIsPlayingChanged = true; + } + } + + QScriptValue animationFrameIndex = object.property("animationFrameIndex"); + if (animationFrameIndex.isValid()) { + float newFrameIndex; + newFrameIndex = animationFrameIndex.toVariant().toFloat(); + if (_defaultSettings || newFrameIndex != _animationFrameIndex) { + _animationFrameIndex = newFrameIndex; + _animationFrameIndexChanged = true; + } + } + + QScriptValue animationFPS = object.property("animationFPS"); + if (animationFPS.isValid()) { + float newFPS; + newFPS = animationFPS.toVariant().toFloat(); + if (_defaultSettings || newFPS != _animationFPS) { + _animationFPS = newFPS; + _animationFPSChanged = true; + } + } + _lastEdited = usecTimestampNow(); } @@ -741,7 +1055,27 @@ void ModelItemProperties::copyToModelItem(ModelItem& modelItem) const { modelItem.setModelRotation(_modelRotation); somethingChanged = true; } - + + if (_animationURLChanged) { + modelItem.setAnimationURL(_animationURL); + somethingChanged = true; + } + + if (_animationIsPlayingChanged) { + modelItem.setAnimationIsPlaying(_animationIsPlaying); + somethingChanged = true; + } + + if (_animationFrameIndexChanged) { + modelItem.setAnimationFrameIndex(_animationFrameIndex); + somethingChanged = true; + } + + if (_animationFPSChanged) { + modelItem.setAnimationFPS(_animationFPS); + somethingChanged = true; + } + if (somethingChanged) { bool wantDebug = false; if (wantDebug) { @@ -761,6 +1095,10 @@ void ModelItemProperties::copyFromModelItem(const ModelItem& modelItem) { _shouldDie = modelItem.getShouldDie(); _modelURL = modelItem.getModelURL(); _modelRotation = modelItem.getModelRotation(); + _animationURL = modelItem.getAnimationURL(); + _animationIsPlaying = modelItem.getAnimationIsPlaying(); + _animationFrameIndex = modelItem.getAnimationFrameIndex(); + _animationFPS = modelItem.getAnimationFPS(); _id = modelItem.getID(); _idSet = true; @@ -772,6 +1110,10 @@ void ModelItemProperties::copyFromModelItem(const ModelItem& modelItem) { _shouldDieChanged = false; _modelURLChanged = false; _modelRotationChanged = false; + _animationURLChanged = false; + _animationIsPlayingChanged = false; + _animationFrameIndexChanged = false; + _animationFPSChanged = false; _defaultSettings = false; } diff --git a/libraries/models/src/ModelItem.h b/libraries/models/src/ModelItem.h index 9edcf482c0..847e58e7c2 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,22 @@ 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 uint16_t MODEL_PACKET_CONTAINS_ANIMATION_PLAYING = 128; +const uint16_t MODEL_PACKET_CONTAINS_ANIMATION_FRAME = 256; +const uint16_t MODEL_PACKET_CONTAINS_ANIMATION_FPS = 512; 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(""); +const float MODEL_DEFAULT_ANIMATION_FPS = 30.0f; + +const PacketVersion VERSION_MODELS_HAVE_ANIMATION = 1; /// 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 +78,10 @@ public: const QString& getModelURL() const { return _modelURL; } const glm::quat& getModelRotation() const { return _modelRotation; } + const QString& getAnimationURL() const { return _animationURL; } + float getAnimationFrameIndex() const { return _animationFrameIndex; } + bool getAnimationIsPlaying() const { return _animationIsPlaying; } + float getAnimationFPS() const { return _animationFPS; } quint64 getLastEdited() const { return _lastEdited; } uint16_t getChangedBits() const; @@ -82,6 +95,10 @@ 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; } + void setAnimationFrameIndex(float value) { _animationFrameIndex = value; _animationFrameIndexChanged = true; } + void setAnimationIsPlaying(bool value) { _animationIsPlaying = value; _animationIsPlayingChanged = true; } + void setAnimationFPS(float value) { _animationFPS = value; _animationFPSChanged = true; } /// used by ModelScriptingInterface to return ModelItemProperties for unknown models void setIsUnknownID() { _id = UNKNOWN_MODEL_ID; _idSet = true; } @@ -97,6 +114,10 @@ private: QString _modelURL; glm::quat _modelRotation; + QString _animationURL; + bool _animationIsPlaying; + float _animationFrameIndex; + float _animationFPS; uint32_t _id; bool _idSet; @@ -109,6 +130,10 @@ private: bool _modelURLChanged; bool _modelRotationChanged; + bool _animationURLChanged; + bool _animationIsPlayingChanged; + bool _animationFrameIndexChanged; + bool _animationFPSChanged; bool _defaultSettings; }; Q_DECLARE_METATYPE(ModelItemProperties); @@ -178,6 +203,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 +223,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 +243,10 @@ 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 setAnimationFrameIndex(float value) { _animationFrameIndex = value; } + void setAnimationIsPlaying(bool value) { _animationIsPlaying = value; } + void setAnimationFPS(float value) { _animationFPS = value; } void setProperties(const ModelItemProperties& properties); @@ -239,6 +271,16 @@ public: static uint32_t getNextCreatorTokenID(); static void handleAddModelResponse(const QByteArray& packet); + 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; } + + static void cleanupLoadedAnimations(); + protected: glm::vec3 _position; rgbColor _color; @@ -256,10 +298,26 @@ protected: quint64 _lastUpdated; quint64 _lastEdited; + 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; + + 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/models/src/ModelTree.h b/libraries/models/src/ModelTree.h index ac25cdc003..10ef62c0a0 100644 --- a/libraries/models/src/ModelTree.h +++ b/libraries/models/src/ModelTree.h @@ -36,6 +36,7 @@ public: // own definition. Implement these to allow your octree based server to support editing virtual bool getWantSVOfileVersions() const { return true; } virtual PacketType expectedDataPacketType() const { return PacketTypeModelData; } + virtual bool canProcessVersion(PacketVersion thisVersion) const { return true; } // we support all versions virtual bool handlesEditPacketType(PacketType packetType) const; virtual int processEditPacketData(PacketType packetType, const unsigned char* packetData, int packetLength, const unsigned char* editData, int maxLength, const SharedNodePointer& senderNode); diff --git a/libraries/models/src/ModelTreeElement.cpp b/libraries/models/src/ModelTreeElement.cpp index 5c5d5100cf..c5dce04fe2 100644 --- a/libraries/models/src/ModelTreeElement.cpp +++ b/libraries/models/src/ModelTreeElement.cpp @@ -106,6 +106,10 @@ void ModelTreeElement::update(ModelTreeUpdateArgs& args) { QList::iterator modelItr = _modelItems->begin(); while(modelItr != _modelItems->end()) { ModelItem& model = (*modelItr); + + // TODO: this _lastChanged isn't actually changing because we're not marking this element as changed. + // how do we want to handle this??? We really only want to consider an element changed when it is + // edited... not just animated... model.update(_lastChanged); // If the model wants to die, or if it's left our bounding box, then move it @@ -324,7 +328,7 @@ int ModelTreeElement::readElementDataFromBuffer(const unsigned char* data, int b dataAt += sizeof(numberOfModels); bytesLeftToRead -= (int)sizeof(numberOfModels); bytesRead += sizeof(numberOfModels); - + if (bytesLeftToRead >= (int)(numberOfModels * expectedBytesPerModel)) { for (uint16_t i = 0; i < numberOfModels; i++) { ModelItem tempModel; 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/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 5b766ecdd7..128677ff2b 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -335,10 +335,8 @@ void Octree::readBitstreamToTree(const unsigned char * bitstream, unsigned long int octalCodeBytes = bytesRequiredForCodeLength(*bitstreamAt); int theseBytesRead = 0; theseBytesRead += octalCodeBytes; - theseBytesRead += readElementData(bitstreamRootElement, bitstreamAt + octalCodeBytes, bufferSizeBytes - (bytesRead + octalCodeBytes), args); - // skip bitstream to new startPoint bitstreamAt += theseBytesRead; bytesRead += theseBytesRead; @@ -1556,6 +1554,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* element, bool Octree::readFromSVOFile(const char* fileName) { bool fileOk = false; + PacketVersion gotVersion = 0; std::ifstream file(fileName, std::ios::in|std::ios::binary|std::ios::ate); if(file.is_open()) { emit importSize(1.0f, 1.0f, 1.0f); @@ -1586,14 +1585,16 @@ bool Octree::readFromSVOFile(const char* fileName) { if (gotType == expectedType) { dataAt += sizeof(expectedType); dataLength -= sizeof(expectedType); - PacketVersion expectedVersion = versionForPacketType(expectedType); - PacketVersion gotVersion = *dataAt; - if (gotVersion == expectedVersion) { - dataAt += sizeof(expectedVersion); - dataLength -= sizeof(expectedVersion); + gotVersion = *dataAt; + if (canProcessVersion(gotVersion)) { + dataAt += sizeof(gotVersion); + dataLength -= sizeof(gotVersion); fileOk = true; + qDebug("SVO file version match. Expected: %d Got: %d", + versionForPacketType(expectedDataPacketType()), gotVersion); } else { - qDebug("SVO file version mismatch. Expected: %d Got: %d", expectedVersion, gotVersion); + qDebug("SVO file version mismatch. Expected: %d Got: %d", + versionForPacketType(expectedDataPacketType()), gotVersion); } } else { qDebug("SVO file type mismatch. Expected: %c Got: %c", expectedType, gotType); @@ -1602,7 +1603,8 @@ bool Octree::readFromSVOFile(const char* fileName) { fileOk = true; // assume the file is ok } if (fileOk) { - ReadBitstreamToTreeParams args(WANT_COLOR, NO_EXISTS_BITS, NULL, 0, SharedNodePointer(), wantImportProgress); + ReadBitstreamToTreeParams args(WANT_COLOR, NO_EXISTS_BITS, NULL, 0, + SharedNodePointer(), wantImportProgress, gotVersion); readBitstreamToTree(dataAt, dataLength, args); } delete[] entireFile; @@ -1615,7 +1617,6 @@ bool Octree::readFromSVOFile(const char* fileName) { } void Octree::writeToSVOFile(const char* fileName, OctreeElement* element) { - std::ofstream file(fileName, std::ios::out|std::ios::binary); if(file.is_open()) { @@ -1638,13 +1639,12 @@ void Octree::writeToSVOFile(const char* fileName, OctreeElement* element) { nodeBag.insert(_rootElement); } - static OctreePacketData packetData; + OctreePacketData packetData; int bytesWritten = 0; bool lastPacketWritten = false; while (!nodeBag.isEmpty()) { OctreeElement* subTree = nodeBag.extract(); - lockForRead(); // do tree locking down here so that we have shorter slices and less thread contention EncodeBitstreamParams params(INT_MAX, IGNORE_VIEW_FRUSTUM, WANT_COLOR, NO_EXISTS_BITS); bytesWritten = encodeTreeBitstream(subTree, &packetData, nodeBag, params); @@ -1666,7 +1666,6 @@ void Octree::writeToSVOFile(const char* fileName, OctreeElement* element) { if (!lastPacketWritten) { file.write((const char*)packetData.getFinalizedData(), packetData.getFinalizedSize()); } - } file.close(); } diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index 4a17cb3c1d..84212586f8 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -170,6 +170,7 @@ public: QUuid sourceUUID; SharedNodePointer sourceNode; bool wantImportProgress; + PacketVersion bitstreamVersion; ReadBitstreamToTreeParams( bool includeColor = WANT_COLOR, @@ -177,13 +178,15 @@ public: OctreeElement* destinationElement = NULL, QUuid sourceUUID = QUuid(), SharedNodePointer sourceNode = SharedNodePointer(), - bool wantImportProgress = false) : + bool wantImportProgress = false, + PacketVersion bitstreamVersion = 0) : includeColor(includeColor), includeExistsBits(includeExistsBits), destinationElement(destinationElement), sourceUUID(sourceUUID), sourceNode(sourceNode), - wantImportProgress(wantImportProgress) + wantImportProgress(wantImportProgress), + bitstreamVersion(bitstreamVersion) {} }; @@ -200,6 +203,9 @@ public: // own definition. Implement these to allow your octree based server to support editing virtual bool getWantSVOfileVersions() const { return false; } virtual PacketType expectedDataPacketType() const { return PacketTypeUnknown; } + virtual bool canProcessVersion(PacketVersion thisVersion) const { + return thisVersion == versionForPacketType(expectedDataPacketType()); } + virtual PacketVersion expectedVersion() const { return versionForPacketType(expectedDataPacketType()); } virtual bool handlesEditPacketType(PacketType packetType) const { return false; } virtual int processEditPacketData(PacketType packetType, const unsigned char* packetData, int packetLength, const unsigned char* editData, int maxLength, const SharedNodePointer& sourceNode) { return 0; } @@ -303,6 +309,7 @@ public: bool getIsViewing() const { return _isViewing; } void setIsViewing(bool isViewing) { _isViewing = isViewing; } + signals: void importSize(float x, float y, float z); diff --git a/libraries/octree/src/OctreeRenderer.cpp b/libraries/octree/src/OctreeRenderer.cpp index c1ce3cb218..1a85518181 100644 --- a/libraries/octree/src/OctreeRenderer.cpp +++ b/libraries/octree/src/OctreeRenderer.cpp @@ -64,6 +64,7 @@ void OctreeRenderer::processDatagram(const QByteArray& dataByteArray, const Shar unsigned int numBytesPacketHeader = numBytesForPacketHeader(dataByteArray); QUuid sourceUUID = uuidFromPacketHeader(dataByteArray); PacketType expectedType = getExpectedPacketType(); + PacketVersion expectedVersion = _tree->expectedVersion(); // TODO: would be better to read this from the packet! if(command == expectedType) { PerformanceWarning warn(showTimingDetails, "OctreeRenderer::processDatagram expected PacketType", showTimingDetails); @@ -115,7 +116,7 @@ void OctreeRenderer::processDatagram(const QByteArray& dataByteArray, const Shar if (sectionLength) { // ask the VoxelTree to read the bitstream into the tree ReadBitstreamToTreeParams args(packetIsColored ? WANT_COLOR : NO_COLOR, WANT_EXISTS_BITS, NULL, - sourceUUID, sourceNode); + sourceUUID, sourceNode, false, expectedVersion); _tree->lockForWrite(); OctreePacketData packetData(packetIsCompressed); packetData.loadFinalizedContent(dataAt, sectionLength); 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"