From c5ffe54a9131328e737506176a04c405a50f6f1f Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Mon, 14 Apr 2014 16:26:30 -0700 Subject: [PATCH 01/27] Working on loading animation data. --- interface/src/renderer/FBXReader.cpp | 89 +++++++++++++++++++++++++++- interface/src/renderer/FBXReader.h | 9 +++ 2 files changed, 95 insertions(+), 3 deletions(-) diff --git a/interface/src/renderer/FBXReader.cpp b/interface/src/renderer/FBXReader.cpp index 959c015eea..43f6ccc065 100644 --- a/interface/src/renderer/FBXReader.cpp +++ b/interface/src/renderer/FBXReader.cpp @@ -444,6 +444,34 @@ QVector getIntVector(const QVariantList& properties, int index) { return vector; } +QVector getLongVector(const QVariantList& properties, int index) { + if (index >= properties.size()) { + return QVector(); + } + QVector vector = properties.at(index).value >(); + if (!vector.isEmpty()) { + return vector; + } + for (; index < properties.size(); index++) { + vector.append(properties.at(index).toLongLong()); + } + return vector; +} + +QVector getFloatVector(const QVariantList& properties, int index) { + if (index >= properties.size()) { + return QVector(); + } + QVector vector = properties.at(index).value >(); + if (!vector.isEmpty()) { + return vector; + } + for (; index < properties.size(); index++) { + vector.append(properties.at(index).toFloat()); + } + return vector; +} + QVector getDoubleVector(const QVariantList& properties, int index) { if (index >= properties.size()) { return QVector(); @@ -901,6 +929,11 @@ public: float averageRadius; // average distance from mesh points to averageVertex }; +class AnimationCurve { +public: + QVector values; +}; + FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) { QHash meshes; QVector blendshapes; @@ -908,10 +941,15 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) QMultiHash childMap; QHash models; QHash clusters; + QHash animationCurves; QHash textureFilenames; QHash materials; QHash diffuseTextures; QHash bumpTextures; + QHash localRotations; + QHash xComponents; + QHash yComponents; + QHash zComponents; QVariantHash joints = mapping.value("joint").toHash(); QString jointEyeLeftName = processID(getString(joints.value("jointEyeLeft", "jointEyeLeft"))); @@ -1124,7 +1162,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) model.postRotation = glm::quat(glm::radians(postRotation)); model.postTransform = glm::translate(-rotationPivot) * glm::translate(scalePivot) * glm::scale(scale) * glm::translate(-scalePivot); - // NOTE: anbgles from the FBX file are in degrees + // NOTE: angles from the FBX file are in degrees // so we convert them to radians for the FBXModel class model.rotationMin = glm::radians(glm::vec3(rotationMinX ? rotationMin.x : -180.0f, rotationMinY ? rotationMin.y : -180.0f, rotationMinZ ? rotationMin.z : -180.0f)); @@ -1204,6 +1242,14 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) blendshapeChannelIndices.insert(id, index); } } + } else if (object.name == "AnimationCurve") { + AnimationCurve curve; + foreach (const FBXNode& subobject, object.children) { + if (subobject.name == "KeyValueFloat") { + curve.values = getFloatVector(subobject.properties, 0); + } + } + animationCurves.insert(getID(object.properties), curve); } } } else if (child.name == "Connections") { @@ -1216,6 +1262,18 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) } else if (type.contains("bump")) { bumpTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); + + } else if (type == "lcl rotation") { + localRotations.insert(getID(connection.properties, 2), getID(connection.properties, 1)); + + } else if (type == "d|x") { + xComponents.insert(getID(connection.properties, 2), getID(connection.properties, 1)); + + } else if (type == "d|y") { + yComponents.insert(getID(connection.properties, 2), getID(connection.properties, 1)); + + } else if (type == "d|z") { + zComponents.insert(getID(connection.properties, 2), getID(connection.properties, 1)); } } parentMap.insert(getID(connection.properties, 1), getID(connection.properties, 2)); @@ -1239,7 +1297,8 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) 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(), - mapping.value("tz").toFloat())) * glm::mat4_cast(offsetRotation) * glm::scale(glm::vec3(offsetScale, offsetScale, offsetScale)); + mapping.value("tz").toFloat())) * glm::mat4_cast(offsetRotation) * + glm::scale(glm::vec3(offsetScale, offsetScale, offsetScale)); // get the list of models in depth-first traversal order QVector modelIDs; @@ -1277,6 +1336,17 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) appendModelIDs(parentMap.value(topID), childMap, models, remainingModels, modelIDs); } + // figure the number of animation frames from the curves + int frameCount = 0; + foreach (const AnimationCurve& curve, animationCurves) { + frameCount = qMax(frameCount, curve.values.size()); + } + for (int i = 0; i < frameCount; i++) { + FBXAnimationFrame frame; + frame.rotations.resize(modelIDs.size()); + geometry.animationFrames.append(frame); + } + // convert the models to joints QVariantList freeJoints = mapping.values("freeJoint"); foreach (const QString& modelID, modelIDs) { @@ -1286,7 +1356,8 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) joint.parentIndex = model.parentIndex; // get the indices of all ancestors starting with the first free one (if any) - joint.freeLineage.append(geometry.joints.size()); + int jointIndex = geometry.joints.size(); + joint.freeLineage.append(jointIndex); int lastFreeIndex = joint.isFree ? 0 : -1; for (int index = joint.parentIndex; index != -1; index = geometry.joints.at(index).parentIndex) { if (geometry.joints.at(index).isFree) { @@ -1325,6 +1396,18 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) joint.shapeType = Shape::UNKNOWN_SHAPE; geometry.joints.append(joint); geometry.jointIndices.insert(model.name, geometry.joints.size()); + + QString rotationID = localRotations.value(modelID); + AnimationCurve xCurve = animationCurves.value(xComponents.value(rotationID)); + AnimationCurve yCurve = animationCurves.value(yComponents.value(rotationID)); + AnimationCurve zCurve = animationCurves.value(zComponents.value(rotationID)); + glm::vec3 defaultValues = glm::degrees(safeEulerAngles(joint.rotation)); + for (int i = 0; i < frameCount; i++) { + geometry.animationFrames[i].rotations[jointIndex] = glm::quat(glm::radians(glm::vec3( + xCurve.values.isEmpty() ? defaultValues.x : xCurve.values.at(i % xCurve.values.size()), + yCurve.values.isEmpty() ? defaultValues.y : yCurve.values.at(i % yCurve.values.size()), + zCurve.values.isEmpty() ? defaultValues.z : zCurve.values.at(i % zCurve.values.size())))); + } } // for each joint we allocate a JointShapeInfo in which we'll store collision shape info QVector jointShapeInfos; diff --git a/interface/src/renderer/FBXReader.h b/interface/src/renderer/FBXReader.h index 73c305e2eb..8d45c2ae23 100644 --- a/interface/src/renderer/FBXReader.h +++ b/interface/src/renderer/FBXReader.h @@ -139,6 +139,13 @@ public: QVector blendshapes; }; +/// A single animation frame extracted from an FBX document. +class FBXAnimationFrame { +public: + + QVector rotations; +}; + /// An attachment to an FBX document. class FBXAttachment { public: @@ -183,6 +190,8 @@ public: Extents bindExtents; Extents meshExtents; + QVector animationFrames; + QVector attachments; int getJointIndex(const QString& name) const { return jointIndices.value(name) - 1; } From a2dc58f7f94ba579a2ccc45a49446166b7c83d00 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Mon, 14 Apr 2014 17:15:58 -0700 Subject: [PATCH 02/27] Moved FBX code to its own library so that we can use it for bots. --- interface/CMakeLists.txt | 1 + interface/src/ModelUploader.cpp | 4 +- interface/src/Util.cpp | 108 +----------------- interface/src/Util.h | 16 --- interface/src/devices/Faceplus.cpp | 3 +- interface/src/devices/Visage.cpp | 3 +- interface/src/renderer/GeometryCache.h | 2 +- libraries/fbx/CMakeLists.txt | 38 ++++++ .../fbx/src}/FBXReader.cpp | 6 +- .../fbx/src}/FBXReader.h | 0 libraries/shared/src/SharedUtil.cpp | 103 +++++++++++++++++ libraries/shared/src/SharedUtil.h | 16 +++ 12 files changed, 169 insertions(+), 131 deletions(-) create mode 100644 libraries/fbx/CMakeLists.txt rename {interface/src/renderer => libraries/fbx/src}/FBXReader.cpp (99%) rename {interface/src/renderer => libraries/fbx/src}/FBXReader.h (100%) diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 6295095b43..557af35e12 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -120,6 +120,7 @@ include(${MACRO_DIR}/LinkHifiLibrary.cmake) link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(voxels ${TARGET_NAME} "${ROOT_DIR}") +link_hifi_library(fbx ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(metavoxels ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(particles ${TARGET_NAME} "${ROOT_DIR}") diff --git a/interface/src/ModelUploader.cpp b/interface/src/ModelUploader.cpp index edbc6c0ad9..5b2688169f 100644 --- a/interface/src/ModelUploader.cpp +++ b/interface/src/ModelUploader.cpp @@ -22,9 +22,9 @@ #include -#include "Application.h" -#include "renderer/FBXReader.h" +#include +#include "Application.h" #include "ModelUploader.h" diff --git a/interface/src/Util.cpp b/interface/src/Util.cpp index cecc363daa..1dae3a4fd6 100644 --- a/interface/src/Util.cpp +++ b/interface/src/Util.cpp @@ -78,112 +78,6 @@ float angle_to(glm::vec3 head_pos, glm::vec3 source_pos, float render_yaw, float return atan2(head_pos.x - source_pos.x, head_pos.z - source_pos.z) + render_yaw + head_yaw; } -// Helper function returns the positive angle (in radians) between two 3D vectors -float angleBetween(const glm::vec3& v1, const glm::vec3& v2) { - return acosf((glm::dot(v1, v2)) / (glm::length(v1) * glm::length(v2))); -} - -// Helper function return the rotation from the first vector onto the second -glm::quat rotationBetween(const glm::vec3& v1, const glm::vec3& v2) { - float angle = angleBetween(v1, v2); - if (glm::isnan(angle) || angle < EPSILON) { - return glm::quat(); - } - glm::vec3 axis; - if (angle > 179.99f * RADIANS_PER_DEGREE) { // 180 degree rotation; must use another axis - axis = glm::cross(v1, glm::vec3(1.0f, 0.0f, 0.0f)); - float axisLength = glm::length(axis); - if (axisLength < EPSILON) { // parallel to x; y will work - axis = glm::normalize(glm::cross(v1, glm::vec3(0.0f, 1.0f, 0.0f))); - } else { - axis /= axisLength; - } - } else { - axis = glm::normalize(glm::cross(v1, v2)); - } - return glm::angleAxis(angle, axis); -} - - - -glm::vec3 extractTranslation(const glm::mat4& matrix) { - return glm::vec3(matrix[3][0], matrix[3][1], matrix[3][2]); -} - -void setTranslation(glm::mat4& matrix, const glm::vec3& translation) { - matrix[3][0] = translation.x; - matrix[3][1] = translation.y; - matrix[3][2] = translation.z; -} - -glm::quat extractRotation(const glm::mat4& matrix, bool assumeOrthogonal) { - // uses the iterative polar decomposition algorithm described by Ken Shoemake at - // http://www.cs.wisc.edu/graphics/Courses/838-s2002/Papers/polar-decomp.pdf - // code adapted from Clyde, https://github.com/threerings/clyde/blob/master/src/main/java/com/threerings/math/Matrix4f.java - - // start with the contents of the upper 3x3 portion of the matrix - glm::mat3 upper = glm::mat3(matrix); - if (!assumeOrthogonal) { - for (int i = 0; i < 10; i++) { - // store the results of the previous iteration - glm::mat3 previous = upper; - - // compute average of the matrix with its inverse transpose - float sd00 = previous[1][1] * previous[2][2] - previous[2][1] * previous[1][2]; - float sd10 = previous[0][1] * previous[2][2] - previous[2][1] * previous[0][2]; - float sd20 = previous[0][1] * previous[1][2] - previous[1][1] * previous[0][2]; - float det = previous[0][0] * sd00 + previous[2][0] * sd20 - previous[1][0] * sd10; - if (fabs(det) == 0.0f) { - // determinant is zero; matrix is not invertible - break; - } - float hrdet = 0.5f / det; - upper[0][0] = +sd00 * hrdet + previous[0][0] * 0.5f; - upper[1][0] = -sd10 * hrdet + previous[1][0] * 0.5f; - upper[2][0] = +sd20 * hrdet + previous[2][0] * 0.5f; - - upper[0][1] = -(previous[1][0] * previous[2][2] - previous[2][0] * previous[1][2]) * hrdet + previous[0][1] * 0.5f; - upper[1][1] = +(previous[0][0] * previous[2][2] - previous[2][0] * previous[0][2]) * hrdet + previous[1][1] * 0.5f; - upper[2][1] = -(previous[0][0] * previous[1][2] - previous[1][0] * previous[0][2]) * hrdet + previous[2][1] * 0.5f; - - upper[0][2] = +(previous[1][0] * previous[2][1] - previous[2][0] * previous[1][1]) * hrdet + previous[0][2] * 0.5f; - upper[1][2] = -(previous[0][0] * previous[2][1] - previous[2][0] * previous[0][1]) * hrdet + previous[1][2] * 0.5f; - upper[2][2] = +(previous[0][0] * previous[1][1] - previous[1][0] * previous[0][1]) * hrdet + previous[2][2] * 0.5f; - - // compute the difference; if it's small enough, we're done - glm::mat3 diff = upper - previous; - if (diff[0][0] * diff[0][0] + diff[1][0] * diff[1][0] + diff[2][0] * diff[2][0] + diff[0][1] * diff[0][1] + - diff[1][1] * diff[1][1] + diff[2][1] * diff[2][1] + diff[0][2] * diff[0][2] + diff[1][2] * diff[1][2] + - diff[2][2] * diff[2][2] < EPSILON) { - break; - } - } - } - - // now that we have a nice orthogonal matrix, we can extract the rotation quaternion - // using the method described in http://en.wikipedia.org/wiki/Rotation_matrix#Conversions - float x2 = fabs(1.0f + upper[0][0] - upper[1][1] - upper[2][2]); - float y2 = fabs(1.0f - upper[0][0] + upper[1][1] - upper[2][2]); - float z2 = fabs(1.0f - upper[0][0] - upper[1][1] + upper[2][2]); - float w2 = fabs(1.0f + upper[0][0] + upper[1][1] + upper[2][2]); - return glm::normalize(glm::quat(0.5f * sqrtf(w2), - 0.5f * sqrtf(x2) * (upper[1][2] >= upper[2][1] ? 1.0f : -1.0f), - 0.5f * sqrtf(y2) * (upper[2][0] >= upper[0][2] ? 1.0f : -1.0f), - 0.5f * sqrtf(z2) * (upper[0][1] >= upper[1][0] ? 1.0f : -1.0f))); -} - -glm::vec3 extractScale(const glm::mat4& matrix) { - return glm::vec3(glm::length(matrix[0]), glm::length(matrix[1]), glm::length(matrix[2])); -} - -float extractUniformScale(const glm::mat4& matrix) { - return extractUniformScale(extractScale(matrix)); -} - -float extractUniformScale(const glm::vec3& scale) { - return (scale.x + scale.y + scale.z) / 3.0f; -} - // Draw a 3D vector floating in space void drawVector(glm::vec3 * vector) { glDisable(GL_LIGHTING); @@ -629,4 +523,4 @@ bool pointInSphere(glm::vec3& point, glm::vec3& sphereCenter, double sphereRadiu return true; } return false; -} \ No newline at end of file +} diff --git a/interface/src/Util.h b/interface/src/Util.h index ac680645a9..4bd1ed604c 100644 --- a/interface/src/Util.h +++ b/interface/src/Util.h @@ -44,22 +44,6 @@ void drawVector(glm::vec3* vector); void printVector(glm::vec3 vec); -float angleBetween(const glm::vec3& v1, const glm::vec3& v2); - -glm::quat rotationBetween(const glm::vec3& v1, const glm::vec3& v2); - -glm::vec3 extractTranslation(const glm::mat4& matrix); - -void setTranslation(glm::mat4& matrix, const glm::vec3& translation); - -glm::quat extractRotation(const glm::mat4& matrix, bool assumeOrthogonal = false); - -glm::vec3 extractScale(const glm::mat4& matrix); - -float extractUniformScale(const glm::mat4& matrix); - -float extractUniformScale(const glm::vec3& scale); - double diffclock(timeval *clock1,timeval *clock2); void renderCollisionOverlay(int width, int height, float magnitude, float red = 0, float blue = 0, float green = 0); diff --git a/interface/src/devices/Faceplus.cpp b/interface/src/devices/Faceplus.cpp index f7f2f1f1bd..f155b994e7 100644 --- a/interface/src/devices/Faceplus.cpp +++ b/interface/src/devices/Faceplus.cpp @@ -15,9 +15,10 @@ #include #endif +#include + #include "Application.h" #include "Faceplus.h" -#include "renderer/FBXReader.h" static int floatVectorMetaTypeId = qRegisterMetaType >(); diff --git a/interface/src/devices/Visage.cpp b/interface/src/devices/Visage.cpp index e2f40e4741..a467d2d4a8 100644 --- a/interface/src/devices/Visage.cpp +++ b/interface/src/devices/Visage.cpp @@ -13,9 +13,10 @@ #include +#include + #include "Application.h" #include "Visage.h" -#include "renderer/FBXReader.h" // this has to go after our normal includes, because its definition of HANDLE conflicts with Qt's #ifdef HAVE_VISAGE diff --git a/interface/src/renderer/GeometryCache.h b/interface/src/renderer/GeometryCache.h index c2d276fb5e..0ad4f73904 100644 --- a/interface/src/renderer/GeometryCache.h +++ b/interface/src/renderer/GeometryCache.h @@ -20,7 +20,7 @@ #include -#include "FBXReader.h" +#include class Model; class NetworkGeometry; diff --git a/libraries/fbx/CMakeLists.txt b/libraries/fbx/CMakeLists.txt new file mode 100644 index 0000000000..10dd3f49f5 --- /dev/null +++ b/libraries/fbx/CMakeLists.txt @@ -0,0 +1,38 @@ +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 fbx) + +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(networking ${TARGET_NAME} "${ROOT_DIR}") +link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}") +link_hifi_library(voxels ${TARGET_NAME} "${ROOT_DIR}") + +# link ZLIB and GnuTLS +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}" Qt5::Widgets "${GNUTLS_LIBRARY}") diff --git a/interface/src/renderer/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp similarity index 99% rename from interface/src/renderer/FBXReader.cpp rename to libraries/fbx/src/FBXReader.cpp index 43f6ccc065..6c51e11150 100644 --- a/interface/src/renderer/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -22,14 +22,14 @@ #include #include -#include - #include +#include #include +#include + #include #include "FBXReader.h" -#include "Util.h" using namespace std; diff --git a/interface/src/renderer/FBXReader.h b/libraries/fbx/src/FBXReader.h similarity index 100% rename from interface/src/renderer/FBXReader.h rename to libraries/fbx/src/FBXReader.h diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index cd98fbdbd2..0541d7fddc 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -659,3 +659,106 @@ glm::vec3 safeEulerAngles(const glm::quat& q) { } } +// Helper function returns the positive angle (in radians) between two 3D vectors +float angleBetween(const glm::vec3& v1, const glm::vec3& v2) { + return acosf((glm::dot(v1, v2)) / (glm::length(v1) * glm::length(v2))); +} + +// Helper function return the rotation from the first vector onto the second +glm::quat rotationBetween(const glm::vec3& v1, const glm::vec3& v2) { + float angle = angleBetween(v1, v2); + if (glm::isnan(angle) || angle < EPSILON) { + return glm::quat(); + } + glm::vec3 axis; + if (angle > 179.99f * RADIANS_PER_DEGREE) { // 180 degree rotation; must use another axis + axis = glm::cross(v1, glm::vec3(1.0f, 0.0f, 0.0f)); + float axisLength = glm::length(axis); + if (axisLength < EPSILON) { // parallel to x; y will work + axis = glm::normalize(glm::cross(v1, glm::vec3(0.0f, 1.0f, 0.0f))); + } else { + axis /= axisLength; + } + } else { + axis = glm::normalize(glm::cross(v1, v2)); + } + return glm::angleAxis(angle, axis); +} + +glm::vec3 extractTranslation(const glm::mat4& matrix) { + return glm::vec3(matrix[3][0], matrix[3][1], matrix[3][2]); +} + +void setTranslation(glm::mat4& matrix, const glm::vec3& translation) { + matrix[3][0] = translation.x; + matrix[3][1] = translation.y; + matrix[3][2] = translation.z; +} + +glm::quat extractRotation(const glm::mat4& matrix, bool assumeOrthogonal) { + // uses the iterative polar decomposition algorithm described by Ken Shoemake at + // http://www.cs.wisc.edu/graphics/Courses/838-s2002/Papers/polar-decomp.pdf + // code adapted from Clyde, https://github.com/threerings/clyde/blob/master/src/main/java/com/threerings/math/Matrix4f.java + + // start with the contents of the upper 3x3 portion of the matrix + glm::mat3 upper = glm::mat3(matrix); + if (!assumeOrthogonal) { + for (int i = 0; i < 10; i++) { + // store the results of the previous iteration + glm::mat3 previous = upper; + + // compute average of the matrix with its inverse transpose + float sd00 = previous[1][1] * previous[2][2] - previous[2][1] * previous[1][2]; + float sd10 = previous[0][1] * previous[2][2] - previous[2][1] * previous[0][2]; + float sd20 = previous[0][1] * previous[1][2] - previous[1][1] * previous[0][2]; + float det = previous[0][0] * sd00 + previous[2][0] * sd20 - previous[1][0] * sd10; + if (fabs(det) == 0.0f) { + // determinant is zero; matrix is not invertible + break; + } + float hrdet = 0.5f / det; + upper[0][0] = +sd00 * hrdet + previous[0][0] * 0.5f; + upper[1][0] = -sd10 * hrdet + previous[1][0] * 0.5f; + upper[2][0] = +sd20 * hrdet + previous[2][0] * 0.5f; + + upper[0][1] = -(previous[1][0] * previous[2][2] - previous[2][0] * previous[1][2]) * hrdet + previous[0][1] * 0.5f; + upper[1][1] = +(previous[0][0] * previous[2][2] - previous[2][0] * previous[0][2]) * hrdet + previous[1][1] * 0.5f; + upper[2][1] = -(previous[0][0] * previous[1][2] - previous[1][0] * previous[0][2]) * hrdet + previous[2][1] * 0.5f; + + upper[0][2] = +(previous[1][0] * previous[2][1] - previous[2][0] * previous[1][1]) * hrdet + previous[0][2] * 0.5f; + upper[1][2] = -(previous[0][0] * previous[2][1] - previous[2][0] * previous[0][1]) * hrdet + previous[1][2] * 0.5f; + upper[2][2] = +(previous[0][0] * previous[1][1] - previous[1][0] * previous[0][1]) * hrdet + previous[2][2] * 0.5f; + + // compute the difference; if it's small enough, we're done + glm::mat3 diff = upper - previous; + if (diff[0][0] * diff[0][0] + diff[1][0] * diff[1][0] + diff[2][0] * diff[2][0] + diff[0][1] * diff[0][1] + + diff[1][1] * diff[1][1] + diff[2][1] * diff[2][1] + diff[0][2] * diff[0][2] + diff[1][2] * diff[1][2] + + diff[2][2] * diff[2][2] < EPSILON) { + break; + } + } + } + + // now that we have a nice orthogonal matrix, we can extract the rotation quaternion + // using the method described in http://en.wikipedia.org/wiki/Rotation_matrix#Conversions + float x2 = fabs(1.0f + upper[0][0] - upper[1][1] - upper[2][2]); + float y2 = fabs(1.0f - upper[0][0] + upper[1][1] - upper[2][2]); + float z2 = fabs(1.0f - upper[0][0] - upper[1][1] + upper[2][2]); + float w2 = fabs(1.0f + upper[0][0] + upper[1][1] + upper[2][2]); + return glm::normalize(glm::quat(0.5f * sqrtf(w2), + 0.5f * sqrtf(x2) * (upper[1][2] >= upper[2][1] ? 1.0f : -1.0f), + 0.5f * sqrtf(y2) * (upper[2][0] >= upper[0][2] ? 1.0f : -1.0f), + 0.5f * sqrtf(z2) * (upper[0][1] >= upper[1][0] ? 1.0f : -1.0f))); +} + +glm::vec3 extractScale(const glm::mat4& matrix) { + return glm::vec3(glm::length(matrix[0]), glm::length(matrix[1]), glm::length(matrix[2])); +} + +float extractUniformScale(const glm::mat4& matrix) { + return extractUniformScale(extractScale(matrix)); +} + +float extractUniformScale(const glm::vec3& scale) { + return (scale.x + scale.y + scale.z) / 3.0f; +} diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index f41c5b8aa2..4f7c643511 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -166,4 +166,20 @@ int unpackFloatVec3FromSignedTwoByteFixed(const unsigned char* sourceBuffer, glm /// \return vec3 with euler angles in radians glm::vec3 safeEulerAngles(const glm::quat& q); +float angleBetween(const glm::vec3& v1, const glm::vec3& v2); + +glm::quat rotationBetween(const glm::vec3& v1, const glm::vec3& v2); + +glm::vec3 extractTranslation(const glm::mat4& matrix); + +void setTranslation(glm::mat4& matrix, const glm::vec3& translation); + +glm::quat extractRotation(const glm::mat4& matrix, bool assumeOrthogonal = false); + +glm::vec3 extractScale(const glm::mat4& matrix); + +float extractUniformScale(const glm::mat4& matrix); + +float extractUniformScale(const glm::vec3& scale); + #endif // hifi_SharedUtil_h From 38c0e99cbecc03134e1a4b028b71558abc455a84 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Mon, 14 Apr 2014 18:12:32 -0700 Subject: [PATCH 03/27] More work on animation loading. --- assignment-client/CMakeLists.txt | 3 +- libraries/particles/CMakeLists.txt | 3 +- libraries/script-engine/CMakeLists.txt | 3 +- .../script-engine/src/AnimationCache.cpp | 65 +++++++++++++++++++ libraries/script-engine/src/AnimationCache.h | 56 ++++++++++++++++ libraries/script-engine/src/ScriptEngine.cpp | 3 +- libraries/script-engine/src/ScriptEngine.h | 2 + 7 files changed, 131 insertions(+), 4 deletions(-) create mode 100644 libraries/script-engine/src/AnimationCache.cpp create mode 100644 libraries/script-engine/src/AnimationCache.h diff --git a/assignment-client/CMakeLists.txt b/assignment-client/CMakeLists.txt index a8343e94ad..b78d4f81f9 100644 --- a/assignment-client/CMakeLists.txt +++ b/assignment-client/CMakeLists.txt @@ -28,6 +28,7 @@ link_hifi_library(audio ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(avatars ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}") 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(metavoxels ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}") @@ -51,4 +52,4 @@ IF (WIN32) target_link_libraries(${TARGET_NAME} Winmm Ws2_32) ENDIF(WIN32) -target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets Qt5::Script "${GNUTLS_LIBRARY}") \ No newline at end of file +target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets Qt5::Script "${GNUTLS_LIBRARY}") diff --git a/libraries/particles/CMakeLists.txt b/libraries/particles/CMakeLists.txt index 26d2b7fc26..1cb60756a2 100644 --- a/libraries/particles/CMakeLists.txt +++ b/libraries/particles/CMakeLists.txt @@ -23,6 +23,7 @@ include_glm(${TARGET_NAME} "${ROOT_DIR}") include(${MACRO_DIR}/LinkHifiLibrary.cmake) 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 ZLIB and GnuTLS @@ -35,4 +36,4 @@ if (WIN32) endif () include_directories(SYSTEM "${ZLIB_INCLUDE_DIRS}" "${GNUTLS_INCLUDE_DIR}") -target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" Qt5::Widgets "${GNUTLS_LIBRARY}") \ No newline at end of file +target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" Qt5::Widgets "${GNUTLS_LIBRARY}") diff --git a/libraries/script-engine/CMakeLists.txt b/libraries/script-engine/CMakeLists.txt index 20569e2fe0..48d13e7742 100644 --- a/libraries/script-engine/CMakeLists.txt +++ b/libraries/script-engine/CMakeLists.txt @@ -24,6 +24,7 @@ include(${MACRO_DIR}/LinkHifiLibrary.cmake) link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}") 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 ZLIB @@ -36,4 +37,4 @@ if (WIN32) endif () include_directories(SYSTEM "${ZLIB_INCLUDE_DIRS}" "${GNUTLS_INCLUDE_DIR}") -target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" "${GNUTLS_LIBRARY}" Qt5::Widgets) \ No newline at end of file +target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" "${GNUTLS_LIBRARY}" Qt5::Widgets) diff --git a/libraries/script-engine/src/AnimationCache.cpp b/libraries/script-engine/src/AnimationCache.cpp new file mode 100644 index 0000000000..2a6aac1992 --- /dev/null +++ b/libraries/script-engine/src/AnimationCache.cpp @@ -0,0 +1,65 @@ +// +// AnimationCache.cpp +// libraries/script-engine/src/ +// +// Created by Andrzej Kapolka on 4/14/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 +#include + +#include "AnimationCache.h" + +QSharedPointer AnimationCache::getAnimation(const QUrl& url) { + return getResource(url).staticCast(); +} + +QSharedPointer AnimationCache::createResource(const QUrl& url, const QSharedPointer& fallback, + bool delayLoad, const void* extra) { + return QSharedPointer(new Animation(url), &Resource::allReferencesCleared); +} + +Animation::Animation(const QUrl& url) : + Resource(url) { +} + +class AnimationReader : public QRunnable { +public: + + AnimationReader(const QWeakPointer& animation, QNetworkReply* reply); + + virtual void run(); + +private: + + QWeakPointer _animation; + QNetworkReply* _reply; +}; + +AnimationReader::AnimationReader(const QWeakPointer& animation, QNetworkReply* reply) : + _animation(animation), + _reply(reply) { +} + +void AnimationReader::run() { + QSharedPointer animation = _animation.toStrongRef(); + if (!animation.isNull()) { + QMetaObject::invokeMethod(animation.data(), "setGeometry", + Q_ARG(const FBXGeometry&, readFBX(_reply->readAll(), QVariantHash()))); + } + _reply->deleteLater(); +} + +void Animation::setGeometry(const FBXGeometry& geometry) { + _geometry = geometry; + finishedLoading(true); +} + +void Animation::downloadFinished(QNetworkReply* reply) { + // send the reader off to the thread pool + QThreadPool::globalInstance()->start(new AnimationReader(_self, reply)); +} diff --git a/libraries/script-engine/src/AnimationCache.h b/libraries/script-engine/src/AnimationCache.h new file mode 100644 index 0000000000..bb8a0f2313 --- /dev/null +++ b/libraries/script-engine/src/AnimationCache.h @@ -0,0 +1,56 @@ +// +// AnimationCache.h +// libraries/script-engine/src/ +// +// Created by Andrzej Kapolka on 4/14/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_AnimationCache_h +#define hifi_AnimationCache_h + +#include + +#include + +class Animation; + +/// Scriptable interface for FBX animation loading. +class AnimationCache : public ResourceCache { + Q_OBJECT + +public: + + QSharedPointer getAnimation(const QUrl& url); + +protected: + + virtual QSharedPointer createResource(const QUrl& url, + const QSharedPointer& fallback, bool delayLoad, const void* extra); +}; + +/// An animation loaded from the network. +class Animation : public Resource { + Q_OBJECT + +public: + + Animation(const QUrl& url); + + const FBXGeometry& getGeometry() const { return _geometry; } + +protected: + + Q_INVOKABLE void setGeometry(const FBXGeometry& geometry); + + virtual void downloadFinished(QNetworkReply* reply); + +private: + + FBXGeometry _geometry; +}; + +#endif // hifi_AnimationCache_h diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index b5291a6a2f..b0e2d1495b 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -204,7 +204,8 @@ void ScriptEngine::init() { registerGlobalObject("Quat", &_quatLibrary); registerGlobalObject("Vec3", &_vec3Library); registerGlobalObject("Uuid", &_uuidLibrary); - + registerGlobalObject("AnimationCache", &_animationCache); + registerGlobalObject("Voxels", &_voxelsScriptingInterface); QScriptValue treeScaleValue = _engine.newVariant(QVariant(TREE_SCALE)); diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 9719c83107..73747cd3e1 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -23,6 +23,7 @@ #include +#include "AnimationCache.h" #include "AbstractControllerScriptingInterface.h" #include "Quat.h" #include "ScriptUUID.h" @@ -125,6 +126,7 @@ private: Quat _quatLibrary; Vec3 _vec3Library; ScriptUUID _uuidLibrary; + AnimationCache _animationCache; }; #endif // hifi_ScriptEngine_h From 8cbad1bf548049fb3c2593e147fd8c750514d716 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 15 Apr 2014 19:37:28 -0700 Subject: [PATCH 04/27] More work on animation loading. --- examples/crazylegs.js | 14 +++++ interface/src/Application.cpp | 1 + interface/src/Application.h | 1 + libraries/fbx/src/FBXReader.cpp | 2 + libraries/fbx/src/FBXReader.h | 3 + .../script-engine/src/AnimationCache.cpp | 55 ++++++++++++++++++- libraries/script-engine/src/AnimationCache.h | 41 +++++++++++++- libraries/script-engine/src/ScriptEngine.cpp | 2 + 8 files changed, 117 insertions(+), 2 deletions(-) diff --git a/examples/crazylegs.js b/examples/crazylegs.js index 6311aea6e4..3554def63a 100644 --- a/examples/crazylegs.js +++ b/examples/crazylegs.js @@ -22,6 +22,8 @@ for (var i = 0; i < jointList.length; i++) { } print("# Joint list end"); +var foo = AnimationCache.getAnimation("http://www.fungibleinsight.com/faces/hip_hop_dancing_2.fbx"); + Script.update.connect(function(deltaTime) { cumulativeTime += deltaTime; MyAvatar.setJointData("joint_R_hip", Quat.fromPitchYawRollDegrees(0.0, 0.0, AMPLITUDE * Math.sin(cumulativeTime * FREQUENCY))); @@ -30,6 +32,18 @@ Script.update.connect(function(deltaTime) { AMPLITUDE * (1.0 + Math.sin(cumulativeTime * FREQUENCY)))); MyAvatar.setJointData("joint_L_knee", Quat.fromPitchYawRollDegrees(0.0, 0.0, AMPLITUDE * (1.0 - Math.sin(cumulativeTime * FREQUENCY)))); + + if (foo.jointNames.length > 0) { + print(foo.jointNames); + print(foo.frames.length); + if (foo.frames.length > 0) { + print(foo.frames[0].rotations.length); + if (foo.frames[0].rotations.length > 0) { + var rot = foo.frames[0].rotations[0]; + print(rot.x + " " + rot.y + " " + rot.z + " " + rot.w); + } + } + } }); Script.scriptEnding.connect(function() { diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 1fabca3711..91fdff96b5 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3387,6 +3387,7 @@ void Application::loadScript(const QString& scriptName) { scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("Settings", SettingsScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("AudioDevice", AudioDeviceScriptingInterface::getInstance()); + scriptEngine->registerGlobalObject("AnimationCache", &_animationCache); QThread* workerThread = new QThread(this); diff --git a/interface/src/Application.h b/interface/src/Application.h index 6a14788caa..4606a3d622 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -468,6 +468,7 @@ private: QSet _keysPressed; GeometryCache _geometryCache; + AnimationCache _animationCache; TextureCache _textureCache; GlowEffect _glowEffect; diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 6c51e11150..5fad95be3d 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -72,6 +72,8 @@ bool FBXGeometry::hasBlendedMeshes() const { } static int fbxGeometryMetaTypeId = qRegisterMetaType(); +static int fbxAnimationFrameMetaTypeId = qRegisterMetaType(); +static int fbxAnimationFrameVectorMetaTypeId = qRegisterMetaType >(); template QVariant readBinaryArray(QDataStream& in) { quint32 arrayLength; diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index 8d45c2ae23..2f840e868e 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -146,6 +146,9 @@ public: QVector rotations; }; +Q_DECLARE_METATYPE(FBXAnimationFrame) +Q_DECLARE_METATYPE(QVector) + /// An attachment to an FBX document. class FBXAttachment { public: diff --git a/libraries/script-engine/src/AnimationCache.cpp b/libraries/script-engine/src/AnimationCache.cpp index 2a6aac1992..078dc382c4 100644 --- a/libraries/script-engine/src/AnimationCache.cpp +++ b/libraries/script-engine/src/AnimationCache.cpp @@ -10,11 +10,20 @@ // #include +#include #include #include "AnimationCache.h" -QSharedPointer AnimationCache::getAnimation(const QUrl& url) { +static int animationPointerMetaTypeId = qRegisterMetaType(); + +AnimationPointer AnimationCache::getAnimation(const QUrl& url) { + if (QThread::currentThread() != thread()) { + AnimationPointer result; + QMetaObject::invokeMethod(this, "getAnimation", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(AnimationPointer, result), Q_ARG(const QUrl&, url)); + return result; + } return getResource(url).staticCast(); } @@ -54,6 +63,30 @@ void AnimationReader::run() { _reply->deleteLater(); } +QStringList Animation::getJointNames() const { + if (QThread::currentThread() != thread()) { + QStringList result; + QMetaObject::invokeMethod(const_cast(this), "getJointNames", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(QStringList, result)); + return result; + } + QStringList names; + foreach (const FBXJoint& joint, _geometry.joints) { + names.append(joint.name); + } + return names; +} + +QVector Animation::getFrames() const { + if (QThread::currentThread() != thread()) { + QVector result; + QMetaObject::invokeMethod(const_cast(this), "getFrames", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(QVector, result)); + return result; + } + return _geometry.animationFrames; +} + void Animation::setGeometry(const FBXGeometry& geometry) { _geometry = geometry; finishedLoading(true); @@ -63,3 +96,23 @@ void Animation::downloadFinished(QNetworkReply* reply) { // send the reader off to the thread pool QThreadPool::globalInstance()->start(new AnimationReader(_self, reply)); } + +QStringList AnimationObject::getJointNames() const { + return qscriptvalue_cast(thisObject())->getJointNames(); +} + +QVector AnimationObject::getFrames() const { + return qscriptvalue_cast(thisObject())->getFrames(); +} + +QVector AnimationFrameObject::getRotations() const { + return qscriptvalue_cast(thisObject()).rotations; +} + +void registerAnimationTypes(QScriptEngine* engine) { + qScriptRegisterSequenceMetaType >(engine); + engine->setDefaultPrototype(qMetaTypeId(), engine->newQObject( + new AnimationFrameObject(), QScriptEngine::ScriptOwnership)); + engine->setDefaultPrototype(qMetaTypeId(), engine->newQObject( + new AnimationObject(), QScriptEngine::ScriptOwnership)); +} diff --git a/libraries/script-engine/src/AnimationCache.h b/libraries/script-engine/src/AnimationCache.h index bb8a0f2313..a382ef40d7 100644 --- a/libraries/script-engine/src/AnimationCache.h +++ b/libraries/script-engine/src/AnimationCache.h @@ -12,19 +12,27 @@ #ifndef hifi_AnimationCache_h #define hifi_AnimationCache_h +#include + #include #include +class QScriptEngine; + class Animation; +typedef QSharedPointer AnimationPointer; + /// Scriptable interface for FBX animation loading. class AnimationCache : public ResourceCache { Q_OBJECT public: - QSharedPointer getAnimation(const QUrl& url); + Q_INVOKABLE AnimationPointer getAnimation(const QString& url) { return getAnimation(QUrl(url)); } + + Q_INVOKABLE AnimationPointer getAnimation(const QUrl& url); protected: @@ -32,6 +40,8 @@ protected: const QSharedPointer& fallback, bool delayLoad, const void* extra); }; +Q_DECLARE_METATYPE(AnimationPointer) + /// An animation loaded from the network. class Animation : public Resource { Q_OBJECT @@ -42,6 +52,10 @@ public: const FBXGeometry& getGeometry() const { return _geometry; } + Q_INVOKABLE QStringList getJointNames() const; + + Q_INVOKABLE QVector getFrames() const; + protected: Q_INVOKABLE void setGeometry(const FBXGeometry& geometry); @@ -53,4 +67,29 @@ private: FBXGeometry _geometry; }; +/// Scriptable wrapper for animation pointers. +class AnimationObject : public QObject, protected QScriptable { + Q_OBJECT + Q_PROPERTY(QStringList jointNames READ getJointNames) + Q_PROPERTY(QVector frames READ getFrames) + +public: + + Q_INVOKABLE QStringList getJointNames() const; + + Q_INVOKABLE QVector getFrames() const; +}; + +/// Scriptable wrapper for animation frames. +class AnimationFrameObject : public QObject, protected QScriptable { + Q_OBJECT + Q_PROPERTY(QVector rotations READ getRotations) + +public: + + Q_INVOKABLE QVector getRotations() const; +}; + +void registerAnimationTypes(QScriptEngine* engine); + #endif // hifi_AnimationCache_h diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index b0e2d1495b..60321e7c54 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -180,11 +180,13 @@ void ScriptEngine::init() { registerVoxelMetaTypes(&_engine); registerEventTypes(&_engine); registerMenuItemProperties(&_engine); + registerAnimationTypes(&_engine); qScriptRegisterMetaType(&_engine, ParticlePropertiesToScriptValue, ParticlePropertiesFromScriptValue); qScriptRegisterMetaType(&_engine, ParticleIDtoScriptValue, ParticleIDfromScriptValue); qScriptRegisterSequenceMetaType >(&_engine); qScriptRegisterSequenceMetaType >(&_engine); + qScriptRegisterSequenceMetaType >(&_engine); qScriptRegisterSequenceMetaType >(&_engine); QScriptValue soundConstructorValue = _engine.newFunction(soundConstructor); From 67cc186964909449818eca8c3b11d158608071d5 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 16 Apr 2014 16:29:22 -0700 Subject: [PATCH 05/27] optional sending of assignment pool from ds assignment page --- domain-server/resources/web/assignment/css/style.css | 6 +++--- domain-server/resources/web/assignment/index.shtml | 9 +++++++-- domain-server/resources/web/assignment/js/assignment.js | 5 +++++ 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/domain-server/resources/web/assignment/css/style.css b/domain-server/resources/web/assignment/css/style.css index b6c26ca9fd..51813a4d73 100644 --- a/domain-server/resources/web/assignment/css/style.css +++ b/domain-server/resources/web/assignment/css/style.css @@ -42,13 +42,13 @@ body { background-color: #28FF57; } -#instance-field { +#extra-fields { position: absolute; right: 20px; - top: 40px; + top: 30px; } -#instance-field input { +#extra-fields input { width: 80px; } diff --git a/domain-server/resources/web/assignment/index.shtml b/domain-server/resources/web/assignment/index.shtml index 64a6d8825f..27c84be985 100644 --- a/domain-server/resources/web/assignment/index.shtml +++ b/domain-server/resources/web/assignment/index.shtml @@ -14,8 +14,13 @@ Run -
- +
+
+ +
+
+ +
diff --git a/domain-server/resources/web/assignment/js/assignment.js b/domain-server/resources/web/assignment/js/assignment.js index a04c8192f4..71f4373251 100644 --- a/domain-server/resources/web/assignment/js/assignment.js +++ b/domain-server/resources/web/assignment/js/assignment.js @@ -22,9 +22,14 @@ $(document).ready(function(){ + '--' + boundary + '--\r\n'; var headers = {}; + if ($('#instance-field input').val()) { headers['ASSIGNMENT-INSTANCES'] = $('#instance-field input').val(); } + + if ($('#pool-field input').val()) { + headers['ASSIGNMENT-POOL'] = $('#pool-field input').val(); + } // post form to assignment in order to create an assignment $.ajax({ From dcccd66c51776e4876a10eff7ce79d3b8aa19147 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Wed, 16 Apr 2014 17:49:35 -0700 Subject: [PATCH 06/27] Changed sound files to raw --- examples/editVoxels.js | 74 +++++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/examples/editVoxels.js b/examples/editVoxels.js index 059b4186a3..ee86d9b599 100644 --- a/examples/editVoxels.js +++ b/examples/editVoxels.js @@ -93,61 +93,61 @@ function SoundArray() { } var addVoxelSound = new SoundArray(); -addVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Add/VA+1.wav"); -addVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Add/VA+2.wav"); -addVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Add/VA+3.wav"); -addVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Add/VA+4.wav"); -addVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Add/VA+5.wav"); -addVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Add/VA+6.wav"); +addVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Add/VA+1.raw"); +addVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Add/VA+2.raw"); +addVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Add/VA+3.raw"); +addVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Add/VA+4.raw"); +addVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Add/VA+5.raw"); +addVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Add/VA+6.raw"); var delVoxelSound = new SoundArray(); -delVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Del/VD+A1.wav"); -delVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Del/VD+A2.wav"); -delVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Del/VD+A3.wav"); -delVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Del/VD+B1.wav"); -delVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Del/VD+B2.wav"); -delVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Del/VD+B3.wav"); +delVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Del/VD+A1.raw"); +delVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Del/VD+A2.raw"); +delVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Del/VD+A3.raw"); +delVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Del/VD+B1.raw"); +delVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Del/VD+B2.raw"); +delVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Del/VD+B3.raw"); var resizeVoxelSound = new SoundArray(); -resizeVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Size/V+Size+Minus.wav"); -resizeVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Size/V+Size+Plus.wav"); +resizeVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Size/V+Size+Minus.raw"); +resizeVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Size/V+Size+Plus.raw"); var voxelSizeMinus = 0; var voxelSizePlus = 1; var swatchesSound = new SoundArray(); -swatchesSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Swatches/Swatch+1.wav"); -swatchesSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Swatches/Swatch+2.wav"); -swatchesSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Swatches/Swatch+3.wav"); -swatchesSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Swatches/Swatch+4.wav"); -swatchesSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Swatches/Swatch+5.wav"); -swatchesSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Swatches/Swatch+6.wav"); -swatchesSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Swatches/Swatch+7.wav"); -swatchesSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Swatches/Swatch+8.wav"); -swatchesSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Swatches/Swatch+9.wav"); +swatchesSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Swatches/Swatch+1.raw"); +swatchesSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Swatches/Swatch+2.raw"); +swatchesSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Swatches/Swatch+3.raw"); +swatchesSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Swatches/Swatch+4.raw"); +swatchesSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Swatches/Swatch+5.raw"); +swatchesSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Swatches/Swatch+6.raw"); +swatchesSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Swatches/Swatch+7.raw"); +swatchesSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Swatches/Swatch+8.raw"); +swatchesSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Swatches/Swatch+9.raw"); var undoSound = new SoundArray(); -undoSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Undo/Undo+1.wav"); -undoSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Undo/Undo+2.wav"); -undoSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Undo/Undo+3.wav"); +undoSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Undo/Undo+1.raw"); +undoSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Undo/Undo+2.raw"); +undoSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Undo/Undo+3.raw"); var scriptInitSound = new SoundArray(); -scriptInitSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Script+Init/Script+Init+A.wav"); -scriptInitSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Script+Init/Script+Init+B.wav"); -scriptInitSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Script+Init/Script+Init+C.wav"); -scriptInitSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Script+Init/Script+Init+D.wav"); +scriptInitSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Script+Init/Script+Init+A.raw"); +scriptInitSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Script+Init/Script+Init+B.raw"); +scriptInitSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Script+Init/Script+Init+C.raw"); +scriptInitSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Script+Init/Script+Init+D.raw"); var modeSwitchSound = new SoundArray(); -modeSwitchSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Mode+Switch/Mode+1.wav"); -modeSwitchSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Mode+Switch/Mode+2.wav"); -modeSwitchSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Mode+Switch/Mode+3.wav"); +modeSwitchSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Mode+Switch/Mode+1.raw"); +modeSwitchSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Mode+Switch/Mode+2.raw"); +modeSwitchSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Mode+Switch/Mode+3.raw"); var initialVoxelSound = new SoundArray(); -initialVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Initial+Voxel/Initial+V.wav"); +initialVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Initial+Voxel/Initial+V.raw"); var colorInheritSound = new SoundArray(); -colorInheritSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Color+Inherit/Inherit+A.wav"); -colorInheritSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Color+Inherit/Inherit+B.wav"); -colorInheritSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Color+Inherit/Inherit+C.wav"); +colorInheritSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Color+Inherit/Inherit+A.raw"); +colorInheritSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Color+Inherit/Inherit+B.raw"); +colorInheritSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Color+Inherit/Inherit+C.raw"); var editToolsOn = true; // starts out off From 0f69bbe23fd5f80cdb7da7db2aec7713b203d7df Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Wed, 16 Apr 2014 20:39:55 -0700 Subject: [PATCH 07/27] More work on animation loading. --- assignment-client/src/Agent.cpp | 11 +++++- examples/crazylegs.js | 14 -------- examples/dancing_bot.js | 35 +++++++++++++++++++ libraries/avatars/src/AvatarData.h | 2 ++ libraries/fbx/src/FBXReader.cpp | 5 ++- .../script-engine/src/AnimationCache.cpp | 6 ++++ libraries/script-engine/src/AnimationCache.h | 2 ++ libraries/script-engine/src/ScriptEngine.cpp | 6 ++-- 8 files changed, 61 insertions(+), 20 deletions(-) create mode 100644 examples/dancing_bot.js diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index f48c4b9401..f4f6e3e37e 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -11,8 +11,10 @@ #include #include +#include #include #include +#include #include #include @@ -20,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -152,8 +155,13 @@ void Agent::run() { scriptURLString = scriptURLString.arg(NodeList::getInstance()->getDomainHandler().getIP().toString(), uuidStringWithoutCurlyBraces(_uuid)); + QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation); + QNetworkAccessManager *networkManager = new QNetworkAccessManager(this); QNetworkReply *reply = networkManager->get(QNetworkRequest(QUrl(scriptURLString))); + QNetworkDiskCache* cache = new QNetworkDiskCache(networkManager); + cache->setCacheDirectory(!cachePath.isEmpty() ? cachePath : "agentCache"); + networkManager->setCache(cache); qDebug() << "Downloading script at" << scriptURLString; @@ -162,8 +170,9 @@ void Agent::run() { loop.exec(); - // let the AvatarData class use our QNetworkAcessManager + // let the AvatarData and ResourceCache classes use our QNetworkAccessManager AvatarData::setNetworkAccessManager(networkManager); + ResourceCache::setNetworkAccessManager(networkManager); QString scriptContents(reply->readAll()); diff --git a/examples/crazylegs.js b/examples/crazylegs.js index 3554def63a..6311aea6e4 100644 --- a/examples/crazylegs.js +++ b/examples/crazylegs.js @@ -22,8 +22,6 @@ for (var i = 0; i < jointList.length; i++) { } print("# Joint list end"); -var foo = AnimationCache.getAnimation("http://www.fungibleinsight.com/faces/hip_hop_dancing_2.fbx"); - Script.update.connect(function(deltaTime) { cumulativeTime += deltaTime; MyAvatar.setJointData("joint_R_hip", Quat.fromPitchYawRollDegrees(0.0, 0.0, AMPLITUDE * Math.sin(cumulativeTime * FREQUENCY))); @@ -32,18 +30,6 @@ Script.update.connect(function(deltaTime) { AMPLITUDE * (1.0 + Math.sin(cumulativeTime * FREQUENCY)))); MyAvatar.setJointData("joint_L_knee", Quat.fromPitchYawRollDegrees(0.0, 0.0, AMPLITUDE * (1.0 - Math.sin(cumulativeTime * FREQUENCY)))); - - if (foo.jointNames.length > 0) { - print(foo.jointNames); - print(foo.frames.length); - if (foo.frames.length > 0) { - print(foo.frames[0].rotations.length); - if (foo.frames[0].rotations.length > 0) { - var rot = foo.frames[0].rotations[0]; - print(rot.x + " " + rot.y + " " + rot.z + " " + rot.w); - } - } - } }); Script.scriptEnding.connect(function() { diff --git a/examples/dancing_bot.js b/examples/dancing_bot.js new file mode 100644 index 0000000000..91d1cd3f57 --- /dev/null +++ b/examples/dancing_bot.js @@ -0,0 +1,35 @@ +// +// dancing_bot.js +// examples +// +// Created by Andrzej Kapolka on 4/16/14. +// Copyright 2014 High Fidelity, Inc. +// +// This is an example script that demonstrates an NPC avatar running an FBX animation loop. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var animation = AnimationCache.getAnimation("http://www.fungibleinsight.com/faces/hip_hop_dancing_2.fbx"); + +Avatar.skeletonModelURL = "http://www.fungibleinsight.com/faces/vincent.fst"; + +Agent.isAvatar = true; + +var jointMapping; + +Script.update.connect(function(deltaTime) { + if (!jointMapping) { + var avatarJointNames = Avatar.jointNames; + var animationJointNames = animation.jointNames; + if (avatarJointNames === 0 || animationJointNames.length === 0) { + return; + } + print(avatarJointNames); + print(animationJointNames); + jointMapping = { }; + } +}); + + diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 0ac12649cf..947fbc41d4 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -99,6 +99,8 @@ class AvatarData : public QObject { Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript WRITE setSkeletonModelURLFromScript) Q_PROPERTY(QString billboardURL READ getBillboardURL WRITE setBillboardFromURL) + Q_PROPERTY(QStringList jointNames READ getJointNames) + Q_PROPERTY(QUuid sessionUUID READ getSessionUUID); public: AvatarData(); diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 5fad95be3d..bb862ad918 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -508,8 +508,7 @@ glm::vec3 parseVec3(const QString& string) { QString processID(const QString& id) { // Blender (at least) prepends a type to the ID, so strip it out - int index = id.indexOf("::"); - return (index == -1) ? id : id.mid(index + 2); + return id.mid(id.lastIndexOf(':') + 1); } QString getID(const QVariantList& properties, int index = 0) { @@ -1014,7 +1013,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) QString name; if (object.properties.size() == 3) { name = object.properties.at(1).toString(); - name = name.left(name.indexOf(QChar('\0'))); + name = processID(name.left(name.indexOf(QChar('\0')))); } else { name = getID(object.properties); diff --git a/libraries/script-engine/src/AnimationCache.cpp b/libraries/script-engine/src/AnimationCache.cpp index 078dc382c4..570de7ed10 100644 --- a/libraries/script-engine/src/AnimationCache.cpp +++ b/libraries/script-engine/src/AnimationCache.cpp @@ -17,8 +17,14 @@ static int animationPointerMetaTypeId = qRegisterMetaType(); +AnimationCache::AnimationCache(QObject* parent) : + ResourceCache(parent) { +} + AnimationPointer AnimationCache::getAnimation(const QUrl& url) { if (QThread::currentThread() != thread()) { + qDebug() << "blocking call!"; + AnimationPointer result; QMetaObject::invokeMethod(this, "getAnimation", Qt::BlockingQueuedConnection, Q_RETURN_ARG(AnimationPointer, result), Q_ARG(const QUrl&, url)); diff --git a/libraries/script-engine/src/AnimationCache.h b/libraries/script-engine/src/AnimationCache.h index a382ef40d7..38fabad57f 100644 --- a/libraries/script-engine/src/AnimationCache.h +++ b/libraries/script-engine/src/AnimationCache.h @@ -30,6 +30,8 @@ class AnimationCache : public ResourceCache { public: + AnimationCache(QObject* parent = NULL); + Q_INVOKABLE AnimationPointer getAnimation(const QString& url) { return getAnimation(QUrl(url)); } Q_INVOKABLE AnimationPointer getAnimation(const QUrl& url); diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 60321e7c54..4dee35c6a5 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -64,7 +64,8 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNam _fileNameString(fileNameString), _quatLibrary(), _vec3Library(), - _uuidLibrary() + _uuidLibrary(), + _animationCache(this) { } @@ -88,7 +89,8 @@ ScriptEngine::ScriptEngine(const QUrl& scriptURL, _fileNameString(), _quatLibrary(), _vec3Library(), - _uuidLibrary() + _uuidLibrary(), + _animationCache(this) { QString scriptURLString = scriptURL.toString(); _fileNameString = scriptURLString; From 00829e028982d83b0c7cf3ca873d0b6c3e062fc4 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Wed, 16 Apr 2014 21:10:59 -0700 Subject: [PATCH 08/27] The actual animation business. --- examples/dancing_bot.js | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/examples/dancing_bot.js b/examples/dancing_bot.js index 91d1cd3f57..de4f8224c3 100644 --- a/examples/dancing_bot.js +++ b/examples/dancing_bot.js @@ -19,6 +19,10 @@ Agent.isAvatar = true; var jointMapping; +var frameIndex = 0.0; + +var FRAME_RATE = 30.0; // frames per second + Script.update.connect(function(deltaTime) { if (!jointMapping) { var avatarJointNames = Avatar.jointNames; @@ -26,9 +30,19 @@ Script.update.connect(function(deltaTime) { if (avatarJointNames === 0 || animationJointNames.length === 0) { return; } - print(avatarJointNames); - print(animationJointNames); - jointMapping = { }; + jointMapping = new Array(avatarJointNames.length); + for (var i = 0; i < avatarJointNames.length; i++) { + jointMapping[i] = animationJointNames.indexOf(avatarJointNames[i]); + } + } + frameIndex += deltaTime * FRAME_RATE; + var frames = animation.frames; + var rotations = frames[Math.floor(frameIndex) % frames.length].rotations; + for (var j = 0; j < jointMapping.length; j++) { + var rotationIndex = jointMapping[j]; + if (rotationIndex != -1) { + Avatar.setJointData(j, rotations[rotationIndex]); + } } }); From 142932bed02561554081faca83188a9dcb1deaa9 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 17 Apr 2014 10:14:11 -0700 Subject: [PATCH 09/27] allow passing of scripted assignments to DS via parameters --- domain-server/src/DomainServer.cpp | 55 +++++++++++++++++++++++++++--- domain-server/src/DomainServer.h | 1 + 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 969b83f64f..9dbb1e478e 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -187,7 +187,7 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { } } - QSet parsedTypes(QSet() << Assignment::AgentType); + QSet parsedTypes; parseAssignmentConfigs(parsedTypes); populateDefaultStaticAssignmentsExcludingTypes(parsedTypes); @@ -222,12 +222,19 @@ void DomainServer::parseAssignmentConfigs(QSet& excludedTypes) if (assignmentType < Assignment::AllTypes && !excludedTypes.contains(assignmentType)) { QVariant mapValue = _argumentVariantMap[variantMapKeys[configIndex]]; + QJsonArray assignmentArray; if (mapValue.type() == QVariant::String) { QJsonDocument deserializedDocument = QJsonDocument::fromJson(mapValue.toString().toUtf8()); - createStaticAssignmentsForType(assignmentType, deserializedDocument.array()); + assignmentArray = deserializedDocument.array(); } else { - createStaticAssignmentsForType(assignmentType, mapValue.toJsonValue().toArray()); + assignmentArray = mapValue.toJsonValue().toArray(); + } + + if (assignmentType != Assignment::AgentType) { + createStaticAssignmentsForType(assignmentType, assignmentArray); + } else { + createScriptedAssignmentsFromArray(assignmentArray); } excludedTypes.insert(assignmentType); @@ -242,6 +249,42 @@ void DomainServer::addStaticAssignmentToAssignmentHash(Assignment* newAssignment _staticAssignmentHash.insert(newAssignment->getUUID(), SharedAssignmentPointer(newAssignment)); } +void DomainServer::createScriptedAssignmentsFromArray(const QJsonArray &configArray) { + foreach(const QJsonValue& jsonValue, configArray) { + if (jsonValue.isObject()) { + QJsonObject jsonObject = jsonValue.toObject(); + + // make sure we were passed a URL, otherwise this is an invalid scripted assignment + const QString ASSIGNMENT_URL_KEY = "url"; + QString assignmentURL = jsonObject[ASSIGNMENT_URL_KEY].toString(); + + if (!assignmentURL.isEmpty()) { + // check the json for a pool + const QString ASSIGNMENT_POOL_KEY = "pool"; + QString assignmentPool = jsonObject[ASSIGNMENT_POOL_KEY].toString(); + + // check for a number of instances, if not passed then default is 1 + const QString ASSIGNMENT_INSTANCES_KEY = "instances"; + int numInstances = jsonObject[ASSIGNMENT_INSTANCES_KEY].toInt(); + numInstances = (numInstances == 0 ? 1 : numInstances); + + for (int i = 0; i < numInstances; i++) { + // add a scripted assignment to the queue for this instance + Assignment* scriptAssignment = new Assignment(Assignment::CreateCommand, + Assignment::AgentType, + assignmentPool); + scriptAssignment->setPayload(assignmentURL.toUtf8()); + + qDebug() << "Adding scripted assignment to queue -" << *scriptAssignment; + qDebug() << "URL for script is" << assignmentURL; + + _assignmentQueue.enqueue(SharedAssignmentPointer(scriptAssignment)); + } + } + } + } +} + void DomainServer::createStaticAssignmentsForType(Assignment::Type type, const QJsonArray& configArray) { // we have a string for config for this type qDebug() << "Parsing config for assignment type" << type; @@ -284,8 +327,10 @@ void DomainServer::createStaticAssignmentsForType(Assignment::Type type, const Q void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet& excludedTypes) { // enumerate over all assignment types and see if we've already excluded it - for (int defaultedType = Assignment::AudioMixerType; defaultedType != Assignment::AllTypes; defaultedType++) { - if (!excludedTypes.contains((Assignment::Type) defaultedType)) { + for (Assignment::Type defaultedType = Assignment::AudioMixerType; + defaultedType != Assignment::AllTypes; + defaultedType = static_cast(static_cast(defaultedType) + 1)) { + if (!excludedTypes.contains(defaultedType) && defaultedType != Assignment::AgentType) { // type has not been set from a command line or config file config, use the default // by clearing whatever exists and writing a single default assignment with no payload Assignment* newAssignment = new Assignment(Assignment::CreateCommand, (Assignment::Type) defaultedType); diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 0153fac49d..9bd10b8c60 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -66,6 +66,7 @@ private: void parseAssignmentConfigs(QSet& excludedTypes); void addStaticAssignmentToAssignmentHash(Assignment* newAssignment); + void createScriptedAssignmentsFromArray(const QJsonArray& configArray); void createStaticAssignmentsForType(Assignment::Type type, const QJsonArray& configArray); void populateDefaultStaticAssignmentsExcludingTypes(const QSet& excludedTypes); From a137dd03623969ff6346e8014fe253acc95db77e Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 17 Apr 2014 10:18:42 -0700 Subject: [PATCH 10/27] grab URL for script from payload if it exists --- assignment-client/src/Agent.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index f48c4b9401..4163991a75 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -148,14 +148,19 @@ void Agent::run() { << NodeType::ParticleServer); // figure out the URL for the script for this agent assignment - QString scriptURLString("http://%1:8080/assignment/%2"); - scriptURLString = scriptURLString.arg(NodeList::getInstance()->getDomainHandler().getIP().toString(), - uuidStringWithoutCurlyBraces(_uuid)); - + QUrl scriptURL; + if (_payload.isEmpty()) { + scriptURL = QUrl(QString("http://%1:8080/assignment/%2") + .arg(NodeList::getInstance()->getDomainHandler().getIP().toString(), + uuidStringWithoutCurlyBraces(_uuid))); + } else { + scriptURL = QUrl(_payload); + } + QNetworkAccessManager *networkManager = new QNetworkAccessManager(this); - QNetworkReply *reply = networkManager->get(QNetworkRequest(QUrl(scriptURLString))); + QNetworkReply *reply = networkManager->get(QNetworkRequest(scriptURL)); - qDebug() << "Downloading script at" << scriptURLString; + qDebug() << "Downloading script at" << scriptURL.toString(); QEventLoop loop; QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit())); From 03c3952e525e87cf3793a2734b25add87924e4a9 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 17 Apr 2014 10:41:46 -0700 Subject: [PATCH 11/27] Better joint mappings, switch to working (more or less) model. --- examples/crazylegs.js | 6 +++--- examples/dancing_bot.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/crazylegs.js b/examples/crazylegs.js index 6311aea6e4..b0f8e937bc 100644 --- a/examples/crazylegs.js +++ b/examples/crazylegs.js @@ -15,12 +15,12 @@ var AMPLITUDE = 45.0; var cumulativeTime = 0.0; -print("# Joint list start"); var jointList = MyAvatar.getJointNames(); +var jointMappings = "\n# Joint list start"; for (var i = 0; i < jointList.length; i++) { - print("jointIndex = " + jointList[i] + " = " + i); + jointMappings = jointMappings + "\njointIndex = " + jointList[i] + " = " + i; } -print("# Joint list end"); +print(jointMappings + "\n# Joint list end"); Script.update.connect(function(deltaTime) { cumulativeTime += deltaTime; diff --git a/examples/dancing_bot.js b/examples/dancing_bot.js index de4f8224c3..b86047d2de 100644 --- a/examples/dancing_bot.js +++ b/examples/dancing_bot.js @@ -13,7 +13,7 @@ var animation = AnimationCache.getAnimation("http://www.fungibleinsight.com/faces/hip_hop_dancing_2.fbx"); -Avatar.skeletonModelURL = "http://www.fungibleinsight.com/faces/vincent.fst"; +Avatar.skeletonModelURL = "http://www.fungibleinsight.com/faces/beta.fst"; Agent.isAvatar = true; From a6136b42ff193bf854c5f16849a5fb8538946763 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 17 Apr 2014 10:55:11 -0700 Subject: [PATCH 12/27] Switch to a working (and more amusing) animation. --- examples/dancing_bot.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/dancing_bot.js b/examples/dancing_bot.js index b86047d2de..3572cc5a11 100644 --- a/examples/dancing_bot.js +++ b/examples/dancing_bot.js @@ -11,7 +11,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -var animation = AnimationCache.getAnimation("http://www.fungibleinsight.com/faces/hip_hop_dancing_2.fbx"); +var animation = AnimationCache.getAnimation("http://www.fungibleinsight.com/faces/gangnam_style_2.fbx"); Avatar.skeletonModelURL = "http://www.fungibleinsight.com/faces/beta.fst"; From 197ce036c4cf48aaf3c8a2f3dd04bd5de2f54af5 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 17 Apr 2014 11:06:28 -0700 Subject: [PATCH 13/27] Remove debugging line. --- libraries/script-engine/src/AnimationCache.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/libraries/script-engine/src/AnimationCache.cpp b/libraries/script-engine/src/AnimationCache.cpp index 570de7ed10..defcce525e 100644 --- a/libraries/script-engine/src/AnimationCache.cpp +++ b/libraries/script-engine/src/AnimationCache.cpp @@ -23,8 +23,6 @@ AnimationCache::AnimationCache(QObject* parent) : AnimationPointer AnimationCache::getAnimation(const QUrl& url) { if (QThread::currentThread() != thread()) { - qDebug() << "blocking call!"; - AnimationPointer result; QMetaObject::invokeMethod(this, "getAnimation", Qt::BlockingQueuedConnection, Q_RETURN_ARG(AnimationPointer, result), Q_ARG(const QUrl&, url)); From 58b063ee1275feb72e55429ed28534e23973abb8 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 17 Apr 2014 11:13:22 -0700 Subject: [PATCH 14/27] Make sure we call allReferencesCleared on the right thread. --- libraries/shared/src/ResourceCache.cpp | 5 +++++ libraries/shared/src/ResourceCache.h | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/libraries/shared/src/ResourceCache.cpp b/libraries/shared/src/ResourceCache.cpp index 30a725c010..04b6265513 100644 --- a/libraries/shared/src/ResourceCache.cpp +++ b/libraries/shared/src/ResourceCache.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -174,6 +175,10 @@ float Resource::getLoadPriority() { } void Resource::allReferencesCleared() { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "allReferencesCleared"); + return; + } if (_cache) { // create and reinsert new shared pointer QSharedPointer self(this, &Resource::allReferencesCleared); diff --git a/libraries/shared/src/ResourceCache.h b/libraries/shared/src/ResourceCache.h index da217516e1..0cfabd26fc 100644 --- a/libraries/shared/src/ResourceCache.h +++ b/libraries/shared/src/ResourceCache.h @@ -123,7 +123,7 @@ public: void setCache(ResourceCache* cache) { _cache = cache; } - void allReferencesCleared(); + Q_INVOKABLE void allReferencesCleared(); protected slots: From 6251b6b81939d4fbb32391cb39fae64a3701d79e Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 17 Apr 2014 13:55:16 -0700 Subject: [PATCH 15/27] Merge fix. --- assignment-client/src/Agent.cpp | 1 + libraries/fbx/src/FBXReader.cpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 2138914fe2..2e4c251005 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -163,6 +163,7 @@ void Agent::run() { QNetworkAccessManager *networkManager = new QNetworkAccessManager(this); QNetworkReply *reply = networkManager->get(QNetworkRequest(scriptURL)); QNetworkDiskCache* cache = new QNetworkDiskCache(networkManager); + QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation); cache->setCacheDirectory(!cachePath.isEmpty() ? cachePath : "agentCache"); networkManager->setCache(cache); diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index bb862ad918..eae8eb3920 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -952,6 +952,8 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) QHash yComponents; QHash zComponents; + printNode(node, 0); + QVariantHash joints = mapping.value("joint").toHash(); QString jointEyeLeftName = processID(getString(joints.value("jointEyeLeft", "jointEyeLeft"))); QString jointEyeRightName = processID(getString(joints.value("jointEyeRight", "jointEyeRight"))); From 197127fbde4d454707d45f9505e3d5726123a4ee Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 17 Apr 2014 15:42:23 -0700 Subject: [PATCH 16/27] Added ability to read FBX textures embedded in FBX files. --- interface/src/ModelUploader.cpp | 8 +-- interface/src/renderer/GeometryCache.cpp | 8 +-- interface/src/renderer/TextureCache.cpp | 66 ++++++++++++++++++------ interface/src/renderer/TextureCache.h | 13 +++-- libraries/fbx/src/FBXReader.cpp | 52 +++++++++++++------ libraries/fbx/src/FBXReader.h | 12 ++++- 6 files changed, 112 insertions(+), 47 deletions(-) diff --git a/interface/src/ModelUploader.cpp b/interface/src/ModelUploader.cpp index 5b2688169f..7f4ed0d36e 100644 --- a/interface/src/ModelUploader.cpp +++ b/interface/src/ModelUploader.cpp @@ -306,14 +306,14 @@ bool ModelUploader::addTextures(const QString& texdir, const QString fbxFile) { foreach (FBXMesh mesh, geometry.meshes) { foreach (FBXMeshPart part, mesh.parts) { - if (!part.diffuseFilename.isEmpty()) { - if (!addPart(texdir + "/" + part.diffuseFilename, + if (!part.diffuseTexture.filename.isEmpty()) { + if (!addPart(texdir + "/" + part.diffuseTexture.filename, QString("texture%1").arg(++_texturesCount))) { return false; } } - if (!part.normalFilename.isEmpty()) { - if (!addPart(texdir + "/" + part.normalFilename, + if (!part.normalTexture.filename.isEmpty()) { + if (!addPart(texdir + "/" + part.normalTexture.filename, QString("texture%1").arg(++_texturesCount))) { return false; } diff --git a/interface/src/renderer/GeometryCache.cpp b/interface/src/renderer/GeometryCache.cpp index 7b4eef1ac1..3a410ac5e2 100644 --- a/interface/src/renderer/GeometryCache.cpp +++ b/interface/src/renderer/GeometryCache.cpp @@ -543,14 +543,14 @@ void NetworkGeometry::setGeometry(const FBXGeometry& geometry) { int totalIndices = 0; foreach (const FBXMeshPart& part, mesh.parts) { NetworkMeshPart networkPart; - if (!part.diffuseFilename.isEmpty()) { + if (!part.diffuseTexture.filename.isEmpty()) { networkPart.diffuseTexture = Application::getInstance()->getTextureCache()->getTexture( - _textureBase.resolved(QUrl(part.diffuseFilename)), false, mesh.isEye); + _textureBase.resolved(QUrl(part.diffuseTexture.filename)), false, mesh.isEye, part.diffuseTexture.content); networkPart.diffuseTexture->setLoadPriorities(_loadPriorities); } - if (!part.normalFilename.isEmpty()) { + if (!part.normalTexture.filename.isEmpty()) { networkPart.normalTexture = Application::getInstance()->getTextureCache()->getTexture( - _textureBase.resolved(QUrl(part.normalFilename)), true); + _textureBase.resolved(QUrl(part.normalTexture.filename)), true, false, part.normalTexture.content); networkPart.normalTexture->setLoadPriorities(_loadPriorities); } networkMesh.parts.append(networkPart); diff --git a/interface/src/renderer/TextureCache.cpp b/interface/src/renderer/TextureCache.cpp index 3f523cf4bb..f31e4f9060 100644 --- a/interface/src/renderer/TextureCache.cpp +++ b/interface/src/renderer/TextureCache.cpp @@ -105,13 +105,22 @@ GLuint TextureCache::getBlueTextureID() { return _blueTextureID; } -QSharedPointer TextureCache::getTexture(const QUrl& url, bool normalMap, bool dilatable) { +/// Extra data for creating textures. +class TextureExtra { +public: + bool normalMap; + const QByteArray& content; +}; + +QSharedPointer TextureCache::getTexture(const QUrl& url, bool normalMap, + bool dilatable, const QByteArray& content) { if (!dilatable) { - return ResourceCache::getResource(url, QUrl(), false, &normalMap).staticCast(); + TextureExtra extra = { normalMap, content }; + return ResourceCache::getResource(url, QUrl(), false, &extra).staticCast(); } QSharedPointer texture = _dilatableNetworkTextures.value(url); if (texture.isNull()) { - texture = QSharedPointer(new DilatableNetworkTexture(url), &Resource::allReferencesCleared); + texture = QSharedPointer(new DilatableNetworkTexture(url, content), &Resource::allReferencesCleared); texture->setSelf(texture); texture->setCache(this); _dilatableNetworkTextures.insert(url, texture); @@ -215,7 +224,9 @@ bool TextureCache::eventFilter(QObject* watched, QEvent* event) { QSharedPointer TextureCache::createResource(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const void* extra) { - return QSharedPointer(new NetworkTexture(url, *(const bool*)extra), &Resource::allReferencesCleared); + const TextureExtra* textureExtra = static_cast(extra); + return QSharedPointer(new NetworkTexture(url, textureExtra->normalMap, textureExtra->content), + &Resource::allReferencesCleared); } QOpenGLFramebufferObject* TextureCache::createFramebufferObject() { @@ -238,8 +249,8 @@ Texture::~Texture() { glDeleteTextures(1, &_id); } -NetworkTexture::NetworkTexture(const QUrl& url, bool normalMap) : - Resource(url), +NetworkTexture::NetworkTexture(const QUrl& url, bool normalMap, const QByteArray& content) : + Resource(url, !content.isEmpty()), _translucent(false) { if (!url.isValid()) { @@ -250,12 +261,19 @@ NetworkTexture::NetworkTexture(const QUrl& url, bool normalMap) : glBindTexture(GL_TEXTURE_2D, getID()); loadSingleColorTexture(normalMap ? OPAQUE_BLUE : OPAQUE_WHITE); glBindTexture(GL_TEXTURE_2D, 0); + + // if we have content, load it after we have our self pointer + if (!content.isEmpty()) { + _startedLoading = true; + QMetaObject::invokeMethod(this, "loadContent", Qt::QueuedConnection, Q_ARG(const QByteArray&, content)); + } } class ImageReader : public QRunnable { public: - ImageReader(const QWeakPointer& texture, QNetworkReply* reply); + ImageReader(const QWeakPointer& texture, QNetworkReply* reply, const QUrl& url = QUrl(), + const QByteArray& content = QByteArray()); virtual void run(); @@ -263,27 +281,37 @@ private: QWeakPointer _texture; QNetworkReply* _reply; + QUrl _url; + QByteArray _content; }; -ImageReader::ImageReader(const QWeakPointer& texture, QNetworkReply* reply) : +ImageReader::ImageReader(const QWeakPointer& texture, QNetworkReply* reply, + const QUrl& url, const QByteArray& content) : _texture(texture), - _reply(reply) { + _reply(reply), + _url(url), + _content(content) { } void ImageReader::run() { QSharedPointer texture = _texture.toStrongRef(); if (texture.isNull()) { - _reply->deleteLater(); + if (_reply) { + _reply->deleteLater(); + } return; } - QUrl url = _reply->url(); - QImage image = QImage::fromData(_reply->readAll()); - _reply->deleteLater(); + if (_reply) { + _url = _reply->url(); + _content = _reply->readAll(); + _reply->deleteLater(); + } + QImage image = QImage::fromData(_content); // enforce a fixed maximum const int MAXIMUM_SIZE = 1024; if (image.width() > MAXIMUM_SIZE || image.height() > MAXIMUM_SIZE) { - qDebug() << "Image greater than maximum size:" << url << image.width() << image.height(); + qDebug() << "Image greater than maximum size:" << _url << image.width() << image.height(); image = image.scaled(MAXIMUM_SIZE, MAXIMUM_SIZE, Qt::KeepAspectRatio); } @@ -315,7 +343,7 @@ void ImageReader::run() { } int imageArea = image.width() * image.height(); if (opaquePixels == imageArea) { - qDebug() << "Image with alpha channel is completely opaque:" << url; + qDebug() << "Image with alpha channel is completely opaque:" << _url; image = image.convertToFormat(QImage::Format_RGB888); } QMetaObject::invokeMethod(texture.data(), "setImage", Q_ARG(const QImage&, image), @@ -327,6 +355,10 @@ void NetworkTexture::downloadFinished(QNetworkReply* reply) { QThreadPool::globalInstance()->start(new ImageReader(_self, reply)); } +void NetworkTexture::loadContent(const QByteArray& content) { + QThreadPool::globalInstance()->start(new ImageReader(_self, NULL, _url, content)); +} + void NetworkTexture::setImage(const QImage& image, bool translucent) { _translucent = translucent; @@ -348,8 +380,8 @@ void NetworkTexture::imageLoaded(const QImage& image) { // nothing by default } -DilatableNetworkTexture::DilatableNetworkTexture(const QUrl& url) : - NetworkTexture(url, false), +DilatableNetworkTexture::DilatableNetworkTexture(const QUrl& url, const QByteArray& content) : + NetworkTexture(url, false, content), _innerRadius(0), _outerRadius(0) { diff --git a/interface/src/renderer/TextureCache.h b/interface/src/renderer/TextureCache.h index e66044d843..f4444b6dfc 100644 --- a/interface/src/renderer/TextureCache.h +++ b/interface/src/renderer/TextureCache.h @@ -44,7 +44,8 @@ public: GLuint getBlueTextureID(); /// Loads a texture from the specified URL. - QSharedPointer getTexture(const QUrl& url, bool normalMap = false, bool dilatable = false); + QSharedPointer getTexture(const QUrl& url, bool normalMap = false, bool dilatable = false, + const QByteArray& content = QByteArray()); /// Returns a pointer to the primary framebuffer object. This render target includes a depth component, and is /// used for scene rendering. @@ -115,7 +116,7 @@ class NetworkTexture : public Resource, public Texture { public: - NetworkTexture(const QUrl& url, bool normalMap); + NetworkTexture(const QUrl& url, bool normalMap, const QByteArray& content); /// Checks whether it "looks like" this texture is translucent /// (majority of pixels neither fully opaque or fully transparent). @@ -124,10 +125,12 @@ public: protected: virtual void downloadFinished(QNetworkReply* reply); - virtual void imageLoaded(const QImage& image); - + + Q_INVOKABLE void loadContent(const QByteArray& content); Q_INVOKABLE void setImage(const QImage& image, bool translucent); + virtual void imageLoaded(const QImage& image); + private: bool _translucent; @@ -139,7 +142,7 @@ class DilatableNetworkTexture : public NetworkTexture { public: - DilatableNetworkTexture(const QUrl& url); + DilatableNetworkTexture(const QUrl& url, const QByteArray& content); /// Returns a pointer to a texture with the requested amount of dilation. QSharedPointer getDilatedTexture(float dilation); diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index eae8eb3920..dc4e7f617e 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -935,6 +935,14 @@ public: QVector values; }; +FBXTexture getTexture(const QString& textureID, const QHash& textureFilenames, + const QHash& textureContent) { + FBXTexture texture; + texture.filename = textureFilenames.value(textureID); + texture.content = textureContent.value(texture.filename); + return texture; +} + FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) { QHash meshes; QVector blendshapes; @@ -944,6 +952,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) QHash clusters; QHash animationCurves; QHash textureFilenames; + QHash textureContent; QHash materials; QHash diffuseTextures; QHash bumpTextures; @@ -952,8 +961,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) QHash yComponents; QHash zComponents; - printNode(node, 0); - QVariantHash joints = mapping.value("joint").toHash(); QString jointEyeLeftName = processID(getString(joints.value("jointEyeLeft", "jointEyeLeft"))); QString jointEyeRightName = processID(getString(joints.value("jointEyeRight", "jointEyeRight"))); @@ -1182,6 +1189,21 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) textureFilenames.insert(getID(object.properties), filename); } } + } else if (object.name == "Video") { + QByteArray filename; + QByteArray content; + foreach (const FBXNode& subobject, object.children) { + if (subobject.name == "RelativeFilename") { + filename = subobject.properties.at(0).toByteArray(); + filename = filename.mid(qMax(filename.lastIndexOf('\\'), filename.lastIndexOf('/')) + 1); + + } else if (subobject.name == "Content" && !subobject.properties.isEmpty()) { + content = subobject.properties.at(0).toByteArray(); + } + } + if (!content.isEmpty()) { + textureContent.insert(filename, content); + } } else if (object.name == "Material") { Material material = { glm::vec3(1.0f, 1.0f, 1.0f), glm::vec3(1.0f, 1.0f, 1.0f), 96.0f }; foreach (const FBXNode& subobject, object.children) { @@ -1263,7 +1285,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) if (type.contains("diffuse")) { diffuseTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); - } else if (type.contains("bump")) { + } else if (type.contains("bump") || type.contains("normal")) { bumpTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); } else if (type == "lcl rotation") { @@ -1463,23 +1485,23 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) if (materials.contains(childID)) { Material material = materials.value(childID); - QByteArray diffuseFilename; + FBXTexture diffuseTexture; QString diffuseTextureID = diffuseTextures.value(childID); if (!diffuseTextureID.isNull()) { - diffuseFilename = textureFilenames.value(diffuseTextureID); - + diffuseTexture = getTexture(diffuseTextureID, textureFilenames, textureContent); + // FBX files generated by 3DSMax have an intermediate texture parent, apparently foreach (const QString& childTextureID, childMap.values(diffuseTextureID)) { if (textureFilenames.contains(childTextureID)) { - diffuseFilename = textureFilenames.value(childTextureID); + diffuseTexture = getTexture(diffuseTextureID, textureFilenames, textureContent); } } } - QByteArray normalFilename; + FBXTexture normalTexture; QString bumpTextureID = bumpTextures.value(childID); if (!bumpTextureID.isNull()) { - normalFilename = textureFilenames.value(bumpTextureID); + normalTexture = getTexture(bumpTextureID, textureFilenames, textureContent); generateTangents = true; } @@ -1489,21 +1511,21 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) part.diffuseColor = material.diffuse; part.specularColor = material.specular; part.shininess = material.shininess; - if (!diffuseFilename.isNull()) { - part.diffuseFilename = diffuseFilename; + if (!diffuseTexture.filename.isNull()) { + part.diffuseTexture = diffuseTexture; } - if (!normalFilename.isNull()) { - part.normalFilename = normalFilename; + if (!normalTexture.filename.isNull()) { + part.normalTexture = normalTexture; } } } materialIndex++; } else if (textureFilenames.contains(childID)) { - QByteArray filename = textureFilenames.value(childID); + FBXTexture texture = getTexture(childID, textureFilenames, textureContent); for (int j = 0; j < extracted.partMaterialTextures.size(); j++) { if (extracted.partMaterialTextures.at(j).second == textureIndex) { - extracted.mesh.parts[j].diffuseFilename = filename; + extracted.mesh.parts[j].diffuseTexture = texture; } } textureIndex++; diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index 2f840e868e..9445daa7df 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -103,6 +103,14 @@ public: glm::mat4 inverseBindMatrix; }; +/// A texture map in an FBX document. +class FBXTexture { +public: + + QByteArray filename; + QByteArray content; +}; + /// A single part of a mesh (with the same material). class FBXMeshPart { public: @@ -114,8 +122,8 @@ public: glm::vec3 specularColor; float shininess; - QByteArray diffuseFilename; - QByteArray normalFilename; + FBXTexture diffuseTexture; + FBXTexture normalTexture; }; /// A single mesh (with optional blendshapes) extracted from an FBX document. From 15d4f59cebb0d272494be04e6fea80b91383c4e2 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 17 Apr 2014 15:52:32 -0700 Subject: [PATCH 17/27] Moved AnimationObject to separate source files. --- .../script-engine/src/AnimationCache.cpp | 20 -------- libraries/script-engine/src/AnimationCache.h | 29 ------------ .../script-engine/src/AnimationObject.cpp | 36 ++++++++++++++ libraries/script-engine/src/AnimationObject.h | 47 +++++++++++++++++++ libraries/script-engine/src/ScriptEngine.cpp | 1 + 5 files changed, 84 insertions(+), 49 deletions(-) create mode 100644 libraries/script-engine/src/AnimationObject.cpp create mode 100644 libraries/script-engine/src/AnimationObject.h diff --git a/libraries/script-engine/src/AnimationCache.cpp b/libraries/script-engine/src/AnimationCache.cpp index defcce525e..8e1493f075 100644 --- a/libraries/script-engine/src/AnimationCache.cpp +++ b/libraries/script-engine/src/AnimationCache.cpp @@ -10,7 +10,6 @@ // #include -#include #include #include "AnimationCache.h" @@ -101,22 +100,3 @@ void Animation::downloadFinished(QNetworkReply* reply) { QThreadPool::globalInstance()->start(new AnimationReader(_self, reply)); } -QStringList AnimationObject::getJointNames() const { - return qscriptvalue_cast(thisObject())->getJointNames(); -} - -QVector AnimationObject::getFrames() const { - return qscriptvalue_cast(thisObject())->getFrames(); -} - -QVector AnimationFrameObject::getRotations() const { - return qscriptvalue_cast(thisObject()).rotations; -} - -void registerAnimationTypes(QScriptEngine* engine) { - qScriptRegisterSequenceMetaType >(engine); - engine->setDefaultPrototype(qMetaTypeId(), engine->newQObject( - new AnimationFrameObject(), QScriptEngine::ScriptOwnership)); - engine->setDefaultPrototype(qMetaTypeId(), engine->newQObject( - new AnimationObject(), QScriptEngine::ScriptOwnership)); -} diff --git a/libraries/script-engine/src/AnimationCache.h b/libraries/script-engine/src/AnimationCache.h index 38fabad57f..23183adf10 100644 --- a/libraries/script-engine/src/AnimationCache.h +++ b/libraries/script-engine/src/AnimationCache.h @@ -12,14 +12,10 @@ #ifndef hifi_AnimationCache_h #define hifi_AnimationCache_h -#include - #include #include -class QScriptEngine; - class Animation; typedef QSharedPointer AnimationPointer; @@ -69,29 +65,4 @@ private: FBXGeometry _geometry; }; -/// Scriptable wrapper for animation pointers. -class AnimationObject : public QObject, protected QScriptable { - Q_OBJECT - Q_PROPERTY(QStringList jointNames READ getJointNames) - Q_PROPERTY(QVector frames READ getFrames) - -public: - - Q_INVOKABLE QStringList getJointNames() const; - - Q_INVOKABLE QVector getFrames() const; -}; - -/// Scriptable wrapper for animation frames. -class AnimationFrameObject : public QObject, protected QScriptable { - Q_OBJECT - Q_PROPERTY(QVector rotations READ getRotations) - -public: - - Q_INVOKABLE QVector getRotations() const; -}; - -void registerAnimationTypes(QScriptEngine* engine); - #endif // hifi_AnimationCache_h diff --git a/libraries/script-engine/src/AnimationObject.cpp b/libraries/script-engine/src/AnimationObject.cpp new file mode 100644 index 0000000000..ede1e82623 --- /dev/null +++ b/libraries/script-engine/src/AnimationObject.cpp @@ -0,0 +1,36 @@ +// +// AnimationObject.cpp +// libraries/script-engine/src/ +// +// Created by Andrzej Kapolka on 4/17/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 + +#include "AnimationCache.h" +#include "AnimationObject.h" + +QStringList AnimationObject::getJointNames() const { + return qscriptvalue_cast(thisObject())->getJointNames(); +} + +QVector AnimationObject::getFrames() const { + return qscriptvalue_cast(thisObject())->getFrames(); +} + +QVector AnimationFrameObject::getRotations() const { + return qscriptvalue_cast(thisObject()).rotations; +} + +void registerAnimationTypes(QScriptEngine* engine) { + qScriptRegisterSequenceMetaType >(engine); + engine->setDefaultPrototype(qMetaTypeId(), engine->newQObject( + new AnimationFrameObject(), QScriptEngine::ScriptOwnership)); + engine->setDefaultPrototype(qMetaTypeId(), engine->newQObject( + new AnimationObject(), QScriptEngine::ScriptOwnership)); +} + diff --git a/libraries/script-engine/src/AnimationObject.h b/libraries/script-engine/src/AnimationObject.h new file mode 100644 index 0000000000..078fc31fb3 --- /dev/null +++ b/libraries/script-engine/src/AnimationObject.h @@ -0,0 +1,47 @@ +// +// AnimationObject.h +// libraries/script-engine/src/ +// +// Created by Andrzej Kapolka on 4/17/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_AnimationObject_h +#define hifi_AnimationObject_h + +#include +#include + +#include + +class QScriptEngine; + +/// Scriptable wrapper for animation pointers. +class AnimationObject : public QObject, protected QScriptable { + Q_OBJECT + Q_PROPERTY(QStringList jointNames READ getJointNames) + Q_PROPERTY(QVector frames READ getFrames) + +public: + + Q_INVOKABLE QStringList getJointNames() const; + + Q_INVOKABLE QVector getFrames() const; +}; + +/// Scriptable wrapper for animation frames. +class AnimationFrameObject : public QObject, protected QScriptable { + Q_OBJECT + Q_PROPERTY(QVector rotations READ getRotations) + +public: + + Q_INVOKABLE QVector getRotations() const; +}; + +void registerAnimationTypes(QScriptEngine* engine); + +#endif // hifi_AnimationObject_h diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 4dee35c6a5..950ba899de 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -28,6 +28,7 @@ #include +#include "AnimationObject.h" #include "MenuItemProperties.h" #include "LocalVoxels.h" #include "ScriptEngine.h" From e303ce1a12ca7a6c51010c809101baacaf0085dd Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 17 Apr 2014 16:37:40 -0700 Subject: [PATCH 18/27] move some Avatar management to AvatarHashMap --- interface/src/avatar/Avatar.cpp | 5 ++ interface/src/avatar/AvatarManager.cpp | 100 +----------------------- interface/src/avatar/AvatarManager.h | 7 +- libraries/avatars/src/AvatarHashMap.cpp | 94 ++++++++++++++++++++++ libraries/avatars/src/AvatarHashMap.h | 12 +++ 5 files changed, 115 insertions(+), 103 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index e9d804d227..78d5131b77 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -685,6 +685,11 @@ void Avatar::setBillboard(const QByteArray& billboard) { } int Avatar::parseDataAtOffset(const QByteArray& packet, int offset) { + if (!_initialized) { + // now that we have data for this Avatar we are go for init + init(); + } + // change in position implies movement glm::vec3 oldPosition = _position; diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 5798f33d0f..a8d8c8f6e3 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -127,22 +127,9 @@ void AvatarManager::renderAvatarFades(const glm::vec3& cameraPosition, Avatar::R } } -AvatarSharedPointer AvatarManager::matchingOrNewAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) { - AvatarSharedPointer matchingAvatar = _avatarHash.value(sessionUUID); - - if (!matchingAvatar) { - // construct a new Avatar for this node - Avatar* avatar = new Avatar(); - avatar->setOwningAvatarMixer(mixerWeakPointer); - - // insert the new avatar into our hash - matchingAvatar = AvatarSharedPointer(avatar); - _avatarHash.insert(sessionUUID, matchingAvatar); - - qDebug() << "Adding avatar with sessionUUID " << sessionUUID << "to AvatarManager hash."; - } - - return matchingAvatar; +void AvatarManager::sharedAvatarAddedToHash(const AvatarSharedPointer& sharedAvatar, + const QWeakPointer& mixerWeakPointer) { + reinterpret_cast(sharedAvatar.data())->setOwningAvatarMixer(mixerWeakPointer); } void AvatarManager::processAvatarMixerDatagram(const QByteArray& datagram, const QWeakPointer& mixerWeakPointer) { @@ -164,87 +151,6 @@ void AvatarManager::processAvatarMixerDatagram(const QByteArray& datagram, const } } -void AvatarManager::processAvatarDataPacket(const QByteArray &datagram, const QWeakPointer &mixerWeakPointer) { - int bytesRead = numBytesForPacketHeader(datagram); - - // enumerate over all of the avatars in this packet - // only add them if mixerWeakPointer points to something (meaning that mixer is still around) - while (bytesRead < datagram.size() && mixerWeakPointer.data()) { - QUuid sessionUUID = QUuid::fromRfc4122(datagram.mid(bytesRead, NUM_BYTES_RFC4122_UUID)); - bytesRead += NUM_BYTES_RFC4122_UUID; - - AvatarSharedPointer matchingAvatarData = matchingOrNewAvatar(sessionUUID, mixerWeakPointer); - - // have the matching (or new) avatar parse the data from the packet - bytesRead += matchingAvatarData->parseDataAtOffset(datagram, bytesRead); - - Avatar* matchingAvatar = reinterpret_cast(matchingAvatarData.data()); - - if (!matchingAvatar->isInitialized()) { - // now that we have AvatarData for this Avatar we are go for init - matchingAvatar->init(); - } - } -} - -void AvatarManager::processAvatarIdentityPacket(const QByteArray &packet, const QWeakPointer& mixerWeakPointer) { - // setup a data stream to parse the packet - QDataStream identityStream(packet); - identityStream.skipRawData(numBytesForPacketHeader(packet)); - - QUuid sessionUUID; - - while (!identityStream.atEnd()) { - - QUrl faceMeshURL, skeletonURL; - QString displayName; - identityStream >> sessionUUID >> faceMeshURL >> skeletonURL >> displayName; - - // mesh URL for a UUID, find avatar in our list - AvatarSharedPointer matchingAvatar = matchingOrNewAvatar(sessionUUID, mixerWeakPointer); - if (matchingAvatar) { - Avatar* avatar = static_cast(matchingAvatar.data()); - - if (avatar->getFaceModelURL() != faceMeshURL) { - avatar->setFaceModelURL(faceMeshURL); - } - - if (avatar->getSkeletonModelURL() != skeletonURL) { - avatar->setSkeletonModelURL(skeletonURL); - } - - if (avatar->getDisplayName() != displayName) { - avatar->setDisplayName(displayName); - } - } - } -} - -void AvatarManager::processAvatarBillboardPacket(const QByteArray& packet, const QWeakPointer& mixerWeakPointer) { - int headerSize = numBytesForPacketHeader(packet); - QUuid sessionUUID = QUuid::fromRfc4122(QByteArray::fromRawData(packet.constData() + headerSize, NUM_BYTES_RFC4122_UUID)); - - AvatarSharedPointer matchingAvatar = matchingOrNewAvatar(sessionUUID, mixerWeakPointer); - if (matchingAvatar) { - Avatar* avatar = static_cast(matchingAvatar.data()); - QByteArray billboard = packet.mid(headerSize + NUM_BYTES_RFC4122_UUID); - if (avatar->getBillboard() != billboard) { - avatar->setBillboard(billboard); - } - } -} - -void AvatarManager::processKillAvatar(const QByteArray& datagram) { - // read the node id - QUuid sessionUUID = QUuid::fromRfc4122(datagram.mid(numBytesForPacketHeader(datagram), NUM_BYTES_RFC4122_UUID)); - - // remove the avatar with that UUID from our hash, if it exists - AvatarHash::iterator matchedAvatar = _avatarHash.find(sessionUUID); - if (matchedAvatar != _avatarHash.end()) { - erase(matchedAvatar); - } -} - AvatarHash::iterator AvatarManager::erase(const AvatarHash::iterator& iterator) { if (iterator.key() != MY_AVATAR_KEY) { qDebug() << "Removing Avatar with UUID" << iterator.key() << "from AvatarManager hash."; diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 78491b3a5d..db2bb0b5ac 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -42,12 +42,7 @@ public slots: private: AvatarManager(const AvatarManager& other); - AvatarSharedPointer matchingOrNewAvatar(const QUuid& nodeUUID, const QWeakPointer& mixerWeakPointer); - - void processAvatarDataPacket(const QByteArray& packet, const QWeakPointer& mixerWeakPointer); - void processAvatarIdentityPacket(const QByteArray& packet, const QWeakPointer& mixerWeakPointer); - void processAvatarBillboardPacket(const QByteArray& packet, const QWeakPointer& mixerWeakPointer); - void processKillAvatar(const QByteArray& datagram); + void sharedAvatarAddedToHash(const AvatarSharedPointer& sharedAvatar, const QWeakPointer& mixerWeakPointer); void simulateAvatarFades(float deltaTime); void renderAvatarFades(const glm::vec3& cameraPosition, Avatar::RenderMode renderMode); diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index 1c2cd4bf92..e3446637ef 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include + #include "AvatarHashMap.h" AvatarHashMap::AvatarHashMap() : @@ -25,3 +27,95 @@ AvatarHash::iterator AvatarHashMap::erase(const AvatarHash::iterator& iterator) return _avatarHash.erase(iterator); } +AvatarSharedPointer AvatarHashMap::newSharedAvatar() { + AvatarData* avatarData = new AvatarData(); + return AvatarSharedPointer(avatarData); +} + +AvatarSharedPointer AvatarHashMap::matchingOrNewAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) { + AvatarSharedPointer matchingAvatar = _avatarHash.value(sessionUUID); + + if (!matchingAvatar) { + // insert the new avatar into our hash + matchingAvatar = newSharedAvatar(); + + qDebug() << "Adding avatar with sessionUUID " << sessionUUID << "to AvatarManager hash."; + _avatarHash.insert(sessionUUID, matchingAvatar); + + sharedAvatarAddedToHash(matchingAvatar, mixerWeakPointer); + } + + return matchingAvatar; +} + +void AvatarHashMap::processAvatarDataPacket(const QByteArray &datagram, const QWeakPointer &mixerWeakPointer) { + int bytesRead = numBytesForPacketHeader(datagram); + + // enumerate over all of the avatars in this packet + // only add them if mixerWeakPointer points to something (meaning that mixer is still around) + while (bytesRead < datagram.size() && mixerWeakPointer.data()) { + QUuid sessionUUID = QUuid::fromRfc4122(datagram.mid(bytesRead, NUM_BYTES_RFC4122_UUID)); + bytesRead += NUM_BYTES_RFC4122_UUID; + + AvatarSharedPointer matchingAvatarData = matchingOrNewAvatar(sessionUUID, mixerWeakPointer); + + // have the matching (or new) avatar parse the data from the packet + bytesRead += matchingAvatarData->parseDataAtOffset(datagram, bytesRead); + } +} + +void AvatarHashMap::processAvatarIdentityPacket(const QByteArray &packet, const QWeakPointer& mixerWeakPointer) { + // setup a data stream to parse the packet + QDataStream identityStream(packet); + identityStream.skipRawData(numBytesForPacketHeader(packet)); + + QUuid sessionUUID; + + while (!identityStream.atEnd()) { + + QUrl faceMeshURL, skeletonURL; + QString displayName; + identityStream >> sessionUUID >> faceMeshURL >> skeletonURL >> displayName; + + // mesh URL for a UUID, find avatar in our list + AvatarSharedPointer matchingAvatar = matchingOrNewAvatar(sessionUUID, mixerWeakPointer); + if (matchingAvatar) { + + if (matchingAvatar->getFaceModelURL() != faceMeshURL) { + matchingAvatar->setFaceModelURL(faceMeshURL); + } + + if (matchingAvatar->getSkeletonModelURL() != skeletonURL) { + matchingAvatar->setSkeletonModelURL(skeletonURL); + } + + if (matchingAvatar->getDisplayName() != displayName) { + matchingAvatar->setDisplayName(displayName); + } + } + } +} + +void AvatarHashMap::processAvatarBillboardPacket(const QByteArray& packet, const QWeakPointer& mixerWeakPointer) { + int headerSize = numBytesForPacketHeader(packet); + QUuid sessionUUID = QUuid::fromRfc4122(QByteArray::fromRawData(packet.constData() + headerSize, NUM_BYTES_RFC4122_UUID)); + + AvatarSharedPointer matchingAvatar = matchingOrNewAvatar(sessionUUID, mixerWeakPointer); + if (matchingAvatar) { + QByteArray billboard = packet.mid(headerSize + NUM_BYTES_RFC4122_UUID); + if (matchingAvatar->getBillboard() != billboard) { + matchingAvatar->setBillboard(billboard); + } + } +} + +void AvatarHashMap::processKillAvatar(const QByteArray& datagram) { + // read the node id + QUuid sessionUUID = QUuid::fromRfc4122(datagram.mid(numBytesForPacketHeader(datagram), NUM_BYTES_RFC4122_UUID)); + + // remove the avatar with that UUID from our hash, if it exists + AvatarHash::iterator matchedAvatar = _avatarHash.find(sessionUUID); + if (matchedAvatar != _avatarHash.end()) { + erase(matchedAvatar); + } +} \ No newline at end of file diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h index aee9cd09f1..c9b318dee8 100644 --- a/libraries/avatars/src/AvatarHashMap.h +++ b/libraries/avatars/src/AvatarHashMap.h @@ -16,6 +16,8 @@ #include #include +#include + #include "AvatarData.h" typedef QSharedPointer AvatarSharedPointer; @@ -32,6 +34,16 @@ public: protected: virtual AvatarHash::iterator erase(const AvatarHash::iterator& iterator); + + virtual AvatarSharedPointer newSharedAvatar(); + virtual void sharedAvatarAddedToHash(const AvatarSharedPointer& sharedAvatar, + const QWeakPointer& mixerWeakPointer) = 0; + AvatarSharedPointer matchingOrNewAvatar(const QUuid& nodeUUID, const QWeakPointer& mixerWeakPointer); + + void processAvatarDataPacket(const QByteArray& packet, const QWeakPointer& mixerWeakPointer); + void processAvatarIdentityPacket(const QByteArray& packet, const QWeakPointer& mixerWeakPointer); + void processAvatarBillboardPacket(const QByteArray& packet, const QWeakPointer& mixerWeakPointer); + void processKillAvatar(const QByteArray& datagram); AvatarHash _avatarHash; }; From 69d3360675e53cd92d817ebf237b5ee43eb0b103 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 17 Apr 2014 16:48:24 -0700 Subject: [PATCH 19/27] add a convenience method to AvatarHashMap to check for avatar removal --- interface/src/avatar/Avatar.cpp | 1 - interface/src/avatar/Avatar.h | 4 ---- interface/src/avatar/AvatarManager.cpp | 13 +++++-------- interface/src/avatar/AvatarManager.h | 2 -- libraries/avatars/src/AvatarData.cpp | 3 ++- libraries/avatars/src/AvatarData.h | 6 ++++++ libraries/avatars/src/AvatarHashMap.cpp | 6 +++++- libraries/avatars/src/AvatarHashMap.h | 4 ++-- 8 files changed, 20 insertions(+), 19 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 78d5131b77..8cfab7da03 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -56,7 +56,6 @@ Avatar::Avatar() : _mouseRayOrigin(0.0f, 0.0f, 0.0f), _mouseRayDirection(0.0f, 0.0f, 0.0f), _moving(false), - _owningAvatarMixer(), _collisionFlags(0), _initialized(false), _shouldRenderBillboard(true) diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 4d68f2168b..ecf1be4899 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -99,9 +99,6 @@ public: /// Returns the distance to use as a LOD parameter. float getLODDistance() const; - - Node* getOwningAvatarMixer() { return _owningAvatarMixer.data(); } - void setOwningAvatarMixer(const QWeakPointer& owningAvatarMixer) { _owningAvatarMixer = owningAvatarMixer; } bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const; @@ -177,7 +174,6 @@ protected: glm::vec3 _mouseRayDirection; float _stringLength; bool _moving; ///< set when position is changing - QWeakPointer _owningAvatarMixer; uint32_t _collisionFlags; diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index a8d8c8f6e3..b4969bae39 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -51,14 +51,16 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { // simulate avatars AvatarHash::iterator avatarIterator = _avatarHash.begin(); while (avatarIterator != _avatarHash.end()) { - Avatar* avatar = static_cast(avatarIterator.value().data()); - if (avatar == static_cast(_myAvatar.data()) || !avatar->isInitialized()) { + AvatarSharedPointer sharedAvatar = avatarIterator.value(); + Avatar* avatar = reinterpret_cast(sharedAvatar.data()); + + if (sharedAvatar == _myAvatar || !avatar->isInitialized()) { // DO NOT update _myAvatar! Its update has already been done earlier in the main loop. // DO NOT update uninitialized Avatars ++avatarIterator; continue; } - if (avatar->getOwningAvatarMixer()) { + if (!shouldKillAvatar(sharedAvatar)) { // this avatar's mixer is still around, go ahead and simulate it avatar->simulate(deltaTime); avatar->setMouseRay(mouseOrigin, mouseDirection); @@ -127,11 +129,6 @@ void AvatarManager::renderAvatarFades(const glm::vec3& cameraPosition, Avatar::R } } -void AvatarManager::sharedAvatarAddedToHash(const AvatarSharedPointer& sharedAvatar, - const QWeakPointer& mixerWeakPointer) { - reinterpret_cast(sharedAvatar.data())->setOwningAvatarMixer(mixerWeakPointer); -} - void AvatarManager::processAvatarMixerDatagram(const QByteArray& datagram, const QWeakPointer& mixerWeakPointer) { switch (packetTypeForPacket(datagram)) { case PacketTypeBulkAvatarData: diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index db2bb0b5ac..1b72052c7e 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -41,8 +41,6 @@ public slots: private: AvatarManager(const AvatarManager& other); - - void sharedAvatarAddedToHash(const AvatarSharedPointer& sharedAvatar, const QWeakPointer& mixerWeakPointer); void simulateAvatarFades(float deltaTime); void renderAvatarFades(const glm::vec3& cameraPosition, Avatar::RenderMode renderMode); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 2f412b5e9a..a59e0d9df3 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -51,7 +51,8 @@ AvatarData::AvatarData() : _displayNameTargetAlpha(0.0f), _displayNameAlpha(0.0f), _billboard(), - _errorLogExpiry(0) + _errorLogExpiry(0), + _owningAvatarMixer() { } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 947fbc41d4..80482cc165 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -44,6 +44,7 @@ typedef unsigned long long quint64; #include #include +#include #include "HeadData.h" #include "HandData.h" @@ -220,6 +221,9 @@ public: QString getSkeletonModelURLFromScript() const { return _skeletonModelURL.toString(); } void setSkeletonModelURLFromScript(const QString& skeletonModelString) { setSkeletonModelURL(QUrl(skeletonModelString)); } + Node* getOwningAvatarMixer() { return _owningAvatarMixer.data(); } + void setOwningAvatarMixer(const QWeakPointer& owningAvatarMixer) { _owningAvatarMixer = owningAvatarMixer; } + virtual float getBoundingRadius() const { return 1.f; } static void setNetworkAccessManager(QNetworkAccessManager* sharedAccessManager) { networkAccessManager = sharedAccessManager; } @@ -278,6 +282,8 @@ protected: static QNetworkAccessManager* networkAccessManager; quint64 _errorLogExpiry; ///< time in future when to log an error + + QWeakPointer _owningAvatarMixer; /// Loads the joint indices, names from the FST file (if any) virtual void updateJointMappings(); diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index e3446637ef..c878eef4c7 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -27,6 +27,10 @@ AvatarHash::iterator AvatarHashMap::erase(const AvatarHash::iterator& iterator) return _avatarHash.erase(iterator); } +bool AvatarHashMap::shouldKillAvatar(const AvatarSharedPointer& sharedAvatar) { + return (sharedAvatar->getOwningAvatarMixer() == NULL); +} + AvatarSharedPointer AvatarHashMap::newSharedAvatar() { AvatarData* avatarData = new AvatarData(); return AvatarSharedPointer(avatarData); @@ -42,7 +46,7 @@ AvatarSharedPointer AvatarHashMap::matchingOrNewAvatar(const QUuid& sessionUUID, qDebug() << "Adding avatar with sessionUUID " << sessionUUID << "to AvatarManager hash."; _avatarHash.insert(sessionUUID, matchingAvatar); - sharedAvatarAddedToHash(matchingAvatar, mixerWeakPointer); + matchingAvatar->setOwningAvatarMixer(mixerWeakPointer); } return matchingAvatar; diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h index c9b318dee8..2db6c79ed2 100644 --- a/libraries/avatars/src/AvatarHashMap.h +++ b/libraries/avatars/src/AvatarHashMap.h @@ -35,9 +35,9 @@ public: protected: virtual AvatarHash::iterator erase(const AvatarHash::iterator& iterator); + bool shouldKillAvatar(const AvatarSharedPointer& sharedAvatar); + virtual AvatarSharedPointer newSharedAvatar(); - virtual void sharedAvatarAddedToHash(const AvatarSharedPointer& sharedAvatar, - const QWeakPointer& mixerWeakPointer) = 0; AvatarSharedPointer matchingOrNewAvatar(const QUuid& nodeUUID, const QWeakPointer& mixerWeakPointer); void processAvatarDataPacket(const QByteArray& packet, const QWeakPointer& mixerWeakPointer); From 3e4369f2fff8f3315fe7219ff32f9b9ef55e3cee Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 17 Apr 2014 16:56:57 -0700 Subject: [PATCH 20/27] kill avatars in hash map after a timeout threshold --- libraries/avatars/src/AvatarData.cpp | 7 ++++++- libraries/avatars/src/AvatarData.h | 6 +++++- libraries/avatars/src/AvatarHashMap.cpp | 6 +++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index a59e0d9df3..2e716296ff 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -52,7 +52,8 @@ AvatarData::AvatarData() : _displayNameAlpha(0.0f), _billboard(), _errorLogExpiry(0), - _owningAvatarMixer() + _owningAvatarMixer(), + _lastUpdateTimer() { } @@ -194,6 +195,10 @@ bool AvatarData::shouldLogError(const quint64& now) { // read data in packet starting at byte offset and return number of bytes parsed int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) { + + // reset the last heard timer since we have new data for this AvatarData + _lastUpdateTimer.restart(); + // lazily allocate memory for HeadData in case we're not an Avatar instance if (!_headData) { _headData = new HeadData(this); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 80482cc165..d1a63c9a58 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -32,6 +32,7 @@ typedef unsigned long long quint64; #include #include +#include #include #include #include @@ -224,6 +225,8 @@ public: Node* getOwningAvatarMixer() { return _owningAvatarMixer.data(); } void setOwningAvatarMixer(const QWeakPointer& owningAvatarMixer) { _owningAvatarMixer = owningAvatarMixer; } + QElapsedTimer& getLastUpdateTimer() { return _lastUpdateTimer; } + virtual float getBoundingRadius() const { return 1.f; } static void setNetworkAccessManager(QNetworkAccessManager* sharedAccessManager) { networkAccessManager = sharedAccessManager; } @@ -284,7 +287,8 @@ protected: quint64 _errorLogExpiry; ///< time in future when to log an error QWeakPointer _owningAvatarMixer; - + QElapsedTimer _lastUpdateTimer; + /// Loads the joint indices, names from the FST file (if any) virtual void updateJointMappings(); diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index c878eef4c7..5f2d27c1a6 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -16,6 +16,7 @@ AvatarHashMap::AvatarHashMap() : _avatarHash() { + } void AvatarHashMap::insert(const QUuid& id, AvatarSharedPointer avatar) { @@ -27,8 +28,11 @@ AvatarHash::iterator AvatarHashMap::erase(const AvatarHash::iterator& iterator) return _avatarHash.erase(iterator); } +const qint64 AVATAR_SILENCE_THRESHOLD_MSECS = 5 * 1000; + bool AvatarHashMap::shouldKillAvatar(const AvatarSharedPointer& sharedAvatar) { - return (sharedAvatar->getOwningAvatarMixer() == NULL); + return (sharedAvatar->getOwningAvatarMixer() == NULL + && sharedAvatar->getLastUpdateTimer().elapsed() > AVATAR_SILENCE_THRESHOLD_MSECS); } AvatarSharedPointer AvatarHashMap::newSharedAvatar() { From e75d14139f0062dae884b92ce9845a7b4cea3e7c Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 17 Apr 2014 17:21:38 -0700 Subject: [PATCH 21/27] fix avatar kill behaviour --- interface/src/avatar/AvatarManager.cpp | 4 ++++ interface/src/avatar/AvatarManager.h | 2 ++ libraries/avatars/src/AvatarHashMap.cpp | 5 ++--- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index b4969bae39..3ff41a85b1 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -148,6 +148,10 @@ void AvatarManager::processAvatarMixerDatagram(const QByteArray& datagram, const } } +AvatarSharedPointer AvatarManager::newSharedAvatar() { + return AvatarSharedPointer(new Avatar()); +} + AvatarHash::iterator AvatarManager::erase(const AvatarHash::iterator& iterator) { if (iterator.key() != MY_AVATAR_KEY) { qDebug() << "Removing Avatar with UUID" << iterator.key() << "from AvatarManager hash."; diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 1b72052c7e..a31162ec1a 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -45,6 +45,8 @@ private: void simulateAvatarFades(float deltaTime); void renderAvatarFades(const glm::vec3& cameraPosition, Avatar::RenderMode renderMode); + AvatarSharedPointer newSharedAvatar(); + // virtual override AvatarHash::iterator erase(const AvatarHash::iterator& iterator); diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index 5f2d27c1a6..f6ff848ffa 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -32,12 +32,11 @@ const qint64 AVATAR_SILENCE_THRESHOLD_MSECS = 5 * 1000; bool AvatarHashMap::shouldKillAvatar(const AvatarSharedPointer& sharedAvatar) { return (sharedAvatar->getOwningAvatarMixer() == NULL - && sharedAvatar->getLastUpdateTimer().elapsed() > AVATAR_SILENCE_THRESHOLD_MSECS); + || sharedAvatar->getLastUpdateTimer().elapsed() > AVATAR_SILENCE_THRESHOLD_MSECS); } AvatarSharedPointer AvatarHashMap::newSharedAvatar() { - AvatarData* avatarData = new AvatarData(); - return AvatarSharedPointer(avatarData); + return AvatarSharedPointer(new AvatarData()); } AvatarSharedPointer AvatarHashMap::matchingOrNewAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) { From 47d4ef0b2d6298bcee8d39a565ff15c806a2df06 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 17 Apr 2014 17:44:41 -0700 Subject: [PATCH 22/27] add a method to determine if avatar with display name exists --- interface/src/avatar/AvatarManager.h | 2 +- libraries/avatars/src/AvatarHashMap.cpp | 24 ++++++++++++++++++++++++ libraries/avatars/src/AvatarHashMap.h | 6 +++++- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index a31162ec1a..fd15554666 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -22,7 +22,7 @@ class MyAvatar; -class AvatarManager : public QObject, public AvatarHashMap { +class AvatarManager : public AvatarHashMap { Q_OBJECT public: AvatarManager(QObject* parent = 0); diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index f6ff848ffa..17fdaeb5bd 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -35,6 +35,30 @@ bool AvatarHashMap::shouldKillAvatar(const AvatarSharedPointer& sharedAvatar) { || sharedAvatar->getLastUpdateTimer().elapsed() > AVATAR_SILENCE_THRESHOLD_MSECS); } +bool AvatarHashMap::containsAvatarWithDisplayName(const QString& displayName) { + + AvatarHash::iterator avatarIterator = _avatarHash.begin(); + while (avatarIterator != _avatarHash.end()) { + AvatarSharedPointer sharedAvatar = avatarIterator.value(); + if (avatarIterator.value()->getDisplayName() == displayName) { + // this is a match + // check if this avatar should still be around + if (!shouldKillAvatar(sharedAvatar)) { + // we have a match, return true + return true; + } else { + // we should remove this avatar, do that now + erase(avatarIterator); + } + + break; + } + } + + // return false, no match + return false; +} + AvatarSharedPointer AvatarHashMap::newSharedAvatar() { return AvatarSharedPointer(new AvatarData()); } diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h index 2db6c79ed2..98e64fb8de 100644 --- a/libraries/avatars/src/AvatarHashMap.h +++ b/libraries/avatars/src/AvatarHashMap.h @@ -23,7 +23,8 @@ typedef QSharedPointer AvatarSharedPointer; typedef QHash AvatarHash; -class AvatarHashMap { +class AvatarHashMap : public QObject { + Q_OBJECT public: AvatarHashMap(); @@ -31,6 +32,9 @@ public: int size() const { return _avatarHash.size(); } virtual void insert(const QUuid& id, AvatarSharedPointer avatar); + +public slots: + bool containsAvatarWithDisplayName(const QString& displayName); protected: virtual AvatarHash::iterator erase(const AvatarHash::iterator& iterator); From f8106e50639ddc459b7700c3b41aa427e4349782 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 17 Apr 2014 17:49:55 -0700 Subject: [PATCH 23/27] add an AvatarHashMap to the Agent --- assignment-client/src/Agent.cpp | 13 ++++++++++++- assignment-client/src/Agent.h | 2 ++ interface/src/avatar/AvatarManager.cpp | 19 ------------------- interface/src/avatar/AvatarManager.h | 3 --- libraries/avatars/src/AvatarHashMap.cpp | 21 ++++++++++++++++++++- libraries/avatars/src/AvatarHashMap.h | 1 + 6 files changed, 35 insertions(+), 24 deletions(-) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 2e4c251005..7f4e811c32 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -33,7 +33,8 @@ Agent::Agent(const QByteArray& packet) : ThreadedAssignment(packet), _voxelEditSender(), _particleEditSender(), - _receivedAudioBuffer(NETWORK_BUFFER_LENGTH_SAMPLES_STEREO) + _receivedAudioBuffer(NETWORK_BUFFER_LENGTH_SAMPLES_STEREO), + _avatarHashMap() { // be the parent of the script engine so it gets moved when we do _scriptEngine.setParent(this); @@ -131,6 +132,16 @@ void Agent::readPendingDatagrams() { // let this continue through to the NodeList so it updates last heard timestamp // for the sending audio mixer NodeList::getInstance()->processNodeData(senderSockAddr, receivedPacket); + } else if (datagramPacketType == PacketTypeAvatarData + || datagramPacketType == PacketTypeAvatarIdentity + || datagramPacketType == PacketTypeAvatarBillboard + || datagramPacketType == PacketTypeKillAvatar) { + // let the avatar hash map process it + _avatarHashMap.processAvatarMixerDatagram(receivedPacket, nodeList->sendingNodeForPacket(receivedPacket)); + + // let this continue through to the NodeList so it updates last heard timestamp + // for the sending avatar-mixer + NodeList::getInstance()->processNodeData(senderSockAddr, receivedPacket); } else { NodeList::getInstance()->processNodeData(senderSockAddr, receivedPacket); } diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h index 2dcd7e3107..9f6a8089cf 100644 --- a/assignment-client/src/Agent.h +++ b/assignment-client/src/Agent.h @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -65,6 +66,7 @@ private: VoxelTreeHeadlessViewer _voxelViewer; MixedAudioRingBuffer _receivedAudioBuffer; + AvatarHashMap _avatarHashMap; }; #endif // hifi_Agent_h diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 3ff41a85b1..f235d6f2e3 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -129,25 +129,6 @@ void AvatarManager::renderAvatarFades(const glm::vec3& cameraPosition, Avatar::R } } -void AvatarManager::processAvatarMixerDatagram(const QByteArray& datagram, const QWeakPointer& mixerWeakPointer) { - switch (packetTypeForPacket(datagram)) { - case PacketTypeBulkAvatarData: - processAvatarDataPacket(datagram, mixerWeakPointer); - break; - case PacketTypeAvatarIdentity: - processAvatarIdentityPacket(datagram, mixerWeakPointer); - break; - case PacketTypeAvatarBillboard: - processAvatarBillboardPacket(datagram, mixerWeakPointer); - break; - case PacketTypeKillAvatar: - processKillAvatar(datagram); - break; - default: - break; - } -} - AvatarSharedPointer AvatarManager::newSharedAvatar() { return AvatarSharedPointer(new Avatar()); } diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index fd15554666..048844ddf2 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -35,9 +35,6 @@ public: void renderAvatars(Avatar::RenderMode renderMode, bool selfAvatarOnly = false); void clearOtherAvatars(); - -public slots: - void processAvatarMixerDatagram(const QByteArray& datagram, const QWeakPointer& mixerWeakPointer); private: AvatarManager(const AvatarManager& other); diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index 17fdaeb5bd..01dba89b02 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -35,6 +35,25 @@ bool AvatarHashMap::shouldKillAvatar(const AvatarSharedPointer& sharedAvatar) { || sharedAvatar->getLastUpdateTimer().elapsed() > AVATAR_SILENCE_THRESHOLD_MSECS); } +void AvatarHashMap::processAvatarMixerDatagram(const QByteArray& datagram, const QWeakPointer& mixerWeakPointer) { + switch (packetTypeForPacket(datagram)) { + case PacketTypeBulkAvatarData: + processAvatarDataPacket(datagram, mixerWeakPointer); + break; + case PacketTypeAvatarIdentity: + processAvatarIdentityPacket(datagram, mixerWeakPointer); + break; + case PacketTypeAvatarBillboard: + processAvatarBillboardPacket(datagram, mixerWeakPointer); + break; + case PacketTypeKillAvatar: + processKillAvatar(datagram); + break; + default: + break; + } +} + bool AvatarHashMap::containsAvatarWithDisplayName(const QString& displayName) { AvatarHash::iterator avatarIterator = _avatarHash.begin(); @@ -70,7 +89,7 @@ AvatarSharedPointer AvatarHashMap::matchingOrNewAvatar(const QUuid& sessionUUID, // insert the new avatar into our hash matchingAvatar = newSharedAvatar(); - qDebug() << "Adding avatar with sessionUUID " << sessionUUID << "to AvatarManager hash."; + qDebug() << "Adding avatar with sessionUUID " << sessionUUID << "to AvatarHashMap."; _avatarHash.insert(sessionUUID, matchingAvatar); matchingAvatar->setOwningAvatarMixer(mixerWeakPointer); diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h index 98e64fb8de..542a2d62ab 100644 --- a/libraries/avatars/src/AvatarHashMap.h +++ b/libraries/avatars/src/AvatarHashMap.h @@ -34,6 +34,7 @@ public: virtual void insert(const QUuid& id, AvatarSharedPointer avatar); public slots: + void processAvatarMixerDatagram(const QByteArray& datagram, const QWeakPointer& mixerWeakPointer); bool containsAvatarWithDisplayName(const QString& displayName); protected: From 75f35c58586ecd54a379ce00a0e10441c2a3d094 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 17 Apr 2014 17:55:16 -0700 Subject: [PATCH 24/27] optionally add an AvatarHashMap instance to the ScriptEngine --- assignment-client/src/Agent.cpp | 1 + libraries/script-engine/src/ScriptEngine.cpp | 8 ++++++++ libraries/script-engine/src/ScriptEngine.h | 2 ++ 3 files changed, 11 insertions(+) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 7f4e811c32..6b8dbde79f 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -202,6 +202,7 @@ void Agent::run() { // give this AvatarData object to the script engine _scriptEngine.setAvatarData(&scriptedAvatar, "Avatar"); + _scriptEngine.setAvatarHashMap(&_avatarHashMap, "AvatarHash"); // register ourselves to the script engine _scriptEngine.registerGlobalObject("Agent", this); diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 950ba899de..684c55fbb0 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -156,6 +156,14 @@ void ScriptEngine::setAvatarData(AvatarData* avatarData, const QString& objectNa registerGlobalObject(objectName, _avatarData); } +void ScriptEngine::setAvatarHashMap(AvatarHashMap* avatarHashMap, const QString& objectName) { + // remove the old Avatar property, if it exists + _engine.globalObject().setProperty(objectName, QScriptValue()); + + // give the script engine the new avatar hash map + registerGlobalObject(objectName, avatarHashMap); +} + bool ScriptEngine::setScriptContents(const QString& scriptContents, const QString& fileNameString) { if (_isRunning) { return false; diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 73747cd3e1..941c6bbe27 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -22,6 +22,7 @@ #include #include +#include #include "AnimationCache.h" #include "AbstractControllerScriptingInterface.h" @@ -63,6 +64,7 @@ public: bool isAvatar() const { return _isAvatar; } void setAvatarData(AvatarData* avatarData, const QString& objectName); + void setAvatarHashMap(AvatarHashMap* avatarHashMap, const QString& objectName); bool isListeningToAudioStream() const { return _isListeningToAudioStream; } void setIsListeningToAudioStream(bool isListeningToAudioStream) { _isListeningToAudioStream = isListeningToAudioStream; } From 77202af20d4db593caa6894de343d4a1dfe602a3 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 17 Apr 2014 18:05:22 -0700 Subject: [PATCH 25/27] add debug for erase from AvatarHashMap, repair check for display name --- assignment-client/src/Agent.cpp | 2 +- interface/src/avatar/AvatarManager.cpp | 1 - libraries/avatars/src/AvatarHashMap.cpp | 3 +++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 6b8dbde79f..0d2103c748 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -132,7 +132,7 @@ void Agent::readPendingDatagrams() { // let this continue through to the NodeList so it updates last heard timestamp // for the sending audio mixer NodeList::getInstance()->processNodeData(senderSockAddr, receivedPacket); - } else if (datagramPacketType == PacketTypeAvatarData + } else if (datagramPacketType == PacketTypeBulkAvatarData || datagramPacketType == PacketTypeAvatarIdentity || datagramPacketType == PacketTypeAvatarBillboard || datagramPacketType == PacketTypeKillAvatar) { diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index f235d6f2e3..59f31388f8 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -135,7 +135,6 @@ AvatarSharedPointer AvatarManager::newSharedAvatar() { AvatarHash::iterator AvatarManager::erase(const AvatarHash::iterator& iterator) { if (iterator.key() != MY_AVATAR_KEY) { - qDebug() << "Removing Avatar with UUID" << iterator.key() << "from AvatarManager hash."; if (reinterpret_cast(iterator.value().data())->isInitialized()) { _avatarFades.push_back(iterator.value()); } diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index 01dba89b02..6b17a3fab8 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -25,6 +25,7 @@ void AvatarHashMap::insert(const QUuid& id, AvatarSharedPointer avatar) { } AvatarHash::iterator AvatarHashMap::erase(const AvatarHash::iterator& iterator) { + qDebug() << "Removing Avatar with UUID" << iterator.key() << "from AvatarHashMap."; return _avatarHash.erase(iterator); } @@ -71,6 +72,8 @@ bool AvatarHashMap::containsAvatarWithDisplayName(const QString& displayName) { } break; + } else { + ++avatarIterator; } } From 9713edbba90eaf3a5055ad55731da8c267185d93 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 18 Apr 2014 09:00:43 -0700 Subject: [PATCH 26/27] Update dancing_bot.js conceal URL from dancing_bot script (even though it will stay in history) --- examples/dancing_bot.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/dancing_bot.js b/examples/dancing_bot.js index 3572cc5a11..7924614d82 100644 --- a/examples/dancing_bot.js +++ b/examples/dancing_bot.js @@ -11,7 +11,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -var animation = AnimationCache.getAnimation("http://www.fungibleinsight.com/faces/gangnam_style_2.fbx"); +var animation = AnimationCache.getAnimation("FBX_URL"); Avatar.skeletonModelURL = "http://www.fungibleinsight.com/faces/beta.fst"; From 6adfb91e935ba472d301fb1c0ee79486a964151d Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 18 Apr 2014 09:40:35 -0700 Subject: [PATCH 27/27] rename AvatarHash to AvatarList --- assignment-client/src/Agent.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 0d2103c748..e39cb39307 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -202,7 +202,7 @@ void Agent::run() { // give this AvatarData object to the script engine _scriptEngine.setAvatarData(&scriptedAvatar, "Avatar"); - _scriptEngine.setAvatarHashMap(&_avatarHashMap, "AvatarHash"); + _scriptEngine.setAvatarHashMap(&_avatarHashMap, "AvatarList"); // register ourselves to the script engine _scriptEngine.registerGlobalObject("Agent", this);