From b15fd2ef944bfa12fe2c440474bb17bababad8a5 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sun, 15 Mar 2015 08:08:39 -0700 Subject: [PATCH 1/3] compatibility and other fixes for obj reader --- libraries/fbx/src/OBJReader.cpp | 272 +++++++++++++++++++++++++++----- libraries/fbx/src/OBJReader.h | 1 + 2 files changed, 233 insertions(+), 40 deletions(-) diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index 4f0f1246d2..b98ea961f9 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -31,6 +31,7 @@ public: }; int nextToken(); const QByteArray& getDatum() const { return _datum; } + bool isNextTokenFloat(); void skipLine() { _device->readLine(); } void pushBackToken(int token) { _pushedBackToken = token; } void ungetChar(char ch) { _device->ungetChar(ch); } @@ -91,14 +92,32 @@ int OBJTokenizer::nextToken() { return NO_TOKEN; } +bool OBJTokenizer::isNextTokenFloat() { + if (nextToken() != OBJTokenizer::DATUM_TOKEN) { + return false; + } + QByteArray token = getDatum(); + pushBackToken(OBJTokenizer::DATUM_TOKEN); + bool ok; + token.toFloat(&ok); + return ok; +} -bool parseOBJGroup(OBJTokenizer &tokenizer, const QVariantHash& mapping, FBXGeometry &geometry) { +bool parseOBJGroup(OBJTokenizer &tokenizer, const QVariantHash& mapping, + FBXGeometry &geometry, QVector& faceNormals, QVector& faceNormalIndexes) { FBXMesh &mesh = geometry.meshes[0]; mesh.parts.append(FBXMeshPart()); FBXMeshPart &meshPart = mesh.parts.last(); bool sawG = false; bool result = true; + meshPart.diffuseColor = glm::vec3(1, 1, 1); + meshPart.specularColor = glm::vec3(1, 1, 1); + meshPart.emissiveColor = glm::vec3(0, 0, 0); + meshPart.emissiveParams = glm::vec2(0, 1); + meshPart.shininess = 40; + meshPart.opacity = 1; + meshPart.materialID = QString("dontknow") + QString::number(mesh.parts.count()); meshPart.opacity = 1.0; meshPart._material = model::MaterialPointer(new model::Material()); @@ -131,11 +150,27 @@ bool parseOBJGroup(OBJTokenizer &tokenizer, const QVariantHash& mapping, FBXGeom break; } float x = std::stof(tokenizer.getDatum().data()); - // notice the order of z and y -- in OBJ files, up is the 3rd value + + if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) { + break; + } + float y = std::stof(tokenizer.getDatum().data()); + if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) { break; } float z = std::stof(tokenizer.getDatum().data()); + + while (tokenizer.isNextTokenFloat()) { + // the spec(s) get(s) vague here. might be w, might be a color... chop it off. + tokenizer.nextToken(); + } + mesh.vertices.append(glm::vec3(x, y, z)); + } else if (token == "vn") { + if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) { + break; + } + float x = std::stof(tokenizer.getDatum().data()); if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) { break; } @@ -143,35 +178,74 @@ bool parseOBJGroup(OBJTokenizer &tokenizer, const QVariantHash& mapping, FBXGeom if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) { break; } - // the spec gets vague here. might be w, might be a color... chop it off. - tokenizer.skipLine(); - mesh.vertices.append(glm::vec3(x, y, z)); - mesh.colors.append(glm::vec3(1, 1, 1)); + float z = std::stof(tokenizer.getDatum().data()); + + while (tokenizer.isNextTokenFloat()) { + // the spec gets vague here. might be w + tokenizer.nextToken(); + } + faceNormals.append(glm::vec3(x, y, z)); } else if (token == "f") { // a face can have 3 or more vertices QVector indices; + QVector normalIndices; while (true) { - if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) { goto done; } - try { - int vertexIndex = std::stoi(tokenizer.getDatum().data()); - // negative indexes count backward from the current end of the vertex list - vertexIndex = (vertexIndex >= 0 ? vertexIndex : mesh.vertices.count() + vertexIndex + 1); - // obj index is 1 based - assert(vertexIndex >= 1); - indices.append(vertexIndex - 1); + if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) { + if (indices.count() == 0) { + goto done; + } + break; } - catch(const std::exception& e) { - // wasn't a number, but it back. + // faces can be: + // vertex-index + // vertex-index/texture-index + // vertex-index/texture-index/surface-normal-index + + QByteArray token = tokenizer.getDatum(); + QList parts = token.split('/'); + assert(parts.count() >= 1); + assert(parts.count() <= 3); + QByteArray vertIndexBA = parts[ 0 ]; + + bool ok; + int vertexIndex = vertIndexBA.toInt(&ok); + if (!ok) { + // it wasn't #/#/#, put it back and exit this loop. tokenizer.pushBackToken(OBJTokenizer::DATUM_TOKEN); break; } + + // if (parts.count() > 1) { + // QByteArray textureIndexBA = parts[ 1 ]; + // } + + if (parts.count() > 2) { + QByteArray normalIndexBA = parts[ 2 ]; + bool ok; + int normalIndex = normalIndexBA.toInt(&ok); + if (ok) { + normalIndices.append(normalIndex - 1); + } + } + + // negative indexes count backward from the current end of the vertex list + vertexIndex = (vertexIndex >= 0 ? vertexIndex : mesh.vertices.count() + vertexIndex + 1); + // obj index is 1 based + assert(vertexIndex >= 1); + indices.append(vertexIndex - 1); } if (indices.count() == 3) { - // flip these around (because of the y/z swap above) so our triangles face outward meshPart.triangleIndices.append(indices[0]); - meshPart.triangleIndices.append(indices[2]); meshPart.triangleIndices.append(indices[1]); + meshPart.triangleIndices.append(indices[2]); + if (normalIndices.count() == 3) { + faceNormalIndexes.append(normalIndices[0]); + faceNormalIndexes.append(normalIndices[1]); + faceNormalIndexes.append(normalIndices[2]); + } else { + // hmm. + } } else if (indices.count() == 4) { meshPart.quadIndices << indices; } else { @@ -205,6 +279,10 @@ FBXGeometry readOBJ(const QByteArray& model, const QVariantHash& mapping) { FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping) { FBXGeometry geometry; OBJTokenizer tokenizer(device); + QVector faceNormalIndexes; + QVector faceNormals; + + faceNormalIndexes.clear(); geometry.meshExtents.reset(); geometry.meshes.append(FBXMesh()); @@ -216,7 +294,7 @@ FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping) { // add a new meshPart to the geometry's single mesh. bool success = true; while (success) { - success = parseOBJGroup(tokenizer, mapping, geometry); + success = parseOBJGroup(tokenizer, mapping, geometry, faceNormals, faceNormalIndexes); } FBXMesh &mesh = geometry.meshes[0]; @@ -235,25 +313,41 @@ FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping) { geometry.joints[0].boneRadius = 0; geometry.joints[0].translation = glm::vec3(0, 0, 0); // geometry.joints[0].preTransform = ; - geometry.joints[0].preRotation = glm::quat(0, 0, 0, 1); - geometry.joints[0].rotation = glm::quat(0, 0, 0, 1); - geometry.joints[0].postRotation = glm::quat(0, 0, 0, 1); + geometry.joints[0].preRotation = glm::quat(1, 0, 0, 0); + geometry.joints[0].rotation = glm::quat(1, 0, 0, 0); + geometry.joints[0].postRotation = glm::quat(1, 0, 0, 0); // geometry.joints[0].postTransform = ; // geometry.joints[0].transform = ; geometry.joints[0].rotationMin = glm::vec3(0, 0, 0); geometry.joints[0].rotationMax = glm::vec3(0, 0, 0); - geometry.joints[0].inverseDefaultRotation = glm::quat(0, 0, 0, 1); - geometry.joints[0].inverseBindRotation = glm::quat(0, 0, 0, 1); + geometry.joints[0].inverseDefaultRotation = glm::quat(1, 0, 0, 0); + geometry.joints[0].inverseBindRotation = glm::quat(1, 0, 0, 0); // geometry.joints[0].bindTransform = ; geometry.joints[0].name = "OBJ"; geometry.joints[0].shapePosition = glm::vec3(0, 0, 0); - geometry.joints[0].shapeRotation = glm::quat(0, 0, 0, 1); + geometry.joints[0].shapeRotation = glm::quat(1, 0, 0, 0); geometry.joints[0].shapeType = SPHERE_SHAPE; - geometry.joints[0].isSkeletonJoint = false; + geometry.joints[0].isSkeletonJoint = true; + + geometry.jointIndices["x"] = 1; + + FBXCluster cluster; + cluster.jointIndex = 0; + cluster.inverseBindMatrix = glm::mat4(1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); + mesh.clusters.append(cluster); + + // The OBJ format has normals for faces. The FBXGeometry structure has normals for points. + // run through all the faces, look-up (or determine) a normal and set the normal for the points + // that make up each face. + QVector pointNormalsSums; + QVector pointNormalsCounts; - // add bogus normal data for this mesh mesh.normals.fill(glm::vec3(0,0,0), mesh.vertices.count()); - mesh.tangents.fill(glm::vec3(0,0,0), mesh.vertices.count()); + pointNormalsSums.fill(glm::vec3(0,0,0), mesh.vertices.count()); + pointNormalsCounts.fill(0, mesh.vertices.count()); foreach (FBXMeshPart meshPart, mesh.parts) { int triCount = meshPart.triangleIndices.count() / 3; @@ -262,21 +356,45 @@ FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping) { int p1Index = meshPart.triangleIndices[i*3+1]; int p2Index = meshPart.triangleIndices[i*3+2]; - glm::vec3 p0 = mesh.vertices[p0Index]; - glm::vec3 p1 = mesh.vertices[p1Index]; - glm::vec3 p2 = mesh.vertices[p2Index]; + assert(p0Index < mesh.vertices.count()); + assert(p1Index < mesh.vertices.count()); + assert(p2Index < mesh.vertices.count()); - glm::vec3 n = glm::cross(p1 - p0, p2 - p0); - glm::vec3 t = glm::cross(p2 - p0, n); + glm::vec3 n0, n1, n2; + if (i < faceNormalIndexes.count()) { + int n0Index = faceNormalIndexes[i*3]; + int n1Index = faceNormalIndexes[i*3+1]; + int n2Index = faceNormalIndexes[i*3+2]; + n0 = faceNormals[n0Index]; + n1 = faceNormals[n1Index]; + n2 = faceNormals[n2Index]; + } else { + // We didn't read normals, add bogus normal data for this face + glm::vec3 p0 = mesh.vertices[p0Index]; + glm::vec3 p1 = mesh.vertices[p1Index]; + glm::vec3 p2 = mesh.vertices[p2Index]; + n0 = glm::cross(p1 - p0, p2 - p0); + n1 = n0; + n2 = n0; + } - mesh.normals[p0Index] = n; - mesh.normals[p1Index] = n; - mesh.normals[p2Index] = n; - - mesh.tangents[p0Index] = t; - mesh.tangents[p1Index] = t; - mesh.tangents[p2Index] = t; + // we sum up the normal for each point and then divide by the count to get an average + pointNormalsSums[p0Index] += n0; + pointNormalsSums[p1Index] += n1; + pointNormalsSums[p2Index] += n2; + pointNormalsCounts[p0Index]++; + pointNormalsCounts[p1Index]++; + pointNormalsCounts[p2Index]++; } + + int vertCount = mesh.vertices.count(); + for (int i = 0; i < vertCount; i++) { + if (pointNormalsCounts[i] > 0) { + mesh.normals[i] = glm::normalize(pointNormalsSums[i] / (float)(pointNormalsCounts[i])); + } + } + + // XXX do same normal calculation for quadCount } } catch(const std::exception& e) { @@ -285,3 +403,77 @@ FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping) { return geometry; } + + + +void fbxDebugDump(const FBXGeometry& fbxgeo) { + qDebug() << "---------------- fbxGeometry ----------------"; + qDebug() << " hasSkeletonJoints =" << fbxgeo.hasSkeletonJoints; + qDebug() << " offset =" << fbxgeo.offset; + qDebug() << " attachments.count() = " << fbxgeo.attachments.count(); + qDebug() << " meshes.count() =" << fbxgeo.meshes.count(); + foreach (FBXMesh mesh, fbxgeo.meshes) { + qDebug() << " vertices.count() =" << mesh.vertices.count(); + qDebug() << " normals.count() =" << mesh.normals.count(); + if (mesh.normals.count() == mesh.vertices.count()) { + for (int i = 0; i < mesh.normals.count(); i++) { + qDebug() << " " << mesh.vertices[ i ] << mesh.normals[ i ]; + } + } + qDebug() << " tangents.count() =" << mesh.tangents.count(); + qDebug() << " colors.count() =" << mesh.colors.count(); + qDebug() << " texCoords.count() =" << mesh.texCoords.count(); + qDebug() << " texCoords1.count() =" << mesh.texCoords1.count(); + qDebug() << " clusterIndices.count() =" << mesh.clusterIndices.count(); + qDebug() << " clusterWeights.count() =" << mesh.clusterWeights.count(); + qDebug() << " meshExtents =" << mesh.meshExtents; + qDebug() << " modelTransform =" << mesh.modelTransform; + qDebug() << " parts.count() =" << mesh.parts.count(); + foreach (FBXMeshPart meshPart, mesh.parts) { + qDebug() << " quadIndices.count() =" << meshPart.quadIndices.count(); + qDebug() << " triangleIndices.count() =" << meshPart.triangleIndices.count(); + qDebug() << " diffuseColor =" << meshPart.diffuseColor; + qDebug() << " specularColor =" << meshPart.specularColor; + qDebug() << " emissiveColor =" << meshPart.emissiveColor; + qDebug() << " emissiveParams =" << meshPart.emissiveParams; + qDebug() << " shininess =" << meshPart.shininess; + qDebug() << " opacity =" << meshPart.opacity; + qDebug() << " materialID =" << meshPart.materialID; + } + qDebug() << " clusters.count() =" << mesh.clusters.count(); + foreach (FBXCluster cluster, mesh.clusters) { + qDebug() << " jointIndex =" << cluster.jointIndex; + qDebug() << " inverseBindMatrix =" << cluster.inverseBindMatrix; + } + } + + qDebug() << " jointIndices =" << fbxgeo.jointIndices; + qDebug() << " joints.count() =" << fbxgeo.joints.count(); + + foreach (FBXJoint joint, fbxgeo.joints) { + qDebug() << " isFree =" << joint.isFree; + qDebug() << " freeLineage" << joint.freeLineage; + qDebug() << " parentIndex" << joint.parentIndex; + qDebug() << " distanceToParent" << joint.distanceToParent; + qDebug() << " boneRadius" << joint.boneRadius; + qDebug() << " translation" << joint.translation; + qDebug() << " preTransform" << joint.preTransform; + qDebug() << " preRotation" << joint.preRotation; + qDebug() << " rotation" << joint.rotation; + qDebug() << " postRotation" << joint.postRotation; + qDebug() << " postTransform" << joint.postTransform; + qDebug() << " transform" << joint.transform; + qDebug() << " rotationMin" << joint.rotationMin; + qDebug() << " rotationMax" << joint.rotationMax; + qDebug() << " inverseDefaultRotation" << joint.inverseDefaultRotation; + qDebug() << " inverseBindRotation" << joint.inverseBindRotation; + qDebug() << " bindTransform" << joint.bindTransform; + qDebug() << " name" << joint.name; + qDebug() << " shapePosition" << joint.shapePosition; + qDebug() << " shapeRotation" << joint.shapeRotation; + qDebug() << " shapeType" << joint.shapeType; + qDebug() << " isSkeletonJoint" << joint.isSkeletonJoint; + } + + qDebug() << "\n"; +} diff --git a/libraries/fbx/src/OBJReader.h b/libraries/fbx/src/OBJReader.h index 2ff55a9a61..8c7aa1aba6 100644 --- a/libraries/fbx/src/OBJReader.h +++ b/libraries/fbx/src/OBJReader.h @@ -4,3 +4,4 @@ FBXGeometry readOBJ(const QByteArray& model, const QVariantHash& mapping); FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping); +void fbxDebugDump(const FBXGeometry& fbxgeo); From d074cec13525d7ba1f473961c5fc9da985f6328f Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sun, 15 Mar 2015 08:14:25 -0700 Subject: [PATCH 2/3] fix obj writer in vhacd util --- tools/vhacd/src/VHACDUtilApp.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tools/vhacd/src/VHACDUtilApp.cpp b/tools/vhacd/src/VHACDUtilApp.cpp index 958a91dbfe..b4c141acae 100644 --- a/tools/vhacd/src/VHACDUtilApp.cpp +++ b/tools/vhacd/src/VHACDUtilApp.cpp @@ -50,15 +50,13 @@ bool writeOBJ(QString outFileName, QVector>& for (unsigned int i = 0; i < hull.m_nPoints; i++) { out << "v "; out << formatFloat(hull.m_points[i*3]) << " "; - // swap y and z because up is 3rd value in OBJ - out << formatFloat(hull.m_points[i*3+2]) << " "; - out << formatFloat(hull.m_points[i*3+1]) << "\n"; + out << formatFloat(hull.m_points[i*3+1]) << " "; + out << formatFloat(hull.m_points[i*3+2]) << "\n"; } for (unsigned int i = 0; i < hull.m_nTriangles; i++) { out << "f "; - // change order to flip normal (due to swapping y and z, above) - out << hull.m_triangles[i*3+1] + 1 + pointStartOffset << " "; out << hull.m_triangles[i*3] + 1 + pointStartOffset << " "; + out << hull.m_triangles[i*3+1] + 1 + pointStartOffset << " "; out << hull.m_triangles[i*3+2] + 1 + pointStartOffset << "\n"; } out << "\n"; From a2bc34ced9b5e547fe39d0188e79b2f3f0ab66e4 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 16 Mar 2015 10:03:55 -0700 Subject: [PATCH 3/3] fix attempt to avoid normalizing a zero length vector, other adjustments to respond to code review --- libraries/fbx/src/OBJReader.cpp | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index b98ea961f9..0aaf8772a2 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -307,25 +307,14 @@ FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping) { geometry.joints.resize(1); geometry.joints[0].isFree = false; - // geometry.joints[0].freeLineage; geometry.joints[0].parentIndex = -1; geometry.joints[0].distanceToParent = 0; geometry.joints[0].boneRadius = 0; geometry.joints[0].translation = glm::vec3(0, 0, 0); - // geometry.joints[0].preTransform = ; - geometry.joints[0].preRotation = glm::quat(1, 0, 0, 0); - geometry.joints[0].rotation = glm::quat(1, 0, 0, 0); - geometry.joints[0].postRotation = glm::quat(1, 0, 0, 0); - // geometry.joints[0].postTransform = ; - // geometry.joints[0].transform = ; geometry.joints[0].rotationMin = glm::vec3(0, 0, 0); geometry.joints[0].rotationMax = glm::vec3(0, 0, 0); - geometry.joints[0].inverseDefaultRotation = glm::quat(1, 0, 0, 0); - geometry.joints[0].inverseBindRotation = glm::quat(1, 0, 0, 0); - // geometry.joints[0].bindTransform = ; geometry.joints[0].name = "OBJ"; geometry.joints[0].shapePosition = glm::vec3(0, 0, 0); - geometry.joints[0].shapeRotation = glm::quat(1, 0, 0, 0); geometry.joints[0].shapeType = SPHERE_SHAPE; geometry.joints[0].isSkeletonJoint = true; @@ -343,11 +332,9 @@ FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping) { // run through all the faces, look-up (or determine) a normal and set the normal for the points // that make up each face. QVector pointNormalsSums; - QVector pointNormalsCounts; mesh.normals.fill(glm::vec3(0,0,0), mesh.vertices.count()); pointNormalsSums.fill(glm::vec3(0,0,0), mesh.vertices.count()); - pointNormalsCounts.fill(0, mesh.vertices.count()); foreach (FBXMeshPart meshPart, mesh.parts) { int triCount = meshPart.triangleIndices.count() / 3; @@ -382,15 +369,13 @@ FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping) { pointNormalsSums[p0Index] += n0; pointNormalsSums[p1Index] += n1; pointNormalsSums[p2Index] += n2; - pointNormalsCounts[p0Index]++; - pointNormalsCounts[p1Index]++; - pointNormalsCounts[p2Index]++; } int vertCount = mesh.vertices.count(); for (int i = 0; i < vertCount; i++) { - if (pointNormalsCounts[i] > 0) { - mesh.normals[i] = glm::normalize(pointNormalsSums[i] / (float)(pointNormalsCounts[i])); + float length = glm::length(pointNormalsSums[i]); + if (length > FLT_EPSILON) { + mesh.normals[i] = glm::normalize(pointNormalsSums[i]); } }