diff --git a/interface/src/renderer/FBXReader.cpp b/interface/src/renderer/FBXReader.cpp index fdce876d09..9b92c531cd 100644 --- a/interface/src/renderer/FBXReader.cpp +++ b/interface/src/renderer/FBXReader.cpp @@ -19,12 +19,6 @@ using namespace std; -FBXNode parseFBX(const QByteArray& data) { - QBuffer buffer(const_cast(&data)); - buffer.open(QIODevice::ReadOnly); - return parseFBX(&buffer); -} - template QVariant readArray(QDataStream& in) { quint32 arrayLength; quint32 encoding; @@ -189,6 +183,41 @@ FBXNode parseFBX(QIODevice* device) { return top; } +QVariantHash parseMapping(QIODevice* device) { + QVariantHash properties; + + QByteArray line; + while (!(line = device->readLine()).isEmpty()) { + if ((line = line.trimmed()).startsWith('#')) { + continue; // comment + } + QList sections = line.split('='); + if (sections.size() < 2) { + continue; + } + QByteArray name = sections.at(0).trimmed(); + if (sections.size() == 2) { + properties.insert(name, sections.at(1).trimmed()); + + } else if (sections.size() == 3) { + QVariantHash heading = properties.value(name).toHash(); + heading.insert(sections.at(1).trimmed(), sections.at(2).trimmed()); + properties.insert(name, heading); + + } else if (sections.size() >= 4) { + QVariantHash heading = properties.value(name).toHash(); + QVariantList contents; + for (int i = 2; i < sections.size(); i++) { + contents.append(sections.at(i).trimmed()); + } + heading.insertMulti(sections.at(1).trimmed(), contents); + properties.insert(name, heading); + } + } + + return properties; +} + QVector createVec3Vector(const QVector& doubleVector) { QVector values; for (const double* it = doubleVector.constData(), *end = it + doubleVector.size(); it != end; ) { @@ -269,19 +298,6 @@ const char* FACESHIFT_BLENDSHAPES[] = { "" }; -QHash createBlendshapeMap() { - QHash map; - for (int i = 0;; i++) { - QByteArray name = FACESHIFT_BLENDSHAPES[i]; - if (name != "") { - map.insert(name, i); - - } else { - return map; - } - } -} - class Transform { public: bool inheritScale; @@ -315,11 +331,10 @@ glm::mat4 getGlobalTransform(const QMultiHash& parentMap, const class ExtractedBlendshape { public: qint64 id; - int index; FBXBlendshape blendshape; }; -FBXGeometry extractFBXGeometry(const FBXNode& node) { +FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) { QHash meshes; QVector blendshapes; QMultiHash parentMap; @@ -329,10 +344,35 @@ FBXGeometry extractFBXGeometry(const FBXNode& node) { QHash textureFilenames; QHash diffuseTextures; QHash bumpTextures; + + QVariantHash joints = mapping.value("joint").toHash(); + QByteArray jointEyeLeftName = joints.value("jointEyeLeft", "jointEyeLeft").toByteArray(); + QByteArray jointEyeRightName = joints.value("jointEyeRight", "jointEyeRight").toByteArray(); + QByteArray jointNeckName = joints.value("jointNeck", "jointNeck").toByteArray(); qint64 jointEyeLeftID = 0; qint64 jointEyeRightID = 0; qint64 jointNeckID = 0; + QVariantHash blendshapeMappings = mapping.value("bs").toHash(); + QHash > blendshapeIndices; + for (int i = 0;; i++) { + QByteArray blendshapeName = FACESHIFT_BLENDSHAPES[i]; + if (blendshapeName.isEmpty()) { + break; + } + QList mappings = blendshapeMappings.values(blendshapeName); + if (mappings.isEmpty()) { + blendshapeIndices.insert("ExpressionBlendshapes." + blendshapeName, QPair(i, 1.0f)); + } else { + foreach (const QVariant& mapping, mappings) { + QVariantList blendshapeMapping = mapping.toList(); + blendshapeIndices.insert(blendshapeMapping.at(0).toByteArray(), + QPair(i, blendshapeMapping.at(1).toFloat())); + } + } + } + QHash > blendshapeChannelIndices; + foreach (const FBXNode& child, node.children) { if (child.name == "Objects") { foreach (const FBXNode& object, child.children) { @@ -387,14 +427,16 @@ FBXGeometry extractFBXGeometry(const FBXNode& node) { } // same with the tex coords - mesh.texCoords.resize(mesh.vertices.size()); - for (int i = 0, n = polygonIndices.size(); i < n; i++) { - int index = polygonIndices.at(i); - int texCoordIndex = texCoordIndices.at(i); - if (texCoordIndex >= 0) { - mesh.texCoords[index < 0 ? (-index - 1) : index] = texCoords.at(texCoordIndex); - } - } + if (!texCoordIndices.isEmpty()) { + mesh.texCoords.resize(mesh.vertices.size()); + for (int i = 0, n = polygonIndices.size(); i < n; i++) { + int index = polygonIndices.at(i); + int texCoordIndex = texCoordIndices.at(i); + if (texCoordIndex >= 0) { + mesh.texCoords[index < 0 ? (-index - 1) : index] = texCoords.at(texCoordIndex); + } + } + } // convert the polygons to quads and triangles for (const int* beginIndex = polygonIndices.constData(), *end = beginIndex + polygonIndices.size(); @@ -441,22 +483,18 @@ FBXGeometry extractFBXGeometry(const FBXNode& node) { } } - // the name is followed by a null and some type info - QByteArray name = object.properties.at(1).toByteArray(); - static QHash blendshapeMap = createBlendshapeMap(); - extracted.index = blendshapeMap.value(name.left(name.indexOf('\0'))); - blendshapes.append(extracted); } } else if (object.name == "Model") { QByteArray name = object.properties.at(1).toByteArray(); - if (name.startsWith("jointEyeLeft") || name.startsWith("EyeL") || name.startsWith("joint_Leye")) { + name = name.left(name.indexOf('\0')); + if (name == jointEyeLeftName || name == "EyeL" || name == "joint_Leye") { jointEyeLeftID = object.properties.at(0).value(); - } else if (name.startsWith("jointEyeRight") || name.startsWith("EyeR") || name.startsWith("joint_Reye")) { + } else if (name == jointEyeRightName || name == "EyeR" || name == "joint_Reye") { jointEyeRightID = object.properties.at(0).value(); - } else if (name.startsWith("jointNeck") || name.startsWith("NeckRot") || name.startsWith("joint_neck")) { + } else if (name == jointNeckName || name == "NeckRot" || name == "joint_neck") { jointNeckID = object.properties.at(0).value(); } glm::vec3 translation; @@ -526,12 +564,18 @@ FBXGeometry extractFBXGeometry(const FBXNode& node) { subobject.properties.at(0).toByteArray()); } } - } else if (object.name == "Deformer" && object.properties.at(2) == "Cluster") { - foreach (const FBXNode& subobject, object.children) { - if (subobject.name == "TransformLink") { - QVector values = subobject.properties.at(0).value >(); - transformLinkMatrices.insert(object.properties.at(0).value(), createMat4(values)); + } else if (object.name == "Deformer") { + if (object.properties.at(2) == "Cluster") { + foreach (const FBXNode& subobject, object.children) { + if (subobject.name == "TransformLink") { + QVector values = subobject.properties.at(0).value >(); + transformLinkMatrices.insert(object.properties.at(0).value(), createMat4(values)); + } } + } else if (object.properties.at(2) == "BlendShapeChannel") { + QByteArray name = object.properties.at(1).toByteArray(); + blendshapeChannelIndices.insert(object.properties.at(0).value(), + blendshapeIndices.value(name.left(name.indexOf('\0')))); } } } @@ -558,13 +602,21 @@ FBXGeometry extractFBXGeometry(const FBXNode& node) { // assign the blendshapes to their corresponding meshes foreach (const ExtractedBlendshape& extracted, blendshapes) { qint64 blendshapeChannelID = parentMap.value(extracted.id); + QPair index = blendshapeChannelIndices.value(blendshapeChannelID); qint64 blendshapeID = parentMap.value(blendshapeChannelID); qint64 meshID = parentMap.value(blendshapeID); FBXMesh& mesh = meshes[meshID]; - mesh.blendshapes.resize(max(mesh.blendshapes.size(), extracted.index + 1)); - mesh.blendshapes[extracted.index] = extracted.blendshape; + mesh.blendshapes.resize(max(mesh.blendshapes.size(), index.first + 1)); + mesh.blendshapes[index.first] = extracted.blendshape; } + // get offset transform from mapping + float offsetScale = mapping.value("scale", 1.0f).toFloat(); + glm::mat4 offset = glm::translate(mapping.value("tx").toFloat(), mapping.value("ty").toFloat(), + mapping.value("tz").toFloat()) * glm::mat4_cast(glm::quat(glm::radians(glm::vec3(mapping.value("rx").toFloat(), + mapping.value("ry").toFloat(), mapping.value("rz").toFloat())))) * + glm::scale(offsetScale, offsetScale, offsetScale); + // as a temporary hack, put the mesh with the most blendshapes on top; assume it to be the face FBXGeometry geometry; int mostBlendshapes = 0; @@ -601,11 +653,11 @@ FBXGeometry extractFBXGeometry(const FBXNode& node) { // see http://stackoverflow.com/questions/13566608/loading-skinning-information-from-fbx for a discussion // of skinning information in FBX - glm::mat4 jointTransform = getGlobalTransform(parentMap, localTransforms, jointID); + glm::mat4 jointTransform = offset * getGlobalTransform(parentMap, localTransforms, jointID); mesh.transform = jointTransform * glm::inverse(transformLinkMatrices.value(clusterID)) * modelTransform; // extract translation component for pivot - glm::mat4 jointTransformScaled = getGlobalTransform(parentMap, localTransforms, jointID, true); + glm::mat4 jointTransformScaled = offset * getGlobalTransform(parentMap, localTransforms, jointID, true); mesh.pivot = glm::vec3(jointTransformScaled[3][0], jointTransformScaled[3][1], jointTransformScaled[3][2]); } } @@ -620,7 +672,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node) { } // extract translation component for neck pivot - glm::mat4 neckTransform = getGlobalTransform(parentMap, localTransforms, jointNeckID, true); + glm::mat4 neckTransform = offset * getGlobalTransform(parentMap, localTransforms, jointNeckID, true); geometry.neckPivot = glm::vec3(neckTransform[3][0], neckTransform[3][1], neckTransform[3][2]); return geometry; @@ -638,3 +690,13 @@ void printNode(const FBXNode& node, int indent) { } } +FBXGeometry readFBX(const QByteArray& model, const QByteArray& mapping) { + QBuffer modelBuffer(const_cast(&model)); + modelBuffer.open(QIODevice::ReadOnly); + + QBuffer mappingBuffer(const_cast(&mapping)); + mappingBuffer.open(QIODevice::ReadOnly); + + return extractFBXGeometry(parseFBX(&modelBuffer), parseMapping(&mappingBuffer)); +} + diff --git a/interface/src/renderer/FBXReader.h b/interface/src/renderer/FBXReader.h index bbdbe4fdaa..f84a06468e 100644 --- a/interface/src/renderer/FBXReader.h +++ b/interface/src/renderer/FBXReader.h @@ -14,8 +14,6 @@ #include -class QIODevice; - class FBXNode; typedef QList FBXNodeList; @@ -68,17 +66,8 @@ public: glm::vec3 neckPivot; }; -/// Parses the input from the supplied data as an FBX file. +/// Reads FBX geometry from the supplied model and mapping data. /// \exception QString if an error occurs in parsing -FBXNode parseFBX(const QByteArray& data); - -/// Parses the input from the supplied device as an FBX file. -/// \exception QString if an error occurs in parsing -FBXNode parseFBX(QIODevice* device); - -/// Extracts the geometry from a parsed FBX node. -FBXGeometry extractFBXGeometry(const FBXNode& node); - -void printNode(const FBXNode& node, int indent = 0); +FBXGeometry readFBX(const QByteArray& model, const QByteArray& mapping); #endif /* defined(__interface__FBXReader__) */ diff --git a/interface/src/renderer/GeometryCache.cpp b/interface/src/renderer/GeometryCache.cpp index 5045b8b447..482545b91d 100644 --- a/interface/src/renderer/GeometryCache.cpp +++ b/interface/src/renderer/GeometryCache.cpp @@ -250,21 +250,37 @@ QSharedPointer GeometryCache::getGeometry(const QUrl& url) { return geometry; } -NetworkGeometry::NetworkGeometry(const QUrl& url) : _reply(NULL) { +NetworkGeometry::NetworkGeometry(const QUrl& url) : + _modelReply(NULL), + _mappingReply(NULL) +{ if (!url.isValid()) { return; } - QNetworkRequest request(url); - request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); - _reply = Application::getInstance()->getNetworkAccessManager()->get(request); + QNetworkRequest modelRequest(url); + modelRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); + _modelReply = Application::getInstance()->getNetworkAccessManager()->get(modelRequest); - connect(_reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(handleDownloadProgress(qint64,qint64))); - connect(_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleReplyError())); + connect(_modelReply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(maybeReadModelWithMapping())); + connect(_modelReply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleModelReplyError())); + + QUrl mappingURL = url; + QString path = url.path(); + mappingURL.setPath(path.left(path.lastIndexOf('.')) + ".fst"); + QNetworkRequest mappingRequest(mappingURL); + mappingRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); + _mappingReply = Application::getInstance()->getNetworkAccessManager()->get(mappingRequest); + + connect(_mappingReply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(maybeReadModelWithMapping())); + connect(_mappingReply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleMappingReplyError())); } NetworkGeometry::~NetworkGeometry() { - if (_reply != NULL) { - delete _reply; + if (_modelReply != NULL) { + delete _modelReply; + } + if (_mappingReply != NULL) { + delete _mappingReply; } foreach (const NetworkMesh& mesh, _meshes) { glDeleteBuffers(1, &mesh.indexBufferID); @@ -272,19 +288,43 @@ NetworkGeometry::~NetworkGeometry() { } } -void NetworkGeometry::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) { - if (bytesReceived < bytesTotal && !_reply->isFinished()) { +void NetworkGeometry::handleModelReplyError() { + qDebug() << _modelReply->errorString() << "\n"; + + _modelReply->disconnect(this); + _modelReply->deleteLater(); + _modelReply = NULL; +} + +void NetworkGeometry::handleMappingReplyError() { + _mappingReply->disconnect(this); + _mappingReply->deleteLater(); + _mappingReply = NULL; + + maybeReadModelWithMapping(); +} + +void NetworkGeometry::maybeReadModelWithMapping() { + if (_modelReply == NULL || !_modelReply->isFinished() || (_mappingReply != NULL && !_mappingReply->isFinished())) { return; } - - QUrl url = _reply->url(); - QByteArray entirety = _reply->readAll(); - _reply->disconnect(this); - _reply->deleteLater(); - _reply = NULL; + + QUrl url = _modelReply->url(); + QByteArray model = _modelReply->readAll(); + _modelReply->disconnect(this); + _modelReply->deleteLater(); + _modelReply = NULL; + + QByteArray mapping; + if (_mappingReply != NULL) { + mapping = _mappingReply->readAll(); + _mappingReply->disconnect(this); + _mappingReply->deleteLater(); + _mappingReply = NULL; + } try { - _geometry = extractFBXGeometry(parseFBX(entirety)); + _geometry = readFBX(model, mapping); } catch (const QString& error) { qDebug() << "Error reading " << url << ": " << error << "\n"; @@ -338,11 +378,3 @@ void NetworkGeometry::handleDownloadProgress(qint64 bytesReceived, qint64 bytesT _meshes.append(networkMesh); } } - -void NetworkGeometry::handleReplyError() { - qDebug() << _reply->errorString() << "\n"; - - _reply->disconnect(this); - _reply->deleteLater(); - _reply = NULL; -} diff --git a/interface/src/renderer/GeometryCache.h b/interface/src/renderer/GeometryCache.h index f885f005b6..cf868fb69b 100644 --- a/interface/src/renderer/GeometryCache.h +++ b/interface/src/renderer/GeometryCache.h @@ -64,12 +64,14 @@ public: private slots: - void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); - void handleReplyError(); + void handleModelReplyError(); + void handleMappingReplyError(); + void maybeReadModelWithMapping(); private: - QNetworkReply* _reply; + QNetworkReply* _modelReply; + QNetworkReply* _mappingReply; FBXGeometry _geometry; QVector _meshes;