From c5ffe54a9131328e737506176a04c405a50f6f1f Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Mon, 14 Apr 2014 16:26:30 -0700 Subject: [PATCH 01/48] 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/48] 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/48] 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/48] 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 8120700787b354ed4bfba0993c6d4ca988a8d9e6 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Thu, 17 Apr 2014 00:03:11 +0200 Subject: [PATCH 05/48] Added urlGoTo --- interface/src/Application.cpp | 39 +++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 1fabca3711..a7a00bf7c7 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -356,6 +356,8 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : QMutexLocker locker(&_settingsMutex); _previousScriptLocation = _settings->value("LastScriptLocation", QVariant("")).toString(); } + //When -url in command line, teleport to location + urlGoTo(argc, constArgv); } Application::~Application() { @@ -3548,3 +3550,40 @@ void Application::takeSnapshot() { Snapshot::saveSnapshot(_glWidget, _myAvatar); } + +void Application::urlGoTo(int argc, const char * constArgv[]) { + //Gets the url (hifi://domain/destination/orientation) + QString customUrl = getCmdOption(argc, constArgv, "-url"); + + if (customUrl.startsWith("hifi://")) { + QStringList urlParts = customUrl.remove(0, CUSTOM_URL_SCHEME.length() + 2).split('/', QString::SkipEmptyParts); + + if (urlParts.count() > 1) { + // if url has 2 or more parts, the first one is domain name + QString domain = urlParts[0]; + + // second part is either a destination coordinate or + // a place name + QString destination = urlParts[1]; + + // any third part is an avatar orientation. + QString orientation = urlParts.count() > 2 ? urlParts[2] : QString(); + + Menu::goToDomain(domain); + + // goto either @user, #place, or x-xx,y-yy,z-zz + // style co-ordinate. + Menu::goTo(destination); + + if (!orientation.isEmpty()) { + // location orientation + Menu::goToOrientation(orientation); + } + } else if (urlParts.count() == 1) { + // location coordinates or place name + QString destination = urlParts[0]; + Menu::goTo(destination); + } + + } +} From d55fb60862a1fdc237a20db3d2a942fba50e6be4 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Thu, 17 Apr 2014 00:03:33 +0200 Subject: [PATCH 06/48] Added urlGoTo --- interface/src/Application.h | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/src/Application.h b/interface/src/Application.h index 6a14788caa..de5f1fe113 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -509,6 +509,7 @@ private: void displayUpdateDialog(); bool shouldSkipVersion(QString latestVersion); void takeSnapshot(); + void urlGoTo(int argc, const char * constArgv[]); TouchEvent _lastTouchEvent; From b3ce36f1309140f9cf4e84c113bff670d0455b39 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Thu, 17 Apr 2014 00:04:27 +0200 Subject: [PATCH 07/48] Changed goTo's to statics --- interface/src/Menu.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 40ed8efdc7..c2aac5672b 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -135,10 +135,10 @@ public: void removeAction(QMenu* menu, const QString& actionName); - bool goToDestination(QString destination); - void goToOrientation(QString orientation); - void goToDomain(const QString newDomain); - void goTo(QString destination); + bool static goToDestination(QString destination); + void static goToOrientation(QString orientation); + void static goToDomain(const QString newDomain); + void static goTo(QString destination); public slots: From fca4e2abba27f4676fe7bdacbb6c1531763444c8 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Thu, 17 Apr 2014 00:09:58 +0200 Subject: [PATCH 08/48] Update Application.h --- interface/src/Application.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Application.h b/interface/src/Application.h index de5f1fe113..a09483a1ba 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -128,6 +128,7 @@ public: void initializeGL(); void paintGL(); void resizeGL(int width, int height); + void urlGoTo(int argc, const char * constArgv[]); void keyPressEvent(QKeyEvent* event); void keyReleaseEvent(QKeyEvent* event); @@ -509,7 +510,6 @@ private: void displayUpdateDialog(); bool shouldSkipVersion(QString latestVersion); void takeSnapshot(); - void urlGoTo(int argc, const char * constArgv[]); TouchEvent _lastTouchEvent; From 0f69bbe23fd5f80cdb7da7db2aec7713b203d7df Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Wed, 16 Apr 2014 20:39:55 -0700 Subject: [PATCH 09/48] 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 10/48] 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 03c3952e525e87cf3793a2734b25add87924e4a9 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 17 Apr 2014 10:41:46 -0700 Subject: [PATCH 11/48] 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/48] 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/48] 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/48] 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 a12b90c65becd64ca9d78cf75ac123b6c49abf5b Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 17 Apr 2014 13:34:37 -0700 Subject: [PATCH 15/48] Add head translation to Faceplus. Closes #2684. --- interface/src/Application.cpp | 1 + interface/src/devices/Faceplus.cpp | 44 ++++++++++++++++++++++++------ interface/src/devices/Faceplus.h | 14 +++++++--- 3 files changed, 47 insertions(+), 12 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index bd7a82b439..978e457ae9 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3020,6 +3020,7 @@ void Application::resetSensors() { _mouseX = _glWidget->width() / 2; _mouseY = _glWidget->height() / 2; + _faceplus.reset(); _faceshift.reset(); _visage.reset(); diff --git a/interface/src/devices/Faceplus.cpp b/interface/src/devices/Faceplus.cpp index f7f2f1f1bd..f06433b663 100644 --- a/interface/src/devices/Faceplus.cpp +++ b/interface/src/devices/Faceplus.cpp @@ -40,9 +40,16 @@ void Faceplus::init() { updateEnabled(); } -void Faceplus::setState(const glm::quat& headRotation, float estimatedEyePitch, float estimatedEyeYaw, - const QVector& blendshapeCoefficients) { - _headRotation = headRotation; +void Faceplus::reset() { + if (_enabled) { + QMetaObject::invokeMethod(_reader, "reset"); + } +} + +void Faceplus::setState(const glm::vec3& headTranslation, const glm::quat& headRotation, + float estimatedEyePitch, float estimatedEyeYaw, const QVector& blendshapeCoefficients) { + _headTranslation = headTranslation; + _headRotation = headRotation; _estimatedEyePitch = estimatedEyePitch; _estimatedEyeYaw = estimatedEyeYaw; _blendshapeCoefficients = blendshapeCoefficients; @@ -149,7 +156,7 @@ FaceplusReader::~FaceplusReader() { void FaceplusReader::init() { #ifdef HAVE_FACEPLUS - if (!faceplus_init("VGA")) { + if (!faceplus_init("hHD")) { qDebug() << "Failed to initialized Faceplus."; return; } @@ -190,7 +197,8 @@ void FaceplusReader::init() { } } _blendshapeCoefficients.resize(maxIndex + 1); - + _referenceInitialized = false; + QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection); #endif } @@ -202,10 +210,24 @@ void FaceplusReader::shutdown() { void FaceplusReader::update() { #ifdef HAVE_FACEPLUS - if (!(faceplus_synchronous_track() && faceplus_current_output_vector(_outputVector.data()))) { + float x, y, rotation, scale; + if (!(faceplus_synchronous_track() && faceplus_current_face_location(&x, &y, &rotation, &scale) && !glm::isnan(x) && + faceplus_current_output_vector(_outputVector.data()))) { QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection); return; } + if (!_referenceInitialized) { + _referenceX = x; + _referenceY = y; + _referenceScale = scale; + _referenceInitialized = true; + } + const float TRANSLATION_SCALE = 10.0f; + const float REFERENCE_DISTANCE = 10.0f; + float depthScale = _referenceScale / scale; + float z = REFERENCE_DISTANCE * (depthScale - 1.0f); + glm::vec3 headTranslation((x - _referenceX) * depthScale * TRANSLATION_SCALE, + (y - _referenceY) * depthScale * TRANSLATION_SCALE, z); glm::quat headRotation(glm::radians(glm::vec3(-_outputVector.at(_headRotationIndices[0]), _outputVector.at(_headRotationIndices[1]), -_outputVector.at(_headRotationIndices[2])))); float estimatedEyePitch = (_outputVector.at(_leftEyeRotationIndices[0]) + @@ -221,10 +243,16 @@ void FaceplusReader::update() { } } - QMetaObject::invokeMethod(Application::getInstance()->getFaceplus(), "setState", Q_ARG(const glm::quat&, headRotation), - Q_ARG(float, estimatedEyePitch), Q_ARG(float, estimatedEyeYaw), Q_ARG(const QVector&, _blendshapeCoefficients)); + QMetaObject::invokeMethod(Application::getInstance()->getFaceplus(), "setState", Q_ARG(const glm::vec3&, headTranslation), + Q_ARG(const glm::quat&, headRotation), Q_ARG(float, estimatedEyePitch), Q_ARG(float, estimatedEyeYaw), + Q_ARG(const QVector&, _blendshapeCoefficients)); QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection); #endif } +void FaceplusReader::reset() { +#ifdef HAVE_FACEPLUS + _referenceInitialized = false; +#endif +} diff --git a/interface/src/devices/Faceplus.h b/interface/src/devices/Faceplus.h index 2b9219f3fd..f819dadca3 100644 --- a/interface/src/devices/Faceplus.h +++ b/interface/src/devices/Faceplus.h @@ -30,11 +30,12 @@ public: virtual ~Faceplus(); void init(); - + void reset(); + bool isActive() const { return _active; } - Q_INVOKABLE void setState(const glm::quat& headRotation, float estimatedEyePitch, float estimatedEyeYaw, - const QVector& blendshapeCoefficients); + Q_INVOKABLE void setState(const glm::vec3& headTranslation, const glm::quat& headRotation, + float estimatedEyePitch, float estimatedEyeYaw, const QVector& blendshapeCoefficients); public slots: @@ -63,6 +64,7 @@ public: Q_INVOKABLE void init(); Q_INVOKABLE void shutdown(); Q_INVOKABLE void update(); + Q_INVOKABLE void reset(); private: @@ -72,7 +74,11 @@ private: int _headRotationIndices[3]; int _leftEyeRotationIndices[2]; int _rightEyeRotationIndices[2]; - QVector _blendshapeCoefficients; + float _referenceX; + float _referenceY; + float _referenceScale; + bool _referenceInitialized; + QVector _blendshapeCoefficients; #endif }; From 0a2670ea23b60de1749d94f7715a84c4867a70a5 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 17 Apr 2014 13:42:29 -0700 Subject: [PATCH 16/48] Detabbed. --- interface/src/Application.cpp | 2 +- interface/src/devices/Faceplus.cpp | 44 +++++++++++++++--------------- interface/src/devices/Faceplus.h | 12 ++++---- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 978e457ae9..119fd72f17 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3020,7 +3020,7 @@ void Application::resetSensors() { _mouseX = _glWidget->width() / 2; _mouseY = _glWidget->height() / 2; - _faceplus.reset(); + _faceplus.reset(); _faceshift.reset(); _visage.reset(); diff --git a/interface/src/devices/Faceplus.cpp b/interface/src/devices/Faceplus.cpp index f06433b663..a5601932b6 100644 --- a/interface/src/devices/Faceplus.cpp +++ b/interface/src/devices/Faceplus.cpp @@ -41,15 +41,15 @@ void Faceplus::init() { } void Faceplus::reset() { - if (_enabled) { - QMetaObject::invokeMethod(_reader, "reset"); - } + if (_enabled) { + QMetaObject::invokeMethod(_reader, "reset"); + } } void Faceplus::setState(const glm::vec3& headTranslation, const glm::quat& headRotation, - float estimatedEyePitch, float estimatedEyeYaw, const QVector& blendshapeCoefficients) { + float estimatedEyePitch, float estimatedEyeYaw, const QVector& blendshapeCoefficients) { _headTranslation = headTranslation; - _headRotation = headRotation; + _headRotation = headRotation; _estimatedEyePitch = estimatedEyePitch; _estimatedEyeYaw = estimatedEyeYaw; _blendshapeCoefficients = blendshapeCoefficients; @@ -211,23 +211,23 @@ void FaceplusReader::shutdown() { void FaceplusReader::update() { #ifdef HAVE_FACEPLUS float x, y, rotation, scale; - if (!(faceplus_synchronous_track() && faceplus_current_face_location(&x, &y, &rotation, &scale) && !glm::isnan(x) && - faceplus_current_output_vector(_outputVector.data()))) { + if (!(faceplus_synchronous_track() && faceplus_current_face_location(&x, &y, &rotation, &scale) && !glm::isnan(x) && + faceplus_current_output_vector(_outputVector.data()))) { QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection); return; } - if (!_referenceInitialized) { - _referenceX = x; - _referenceY = y; - _referenceScale = scale; - _referenceInitialized = true; - } - const float TRANSLATION_SCALE = 10.0f; - const float REFERENCE_DISTANCE = 10.0f; - float depthScale = _referenceScale / scale; - float z = REFERENCE_DISTANCE * (depthScale - 1.0f); - glm::vec3 headTranslation((x - _referenceX) * depthScale * TRANSLATION_SCALE, - (y - _referenceY) * depthScale * TRANSLATION_SCALE, z); + if (!_referenceInitialized) { + _referenceX = x; + _referenceY = y; + _referenceScale = scale; + _referenceInitialized = true; + } + const float TRANSLATION_SCALE = 10.0f; + const float REFERENCE_DISTANCE = 10.0f; + float depthScale = _referenceScale / scale; + float z = REFERENCE_DISTANCE * (depthScale - 1.0f); + glm::vec3 headTranslation((x - _referenceX) * depthScale * TRANSLATION_SCALE, + (y - _referenceY) * depthScale * TRANSLATION_SCALE, z); glm::quat headRotation(glm::radians(glm::vec3(-_outputVector.at(_headRotationIndices[0]), _outputVector.at(_headRotationIndices[1]), -_outputVector.at(_headRotationIndices[2])))); float estimatedEyePitch = (_outputVector.at(_leftEyeRotationIndices[0]) + @@ -244,8 +244,8 @@ void FaceplusReader::update() { } QMetaObject::invokeMethod(Application::getInstance()->getFaceplus(), "setState", Q_ARG(const glm::vec3&, headTranslation), - Q_ARG(const glm::quat&, headRotation), Q_ARG(float, estimatedEyePitch), Q_ARG(float, estimatedEyeYaw), - Q_ARG(const QVector&, _blendshapeCoefficients)); + Q_ARG(const glm::quat&, headRotation), Q_ARG(float, estimatedEyePitch), Q_ARG(float, estimatedEyeYaw), + Q_ARG(const QVector&, _blendshapeCoefficients)); QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection); #endif @@ -253,6 +253,6 @@ void FaceplusReader::update() { void FaceplusReader::reset() { #ifdef HAVE_FACEPLUS - _referenceInitialized = false; + _referenceInitialized = false; #endif } diff --git a/interface/src/devices/Faceplus.h b/interface/src/devices/Faceplus.h index f819dadca3..f3c680c2d6 100644 --- a/interface/src/devices/Faceplus.h +++ b/interface/src/devices/Faceplus.h @@ -35,7 +35,7 @@ public: bool isActive() const { return _active; } Q_INVOKABLE void setState(const glm::vec3& headTranslation, const glm::quat& headRotation, - float estimatedEyePitch, float estimatedEyeYaw, const QVector& blendshapeCoefficients); + float estimatedEyePitch, float estimatedEyeYaw, const QVector& blendshapeCoefficients); public slots: @@ -64,7 +64,7 @@ public: Q_INVOKABLE void init(); Q_INVOKABLE void shutdown(); Q_INVOKABLE void update(); - Q_INVOKABLE void reset(); + Q_INVOKABLE void reset(); private: @@ -75,10 +75,10 @@ private: int _leftEyeRotationIndices[2]; int _rightEyeRotationIndices[2]; float _referenceX; - float _referenceY; - float _referenceScale; - bool _referenceInitialized; - QVector _blendshapeCoefficients; + float _referenceY; + float _referenceScale; + bool _referenceInitialized; + QVector _blendshapeCoefficients; #endif }; From 6251b6b81939d4fbb32391cb39fae64a3701d79e Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 17 Apr 2014 13:55:16 -0700 Subject: [PATCH 17/48] 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 3acc518e8a3f8dcbed27f699a7471f75fa373232 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 17 Apr 2014 15:18:15 -0700 Subject: [PATCH 18/48] make float literals obey the coding standard --- interface/src/Camera.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/src/Camera.cpp b/interface/src/Camera.cpp index 2ef32dac5f..f9ee5bdd25 100644 --- a/interface/src/Camera.cpp +++ b/interface/src/Camera.cpp @@ -45,7 +45,7 @@ Camera::Camera() : _idealPosition(0.0f, 0.0f, 0.0f), _targetPosition(0.0f, 0.0f, 0.0f), _fieldOfView(DEFAULT_FIELD_OF_VIEW_DEGREES), - _aspectRatio(16.f/9.f), + _aspectRatio(16.0f/9.0f), _nearClip(0.08f), // default _farClip(50.0f * TREE_SCALE), // default _upShift(0.0f), @@ -94,8 +94,8 @@ void Camera::updateFollowMode(float deltaTime) { // derive t from tightness float t = _tightness * _modeShift * deltaTime; - if (t > 1.0) { - t = 1.0; + if (t > 1.0f) { + t = 1.0f; } // handle keepLookingAt From 3072161a303e43b69847ececadb97623304e0b9c Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 17 Apr 2014 15:18:46 -0700 Subject: [PATCH 19/48] improved collision shapes of models --- interface/src/renderer/FBXReader.cpp | 21 ++++++++++++++++++--- interface/src/renderer/Model.cpp | 26 ++++++++++++++++++++------ 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/interface/src/renderer/FBXReader.cpp b/interface/src/renderer/FBXReader.cpp index 959c015eea..32b5686558 100644 --- a/interface/src/renderer/FBXReader.cpp +++ b/interface/src/renderer/FBXReader.cpp @@ -1516,7 +1516,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) } float radiusScale = extractUniformScale(joint.transform * fbxCluster.inverseBindMatrix); JointShapeInfo& jointShapeInfo = jointShapeInfos[jointIndex]; - jointShapeInfo.boneBegin = rotateMeshToJoint * (radiusScale * (boneBegin - boneEnd)); float totalWeight = 0.0f; for (int j = 0; j < cluster.indices.size(); j++) { @@ -1578,7 +1577,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) } } float radiusScale = extractUniformScale(joint.transform * firstFBXCluster.inverseBindMatrix); - jointShapeInfo.boneBegin = rotateMeshToJoint * (radiusScale * (boneBegin - boneEnd)); glm::vec3 averageVertex(0.f); foreach (const glm::vec3& vertex, extracted.mesh.vertices) { @@ -1614,6 +1612,14 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) FBXJoint& joint = geometry.joints[i]; JointShapeInfo& jointShapeInfo = jointShapeInfos[i]; + if (joint.parentIndex == -1) { + jointShapeInfo.boneBegin = glm::vec3(0.0f); + } else { + const FBXJoint& parentJoint = geometry.joints[joint.parentIndex]; + glm::quat inverseRotation = glm::inverse(extractRotation(joint.transform)); + jointShapeInfo.boneBegin = inverseRotation * (extractTranslation(parentJoint.transform) - extractTranslation(joint.transform)); + } + // we use a capsule if the joint ANY mesh vertices successfully projected onto the bone // AND its boneRadius is not too close to zero bool collideLikeCapsule = jointShapeInfo.numProjectedVertices > 0 @@ -1625,12 +1631,12 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) joint.shapeType = Shape::CAPSULE_SHAPE; } else { // collide the joint like a sphere + joint.shapeType = Shape::SPHERE_SHAPE; if (jointShapeInfo.numVertices > 0) { jointShapeInfo.averageVertex /= (float)jointShapeInfo.numVertices; joint.shapePosition = jointShapeInfo.averageVertex; } else { joint.shapePosition = glm::vec3(0.f); - joint.shapeType = Shape::SPHERE_SHAPE; } if (jointShapeInfo.numProjectedVertices == 0 && jointShapeInfo.numVertices > 0) { @@ -1639,6 +1645,15 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) jointShapeInfo.averageRadius /= (float)jointShapeInfo.numVertices; joint.boneRadius = jointShapeInfo.averageRadius; } + + float distanceFromEnd = glm::length(joint.shapePosition); + float distanceFromBegin = glm::distance(joint.shapePosition, jointShapeInfo.boneBegin); + if (distanceFromEnd > joint.distanceToParent && distanceFromBegin > joint.distanceToParent) { + // The shape is further from both joint endpoints than the endpoints are from each other + // which probably means the model has a bad transform somewhere. We disable this shape + // by setting its radius to zero. + joint.boneRadius = 0.0f; + } } } geometry.palmDirection = parseVec3(mapping.value("palmDirection", "0, -1, 0").toString()); diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 2d47a077b7..bcde861e84 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -510,24 +510,38 @@ void Model::rebuildShapes() { capsule->getEndPoint(endPoint); glm::vec3 startPoint; capsule->getStartPoint(startPoint); - glm::vec3 axis = (halfHeight + radius) * glm::normalize(endPoint - startPoint); + + // add some points that bound a sphere at the center of the capsule + glm::vec3 axis = glm::vec3(radius); shapeExtents.addPoint(worldPosition + axis); shapeExtents.addPoint(worldPosition - axis); + + // add the two furthest surface points of the capsule + axis = (halfHeight + radius) * glm::normalize(endPoint - startPoint); + shapeExtents.addPoint(worldPosition + axis); + shapeExtents.addPoint(worldPosition - axis); + + + totalExtents.addExtents(shapeExtents); } else { SphereShape* sphere = new SphereShape(radius, worldPosition); _jointShapes.push_back(sphere); - glm::vec3 axis = glm::vec3(radius); - shapeExtents.addPoint(worldPosition + axis); - shapeExtents.addPoint(worldPosition - axis); + if (radius > 0.0f) { + // only include sphere shapes with non-zero radius + glm::vec3 axis = glm::vec3(radius); + shapeExtents.addPoint(worldPosition + axis); + shapeExtents.addPoint(worldPosition - axis); + totalExtents.addExtents(shapeExtents); + } } - totalExtents.addExtents(shapeExtents); } // bounding shape // NOTE: we assume that the longest side of totalExtents is the yAxis glm::vec3 diagonal = totalExtents.maximum - totalExtents.minimum; - float capsuleRadius = 0.25f * (diagonal.x + diagonal.z); // half the average of x and z + // the radius is half the RMS of the X and Z sides: + float capsuleRadius = 0.5f * sqrtf(0.5f * (diagonal.x * diagonal.x + diagonal.z * diagonal.z)); _boundingShape.setRadius(capsuleRadius); _boundingShape.setHalfHeight(0.5f * diagonal.y - capsuleRadius); _boundingShapeLocalOffset = inverseRotation * (0.5f * (totalExtents.maximum + totalExtents.minimum) - rootPosition); From 197127fbde4d454707d45f9505e3d5726123a4ee Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 17 Apr 2014 15:42:23 -0700 Subject: [PATCH 20/48] 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 21/48] 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 6d1f4ed9420f2c238a2afb9834e189d0651be913 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Fri, 18 Apr 2014 01:13:06 +0200 Subject: [PATCH 22/48] Fix format --- interface/src/Application.cpp | 47 +++++++++++++++++------------------ 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index a7a00bf7c7..592370705a 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3555,35 +3555,34 @@ void Application::urlGoTo(int argc, const char * constArgv[]) { //Gets the url (hifi://domain/destination/orientation) QString customUrl = getCmdOption(argc, constArgv, "-url"); - if (customUrl.startsWith("hifi://")) { - QStringList urlParts = customUrl.remove(0, CUSTOM_URL_SCHEME.length() + 2).split('/', QString::SkipEmptyParts); + if (customUrl.startsWith("hifi://")) { + QStringList urlParts = customUrl.remove(0, CUSTOM_URL_SCHEME.length() + 2).split('/', QString::SkipEmptyParts); + if (urlParts.count() > 1) { + // if url has 2 or more parts, the first one is domain name + QString domain = urlParts[0]; - if (urlParts.count() > 1) { - // if url has 2 or more parts, the first one is domain name - QString domain = urlParts[0]; + // second part is either a destination coordinate or + // a place name + QString destination = urlParts[1]; - // second part is either a destination coordinate or - // a place name - QString destination = urlParts[1]; + // any third part is an avatar orientation. + QString orientation = urlParts.count() > 2 ? urlParts[2] : QString(); - // any third part is an avatar orientation. - QString orientation = urlParts.count() > 2 ? urlParts[2] : QString(); - - Menu::goToDomain(domain); + Menu::goToDomain(domain); - // goto either @user, #place, or x-xx,y-yy,z-zz - // style co-ordinate. - Menu::goTo(destination); + // goto either @user, #place, or x-xx,y-yy,z-zz + // style co-ordinate. + Menu::goTo(destination); - if (!orientation.isEmpty()) { - // location orientation - Menu::goToOrientation(orientation); - } - } else if (urlParts.count() == 1) { - // location coordinates or place name - QString destination = urlParts[0]; - Menu::goTo(destination); + if (!orientation.isEmpty()) { + // location orientation + Menu::goToOrientation(orientation); } - + } else if (urlParts.count() == 1) { + // location coordinates or place name + QString destination = urlParts[0]; + Menu::goTo(destination); } + + } } From cfdbdad2d8823dcd7d4591502049b1a861ce4e2f Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 17 Apr 2014 16:22:35 -0700 Subject: [PATCH 23/48] improved method for disabling bad joint shapes --- interface/src/renderer/FBXReader.cpp | 4 ++-- interface/src/renderer/FBXReader.h | 4 +++- interface/src/renderer/Model.cpp | 28 +++++++++++++++++++--------- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/interface/src/renderer/FBXReader.cpp b/interface/src/renderer/FBXReader.cpp index 32b5686558..ff3fa8667f 100644 --- a/interface/src/renderer/FBXReader.cpp +++ b/interface/src/renderer/FBXReader.cpp @@ -1651,8 +1651,8 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) if (distanceFromEnd > joint.distanceToParent && distanceFromBegin > joint.distanceToParent) { // The shape is further from both joint endpoints than the endpoints are from each other // which probably means the model has a bad transform somewhere. We disable this shape - // by setting its radius to zero. - joint.boneRadius = 0.0f; + // by setting its type to UNKNOWN_SHAPE. + joint.shapeType = Shape::UNKNOWN_SHAPE; } } } diff --git a/interface/src/renderer/FBXReader.h b/interface/src/renderer/FBXReader.h index 73c305e2eb..366ab12180 100644 --- a/interface/src/renderer/FBXReader.h +++ b/interface/src/renderer/FBXReader.h @@ -18,6 +18,8 @@ #include #include +#include + #include #include @@ -91,7 +93,7 @@ public: QString name; glm::vec3 shapePosition; // in joint frame glm::quat shapeRotation; // in joint frame - int shapeType; + Shape::Type shapeType; }; diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index bcde861e84..ca0b46841d 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -500,7 +500,12 @@ void Model::rebuildShapes() { float radius = uniformScale * joint.boneRadius; float halfHeight = 0.5f * uniformScale * joint.distanceToParent; - if (joint.shapeType == Shape::CAPSULE_SHAPE && halfHeight > EPSILON) { + Shape::Type type = joint.shapeType; + if (type == Shape::CAPSULE_SHAPE && halfHeight < EPSILON) { + // this capsule is effectively a sphere + type = Shape::SPHERE_SHAPE; + } + if (type == Shape::CAPSULE_SHAPE) { CapsuleShape* capsule = new CapsuleShape(radius, halfHeight); capsule->setPosition(worldPosition); capsule->setRotation(_jointStates[i].combinedRotation * joint.shapeRotation); @@ -523,18 +528,23 @@ void Model::rebuildShapes() { totalExtents.addExtents(shapeExtents); - } else { + } else if (type == Shape::SPHERE_SHAPE) { SphereShape* sphere = new SphereShape(radius, worldPosition); _jointShapes.push_back(sphere); - if (radius > 0.0f) { - // only include sphere shapes with non-zero radius - glm::vec3 axis = glm::vec3(radius); - shapeExtents.addPoint(worldPosition + axis); - shapeExtents.addPoint(worldPosition - axis); - totalExtents.addExtents(shapeExtents); - } + glm::vec3 axis = glm::vec3(radius); + shapeExtents.addPoint(worldPosition + axis); + shapeExtents.addPoint(worldPosition - axis); + totalExtents.addExtents(shapeExtents); + } else { + // this shape type is not handled and the joint shouldn't collide, + // however we must have a shape for each joint, + // so we make a bogus sphere and put it at the center of the model + // TODO: implement collision groups for more control over what collides with what + SphereShape* sphere = new SphereShape(0.f, _offset); + _jointShapes.push_back(sphere); } + } // bounding shape From e303ce1a12ca7a6c51010c809101baacaf0085dd Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 17 Apr 2014 16:37:40 -0700 Subject: [PATCH 24/48] 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 25/48] 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 26/48] 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 828bedc3e80088777000bf73af7c89fa10c652bb Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 17 Apr 2014 17:07:02 -0700 Subject: [PATCH 27/48] Slight fix for dancing bot script. --- 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..2c0a35adb7 100644 --- a/examples/dancing_bot.js +++ b/examples/dancing_bot.js @@ -27,7 +27,7 @@ Script.update.connect(function(deltaTime) { if (!jointMapping) { var avatarJointNames = Avatar.jointNames; var animationJointNames = animation.jointNames; - if (avatarJointNames === 0 || animationJointNames.length === 0) { + if (avatarJointNames.length === 0 || animationJointNames.length === 0) { return; } jointMapping = new Array(avatarJointNames.length); From e75d14139f0062dae884b92ce9845a7b4cea3e7c Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 17 Apr 2014 17:21:38 -0700 Subject: [PATCH 28/48] 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 29/48] 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 30/48] 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 31/48] 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 32/48] 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 fa787adce2f26c7f4257508cb03fc45b890d84db Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 18 Apr 2014 08:55:32 -0700 Subject: [PATCH 33/48] added wet/dry mix, and alternate distance attenuation features --- examples/audioReflectorTools.js | 140 ++++++++++++++++++++++++++++-- interface/src/Application.cpp | 4 + interface/src/Audio.cpp | 1 + interface/src/Audio.h | 1 + interface/src/AudioReflector.cpp | 142 ++++++++++++++++++++++++++----- interface/src/AudioReflector.h | 47 ++++++++-- interface/src/Menu.cpp | 8 ++ interface/src/Menu.h | 4 + interface/src/ui/Stats.cpp | 19 ++++- 9 files changed, 327 insertions(+), 39 deletions(-) diff --git a/examples/audioReflectorTools.js b/examples/audioReflectorTools.js index 3cc6a1a21e..76869de578 100644 --- a/examples/audioReflectorTools.js +++ b/examples/audioReflectorTools.js @@ -19,6 +19,8 @@ var reflectiveScale = 100.0; var diffusionScale = 100.0; var absorptionScale = 100.0; var combFilterScale = 50.0; +var originalScale = 2.0; +var echoesScale = 2.0; // these three properties are bound together, if you change one, the others will also change var reflectiveRatio = AudioReflector.getReflectiveRatio(); @@ -421,6 +423,84 @@ var absorptionThumb = Overlays.addOverlay("image", { alpha: 1 }); +var originalY = topY; +topY += sliderHeight; + +var originalLabel = Overlays.addOverlay("text", { + x: 40, + y: originalY, + width: 60, + height: sliderHeight, + color: { red: 0, green: 0, blue: 0}, + textColor: { red: 255, green: 255, blue: 255}, + topMargin: 6, + leftMargin: 5, + text: "Original\nMix:" + }); + + +var originalSlider = Overlays.addOverlay("image", { + // alternate form of expressing bounds + bounds: { x: 100, y: originalY, width: 150, height: sliderHeight}, + subImage: { x: 46, y: 0, width: 200, height: 71 }, + imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/slider.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1 + }); + + +var originalMinThumbX = 110; +var originalMaxThumbX = originalMinThumbX + 110; +var originalThumbX = originalMinThumbX + ((originalMaxThumbX - originalMinThumbX) * (AudioReflector.getOriginalSourceAttenuation() / originalScale)); +var originalThumb = Overlays.addOverlay("image", { + x: originalThumbX, + y: originalY+9, + width: 18, + height: 17, + imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/thumb.png", + color: { red: 128, green: 128, blue: 0}, + alpha: 1 + }); + +var echoesY = topY; +topY += sliderHeight; + +var echoesLabel = Overlays.addOverlay("text", { + x: 40, + y: echoesY, + width: 60, + height: sliderHeight, + color: { red: 0, green: 0, blue: 0}, + textColor: { red: 255, green: 255, blue: 255}, + topMargin: 6, + leftMargin: 5, + text: "Echoes\nMix:" + }); + + +var echoesSlider = Overlays.addOverlay("image", { + // alternate form of expressing bounds + bounds: { x: 100, y: echoesY, width: 150, height: sliderHeight}, + subImage: { x: 46, y: 0, width: 200, height: 71 }, + imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/slider.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1 + }); + + +var echoesMinThumbX = 110; +var echoesMaxThumbX = echoesMinThumbX + 110; +var echoesThumbX = echoesMinThumbX + ((echoesMaxThumbX - echoesMinThumbX) * (AudioReflector.getEchoesAttenuation() / echoesScale)); +var echoesThumb = Overlays.addOverlay("image", { + x: echoesThumbX, + y: echoesY+9, + width: 18, + height: 17, + imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/thumb.png", + color: { red: 128, green: 128, blue: 0}, + alpha: 1 + }); + // When our script shuts down, we should clean up all of our overlays function scriptEnding() { @@ -460,6 +540,14 @@ function scriptEnding() { Overlays.deleteOverlay(absorptionThumb); Overlays.deleteOverlay(absorptionSlider); + Overlays.deleteOverlay(echoesLabel); + Overlays.deleteOverlay(echoesThumb); + Overlays.deleteOverlay(echoesSlider); + + Overlays.deleteOverlay(originalLabel); + Overlays.deleteOverlay(originalThumb); + Overlays.deleteOverlay(originalSlider); + } Script.scriptEnding.connect(scriptEnding); @@ -483,6 +571,8 @@ var movingSliderLocalFactor = false; var movingSliderReflective = false; var movingSliderDiffusion = false; var movingSliderAbsorption = false; +var movingSliderOriginal = false; +var movingSliderEchoes = false; var thumbClickOffsetX = 0; function mouseMoveEvent(event) { @@ -546,7 +636,6 @@ function mouseMoveEvent(event) { var combFilter = ((newThumbX - combFilterMinThumbX) / (combFilterMaxThumbX - combFilterMinThumbX)) * combFilterScale; AudioReflector.setCombFilterWindow(combFilter); } - if (movingSliderLocalFactor) { newThumbX = event.x - thumbClickOffsetX; if (newThumbX < localFactorMinThumbX) { @@ -598,6 +687,30 @@ function mouseMoveEvent(event) { var diffusion = ((newThumbX - diffusionMinThumbX) / (diffusionMaxThumbX - diffusionMinThumbX)) * diffusionScale; setDiffusionRatio(diffusion); } + if (movingSliderEchoes) { + newThumbX = event.x - thumbClickOffsetX; + if (newThumbX < echoesMinThumbX) { + newThumbX = echoesMminThumbX; + } + if (newThumbX > echoesMaxThumbX) { + newThumbX = echoesMaxThumbX; + } + Overlays.editOverlay(echoesThumb, { x: newThumbX } ); + var echoes = ((newThumbX - echoesMinThumbX) / (echoesMaxThumbX - echoesMinThumbX)) * echoesScale; + AudioReflector.setEchoesAttenuation(echoes); + } + if (movingSliderOriginal) { + newThumbX = event.x - thumbClickOffsetX; + if (newThumbX < originalMinThumbX) { + newThumbX = originalMminThumbX; + } + if (newThumbX > originalMaxThumbX) { + newThumbX = originalMaxThumbX; + } + Overlays.editOverlay(originalThumb, { x: newThumbX } ); + var original = ((newThumbX - originalMinThumbX) / (originalMaxThumbX - originalMinThumbX)) * originalScale; + AudioReflector.setOriginalSourceAttenuation(original); + } } @@ -640,7 +753,16 @@ function mousePressEvent(event) { movingSliderReflective = true; thumbClickOffsetX = event.x - reflectiveThumbX; } + if (clickedOverlay == originalThumb) { + movingSliderOriginal = true; + thumbClickOffsetX = event.x - originalThumbX; + } + if (clickedOverlay == echoesThumb) { + movingSliderEchoes = true; + thumbClickOffsetX = event.x - echoesThumbX; + } } + function mouseReleaseEvent(event) { if (movingSliderDelay) { movingSliderDelay = false; @@ -672,14 +794,12 @@ function mouseReleaseEvent(event) { AudioReflector.setCombFilterWindow(combFilter); combFilterThumbX = newThumbX; } - if (movingSliderLocalFactor) { movingSliderLocalFactor = false; var localFactor = ((newThumbX - localFactorMinThumbX) / (localFactorMaxThumbX - localFactorMinThumbX)) * localFactorScale; AudioReflector.setLocalAudioAttenuationFactor(localFactor); localFactorThumbX = newThumbX; } - if (movingSliderReflective) { movingSliderReflective = false; var reflective = ((newThumbX - reflectiveMinThumbX) / (reflectiveMaxThumbX - reflectiveMinThumbX)) * reflectiveScale; @@ -687,7 +807,6 @@ function mouseReleaseEvent(event) { reflectiveThumbX = newThumbX; updateRatioSliders(); } - if (movingSliderDiffusion) { movingSliderDiffusion = false; var diffusion = ((newThumbX - diffusionMinThumbX) / (diffusionMaxThumbX - diffusionMinThumbX)) * diffusionScale; @@ -695,7 +814,6 @@ function mouseReleaseEvent(event) { diffusionThumbX = newThumbX; updateRatioSliders(); } - if (movingSliderAbsorption) { movingSliderAbsorption = false; var absorption = ((newThumbX - absorptionMinThumbX) / (absorptionMaxThumbX - absorptionMinThumbX)) * absorptionScale; @@ -703,6 +821,18 @@ function mouseReleaseEvent(event) { absorptionThumbX = newThumbX; updateRatioSliders(); } + if (movingSliderEchoes) { + movingSliderEchoes = false; + var echoes = ((newThumbX - echoesMinThumbX) / (echoesMaxThumbX - echoesMinThumbX)) * echoesScale; + AudioReflector.setEchoesAttenuation(echoes); + echoesThumbX = newThumbX; + } + if (movingSliderOriginal) { + movingSliderOriginal = false; + var original = ((newThumbX - originalMinThumbX) / (originalMaxThumbX - originalMinThumbX)) * originalScale; + AudioReflector.setOriginalSourceAttenuation(original); + originalThumbX = newThumbX; + } } Controller.mouseMoveEvent.connect(mouseMoveEvent); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index bd7a82b439..7480b16334 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1679,8 +1679,12 @@ void Application::init() { _audioReflector.setMyAvatar(getAvatar()); _audioReflector.setVoxels(_voxels.getTree()); _audioReflector.setAudio(getAudio()); + _audioReflector.setAvatarManager(&_avatarManager); + connect(getAudio(), &Audio::processInboundAudio, &_audioReflector, &AudioReflector::processInboundAudio,Qt::DirectConnection); connect(getAudio(), &Audio::processLocalAudio, &_audioReflector, &AudioReflector::processLocalAudio,Qt::DirectConnection); + connect(getAudio(), &Audio::preProcessOriginalInboundAudio, &_audioReflector, + &AudioReflector::preProcessOriginalInboundAudio,Qt::DirectConnection); // save settings when avatar changes connect(_myAvatar, &MyAvatar::transformChanged, this, &Application::bumpSettings); diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index daa7c036eb..830e2fe69b 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -784,6 +784,7 @@ void Audio::processReceivedAudio(const QByteArray& audioByteArray) { _ringBuffer.readSamples((int16_t*)buffer.data(), numNetworkOutputSamples); // Accumulate direct transmission of audio from sender to receiver if (Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingIncludeOriginal)) { + emit preProcessOriginalInboundAudio(sampleTime, buffer, _desiredOutputFormat); addSpatialAudioToBuffer(sampleTime, buffer, numNetworkOutputSamples); } diff --git a/interface/src/Audio.h b/interface/src/Audio.h index 3b19d98146..96def43dd2 100644 --- a/interface/src/Audio.h +++ b/interface/src/Audio.h @@ -99,6 +99,7 @@ public slots: signals: bool muteToggled(); + void preProcessOriginalInboundAudio(unsigned int sampleTime, QByteArray& samples, const QAudioFormat& format); void processInboundAudio(unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format); void processLocalAudio(unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format); diff --git a/interface/src/AudioReflector.cpp b/interface/src/AudioReflector.cpp index 52558f1d59..67b6120354 100644 --- a/interface/src/AudioReflector.cpp +++ b/interface/src/AudioReflector.cpp @@ -25,6 +25,8 @@ const float SLIGHTLY_SHORT = 0.999f; // slightly inside the distance so we're on const float DEFAULT_ABSORPTION_RATIO = 0.125; // 12.5% is absorbed const float DEFAULT_DIFFUSION_RATIO = 0.125; // 12.5% is diffused +const float DEFAULT_ORIGINAL_ATTENUATION = 1.0f; +const float DEFAULT_ECHO_ATTENUATION = 1.0f; AudioReflector::AudioReflector(QObject* parent) : QObject(parent), @@ -36,6 +38,8 @@ AudioReflector::AudioReflector(QObject* parent) : _diffusionFanout(DEFAULT_DIFFUSION_FANOUT), _absorptionRatio(DEFAULT_ABSORPTION_RATIO), _diffusionRatio(DEFAULT_DIFFUSION_RATIO), + _originalSourceAttenuation(DEFAULT_ORIGINAL_ATTENUATION), + _allEchoesAttenuation(DEFAULT_ECHO_ATTENUATION), _withDiffusion(false), _lastPreDelay(DEFAULT_PRE_DELAY), _lastSoundMsPerMeter(DEFAULT_MS_DELAY_PER_METER), @@ -43,20 +47,29 @@ AudioReflector::AudioReflector(QObject* parent) : _lastLocalAudioAttenuationFactor(DEFAULT_LOCAL_ATTENUATION_FACTOR), _lastDiffusionFanout(DEFAULT_DIFFUSION_FANOUT), _lastAbsorptionRatio(DEFAULT_ABSORPTION_RATIO), - _lastDiffusionRatio(DEFAULT_DIFFUSION_RATIO) + _lastDiffusionRatio(DEFAULT_DIFFUSION_RATIO), + _lastDontDistanceAttenuate(false), + _lastAlternateDistanceAttenuate(false) { _reflections = 0; _diffusionPathCount = 0; - _averageAttenuation = 0.0f; - _maxAttenuation = 0.0f; - _minAttenuation = 0.0f; - _averageDelay = 0; - _maxDelay = 0; - _minDelay = 0; + _averageAttenuationOfficial = _averageAttenuation = 0.0f; + _maxAttenuationOfficial = _maxAttenuation = 0.0f; + _minAttenuationOfficial = _minAttenuation = 0.0f; + _averageDelayOfficial = _averageDelay = 0; + _maxDelayOfficial = _maxDelay = 0; + _minDelayOfficial = _minDelay = 0; + _inboundEchoesCount = 0; + _inboundEchoesSuppressedCount = 0; + _localEchoesCount = 0; + _localEchoesSuppressedCount = 0; } bool AudioReflector::haveAttributesChanged() { bool withDiffusion = Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingWithDiffusions); + bool dontDistanceAttenuate = Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingDontDistanceAttenuate); + bool alternateDistanceAttenuate = Menu::getInstance()->isOptionChecked( + MenuOption::AudioSpatialProcessingAlternateDistanceAttenuate); bool attributesChange = (_withDiffusion != withDiffusion || _lastPreDelay != _preDelay @@ -64,7 +77,9 @@ bool AudioReflector::haveAttributesChanged() { || _lastDistanceAttenuationScalingFactor != _distanceAttenuationScalingFactor || _lastDiffusionFanout != _diffusionFanout || _lastAbsorptionRatio != _absorptionRatio - || _lastDiffusionRatio != _diffusionRatio); + || _lastDiffusionRatio != _diffusionRatio + || _lastDontDistanceAttenuate != dontDistanceAttenuate + || _lastAlternateDistanceAttenuate != alternateDistanceAttenuate); if (attributesChange) { _withDiffusion = withDiffusion; @@ -74,6 +89,8 @@ bool AudioReflector::haveAttributesChanged() { _lastDiffusionFanout = _diffusionFanout; _lastAbsorptionRatio = _absorptionRatio; _lastDiffusionRatio = _diffusionRatio; + _lastDontDistanceAttenuate = dontDistanceAttenuate; + _lastAlternateDistanceAttenuate = alternateDistanceAttenuate; } return attributesChange; @@ -107,19 +124,47 @@ float AudioReflector::getDelayFromDistance(float distance) { // attenuation = from the Audio Mixer float AudioReflector::getDistanceAttenuationCoefficient(float distance) { - const float DISTANCE_SCALE = 2.5f; - const float GEOMETRIC_AMPLITUDE_SCALAR = 0.3f; - const float DISTANCE_LOG_BASE = 2.5f; - const float DISTANCE_SCALE_LOG = logf(DISTANCE_SCALE) / logf(DISTANCE_LOG_BASE); + + + bool doDistanceAttenuation = !Menu::getInstance()->isOptionChecked( + MenuOption::AudioSpatialProcessingDontDistanceAttenuate); + + bool originalFormula = !Menu::getInstance()->isOptionChecked( + MenuOption::AudioSpatialProcessingAlternateDistanceAttenuate); - float distanceSquareToSource = distance * distance; + + float distanceCoefficient = 1.0f; + + if (doDistanceAttenuation) { + + if (originalFormula) { + const float DISTANCE_SCALE = 2.5f; + const float GEOMETRIC_AMPLITUDE_SCALAR = 0.3f; + const float DISTANCE_LOG_BASE = 2.5f; + const float DISTANCE_SCALE_LOG = logf(DISTANCE_SCALE) / logf(DISTANCE_LOG_BASE); + + float distanceSquareToSource = distance * distance; - // calculate the distance coefficient using the distance to this node - float distanceCoefficient = powf(GEOMETRIC_AMPLITUDE_SCALAR, - DISTANCE_SCALE_LOG + - (0.5f * logf(distanceSquareToSource) / logf(DISTANCE_LOG_BASE)) - 1); - - distanceCoefficient = std::min(1.0f, distanceCoefficient * getDistanceAttenuationScalingFactor()); + // calculate the distance coefficient using the distance to this node + distanceCoefficient = powf(GEOMETRIC_AMPLITUDE_SCALAR, + DISTANCE_SCALE_LOG + + (0.5f * logf(distanceSquareToSource) / logf(DISTANCE_LOG_BASE)) - 1); + distanceCoefficient = std::min(1.0f, distanceCoefficient * getDistanceAttenuationScalingFactor()); + } else { + + // From Fred: If we wanted something that would produce a tail that could go up to 5 seconds in a + // really big room, that would suggest the sound still has to be in the audible after traveling about + // 1500 meters. If it’s a sound of average volume, we probably have about 30 db, or 5 base2 orders + // of magnitude we can drop down before the sound becomes inaudible. (That’s approximate headroom + // based on a few sloppy assumptions.) So we could try a factor like 1 / (2^(D/300)) for starters. + // 1 / (2^(D/300)) + const float DISTANCE_BASE = 2.0f; + const float DISTANCE_DENOMINATOR = 300.0f; + const float DISTANCE_NUMERATOR = 300.0f; + distanceCoefficient = DISTANCE_NUMERATOR / powf(DISTANCE_BASE, (distance / DISTANCE_DENOMINATOR )); + distanceCoefficient = std::min(1.0f, distanceCoefficient * getDistanceAttenuationScalingFactor()); + } + } return distanceCoefficient; } @@ -236,11 +281,13 @@ void AudioReflector::injectAudiblePoint(AudioSource source, const AudiblePoint& rightSample = originalSamplesData[(sample * NUMBER_OF_CHANNELS) + 1]; } - attenuatedLeftSamplesData[sample * NUMBER_OF_CHANNELS] = leftSample * leftEarAttenuation; + attenuatedLeftSamplesData[sample * NUMBER_OF_CHANNELS] = + leftSample * leftEarAttenuation * _allEchoesAttenuation; attenuatedLeftSamplesData[sample * NUMBER_OF_CHANNELS + 1] = 0; attenuatedRightSamplesData[sample * NUMBER_OF_CHANNELS] = 0; - attenuatedRightSamplesData[sample * NUMBER_OF_CHANNELS + 1] = rightSample * rightEarAttenuation; + attenuatedRightSamplesData[sample * NUMBER_OF_CHANNELS + 1] = + rightSample * rightEarAttenuation * _allEchoesAttenuation; } // now inject the attenuated array with the appropriate delay @@ -249,9 +296,25 @@ void AudioReflector::injectAudiblePoint(AudioSource source, const AudiblePoint& _audio->addSpatialAudioToBuffer(sampleTimeLeft, attenuatedLeftSamples, totalNumberOfSamples); _audio->addSpatialAudioToBuffer(sampleTimeRight, attenuatedRightSamples, totalNumberOfSamples); + + _injectedEchoes++; } } + +void AudioReflector::preProcessOriginalInboundAudio(unsigned int sampleTime, + QByteArray& samples, const QAudioFormat& format) { + + if (_originalSourceAttenuation != 1.0f) { + int numberOfSamples = (samples.size() / sizeof(int16_t)); + int16_t* sampleData = (int16_t*)samples.data(); + for (int i = 0; i < numberOfSamples; i++) { + sampleData[i] = sampleData[i] * _originalSourceAttenuation; + } + } + +} + void AudioReflector::processLocalAudio(unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format) { if (Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingProcessLocalAudio)) { const int NUM_CHANNELS_INPUT = 1; @@ -272,6 +335,8 @@ void AudioReflector::processLocalAudio(unsigned int sampleTime, const QByteArray _localAudioDelays.clear(); _localEchoesSuppressed.clear(); echoAudio(LOCAL_AUDIO, sampleTime, stereoInputData, outputFormat); + _localEchoesCount = _localAudioDelays.size(); + _localEchoesSuppressedCount = _localEchoesSuppressed.size(); } } } @@ -280,9 +345,13 @@ void AudioReflector::processInboundAudio(unsigned int sampleTime, const QByteArr _inboundAudioDelays.clear(); _inboundEchoesSuppressed.clear(); echoAudio(INBOUND_AUDIO, sampleTime, samples, format); + _inboundEchoesCount = _inboundAudioDelays.size(); + _inboundEchoesSuppressedCount = _inboundEchoesSuppressed.size(); } void AudioReflector::echoAudio(AudioSource source, unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format) { + QMutexLocker locker(&_mutex); + _maxDelay = 0; _maxAttenuation = 0.0f; _minDelay = std::numeric_limits::max(); @@ -292,14 +361,20 @@ void AudioReflector::echoAudio(AudioSource source, unsigned int sampleTime, cons _totalAttenuation = 0.0f; _attenuationCount = 0; - QMutexLocker locker(&_mutex); - // depending on if we're processing local or external audio, pick the correct points vector QVector& audiblePoints = source == INBOUND_AUDIO ? _inboundAudiblePoints : _localAudiblePoints; + int injectCalls = 0; + _injectedEchoes = 0; foreach(const AudiblePoint& audiblePoint, audiblePoints) { + injectCalls++; injectAudiblePoint(source, audiblePoint, samples, sampleTime, format.sampleRate()); } + + /* + qDebug() << "injectCalls=" << injectCalls; + qDebug() << "_injectedEchoes=" << _injectedEchoes; + */ _averageDelay = _delayCount == 0 ? 0 : _totalDelay / _delayCount; _averageAttenuation = _attenuationCount == 0 ? 0 : _totalAttenuation / _attenuationCount; @@ -308,6 +383,14 @@ void AudioReflector::echoAudio(AudioSource source, unsigned int sampleTime, cons _minDelay = 0.0f; _minAttenuation = 0.0f; } + + _maxDelayOfficial = _maxDelay; + _minDelayOfficial = _minDelay; + _maxAttenuationOfficial = _maxAttenuation; + _minAttenuationOfficial = _minAttenuation; + _averageDelayOfficial = _averageDelay; + _averageAttenuationOfficial = _averageAttenuation; + } void AudioReflector::drawVector(const glm::vec3& start, const glm::vec3& end, const glm::vec3& color) { @@ -359,6 +442,19 @@ void AudioReflector::addAudioPath(AudioSource source, const glm::vec3& origin, c audioPaths.push_back(path); } +// NOTE: This is a prototype of an eventual utility that will identify the speaking sources for the inbound audio +// stream. It's not currently called but will be added soon. +void AudioReflector::identifyAudioSources() { + // looking for audio sources.... + foreach (const AvatarSharedPointer& avatarPointer, _avatarManager->getAvatarHash()) { + Avatar* avatar = static_cast(avatarPointer.data()); + if (!avatar->isInitialized()) { + continue; + } + qDebug() << "avatar["<< avatar <<"] loudness:" << avatar->getAudioLoudness(); + } +} + void AudioReflector::calculateAllReflections() { // only recalculate when we've moved, or if the attributes have changed // TODO: what about case where new voxels are added in front of us??? diff --git a/interface/src/AudioReflector.h b/interface/src/AudioReflector.h index 2408b70a96..cd5aaad276 100644 --- a/interface/src/AudioReflector.h +++ b/interface/src/AudioReflector.h @@ -15,6 +15,7 @@ #include "Audio.h" #include "avatar/MyAvatar.h" +#include "avatar/AvatarManager.h" enum AudioSource { LOCAL_AUDIO, @@ -69,25 +70,27 @@ public: void setVoxels(VoxelTree* voxels) { _voxels = voxels; } void setMyAvatar(MyAvatar* myAvatar) { _myAvatar = myAvatar; } void setAudio(Audio* audio) { _audio = audio; } + void setAvatarManager(AvatarManager* avatarManager) { _avatarManager = avatarManager; } void render(); /// must be called in the application render loop + void preProcessOriginalInboundAudio(unsigned int sampleTime, QByteArray& samples, const QAudioFormat& format); void processInboundAudio(unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format); void processLocalAudio(unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format); public slots: // statistics int getReflections() const { return _reflections; } - float getAverageDelayMsecs() const { return _averageDelay; } - float getAverageAttenuation() const { return _averageAttenuation; } - float getMaxDelayMsecs() const { return _maxDelay; } - float getMaxAttenuation() const { return _maxAttenuation; } - float getMinDelayMsecs() const { return _minDelay; } - float getMinAttenuation() const { return _minAttenuation; } + float getAverageDelayMsecs() const { return _averageDelayOfficial; } + float getAverageAttenuation() const { return _averageAttenuationOfficial; } + float getMaxDelayMsecs() const { return _maxDelayOfficial; } + float getMaxAttenuation() const { return _maxAttenuationOfficial; } + float getMinDelayMsecs() const { return _minDelayOfficial; } + float getMinAttenuation() const { return _minAttenuationOfficial; } float getDelayFromDistance(float distance); int getDiffusionPathCount() const { return _diffusionPathCount; } - int getEchoesInjected() const { return _inboundAudioDelays.size() + _localAudioDelays.size(); } - int getEchoesSuppressed() const { return _inboundEchoesSuppressed.size() + _localEchoesSuppressed.size(); } + int getEchoesInjected() const { return _inboundEchoesCount + _localEchoesCount; } + int getEchoesSuppressed() const { return _inboundEchoesSuppressedCount + _localEchoesSuppressedCount; } /// ms of delay added to all echos float getPreDelay() const { return _preDelay; } @@ -126,12 +129,19 @@ public slots: float getReflectiveRatio() const { return (1.0f - (_absorptionRatio + _diffusionRatio)); } void setReflectiveRatio(float ratio); + // wet/dry mix - these don't affect any reflection calculations, only the final mix volumes + float getOriginalSourceAttenuation() const { return _originalSourceAttenuation; } + void setOriginalSourceAttenuation(float value) { _originalSourceAttenuation = value; } + float getEchoesAttenuation() const { return _allEchoesAttenuation; } + void setEchoesAttenuation(float value) { _allEchoesAttenuation = value; } + signals: private: VoxelTree* _voxels; // used to access voxel scene MyAvatar* _myAvatar; // access to listener Audio* _audio; // access to audio API + AvatarManager* _avatarManager; // access to avatar manager API // Helpers for drawing void drawVector(const glm::vec3& start, const glm::vec3& end, const glm::vec3& color); @@ -147,11 +157,18 @@ private: float _averageDelay; float _maxDelay; float _minDelay; + float _averageDelayOfficial; + float _maxDelayOfficial; + float _minDelayOfficial; int _attenuationCount; float _totalAttenuation; float _averageAttenuation; float _maxAttenuation; float _minAttenuation; + float _averageAttenuationOfficial; + float _maxAttenuationOfficial; + float _minAttenuationOfficial; + glm::vec3 _listenerPosition; glm::vec3 _origin; @@ -161,11 +178,15 @@ private: QVector _inboundAudiblePoints; /// the audible points that have been calculated from the inbound audio paths QMap _inboundAudioDelays; /// delay times for currently injected audio points QVector _inboundEchoesSuppressed; /// delay times for currently injected audio points + int _inboundEchoesCount; + int _inboundEchoesSuppressedCount; QVector _localAudioPaths; /// audio paths we're processing for local audio QVector _localAudiblePoints; /// the audible points that have been calculated from the local audio paths QMap _localAudioDelays; /// delay times for currently injected audio points QVector _localEchoesSuppressed; /// delay times for currently injected audio points + int _localEchoesCount; + int _localEchoesSuppressedCount; // adds a sound source to begin an audio path trace, these can be the initial sound sources with their directional properties, // as well as diffusion sound sources @@ -182,6 +203,7 @@ private: void calculateAllReflections(); int countDiffusionPaths(); glm::vec3 getFaceNormal(BoxFace face); + void identifyAudioSources(); void injectAudiblePoint(AudioSource source, const AudiblePoint& audiblePoint, const QByteArray& samples, unsigned int sampleTime, int sampleRate); void echoAudio(AudioSource source, unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format); @@ -197,13 +219,16 @@ private: float _distanceAttenuationScalingFactor; float _localAudioAttenuationFactor; float _combFilterWindow; - int _diffusionFanout; // number of points of diffusion from each reflection point // all elements have the same material for now... float _absorptionRatio; float _diffusionRatio; float _reflectiveRatio; + + // wet/dry mix - these don't affect any reflection calculations, only the final mix volumes + float _originalSourceAttenuation; /// each sample of original signal will be multiplied by this + float _allEchoesAttenuation; /// each sample of all echo signals will be multiplied by this // remember the last known values at calculation bool haveAttributesChanged(); @@ -216,6 +241,10 @@ private: int _lastDiffusionFanout; float _lastAbsorptionRatio; float _lastDiffusionRatio; + bool _lastDontDistanceAttenuate; + bool _lastAlternateDistanceAttenuate; + + int _injectedEchoes; }; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 85c3d5b0c3..b05e5c91bc 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -429,6 +429,14 @@ Menu::Menu() : Qt::CTRL | Qt::SHIFT | Qt::Key_A, true); + addCheckableActionToQMenuAndActionHash(spatialAudioMenu, MenuOption::AudioSpatialProcessingDontDistanceAttenuate, + Qt::CTRL | Qt::SHIFT | Qt::Key_Y, + false); + + addCheckableActionToQMenuAndActionHash(spatialAudioMenu, MenuOption::AudioSpatialProcessingAlternateDistanceAttenuate, + Qt::CTRL | Qt::SHIFT | Qt::Key_U, + false); + addActionToQMenuAndActionHash(developerMenu, MenuOption::PasteToVoxel, Qt::CTRL | Qt::SHIFT | Qt::Key_V, this, diff --git a/interface/src/Menu.h b/interface/src/Menu.h index a62f54b0c6..c17c9cc507 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -268,6 +268,10 @@ namespace MenuOption { const QString AudioSpatialProcessingSlightlyRandomSurfaces = "Slightly Random Surfaces"; const QString AudioSpatialProcessingStereoSource = "Stereo Source"; const QString AudioSpatialProcessingWithDiffusions = "With Diffusions"; + const QString AudioSpatialProcessingDontDistanceAttenuate = "Don't calculate distance attenuation"; + const QString AudioSpatialProcessingAlternateDistanceAttenuate = "Alternate distance attenuation"; + + const QString Avatars = "Avatars"; const QString Bandwidth = "Bandwidth Display"; diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 64616cbdf8..a2d615e04d 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -343,7 +343,7 @@ void Stats::display( lines = _expanded ? 12 : 3; if (_expanded && Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessing)) { - lines += 8; // spatial audio processing adds 1 spacing line and 7 extra lines of info + lines += 9; // spatial audio processing adds 1 spacing line and 8 extra lines of info } drawBackground(backgroundColor, horizontalOffset, 0, glWidget->width() - horizontalOffset, lines * STATS_PELS_PER_LINE + 10); @@ -540,11 +540,19 @@ void Stats::display( verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, reflectionsStatus, color); + + bool distanceAttenuationDisabled = Menu::getInstance()->isOptionChecked( + MenuOption::AudioSpatialProcessingDontDistanceAttenuate); - sprintf(reflectionsStatus, "Attenuation: average %5.3f, max %5.3f, min %5.3f, Factor: %5.3f", + bool alternateDistanceAttenuationEnabled = Menu::getInstance()->isOptionChecked( + MenuOption::AudioSpatialProcessingAlternateDistanceAttenuate); + + sprintf(reflectionsStatus, "Attenuation: average %5.3f, max %5.3f, min %5.3f, %s: %5.3f", audioReflector->getAverageAttenuation(), audioReflector->getMaxAttenuation(), audioReflector->getMinAttenuation(), + (distanceAttenuationDisabled ? "Distance Factor [DISABLED]" : + alternateDistanceAttenuationEnabled ? "Distance Factor [ALTERNATE]" : "Distance Factor [STANARD]"), audioReflector->getDistanceAttenuationScalingFactor()); verticalOffset += STATS_PELS_PER_LINE; @@ -585,6 +593,13 @@ void Stats::display( verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, reflectionsStatus, color); + sprintf(reflectionsStatus, "Wet/Dry Mix: Original: %5.3f Echoes: %5.3f", + audioReflector->getOriginalSourceAttenuation(), + audioReflector->getEchoesAttenuation()); + + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, reflectionsStatus, color); + } } From 9713edbba90eaf3a5055ad55731da8c267185d93 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 18 Apr 2014 09:00:43 -0700 Subject: [PATCH 34/48] 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 35/48] 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); From 2dc39512a2ef555b11831e036a51de8f23f25c92 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 18 Apr 2014 10:19:09 -0700 Subject: [PATCH 36/48] CR feedback --- interface/src/AudioReflector.cpp | 24 ++++++++++++------------ interface/src/AudioReflector.h | 24 ++++++++++++------------ 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/interface/src/AudioReflector.cpp b/interface/src/AudioReflector.cpp index 67b6120354..e66735c403 100644 --- a/interface/src/AudioReflector.cpp +++ b/interface/src/AudioReflector.cpp @@ -53,12 +53,12 @@ AudioReflector::AudioReflector(QObject* parent) : { _reflections = 0; _diffusionPathCount = 0; - _averageAttenuationOfficial = _averageAttenuation = 0.0f; - _maxAttenuationOfficial = _maxAttenuation = 0.0f; - _minAttenuationOfficial = _minAttenuation = 0.0f; - _averageDelayOfficial = _averageDelay = 0; - _maxDelayOfficial = _maxDelay = 0; - _minDelayOfficial = _minDelay = 0; + _officialAverageAttenuation = _averageAttenuation = 0.0f; + _officialMaxAttenuation = _maxAttenuation = 0.0f; + _officialMinAttenuation = _minAttenuation = 0.0f; + _officialAverageDelay = _averageDelay = 0; + _officialMaxDelay = _maxDelay = 0; + _officialMinDelay = _minDelay = 0; _inboundEchoesCount = 0; _inboundEchoesSuppressedCount = 0; _localEchoesCount = 0; @@ -384,12 +384,12 @@ void AudioReflector::echoAudio(AudioSource source, unsigned int sampleTime, cons _minAttenuation = 0.0f; } - _maxDelayOfficial = _maxDelay; - _minDelayOfficial = _minDelay; - _maxAttenuationOfficial = _maxAttenuation; - _minAttenuationOfficial = _minAttenuation; - _averageDelayOfficial = _averageDelay; - _averageAttenuationOfficial = _averageAttenuation; + _officialMaxDelay = _maxDelay; + _officialMinDelay = _minDelay; + _officialMaxAttenuation = _maxAttenuation; + _officialMinAttenuation = _minAttenuation; + _officialAverageDelay = _averageDelay; + _officialAverageAttenuation = _averageAttenuation; } diff --git a/interface/src/AudioReflector.h b/interface/src/AudioReflector.h index cd5aaad276..582345e064 100644 --- a/interface/src/AudioReflector.h +++ b/interface/src/AudioReflector.h @@ -81,12 +81,12 @@ public: public slots: // statistics int getReflections() const { return _reflections; } - float getAverageDelayMsecs() const { return _averageDelayOfficial; } - float getAverageAttenuation() const { return _averageAttenuationOfficial; } - float getMaxDelayMsecs() const { return _maxDelayOfficial; } - float getMaxAttenuation() const { return _maxAttenuationOfficial; } - float getMinDelayMsecs() const { return _minDelayOfficial; } - float getMinAttenuation() const { return _minAttenuationOfficial; } + float getAverageDelayMsecs() const { return _officialAverageDelay; } + float getAverageAttenuation() const { return _officialAverageAttenuation; } + float getMaxDelayMsecs() const { return _officialMaxDelay; } + float getMaxAttenuation() const { return _officialMaxAttenuation; } + float getMinDelayMsecs() const { return _officialMinDelay; } + float getMinAttenuation() const { return _officialMinAttenuation; } float getDelayFromDistance(float distance); int getDiffusionPathCount() const { return _diffusionPathCount; } int getEchoesInjected() const { return _inboundEchoesCount + _localEchoesCount; } @@ -157,17 +157,17 @@ private: float _averageDelay; float _maxDelay; float _minDelay; - float _averageDelayOfficial; - float _maxDelayOfficial; - float _minDelayOfficial; + float _officialAverageDelay; + float _officialMaxDelay; + float _officialMinDelay; int _attenuationCount; float _totalAttenuation; float _averageAttenuation; float _maxAttenuation; float _minAttenuation; - float _averageAttenuationOfficial; - float _maxAttenuationOfficial; - float _minAttenuationOfficial; + float _officialAverageAttenuation; + float _officialMaxAttenuation; + float _officialMinAttenuation; glm::vec3 _listenerPosition; From e4d30c5f1f314a559a73b4df6fd43986ca4c02c2 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 18 Apr 2014 10:57:50 -0700 Subject: [PATCH 37/48] Update dancing_bot.js put gangnam style animation back - it's royalty free --- 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 7924614d82..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("FBX_URL"); +var animation = AnimationCache.getAnimation("http://www.fungibleinsight.com/faces/gangnam_style_2.fbx"); Avatar.skeletonModelURL = "http://www.fungibleinsight.com/faces/beta.fst"; From 26487aad0682f0a1c845fcce0467ad2685f67b9e Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 18 Apr 2014 11:19:21 -0700 Subject: [PATCH 38/48] more correct bounding shapes for Models --- interface/src/renderer/Model.cpp | 78 ++++++++++++++++++++++---------- 1 file changed, 54 insertions(+), 24 deletions(-) diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index ca0b46841d..f8de7210ea 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -60,11 +60,11 @@ Model::SkinLocations Model::_skinNormalMapLocations; Model::SkinLocations Model::_skinShadowLocations; void Model::setScale(const glm::vec3& scale) { - glm::vec3 deltaScale = _scale - scale; + float scaleLength = glm::length(_scale); + float relativeDeltaScale = glm::length(_scale - scale) / scaleLength; - // decreased epsilon because this wasn't handling scale changes of 0.01 - const float SMALLER_EPSILON = EPSILON * 0.0001f; - if (glm::length2(deltaScale) > SMALLER_EPSILON) { + const float ONE_PERCENT = 0.01f; + if (relativeDeltaScale > ONE_PERCENT || scaleLength < EPSILON) { _scale = scale; rebuildShapes(); } @@ -468,20 +468,51 @@ void Model::clearShapes() { void Model::rebuildShapes() { clearShapes(); + const FBXGeometry& geometry = _geometry->getFBXGeometry(); - if (_jointStates.isEmpty()) { + if (geometry.joints.isEmpty()) { return; } - // make sure all the joints are updated correctly before we try to create their shapes - for (int i = 0; i < _jointStates.size(); i++) { - updateJointState(i); - } - - const FBXGeometry& geometry = _geometry->getFBXGeometry(); + int numJoints = geometry.joints.size(); + QVector transforms; + transforms.fill(glm::mat4(), numJoints); + QVector combinedRotations; + combinedRotations.fill(glm::quat(), numJoints); + QVector shapeIsSet; + shapeIsSet.fill(false, numJoints); + int rootIndex = 0; + float uniformScale = extractUniformScale(_scale); - glm::quat inverseRotation = glm::inverse(_rotation); - glm::vec3 rootPosition(0.f); + int numShapesSet = 0; + int lastNumShapesSet = -1; + while (numShapesSet < numJoints && numShapesSet != lastNumShapesSet) { + lastNumShapesSet = numShapesSet; + for (int i = 0; i < numJoints; ++i) { + if (shapeIsSet[i]) { + continue; + } + const FBXJoint& joint = geometry.joints[i]; + int parentIndex = joint.parentIndex; + if (parentIndex == -1) { + rootIndex = i; + glm::mat4 baseTransform = glm::mat4_cast(_rotation) * uniformScale * glm::translate(_offset); + glm::quat combinedRotation = joint.preRotation * joint.rotation * joint.postRotation; + transforms[i] = baseTransform * geometry.offset * glm::translate(joint.translation) * joint.preTransform * + glm::mat4_cast(combinedRotation) * joint.postTransform; + combinedRotations[i] = _rotation * combinedRotation; + ++numShapesSet; + shapeIsSet[i] = true; + } else if (shapeIsSet[parentIndex]) { + glm::quat combinedRotation = joint.preRotation * joint.rotation * joint.postRotation; + transforms[i] = transforms[parentIndex] * glm::translate(joint.translation) * joint.preTransform * + glm::mat4_cast(combinedRotation) * joint.postTransform; + combinedRotations[i] = combinedRotations[parentIndex] * combinedRotation; + ++numShapesSet; + shapeIsSet[i] = true; + } + } + } // joint shapes Extents totalExtents; @@ -489,15 +520,10 @@ void Model::rebuildShapes() { for (int i = 0; i < _jointStates.size(); i++) { const FBXJoint& joint = geometry.joints[i]; - glm::vec3 jointToShapeOffset = uniformScale * (_jointStates[i].combinedRotation * joint.shapePosition); - glm::vec3 worldPosition = extractTranslation(_jointStates[i].transform) + jointToShapeOffset + _translation; + glm::vec3 worldPosition = extractTranslation(transforms[i]); Extents shapeExtents; shapeExtents.reset(); - if (joint.parentIndex == -1) { - rootPosition = worldPosition; - } - float radius = uniformScale * joint.boneRadius; float halfHeight = 0.5f * uniformScale * joint.distanceToParent; Shape::Type type = joint.shapeType; @@ -508,7 +534,7 @@ void Model::rebuildShapes() { if (type == Shape::CAPSULE_SHAPE) { CapsuleShape* capsule = new CapsuleShape(radius, halfHeight); capsule->setPosition(worldPosition); - capsule->setRotation(_jointStates[i].combinedRotation * joint.shapeRotation); + capsule->setRotation(combinedRotations[i] * joint.shapeRotation); _jointShapes.push_back(capsule); glm::vec3 endPoint; @@ -526,7 +552,6 @@ void Model::rebuildShapes() { shapeExtents.addPoint(worldPosition + axis); shapeExtents.addPoint(worldPosition - axis); - totalExtents.addExtents(shapeExtents); } else if (type == Shape::SPHERE_SHAPE) { SphereShape* sphere = new SphereShape(radius, worldPosition); @@ -539,12 +564,11 @@ void Model::rebuildShapes() { } else { // this shape type is not handled and the joint shouldn't collide, // however we must have a shape for each joint, - // so we make a bogus sphere and put it at the center of the model + // so we make a bogus sphere with zero radius. // TODO: implement collision groups for more control over what collides with what - SphereShape* sphere = new SphereShape(0.f, _offset); + SphereShape* sphere = new SphereShape(0.f, worldPosition); _jointShapes.push_back(sphere); } - } // bounding shape @@ -554,7 +578,12 @@ void Model::rebuildShapes() { float capsuleRadius = 0.5f * sqrtf(0.5f * (diagonal.x * diagonal.x + diagonal.z * diagonal.z)); _boundingShape.setRadius(capsuleRadius); _boundingShape.setHalfHeight(0.5f * diagonal.y - capsuleRadius); + + glm::quat inverseRotation = glm::inverse(_rotation); + glm::vec3 rootPosition = extractTranslation(transforms[rootIndex]); _boundingShapeLocalOffset = inverseRotation * (0.5f * (totalExtents.maximum + totalExtents.minimum) - rootPosition); + _boundingShape.setPosition(_translation - _rotation * _boundingShapeLocalOffset); + _boundingShape.setRotation(_rotation); } void Model::updateShapePositions() { @@ -581,6 +610,7 @@ void Model::updateShapePositions() { _boundingRadius = sqrtf(_boundingRadius); _shapesAreDirty = false; _boundingShape.setPosition(rootPosition + _rotation * _boundingShapeLocalOffset); + _boundingShape.setRotation(_rotation); } } From e9768ca4fc190453515b6e3d48d53e73b15445d6 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 18 Apr 2014 13:34:30 -0700 Subject: [PATCH 39/48] add missing pool handling to DS --- domain-server/src/DomainServer.cpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 9dbb1e478e..f32eb99733 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -847,8 +847,9 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url // this is a script upload - ask the HTTPConnection to parse the form data QList formData = connection->parseFormData(); - // check how many instances of this assignment the user wants by checking the ASSIGNMENT-INSTANCES header + // check optional headers for # of instances and pool const QString ASSIGNMENT_INSTANCES_HEADER = "ASSIGNMENT-INSTANCES"; + const QString ASSIGNMENT_POOL_HEADER = "ASSIGNMENT-POOL"; QByteArray assignmentInstancesValue = connection->requestHeaders().value(ASSIGNMENT_INSTANCES_HEADER.toLocal8Bit()); @@ -860,25 +861,34 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url numInstances = assignmentInstancesValue.toInt(); } - + + QString assignmentPool = emptyPool; + QByteArray assignmentPoolValue = connection->requestHeaders().value(ASSIGNMENT_POOL_HEADER.toLocal8Bit()); + + if (!assignmentPoolValue.isEmpty()) { + // specific pool requested, set that on the created assignment + assignmentPool = QString(assignmentPoolValue); + } + const char ASSIGNMENT_SCRIPT_HOST_LOCATION[] = "resources/web/assignment"; for (int i = 0; i < numInstances; i++) { // create an assignment for this saved script - Assignment* scriptAssignment = new Assignment(Assignment::CreateCommand, Assignment::AgentType); + Assignment* scriptAssignment = new Assignment(Assignment::CreateCommand, Assignment::AgentType, assignmentPool); QString newPath(ASSIGNMENT_SCRIPT_HOST_LOCATION); newPath += "/"; // append the UUID for this script as the new filename, remove the curly braces newPath += uuidStringWithoutCurlyBraces(scriptAssignment->getUUID()); - // create a file with the GUID of the assignment in the script host locaiton + // create a file with the GUID of the assignment in the script host location QFile scriptFile(newPath); scriptFile.open(QIODevice::WriteOnly); scriptFile.write(formData[0].second); - qDebug("Saved a script for assignment at %s", qPrintable(newPath)); + qDebug() << qPrintable(QString("Saved a script for assignment at %1%2") + .arg(newPath).arg(assignmentPool == emptyPool ? "" : " - pool is " + assignmentPool)); // add the script assigment to the assignment queue _assignmentQueue.enqueue(SharedAssignmentPointer(scriptAssignment)); From 6ef4e145c59281a63178d9a26c10f63906b21f2a Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 18 Apr 2014 13:51:58 -0700 Subject: [PATCH 40/48] add better Sound error handling --- libraries/audio/src/Sound.cpp | 16 ++++++++++++---- libraries/audio/src/Sound.h | 6 +++--- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/libraries/audio/src/Sound.cpp b/libraries/audio/src/Sound.cpp index 001f863e03..3aae6b995d 100644 --- a/libraries/audio/src/Sound.cpp +++ b/libraries/audio/src/Sound.cpp @@ -73,15 +73,18 @@ Sound::Sound(const QUrl& sampleURL, QObject* parent) : // QNetworkAccess manager to grab the raw audio file at the given URL QNetworkAccessManager *manager = new QNetworkAccessManager(this); - connect(manager, SIGNAL(finished(QNetworkReply*)), - this, SLOT(replyFinished(QNetworkReply*))); qDebug() << "Requesting audio file" << sampleURL.toDisplayString(); - manager->get(QNetworkRequest(sampleURL)); + + QNetworkReply* soundDownload = manager->get(QNetworkRequest(sampleURL)); + connect(soundDownload, &QNetworkReply::finished, this, &Sound::replyFinished); + connect(soundDownload, SLOT(error(QNetworkReply::NetworkError)), this, SIGNAL(error(QNetworkReply::NetworkError))); } -void Sound::replyFinished(QNetworkReply* reply) { +void Sound::replyFinished() { + QNetworkReply* reply = reinterpret_cast(sender()); + // replace our byte array with the downloaded data QByteArray rawAudioByteArray = reply->readAll(); @@ -110,6 +113,11 @@ void Sound::replyFinished(QNetworkReply* reply) { } } +void Sound::replyError(QNetworkReply::NetworkError code) { + QNetworkReply* reply = reinterpret_cast(sender()); + qDebug() << "Error downloading sound file at" << reply->url().toString() << "-" << reply->errorString(); +} + void Sound::downSample(const QByteArray& rawAudioByteArray) { // assume that this was a RAW file and is now an array of samples that are diff --git a/libraries/audio/src/Sound.h b/libraries/audio/src/Sound.h index e1613fec51..50b2bbadaa 100644 --- a/libraries/audio/src/Sound.h +++ b/libraries/audio/src/Sound.h @@ -13,8 +13,7 @@ #define hifi_Sound_h #include - -class QNetworkReply; +#include class Sound : public QObject { Q_OBJECT @@ -31,7 +30,8 @@ private: void interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray); private slots: - void replyFinished(QNetworkReply* reply); + void replyFinished(); + void replyError(QNetworkReply::NetworkError code); }; #endif // hifi_Sound_h From 3b8cf8c172dd4470edb579c3a1b2e32220dacee5 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 18 Apr 2014 13:55:22 -0700 Subject: [PATCH 41/48] fix mixed signal and slot --- libraries/audio/src/Sound.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/audio/src/Sound.cpp b/libraries/audio/src/Sound.cpp index 3aae6b995d..793fd51a56 100644 --- a/libraries/audio/src/Sound.cpp +++ b/libraries/audio/src/Sound.cpp @@ -78,7 +78,7 @@ Sound::Sound(const QUrl& sampleURL, QObject* parent) : QNetworkReply* soundDownload = manager->get(QNetworkRequest(sampleURL)); connect(soundDownload, &QNetworkReply::finished, this, &Sound::replyFinished); - connect(soundDownload, SLOT(error(QNetworkReply::NetworkError)), this, SIGNAL(error(QNetworkReply::NetworkError))); + connect(soundDownload, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(error(QNetworkReply::NetworkError))); } void Sound::replyFinished() { From aa5d7aa0b1963b236e2385378a4c2d92a6ac1f03 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 18 Apr 2014 13:58:33 -0700 Subject: [PATCH 42/48] fix incorrectly named slot in Sound --- libraries/audio/src/Sound.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/audio/src/Sound.cpp b/libraries/audio/src/Sound.cpp index 793fd51a56..bcae878259 100644 --- a/libraries/audio/src/Sound.cpp +++ b/libraries/audio/src/Sound.cpp @@ -78,7 +78,7 @@ Sound::Sound(const QUrl& sampleURL, QObject* parent) : QNetworkReply* soundDownload = manager->get(QNetworkRequest(sampleURL)); connect(soundDownload, &QNetworkReply::finished, this, &Sound::replyFinished); - connect(soundDownload, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(error(QNetworkReply::NetworkError))); + connect(soundDownload, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(replyError(QNetworkReply::NetworkError))); } void Sound::replyFinished() { From 1a895b9342d410607c0ff92d382b181b9c84672d Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 18 Apr 2014 14:24:50 -0700 Subject: [PATCH 43/48] add property for display name to JS --- libraries/avatars/src/AvatarData.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index d1a63c9a58..250ae07b01 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -97,13 +97,14 @@ class AvatarData : public QObject { Q_PROPERTY(float audioLoudness READ getAudioLoudness WRITE setAudioLoudness) Q_PROPERTY(float audioAverageLoudness READ getAudioAverageLoudness WRITE setAudioAverageLoudness) + Q_PROPERTY(QString displayName READ getDisplayName WRITE setDisplayName) Q_PROPERTY(QString faceModelURL READ getFaceModelURLFromScript WRITE setFaceModelURLFromScript) 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); + Q_PROPERTY(QUuid sessionUUID READ getSessionUUID) public: AvatarData(); virtual ~AvatarData(); From 92f2794ea80a552f50a36ac66e43f9cea5a71e1d Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 18 Apr 2014 14:26:56 -0700 Subject: [PATCH 44/48] add a property to Sound to tell if empty --- libraries/audio/src/Sound.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/audio/src/Sound.h b/libraries/audio/src/Sound.h index 50b2bbadaa..ae2fd391a1 100644 --- a/libraries/audio/src/Sound.h +++ b/libraries/audio/src/Sound.h @@ -17,10 +17,14 @@ class Sound : public QObject { Q_OBJECT + + Q_PROPERTY(bool empty READ isEmpty) public: Sound(const QUrl& sampleURL, QObject* parent = NULL); Sound(float volume, float frequency, float duration, float decay, QObject* parent = NULL); + bool isEmpty() const { return _byteArray.isEmpty(); } + const QByteArray& getByteArray() { return _byteArray; } private: From c1e54d370c3e56adf84bcd6d2205ff229a4b35a5 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 18 Apr 2014 14:46:56 -0700 Subject: [PATCH 45/48] change Sound property to hasDownloaded --- libraries/audio/src/Sound.cpp | 5 ++++- libraries/audio/src/Sound.h | 5 +++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/libraries/audio/src/Sound.cpp b/libraries/audio/src/Sound.cpp index bcae878259..5bd63b7959 100644 --- a/libraries/audio/src/Sound.cpp +++ b/libraries/audio/src/Sound.cpp @@ -67,7 +67,8 @@ Sound::Sound(float volume, float frequency, float duration, float decay, QObject } Sound::Sound(const QUrl& sampleURL, QObject* parent) : - QObject(parent) + QObject(parent), + _hasDownloaded(false) { // assume we have a QApplication or QCoreApplication instance and use the // QNetworkAccess manager to grab the raw audio file at the given URL @@ -111,6 +112,8 @@ void Sound::replyFinished() { } else { qDebug() << "Network reply without 'Content-Type'."; } + + _hasDownloaded = true; } void Sound::replyError(QNetworkReply::NetworkError code) { diff --git a/libraries/audio/src/Sound.h b/libraries/audio/src/Sound.h index ae2fd391a1..c953ae1b33 100644 --- a/libraries/audio/src/Sound.h +++ b/libraries/audio/src/Sound.h @@ -23,13 +23,14 @@ public: Sound(const QUrl& sampleURL, QObject* parent = NULL); Sound(float volume, float frequency, float duration, float decay, QObject* parent = NULL); - bool isEmpty() const { return _byteArray.isEmpty(); } + bool hasDownloaded() const { return _hasDownloaded; } const QByteArray& getByteArray() { return _byteArray; } private: QByteArray _byteArray; - + bool _hasDownloaded; + void downSample(const QByteArray& rawAudioByteArray); void interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray); From 72fe9b94b1c25f5e11086bea97346ba9ab32c75e Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 18 Apr 2014 14:48:26 -0700 Subject: [PATCH 46/48] fix property in sound --- libraries/audio/src/Sound.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/audio/src/Sound.h b/libraries/audio/src/Sound.h index c953ae1b33..c473cdff83 100644 --- a/libraries/audio/src/Sound.h +++ b/libraries/audio/src/Sound.h @@ -18,7 +18,7 @@ class Sound : public QObject { Q_OBJECT - Q_PROPERTY(bool empty READ isEmpty) + Q_PROPERTY(bool downloaded READ hasDownloaded) public: Sound(const QUrl& sampleURL, QObject* parent = NULL); Sound(float volume, float frequency, float duration, float decay, QObject* parent = NULL); From d489e9dcd403114263c6d360b00e4aba75c3fca3 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Fri, 18 Apr 2014 23:55:45 +0200 Subject: [PATCH 47/48] Fix format --- interface/src/Application.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 592370705a..0abcd9d1af 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3552,11 +3552,11 @@ void Application::takeSnapshot() { } void Application::urlGoTo(int argc, const char * constArgv[]) { - //Gets the url (hifi://domain/destination/orientation) - QString customUrl = getCmdOption(argc, constArgv, "-url"); + //Gets the url (hifi://domain/destination/orientation) + QString customUrl = getCmdOption(argc, constArgv, "-url"); - if (customUrl.startsWith("hifi://")) { - QStringList urlParts = customUrl.remove(0, CUSTOM_URL_SCHEME.length() + 2).split('/', QString::SkipEmptyParts); + if (customUrl.startsWith("hifi://")) { + QStringList urlParts = customUrl.remove(0, CUSTOM_URL_SCHEME.length() + 2).split('/', QString::SkipEmptyParts); if (urlParts.count() > 1) { // if url has 2 or more parts, the first one is domain name QString domain = urlParts[0]; @@ -3583,6 +3583,5 @@ void Application::urlGoTo(int argc, const char * constArgv[]) { QString destination = urlParts[0]; Menu::goTo(destination); } - } } From 0a7ea17d43c7ad69645e496c458e21074d4235be Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 18 Apr 2014 15:10:15 -0700 Subject: [PATCH 48/48] fix a geometry race condition crash --- interface/src/renderer/Model.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index f8de7210ea..ef0911f673 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -468,8 +468,13 @@ void Model::clearShapes() { void Model::rebuildShapes() { clearShapes(); + + if (!_geometry) { + return; + } + const FBXGeometry& geometry = _geometry->getFBXGeometry(); - + if (geometry.joints.isEmpty()) { return; }