From ba25087b3ea473060774ca0b61e21d9a5474feb9 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 15 Oct 2013 15:58:34 -0700 Subject: [PATCH] Support for multiple mesh "parts" (with different materials), fix for untextured meshes. --- interface/src/avatar/BlendFace.cpp | 58 +++++++------ interface/src/avatar/BlendFace.h | 2 +- interface/src/renderer/FBXReader.cpp | 102 +++++++++++++---------- interface/src/renderer/FBXReader.h | 26 ++++-- interface/src/renderer/GeometryCache.cpp | 66 ++++++++++----- interface/src/renderer/GeometryCache.h | 11 ++- interface/src/renderer/TextureCache.cpp | 26 +++++- interface/src/renderer/TextureCache.h | 6 +- 8 files changed, 191 insertions(+), 106 deletions(-) diff --git a/interface/src/avatar/BlendFace.cpp b/interface/src/avatar/BlendFace.cpp index 8615279bbb..8f43752736 100644 --- a/interface/src/avatar/BlendFace.cpp +++ b/interface/src/avatar/BlendFace.cpp @@ -236,10 +236,11 @@ bool BlendFace::render(float alpha) { glBindBuffer(GL_ARRAY_BUFFER, 0); } _blendedVertexBufferIDs.append(id); + + QVector > dilated; + dilated.resize(mesh.parts.size()); + _dilatedTextures.append(dilated); } - - // make sure we have the right number of dilated texture pointers - _dilatedTextures.resize(geometry.meshes.size()); } glEnableClientState(GL_VERTEX_ARRAY); @@ -255,23 +256,6 @@ bool BlendFace::render(float alpha) { const FBXMesh& mesh = geometry.meshes.at(i); int vertexCount = mesh.vertices.size(); - // apply eye rotation if appropriate - Texture* texture = networkMesh.diffuseTexture.data(); - if (mesh.isEye) { - if (texture != NULL) { - texture = (_dilatedTextures[i] = static_cast(texture)->getDilatedTexture( - _owningHead->getPupilDilation())).data(); - } - } - - // apply material properties - glm::vec4 diffuse = glm::vec4(mesh.diffuseColor, alpha); - glm::vec4 specular = glm::vec4(mesh.specularColor, alpha); - glMaterialfv(GL_FRONT, GL_AMBIENT, (const float*)&diffuse); - glMaterialfv(GL_FRONT, GL_DIFFUSE, (const float*)&diffuse); - glMaterialfv(GL_FRONT, GL_SPECULAR, (const float*)&specular); - glMaterialf(GL_FRONT, GL_SHININESS, mesh.shininess); - glBindBuffer(GL_ARRAY_BUFFER, networkMesh.vertexBufferID); const MeshState& state = _meshStates.at(i); @@ -297,8 +281,6 @@ bool BlendFace::render(float alpha) { _program.bind(); } - glBindTexture(GL_TEXTURE_2D, texture == NULL ? 0 : texture->getID()); - if (mesh.blendshapes.isEmpty() && mesh.springiness == 0.0f) { glTexCoordPointer(2, GL_FLOAT, 0, (void*)(vertexCount * 2 * sizeof(glm::vec3))); @@ -343,10 +325,36 @@ bool BlendFace::render(float alpha) { glVertexPointer(3, GL_FLOAT, 0, 0); glNormalPointer(GL_FLOAT, 0, (void*)(vertexCount * sizeof(glm::vec3))); - glDrawRangeElementsEXT(GL_QUADS, 0, vertexCount - 1, mesh.quadIndices.size(), GL_UNSIGNED_INT, 0); - glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertexCount - 1, mesh.triangleIndices.size(), - GL_UNSIGNED_INT, (void*)(mesh.quadIndices.size() * sizeof(int))); + qint64 offset = 0; + for (int j = 0; j < networkMesh.parts.size(); j++) { + const NetworkMeshPart& networkPart = networkMesh.parts.at(j); + const FBXMeshPart& part = mesh.parts.at(j); + // apply material properties + glm::vec4 diffuse = glm::vec4(part.diffuseColor, alpha); + glm::vec4 specular = glm::vec4(part.specularColor, alpha); + glMaterialfv(GL_FRONT, GL_AMBIENT, (const float*)&diffuse); + glMaterialfv(GL_FRONT, GL_DIFFUSE, (const float*)&diffuse); + glMaterialfv(GL_FRONT, GL_SPECULAR, (const float*)&specular); + glMaterialf(GL_FRONT, GL_SHININESS, part.shininess); + + Texture* texture = networkPart.diffuseTexture.data(); + if (mesh.isEye) { + if (texture != NULL) { + texture = (_dilatedTextures[i][j] = static_cast(texture)->getDilatedTexture( + _owningHead->getPupilDilation())).data(); + } + } + glBindTexture(GL_TEXTURE_2D, texture == NULL ? Application::getInstance()->getTextureCache()->getWhiteTextureID() : + texture->getID()); + + glDrawRangeElementsEXT(GL_QUADS, 0, vertexCount - 1, part.quadIndices.size(), GL_UNSIGNED_INT, (void*)offset); + offset += part.quadIndices.size() * sizeof(int); + glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertexCount - 1, part.triangleIndices.size(), + GL_UNSIGNED_INT, (void*)offset); + offset += part.triangleIndices.size() * sizeof(int); + } + if (state.worldSpaceVertices.isEmpty()) { if (state.clusterMatrices.size() > 1) { _skinProgram.disableAttributeArray(_clusterIndicesLocation); diff --git a/interface/src/avatar/BlendFace.h b/interface/src/avatar/BlendFace.h index 6add54d6b8..4721031107 100644 --- a/interface/src/avatar/BlendFace.h +++ b/interface/src/avatar/BlendFace.h @@ -76,7 +76,7 @@ private: QVector _meshStates; QVector _blendedVertexBufferIDs; - QVector > _dilatedTextures; + QVector > > _dilatedTextures; bool _resetStates; QVector _blendedVertices; diff --git a/interface/src/renderer/FBXReader.cpp b/interface/src/renderer/FBXReader.cpp index 4e98e81813..19b502503f 100644 --- a/interface/src/renderer/FBXReader.cpp +++ b/interface/src/renderer/FBXReader.cpp @@ -426,6 +426,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) QVector normalIndices; QVector texCoords; QVector texCoordIndices; + QVector materials; foreach (const FBXNode& data, object.children) { if (data.name == "Vertices") { mesh.vertices = createVec3Vector(data.properties.at(0).value >()); @@ -459,6 +460,12 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) texCoordIndices = subdata.properties.at(0).value >(); } } + } else if (data.name == "LayerElementMaterial") { + foreach (const FBXNode& subdata, data.children) { + if (subdata.name == "Materials") { + materials = subdata.properties.at(0).value >(); + } + } } } @@ -494,25 +501,30 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) } // convert the polygons to quads and triangles + int polygonIndex = 0; for (const int* beginIndex = polygonIndices.constData(), *end = beginIndex + polygonIndices.size(); - beginIndex != end; ) { + beginIndex != end; polygonIndex++) { const int* endIndex = beginIndex; while (*endIndex++ >= 0); + int materialIndex = (polygonIndex < materials.size()) ? materials.at(polygonIndex) : 0; + mesh.parts.resize(max(mesh.parts.size(), materialIndex + 1)); + FBXMeshPart& part = mesh.parts[materialIndex]; + if (endIndex - beginIndex == 4) { - mesh.quadIndices.append(*beginIndex++); - mesh.quadIndices.append(*beginIndex++); - mesh.quadIndices.append(*beginIndex++); - mesh.quadIndices.append(-*beginIndex++ - 1); + part.quadIndices.append(*beginIndex++); + part.quadIndices.append(*beginIndex++); + part.quadIndices.append(*beginIndex++); + part.quadIndices.append(-*beginIndex++ - 1); } else { for (const int* nextIndex = beginIndex + 1;; ) { - mesh.triangleIndices.append(*beginIndex); - mesh.triangleIndices.append(*nextIndex++); + part.triangleIndices.append(*beginIndex); + part.triangleIndices.append(*nextIndex++); if (*nextIndex >= 0) { - mesh.triangleIndices.append(*nextIndex); + part.triangleIndices.append(*nextIndex); } else { - mesh.triangleIndices.append(-*nextIndex - 1); + part.triangleIndices.append(-*nextIndex - 1); break; } } @@ -754,21 +766,23 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) glm::mat4 modelTransform = getGlobalTransform(parentMap, models, modelID); // look for textures, material properties + int partIndex = 0; foreach (qint64 childID, childMap.values(modelID)) { - if (!materials.contains(childID)) { + if (!materials.contains(childID) || partIndex >= mesh.parts.size()) { continue; } Material material = materials.value(childID); - mesh.diffuseColor = material.diffuse; - mesh.specularColor = material.specular; - mesh.shininess = material.shininess; + FBXMeshPart& part = mesh.parts[partIndex++]; + part.diffuseColor = material.diffuse; + part.specularColor = material.specular; + part.shininess = material.shininess; qint64 diffuseTextureID = diffuseTextures.value(childID); if (diffuseTextureID != 0) { - mesh.diffuseFilename = textureFilenames.value(diffuseTextureID); + part.diffuseFilename = textureFilenames.value(diffuseTextureID); } qint64 bumpTextureID = bumpTextures.value(childID); if (bumpTextureID != 0) { - mesh.normalFilename = textureFilenames.value(bumpTextureID); + part.normalFilename = textureFilenames.value(bumpTextureID); } } @@ -832,34 +846,36 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) QSet > edges; mesh.vertexConnections.resize(mesh.vertices.size()); - for (int i = 0; i < mesh.quadIndices.size(); i += 4) { - int index0 = mesh.quadIndices.at(i); - int index1 = mesh.quadIndices.at(i + 1); - int index2 = mesh.quadIndices.at(i + 2); - int index3 = mesh.quadIndices.at(i + 3); - - edges.insert(QPair(qMin(index0, index1), qMax(index0, index1))); - edges.insert(QPair(qMin(index1, index2), qMax(index1, index2))); - edges.insert(QPair(qMin(index2, index3), qMax(index2, index3))); - edges.insert(QPair(qMin(index3, index0), qMax(index3, index0))); - - mesh.vertexConnections[index0].append(QPair(index3, index1)); - mesh.vertexConnections[index1].append(QPair(index0, index2)); - mesh.vertexConnections[index2].append(QPair(index1, index3)); - mesh.vertexConnections[index3].append(QPair(index2, index0)); - } - for (int i = 0; i < mesh.triangleIndices.size(); i += 3) { - int index0 = mesh.triangleIndices.at(i); - int index1 = mesh.triangleIndices.at(i + 1); - int index2 = mesh.triangleIndices.at(i + 2); - - edges.insert(QPair(qMin(index0, index1), qMax(index0, index1))); - edges.insert(QPair(qMin(index1, index2), qMax(index1, index2))); - edges.insert(QPair(qMin(index2, index0), qMax(index2, index0))); - - mesh.vertexConnections[index0].append(QPair(index2, index1)); - mesh.vertexConnections[index1].append(QPair(index0, index2)); - mesh.vertexConnections[index2].append(QPair(index1, index0)); + foreach (const FBXMeshPart& part, mesh.parts) { + for (int i = 0; i < part.quadIndices.size(); i += 4) { + int index0 = part.quadIndices.at(i); + int index1 = part.quadIndices.at(i + 1); + int index2 = part.quadIndices.at(i + 2); + int index3 = part.quadIndices.at(i + 3); + + edges.insert(QPair(qMin(index0, index1), qMax(index0, index1))); + edges.insert(QPair(qMin(index1, index2), qMax(index1, index2))); + edges.insert(QPair(qMin(index2, index3), qMax(index2, index3))); + edges.insert(QPair(qMin(index3, index0), qMax(index3, index0))); + + mesh.vertexConnections[index0].append(QPair(index3, index1)); + mesh.vertexConnections[index1].append(QPair(index0, index2)); + mesh.vertexConnections[index2].append(QPair(index1, index3)); + mesh.vertexConnections[index3].append(QPair(index2, index0)); + } + for (int i = 0; i < part.triangleIndices.size(); i += 3) { + int index0 = part.triangleIndices.at(i); + int index1 = part.triangleIndices.at(i + 1); + int index2 = part.triangleIndices.at(i + 2); + + edges.insert(QPair(qMin(index0, index1), qMax(index0, index1))); + edges.insert(QPair(qMin(index1, index2), qMax(index1, index2))); + edges.insert(QPair(qMin(index2, index0), qMax(index2, index0))); + + mesh.vertexConnections[index0].append(QPair(index2, index1)); + mesh.vertexConnections[index1].append(QPair(index0, index2)); + mesh.vertexConnections[index2].append(QPair(index1, index0)); + } } for (QSet >::const_iterator edge = edges.constBegin(); edge != edges.constEnd(); edge++) { diff --git a/interface/src/renderer/FBXReader.h b/interface/src/renderer/FBXReader.h index f983715d60..85e463f8bf 100644 --- a/interface/src/renderer/FBXReader.h +++ b/interface/src/renderer/FBXReader.h @@ -56,12 +56,27 @@ public: glm::mat4 inverseBindMatrix; }; -/// A single mesh (with optional blendshapes) extracted from an FBX document. -class FBXMesh { +/// A single part of a mesh (with the same material). +class FBXMeshPart { public: QVector quadIndices; QVector triangleIndices; + + glm::vec3 diffuseColor; + glm::vec3 specularColor; + float shininess; + + QByteArray diffuseFilename; + QByteArray normalFilename; +}; + +/// A single mesh (with optional blendshapes) extracted from an FBX document. +class FBXMesh { +public: + + QVector parts; + QVector vertices; QVector normals; QVector texCoords; @@ -72,13 +87,6 @@ public: bool isEye; - glm::vec3 diffuseColor; - glm::vec3 specularColor; - float shininess; - - QByteArray diffuseFilename; - QByteArray normalFilename; - QVector blendshapes; float springiness; diff --git a/interface/src/renderer/GeometryCache.cpp b/interface/src/renderer/GeometryCache.cpp index 408d159198..ea3f8fa1fc 100644 --- a/interface/src/renderer/GeometryCache.cpp +++ b/interface/src/renderer/GeometryCache.cpp @@ -290,19 +290,26 @@ NetworkGeometry::~NetworkGeometry() { glm::vec4 NetworkGeometry::computeAverageColor() const { glm::vec4 totalColor; - int totalVertices = 0; + int totalTriangles = 0; for (int i = 0; i < _meshes.size(); i++) { - if (_geometry.meshes.at(i).isEye) { + const FBXMesh& mesh = _geometry.meshes.at(i); + if (mesh.isEye) { continue; // skip eyes } - glm::vec4 color = glm::vec4(_geometry.meshes.at(i).diffuseColor, 1.0f); - if (_meshes.at(i).diffuseTexture) { - color *= _meshes.at(i).diffuseTexture->getAverageColor(); + const NetworkMesh& networkMesh = _meshes.at(i); + for (int j = 0; j < mesh.parts.size(); j++) { + const FBXMeshPart& part = mesh.parts.at(j); + const NetworkMeshPart& networkPart = networkMesh.parts.at(j); + glm::vec4 color = glm::vec4(part.diffuseColor, 1.0f); + if (networkPart.diffuseTexture) { + color *= networkPart.diffuseTexture->getAverageColor(); + } + int triangles = part.quadIndices.size() * 2 + part.triangleIndices.size(); + totalColor += color * triangles; + totalTriangles += triangles; } - totalColor += color * _geometry.meshes.at(i).vertices.size(); - totalVertices += _geometry.meshes.at(i).vertices.size(); } - return (totalVertices == 0) ? glm::vec4(1.0f, 1.0f, 1.0f, 1.0f) : totalColor / totalVertices; + return (totalTriangles == 0) ? glm::vec4(1.0f, 1.0f, 1.0f, 1.0f) : totalColor / totalTriangles; } void NetworkGeometry::handleModelReplyError() { @@ -351,13 +358,36 @@ void NetworkGeometry::maybeReadModelWithMapping() { foreach (const FBXMesh& mesh, _geometry.meshes) { NetworkMesh networkMesh; + int totalIndices = 0; + foreach (const FBXMeshPart& part, mesh.parts) { + NetworkMeshPart networkPart; + QString basePath = url.path(); + basePath = basePath.left(basePath.lastIndexOf('/') + 1); + if (!part.diffuseFilename.isEmpty()) { + url.setPath(basePath + part.diffuseFilename); + networkPart.diffuseTexture = Application::getInstance()->getTextureCache()->getTexture(url, mesh.isEye); + } + if (!part.normalFilename.isEmpty()) { + url.setPath(basePath + part.normalFilename); + networkPart.normalTexture = Application::getInstance()->getTextureCache()->getTexture(url); + } + networkMesh.parts.append(networkPart); + + totalIndices += (part.quadIndices.size() + part.triangleIndices.size()); + } + glGenBuffers(1, &networkMesh.indexBufferID); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, networkMesh.indexBufferID); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, (mesh.quadIndices.size() + mesh.triangleIndices.size()) * sizeof(int), - NULL, GL_STATIC_DRAW); - glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, mesh.quadIndices.size() * sizeof(int), mesh.quadIndices.constData()); - glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, mesh.quadIndices.size() * sizeof(int), - mesh.triangleIndices.size() * sizeof(int), mesh.triangleIndices.constData()); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, totalIndices * sizeof(int), NULL, GL_STATIC_DRAW); + int offset = 0; + foreach (const FBXMeshPart& part, mesh.parts) { + glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, part.quadIndices.size() * sizeof(int), + part.quadIndices.constData()); + offset += part.quadIndices.size() * sizeof(int); + glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, part.triangleIndices.size() * sizeof(int), + part.triangleIndices.constData()); + offset += part.triangleIndices.size() * sizeof(int); + } glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); glGenBuffers(1, &networkMesh.vertexBufferID); @@ -398,16 +428,6 @@ void NetworkGeometry::maybeReadModelWithMapping() { glBindBuffer(GL_ARRAY_BUFFER, 0); - QString basePath = url.path(); - basePath = basePath.left(basePath.lastIndexOf('/') + 1); - if (!mesh.diffuseFilename.isEmpty()) { - url.setPath(basePath + mesh.diffuseFilename); - networkMesh.diffuseTexture = Application::getInstance()->getTextureCache()->getTexture(url, mesh.isEye); - } - if (!mesh.normalFilename.isEmpty()) { - url.setPath(basePath + mesh.normalFilename); - networkMesh.normalTexture = Application::getInstance()->getTextureCache()->getTexture(url); - } _meshes.append(networkMesh); } } diff --git a/interface/src/renderer/GeometryCache.h b/interface/src/renderer/GeometryCache.h index ba7ef8db0e..ac6669ad2f 100644 --- a/interface/src/renderer/GeometryCache.h +++ b/interface/src/renderer/GeometryCache.h @@ -80,6 +80,14 @@ private: QVector _meshes; }; +/// The state associated with a single mesh part. +class NetworkMeshPart { +public: + + QSharedPointer diffuseTexture; + QSharedPointer normalTexture; +}; + /// The state associated with a single mesh. class NetworkMesh { public: @@ -87,8 +95,7 @@ public: GLuint indexBufferID; GLuint vertexBufferID; - QSharedPointer diffuseTexture; - QSharedPointer normalTexture; + QVector parts; }; #endif /* defined(__interface__GeometryCache__) */ diff --git a/interface/src/renderer/TextureCache.cpp b/interface/src/renderer/TextureCache.cpp index d12b8098da..c20826989c 100644 --- a/interface/src/renderer/TextureCache.cpp +++ b/interface/src/renderer/TextureCache.cpp @@ -17,14 +17,22 @@ #include "Application.h" #include "TextureCache.h" -TextureCache::TextureCache() : _permutationNormalTextureID(0), - _primaryFramebufferObject(NULL), _secondaryFramebufferObject(NULL), _tertiaryFramebufferObject(NULL) { +TextureCache::TextureCache() : + _permutationNormalTextureID(0), + _whiteTextureID(0), + _primaryFramebufferObject(NULL), + _secondaryFramebufferObject(NULL), + _tertiaryFramebufferObject(NULL) +{ } TextureCache::~TextureCache() { if (_permutationNormalTextureID != 0) { glDeleteTextures(1, &_permutationNormalTextureID); } + if (_whiteTextureID != 0) { + glDeleteTextures(1, &_whiteTextureID); + } foreach (GLuint id, _fileTextureIDs) { glDeleteTextures(1, &id); } @@ -66,6 +74,20 @@ GLuint TextureCache::getPermutationNormalTextureID() { return _permutationNormalTextureID; } +GLuint TextureCache::getWhiteTextureID() { + if (_whiteTextureID == 0) { + glGenTextures(1, &_whiteTextureID); + glBindTexture(GL_TEXTURE_2D, _whiteTextureID); + + const char OPAQUE_WHITE[] = { 0xFF, 0xFF, 0xFF, 0xFF }; + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, OPAQUE_WHITE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + glBindTexture(GL_TEXTURE_2D, 0); + } + return _whiteTextureID; +} + GLuint TextureCache::getFileTextureID(const QString& filename) { GLuint id = _fileTextureIDs.value(filename); if (id == 0) { diff --git a/interface/src/renderer/TextureCache.h b/interface/src/renderer/TextureCache.h index 4136d23d86..acd3c70bd8 100644 --- a/interface/src/renderer/TextureCache.h +++ b/interface/src/renderer/TextureCache.h @@ -37,6 +37,9 @@ public: /// the second, a set of random unit vectors to be used as noise gradients. GLuint getPermutationNormalTextureID(); + /// Returns the ID of an opaque white texture (useful for a default). + GLuint getWhiteTextureID(); + /// Returns the ID of a texture containing the contents of the specified file, loading it if necessary. GLuint getFileTextureID(const QString& filename); @@ -65,7 +68,8 @@ private: QOpenGLFramebufferObject* createFramebufferObject(); GLuint _permutationNormalTextureID; - + GLuint _whiteTextureID; + QHash _fileTextureIDs; QHash > _networkTextures;