From c5ffe54a9131328e737506176a04c405a50f6f1f Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Mon, 14 Apr 2014 16:26:30 -0700 Subject: [PATCH 01/13] 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/13] 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/13] 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/13] 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 0f69bbe23fd5f80cdb7da7db2aec7713b203d7df Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Wed, 16 Apr 2014 20:39:55 -0700 Subject: [PATCH 05/13] 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 06/13] 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 07/13] 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 08/13] 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 09/13] 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 10/13] Make sure we call allReferencesCleared on the right thread. --- libraries/shared/src/ResourceCache.cpp | 5 +++++ libraries/shared/src/ResourceCache.h | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/libraries/shared/src/ResourceCache.cpp b/libraries/shared/src/ResourceCache.cpp index 30a725c010..04b6265513 100644 --- a/libraries/shared/src/ResourceCache.cpp +++ b/libraries/shared/src/ResourceCache.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -174,6 +175,10 @@ float Resource::getLoadPriority() { } void Resource::allReferencesCleared() { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "allReferencesCleared"); + return; + } if (_cache) { // create and reinsert new shared pointer QSharedPointer self(this, &Resource::allReferencesCleared); diff --git a/libraries/shared/src/ResourceCache.h b/libraries/shared/src/ResourceCache.h index da217516e1..0cfabd26fc 100644 --- a/libraries/shared/src/ResourceCache.h +++ b/libraries/shared/src/ResourceCache.h @@ -123,7 +123,7 @@ public: void setCache(ResourceCache* cache) { _cache = cache; } - void allReferencesCleared(); + Q_INVOKABLE void allReferencesCleared(); protected slots: From 6251b6b81939d4fbb32391cb39fae64a3701d79e Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 17 Apr 2014 13:55:16 -0700 Subject: [PATCH 11/13] Merge fix. --- assignment-client/src/Agent.cpp | 1 + libraries/fbx/src/FBXReader.cpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 2138914fe2..2e4c251005 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -163,6 +163,7 @@ void Agent::run() { QNetworkAccessManager *networkManager = new QNetworkAccessManager(this); QNetworkReply *reply = networkManager->get(QNetworkRequest(scriptURL)); QNetworkDiskCache* cache = new QNetworkDiskCache(networkManager); + QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation); cache->setCacheDirectory(!cachePath.isEmpty() ? cachePath : "agentCache"); networkManager->setCache(cache); diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index bb862ad918..eae8eb3920 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -952,6 +952,8 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) QHash yComponents; QHash zComponents; + printNode(node, 0); + QVariantHash joints = mapping.value("joint").toHash(); QString jointEyeLeftName = processID(getString(joints.value("jointEyeLeft", "jointEyeLeft"))); QString jointEyeRightName = processID(getString(joints.value("jointEyeRight", "jointEyeRight"))); From 197127fbde4d454707d45f9505e3d5726123a4ee Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 17 Apr 2014 15:42:23 -0700 Subject: [PATCH 12/13] 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 13/13] 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"