From 09fa782c616d8564d107438a5724fcefac444f33 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 19 Sep 2013 15:05:17 -0700 Subject: [PATCH] More work on FBX reading. --- assignment-client/src/avatars/AvatarMixer.cpp | 3 +- interface/src/avatar/BlendFace.cpp | 147 +++++++++++------- interface/src/avatar/BlendFace.h | 10 +- interface/src/renderer/FBXReader.cpp | 104 +++++++++++++ interface/src/renderer/FBXReader.h | 26 ++++ libraries/shared/src/PacketHeaders.h | 1 + 6 files changed, 226 insertions(+), 65 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 49c0b02d0c..ed7bf91d70 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -139,6 +139,7 @@ void AvatarMixer::run() { break; case PACKET_TYPE_AVATAR_VOXEL_URL: case PACKET_TYPE_AVATAR_FACE_VIDEO: + case PACKET_TYPE_AVATAR_FACE_URL: // grab the node ID from the packet unpackNodeId(packetData + numBytesForPacketHeader(packetData), &nodeID); @@ -158,4 +159,4 @@ void AvatarMixer::run() { } nodeList->stopSilentNodeRemovalThread(); -} \ No newline at end of file +} diff --git a/interface/src/avatar/BlendFace.cpp b/interface/src/avatar/BlendFace.cpp index 7e30cfdcda..71988a1a41 100644 --- a/interface/src/avatar/BlendFace.cpp +++ b/interface/src/avatar/BlendFace.cpp @@ -11,7 +11,6 @@ #include "Application.h" #include "BlendFace.h" #include "Head.h" -#include "renderer/FBXReader.h" using namespace fs; using namespace std; @@ -48,32 +47,27 @@ bool BlendFace::render(float alpha) { glColor4f(1.0f, 1.0f, 1.0f, alpha); // start with the base - int vertexCount = _baseVertices.size(); + int vertexCount = _geometry.vertices.size(); _blendedVertices.resize(vertexCount); - memcpy(_blendedVertices.data(), _baseVertices.data(), vertexCount * sizeof(fsVector3f)); + memcpy(_blendedVertices.data(), _geometry.vertices.constData(), vertexCount * sizeof(glm::vec3)); // blend in each coefficient const vector& coefficients = _owningHead->getBlendshapeCoefficients(); for (int i = 0; i < coefficients.size(); i++) { float coefficient = coefficients[i]; - if (coefficient == 0.0f || i >= _blendshapes.size()) { + if (coefficient == 0.0f || i >= _geometry.blendshapes.size()) { continue; } - const fsVector3f* source = _blendshapes[i].m_vertices.data(); - fsVector3f* dest = _blendedVertices.data(); - for (int j = 0; j < vertexCount; j++) { - dest->x += source->x * coefficient; - dest->y += source->y * coefficient; - dest->z += source->z * coefficient; - - source++; - dest++; + const glm::vec3* source = _geometry.blendshapes[i].vertices.constData(); + for (const int* index = _geometry.blendshapes[i].indices.constData(), + *end = index + _geometry.blendshapes[i].indices.size(); index != end; index++, source++) { + _blendedVertices[*index] += *source * coefficient; } } // update the blended vertices glBindBuffer(GL_ARRAY_BUFFER, _vboID); - glBufferSubData(GL_ARRAY_BUFFER, 0, _blendedVertices.size() * sizeof(fsVector3f), _blendedVertices.data()); + glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount * sizeof(glm::vec3), _blendedVertices.constData()); // tell OpenGL where to find vertex information glEnableClientState(GL_VERTEX_ARRAY); @@ -82,9 +76,9 @@ bool BlendFace::render(float alpha) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _iboID); - glDrawRangeElementsEXT(GL_QUADS, 0, _baseVertices.size() - 1, _quadIndexCount, GL_UNSIGNED_INT, 0); - glDrawRangeElementsEXT(GL_TRIANGLES, 0, _baseVertices.size() - 1, _triangleIndexCount, GL_UNSIGNED_INT, - (void*)(_quadIndexCount * sizeof(int))); + glDrawRangeElementsEXT(GL_QUADS, 0, vertexCount - 1, _geometry.quadIndices.size(), GL_UNSIGNED_INT, 0); + glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertexCount - 1, _geometry.triangleIndices.size(), GL_UNSIGNED_INT, + (void*)(_geometry.quadIndices.size() * sizeof(int))); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); @@ -124,51 +118,42 @@ void BlendFace::setModelURL(const QUrl& url) { connect(_modelReply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleModelReplyError())); } +glm::vec3 createVec3(const fsVector3f& vector) { + return glm::vec3(vector.x, vector.y, vector.z); +} + void BlendFace::setRig(const fsMsgRig& rig) { - if (rig.mesh().m_tris.empty()) { - // clear any existing geometry - if (_iboID != 0) { - glDeleteBuffers(1, &_iboID); - glDeleteBuffers(1, &_vboID); - _iboID = 0; + // convert to FBX geometry + FBXGeometry geometry; + + for (vector::const_iterator it = rig.mesh().m_quads.begin(), end = rig.mesh().m_quads.end(); it != end; it++) { + geometry.quadIndices.append(it->x); + geometry.quadIndices.append(it->y); + geometry.quadIndices.append(it->z); + geometry.quadIndices.append(it->w); + } + + for (vector::const_iterator it = rig.mesh().m_tris.begin(), end = rig.mesh().m_tris.end(); it != end; it++) { + geometry.triangleIndices.append(it->x); + geometry.triangleIndices.append(it->y); + geometry.triangleIndices.append(it->z); + } + + for (vector::const_iterator it = rig.mesh().m_vertex_data.m_vertices.begin(), + end = rig.mesh().m_vertex_data.m_vertices.end(); it != end; it++) { + geometry.vertices.append(glm::vec3(it->x, it->y, it->z)); + } + + for (vector::const_iterator it = rig.blendshapes().begin(), end = rig.blendshapes().end(); it != end; it++) { + FBXBlendshape blendshape; + for (int i = 0, n = it->m_vertices.size(); i < n; i++) { + blendshape.indices.append(i); + blendshape.vertices.append(createVec3(it->m_vertices[i])); } - return; + geometry.blendshapes.append(blendshape); } - if (_iboID == 0) { - glGenBuffers(1, &_iboID); - glGenBuffers(1, &_vboID); - } - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _iboID); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, rig.mesh().m_quads.size() * sizeof(fsVector4i) + - rig.mesh().m_tris.size() * sizeof(fsVector3i), NULL, GL_STATIC_DRAW); - glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, rig.mesh().m_quads.size() * sizeof(fsVector4i), rig.mesh().m_quads.data()); - glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, rig.mesh().m_quads.size() * sizeof(fsVector4i), - rig.mesh().m_tris.size() * sizeof(fsVector3i), rig.mesh().m_tris.data()); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - glBindBuffer(GL_ARRAY_BUFFER, _vboID); - glBufferData(GL_ARRAY_BUFFER, rig.mesh().m_vertex_data.m_vertices.size() * sizeof(fsVector3f), NULL, GL_DYNAMIC_DRAW); - glBindBuffer(GL_ARRAY_BUFFER, 0); - - _quadIndexCount = rig.mesh().m_quads.size() * 4; - _triangleIndexCount = rig.mesh().m_tris.size() * 3; - _baseVertices = rig.mesh().m_vertex_data.m_vertices; - _blendshapes = rig.blendshapes(); - - // subtract the neutral locations from the blend shapes; we want the deltas - int vertexCount = _baseVertices.size(); - for (vector::iterator it = _blendshapes.begin(); it != _blendshapes.end(); it++) { - const fsVector3f* neutral = _baseVertices.data(); - fsVector3f* offset = it->m_vertices.data(); - for (int i = 0; i < vertexCount; i++) { - offset->x -= neutral->x; - offset->y -= neutral->y; - offset->z -= neutral->z; - - neutral++; - offset++; - } - } + setGeometry(geometry); } void BlendFace::handleModelDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) { @@ -182,10 +167,11 @@ void BlendFace::handleModelDownloadProgress(qint64 bytesReceived, qint64 bytesTo _modelReply = 0; try { - printNode(parseFBX(entirety)); + setGeometry(extractFBXGeometry(parseFBX(entirety))); } catch (const QString& error) { qDebug() << error << "\n"; + return; } } @@ -197,3 +183,46 @@ void BlendFace::handleModelReplyError() { _modelReply = 0; } +void BlendFace::setGeometry(const FBXGeometry& geometry) { + if (geometry.vertices.isEmpty()) { + // clear any existing geometry + if (_iboID != 0) { + glDeleteBuffers(1, &_iboID); + glDeleteBuffers(1, &_vboID); + _iboID = 0; + } + return; + } + if (_iboID == 0) { + glGenBuffers(1, &_iboID); + glGenBuffers(1, &_vboID); + } + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _iboID); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, (geometry.quadIndices.size() + geometry.triangleIndices.size()) * sizeof(int), + NULL, GL_STATIC_DRAW); + glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, geometry.quadIndices.size() * sizeof(int), geometry.quadIndices.constData()); + glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, geometry.quadIndices.size() * sizeof(int), + geometry.triangleIndices.size() * sizeof(int), geometry.triangleIndices.constData()); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + + glBindBuffer(GL_ARRAY_BUFFER, _vboID); + glBufferData(GL_ARRAY_BUFFER, geometry.vertices.size() * sizeof(glm::vec3), NULL, GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + _geometry = geometry; + + // subtract the neutral locations from the blend shapes; we want the deltas + /* + int vertexCount = geometry.vertices.size(); + for (QVector::iterator it = _geometry.blendshapes.begin(); it != _geometry.blendshapes.end(); it++) { + const glm::vec3* neutral = geometry.vertices.constData(); + glm::vec3* offset = it->vertices.data(); + for (int i = 0; i < vertexCount; i++) { + *offset -= *neutral; + + neutral++; + offset++; + } + } + */ +} diff --git a/interface/src/avatar/BlendFace.h b/interface/src/avatar/BlendFace.h index 184b31a236..937d696edf 100644 --- a/interface/src/avatar/BlendFace.h +++ b/interface/src/avatar/BlendFace.h @@ -15,6 +15,7 @@ #include #include "InterfaceConfig.h" +#include "renderer/FBXReader.h" class QNetworkReply; @@ -45,6 +46,8 @@ private slots: private: + void setGeometry(const FBXGeometry& geometry); + Head* _owningHead; QUrl _modelURL; @@ -54,11 +57,8 @@ private: GLuint _iboID; GLuint _vboID; - GLsizei _quadIndexCount; - GLsizei _triangleIndexCount; - std::vector _baseVertices; - std::vector _blendshapes; - std::vector _blendedVertices; + FBXGeometry _geometry; + QVector _blendedVertices; }; #endif /* defined(__interface__BlendFace__) */ diff --git a/interface/src/renderer/FBXReader.cpp b/interface/src/renderer/FBXReader.cpp index 385ed17d4d..8c7350e790 100644 --- a/interface/src/renderer/FBXReader.cpp +++ b/interface/src/renderer/FBXReader.cpp @@ -14,6 +14,8 @@ #include "FBXReader.h" +using namespace std; + FBXNode parseFBX(const QByteArray& data) { QBuffer buffer(const_cast(&data)); buffer.open(QIODevice::ReadOnly); @@ -154,6 +156,9 @@ FBXNode parseFBX(QIODevice* device) { QDataStream in(device); in.setByteOrder(QDataStream::LittleEndian); + // see http://code.blender.org/index.php/2013/08/fbx-binary-file-format-specification/ for an explanation + // of the FBX format + // verify the prolog const QByteArray EXPECTED_PROLOG = "Kaydara FBX Binary "; if (device->read(EXPECTED_PROLOG.size()) != EXPECTED_PROLOG) { @@ -179,6 +184,104 @@ FBXNode parseFBX(QIODevice* device) { return top; } +QVector createVec3Vector(const QVector& doubleVector) { + QVector values; + for (const double* it = doubleVector.constData(), *end = it + doubleVector.size(); it != end; ) { + values.append(glm::vec3(*it++, *it++, *it++)); + } + return values; +} + +FBXGeometry extractFBXGeometry(const FBXNode& node) { + FBXGeometry geometry; + + // "Objects" + // "Geometry" QVariant(qlonglong, 5386006736) QVariant(QByteArray, "QVariant(QByteArray, "Mesh") + // "Vertices" QVector + // "PolygonVertexIndex" QVector + // "LayerElementNormal" + // "Normals" QVector + // "Geometry" QVariant(qlonglong, 5470612480) QVariant(QByteArray, "BrowsD_LQVariant(QByteArray, "Shape") + // "Vertices" QVector + // "Normals" QVector + + foreach (const FBXNode& child, node.children) { + if (child.name == "Objects") { + foreach (const FBXNode& object, child.children) { + if (object.name == "Geometry") { + if (object.properties.at(2) == "Mesh") { + QVector vertices; + QVector polygonIndices; + foreach (const FBXNode& data, object.children) { + if (data.name == "Vertices") { + geometry.vertices = createVec3Vector(data.properties.at(0).value >()); + + } else if (data.name == "PolygonVertexIndex") { + polygonIndices = data.properties.at(0).value >(); + + } else if (data.name == "LayerElementNormal") { + foreach (const FBXNode& subdata, data.children) { + if (subdata.name == "Normals") { + geometry.normals = createVec3Vector( + subdata.properties.at(0).value >()); + } + } + } + } + // convert the polygons to quads and triangles + for (const int* beginIndex = polygonIndices.constData(), *end = beginIndex + polygonIndices.size(); + beginIndex != end; ) { + const int* endIndex = beginIndex; + while (*endIndex++ >= 0); + + if (endIndex - beginIndex == 4) { + geometry.quadIndices.append(*beginIndex++); + geometry.quadIndices.append(*beginIndex++); + geometry.quadIndices.append(*beginIndex++); + geometry.quadIndices.append(-*beginIndex++ - 1); + + } else { + for (const int* nextIndex = beginIndex + 1;; ) { + geometry.triangleIndices.append(*beginIndex); + geometry.triangleIndices.append(*nextIndex++); + if (*nextIndex >= 0) { + geometry.triangleIndices.append(*nextIndex); + } else { + geometry.triangleIndices.append(-*nextIndex - 1); + break; + } + } + beginIndex = endIndex; + } + } + + } else { // object.properties.at(2) == "Shape" + FBXBlendshape blendshape; + foreach (const FBXNode& data, object.children) { + if (data.name == "Indexes") { + blendshape.indices = data.properties.at(0).value >(); + + } else if (data.name == "Vertices") { + blendshape.vertices = createVec3Vector(data.properties.at(0).value >()); + + } else if (data.name == "Normals") { + blendshape.normals = createVec3Vector(data.properties.at(0).value >()); + } + } + + // the name is followed by a null and some type info + QByteArray name = object.properties.at(1).toByteArray(); + name = name.left(name.indexOf('\0')); + geometry.blendshapes.append(blendshape); + } + } + } + } + } + + return geometry; +} + void printNode(const FBXNode& node, int indent) { QByteArray spaces(indent, ' '); qDebug("%s%s: ", spaces.data(), node.name.data()); @@ -190,3 +293,4 @@ void printNode(const FBXNode& node, int indent) { printNode(child, indent + 1); } } + diff --git a/interface/src/renderer/FBXReader.h b/interface/src/renderer/FBXReader.h index 92fa293560..52447d20b4 100644 --- a/interface/src/renderer/FBXReader.h +++ b/interface/src/renderer/FBXReader.h @@ -12,6 +12,8 @@ #include #include +#include + class QIODevice; class FBXNode; @@ -27,6 +29,27 @@ public: FBXNodeList children; }; +/// A single blendshape extracted from an FBX document. +class FBXBlendshape { +public: + + QVector indices; + QVector vertices; + QVector normals; +}; + +/// Base geometry with blendshapes mapped by name. +class FBXGeometry { +public: + + QVector quadIndices; + QVector triangleIndices; + QVector vertices; + QVector normals; + + QVector blendshapes; +}; + /// Parses the input from the supplied data as an FBX file. /// \exception QString if an error occurs in parsing FBXNode parseFBX(const QByteArray& data); @@ -35,6 +58,9 @@ FBXNode parseFBX(const QByteArray& data); /// \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); #endif /* defined(__interface__FBXReader__) */ diff --git a/libraries/shared/src/PacketHeaders.h b/libraries/shared/src/PacketHeaders.h index 10ddbf56ad..59379d5dd8 100644 --- a/libraries/shared/src/PacketHeaders.h +++ b/libraries/shared/src/PacketHeaders.h @@ -31,6 +31,7 @@ const PACKET_TYPE PACKET_TYPE_VOXEL_DATA_MONOCHROME = 'v'; const PACKET_TYPE PACKET_TYPE_BULK_AVATAR_DATA = 'X'; const PACKET_TYPE PACKET_TYPE_AVATAR_VOXEL_URL = 'U'; const PACKET_TYPE PACKET_TYPE_AVATAR_FACE_VIDEO = 'F'; +const PACKET_TYPE PACKET_TYPE_AVATAR_FACE_URL = 'f'; const PACKET_TYPE PACKET_TYPE_TRANSMITTER_DATA_V2 = 'T'; const PACKET_TYPE PACKET_TYPE_ENVIRONMENT_DATA = 'e'; const PACKET_TYPE PACKET_TYPE_DOMAIN_LIST_REQUEST = 'L';