diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index ddb2c6ba31..0deb241343 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -116,6 +116,24 @@ bool OBJTokenizer::isNextTokenFloat() { return ok; } +void setMeshPartDefaults(FBXMeshPart &meshPart, QString materialID) { + 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 = materialID; + meshPart.opacity = 1.0; + meshPart._material = model::MaterialPointer(new model::Material()); + meshPart._material->setDiffuse(glm::vec3(1.0, 1.0, 1.0)); + meshPart._material->setOpacity(1.0); + meshPart._material->setSpecular(glm::vec3(1.0, 1.0, 1.0)); + meshPart._material->setShininess(96.0); + meshPart._material->setEmissive(glm::vec3(0.0, 0.0, 0.0)); +} + bool parseOBJGroup(OBJTokenizer &tokenizer, const QVariantHash& mapping, FBXGeometry &geometry, QVector& faceNormals, QVector& faceNormalIndexes, float& scaleGuess) { @@ -125,21 +143,7 @@ bool parseOBJGroup(OBJTokenizer &tokenizer, const QVariantHash& mapping, 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()); - meshPart._material->setDiffuse(glm::vec3(1.0, 1.0, 1.0)); - meshPart._material->setOpacity(1.0); - meshPart._material->setSpecular(glm::vec3(1.0, 1.0, 1.0)); - meshPart._material->setShininess(96.0); - meshPart._material->setEmissive(glm::vec3(0.0, 0.0, 0.0)); + setMeshPartDefaults(meshPart, QString("dontknow") + QString::number(mesh.parts.count())); while (true) { int tokenType = tokenizer.nextToken(); diff --git a/libraries/fbx/src/OBJReader.h b/libraries/fbx/src/OBJReader.h index 8c7aa1aba6..a272e46f2d 100644 --- a/libraries/fbx/src/OBJReader.h +++ b/libraries/fbx/src/OBJReader.h @@ -5,3 +5,4 @@ FBXGeometry readOBJ(const QByteArray& model, const QVariantHash& mapping); FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping); void fbxDebugDump(const FBXGeometry& fbxgeo); +void setMeshPartDefaults(FBXMeshPart &meshPart, QString materialID); diff --git a/tools/vhacd/src/VHACDUtil.cpp b/tools/vhacd/src/VHACDUtil.cpp index c05f5327ca..92ae62db13 100644 --- a/tools/vhacd/src/VHACDUtil.cpp +++ b/tools/vhacd/src/VHACDUtil.cpp @@ -50,42 +50,92 @@ bool vhacd::VHACDUtil::loadFBX(const QString filename, FBXGeometry& result) { } +unsigned int getTrianglesInMeshPart(const FBXMeshPart &meshPart, std::vector& triangles) { + // append all the triangles (and converted quads) from this mesh-part to triangles + std::vector meshPartTriangles = meshPart.triangleIndices.toStdVector(); + triangles.insert(triangles.end(), meshPartTriangles.begin(), meshPartTriangles.end()); -// void vhacd::VHACDUtil::fattenMeshes(vhacd::LoadFBXResults *meshes, vhacd::LoadFBXResults *results) const { + // convert quads to triangles + unsigned int triangleCount = meshPart.triangleIndices.size() / 3; + unsigned int quadCount = meshPart.quadIndices.size() / 4; + for (unsigned int i = 0; i < quadCount; i++) { + unsigned int p0Index = meshPart.quadIndices[i * 4]; + unsigned int p1Index = meshPart.quadIndices[i * 4 + 1]; + unsigned int p2Index = meshPart.quadIndices[i * 4 + 2]; + unsigned int p3Index = meshPart.quadIndices[i * 4 + 3]; + // split each quad into two triangles + triangles.push_back(p0Index); + triangles.push_back(p1Index); + triangles.push_back(p2Index); + triangles.push_back(p0Index); + triangles.push_back(p2Index); + triangles.push_back(p3Index); + triangleCount += 2; + } -// for (int i = 0; i < meshes->meshCount; i++) { -// QVector vertices = meshes->perMeshVertices.at(i); -// QVector triangles = meshes->perMeshTriangleIndices.at(i); -// const float largestDimension = meshes->perMeshLargestDimension.at(i); + return triangleCount; +} -// results->perMeshVertices.append(vertices); -// results->perMeshTriangleIndices.append(triangles); -// results->perMeshLargestDimension.append(largestDimension); -// for (int j = 0; j < triangles.size(); j += 3) { -// auto p0 = vertices[triangles[j]]; -// auto p1 = vertices[triangles[j+1]]; -// auto p2 = vertices[triangles[j+2]]; +void vhacd::VHACDUtil::fattenMeshes(const FBXMesh& mesh, FBXMesh& result, + unsigned int& meshPartCount, + unsigned int startMeshIndex, unsigned int endMeshIndex) const { + // this is used to make meshes generated from a highfield collidable. each triangle + // is converted into a tetrahedron and made into its own mesh-part. -// auto d0 = p1 - p0; -// auto d1 = p2 - p0; + std::vector triangles; + foreach (const FBXMeshPart &meshPart, mesh.parts) { + if (meshPartCount < startMeshIndex || meshPartCount >= endMeshIndex) { + meshPartCount++; + continue; + } + getTrianglesInMeshPart(meshPart, triangles); + } -// auto cp = glm::cross(d0, d1); -// cp = -2.0f * glm::normalize(cp); + unsigned int triangleCount = triangles.size() / 3; + if (triangleCount == 0) { + return; + } -// auto p3 = p0 + cp; - -// auto n = results->perMeshVertices.size(); -// results->perMeshVertices[i] << p3; + int indexStartOffset = result.vertices.size(); -// results->perMeshTriangleIndices[i] << triangles[j] << n << triangles[j + 1]; -// results->perMeshTriangleIndices[i] << triangles[j + 1] << n << triangles[j + 2]; -// results->perMeshTriangleIndices[i] << triangles[j + 2] << n << triangles[j]; -// } + // new mesh gets the transformed points from the original + for (int i = 0; i < mesh.vertices.size(); i++) { + // apply the source mesh's transform to the points + glm::vec4 v = mesh.modelTransform * glm::vec4(mesh.vertices[i], 1.0f); + result.vertices += glm::vec3(v); + } -// results->meshCount++; -// } -// } + // turn each triangle into a tetrahedron + + for (unsigned int i = 0; i < triangleCount; i++) { + int index0 = triangles[i * 3] + indexStartOffset; + int index1 = triangles[i * 3 + 1] + indexStartOffset; + int index2 = triangles[i * 3 + 2] + indexStartOffset; + + glm::vec3 p0 = result.vertices[index0]; + glm::vec3 p1 = result.vertices[index1]; + glm::vec3 p2 = result.vertices[index2]; + glm::vec3 av = (p0 + p1 + p2) / 3.0f; // center of the triangular face + + float dropAmount = 0; + dropAmount = glm::max(glm::length(p1 - p0), dropAmount); + dropAmount = glm::max(glm::length(p2 - p1), dropAmount); + dropAmount = glm::max(glm::length(p0 - p2), dropAmount); + + glm::vec3 p3 = av - glm::vec3(0, dropAmount, 0); // a point 1 meter below the average of this triangle's points + int index3 = result.vertices.size(); + result.vertices << p3; // add the new point to the result mesh + + FBXMeshPart newMeshPart; + setMeshPartDefaults(newMeshPart, "unknown"); + newMeshPart.triangleIndices << index0 << index1 << index2; + newMeshPart.triangleIndices << index0 << index3 << index1; + newMeshPart.triangleIndices << index1 << index3 << index2; + newMeshPart.triangleIndices << index2 << index3 << index0; + result.parts.append(newMeshPart); + } +} @@ -127,8 +177,7 @@ bool vhacd::VHACDUtil::computeVHACD(FBXGeometry& geometry, FBXGeometry& result, int startMeshIndex, int endMeshIndex, - float minimumMeshSize, float maximumMeshSize, - bool fattenFaces) { + float minimumMeshSize, float maximumMeshSize) { // count the mesh-parts int meshCount = 0; foreach (const FBXMesh& mesh, geometry.meshes) { @@ -168,27 +217,8 @@ bool vhacd::VHACDUtil::computeVHACD(FBXGeometry& geometry, qDebug() << "--------------------"; - std::vector triangles = meshPart.triangleIndices.toStdVector(); - - AABox aaBox = getAABoxForMeshPart(mesh, meshPart); - - // convert quads to triangles - unsigned int triangleCount = meshPart.triangleIndices.size() / 3; - unsigned int quadCount = meshPart.quadIndices.size() / 4; - for (unsigned int i = 0; i < quadCount; i++) { - unsigned int p0Index = meshPart.quadIndices[i * 4]; - unsigned int p1Index = meshPart.quadIndices[i * 4 + 1]; - unsigned int p2Index = meshPart.quadIndices[i * 4 + 2]; - unsigned int p3Index = meshPart.quadIndices[i * 4 + 3]; - // split each quad into two triangles - triangles.push_back(p0Index); - triangles.push_back(p1Index); - triangles.push_back(p2Index); - triangles.push_back(p0Index); - triangles.push_back(p2Index); - triangles.push_back(p3Index); - triangleCount += 2; - } + std::vector triangles; + unsigned int triangleCount = getTrianglesInMeshPart(meshPart, triangles); // only process meshes with triangles if (triangles.size() <= 0) { @@ -198,6 +228,7 @@ bool vhacd::VHACDUtil::computeVHACD(FBXGeometry& geometry, } int nPoints = vertices.size(); + AABox aaBox = getAABoxForMeshPart(mesh, meshPart); const float largestDimension = aaBox.getLargestDimension(); qDebug() << "Mesh " << count << " -- " << nPoints << " points, " << triangleCount << " triangles, " diff --git a/tools/vhacd/src/VHACDUtil.h b/tools/vhacd/src/VHACDUtil.h index 8d0c6acc32..34ad39a6f6 100644 --- a/tools/vhacd/src/VHACDUtil.h +++ b/tools/vhacd/src/VHACDUtil.h @@ -26,14 +26,16 @@ namespace vhacd { class VHACDUtil { public: bool loadFBX(const QString filename, FBXGeometry& result); - // void combineMeshes(vhacd::LoadFBXResults *meshes, vhacd::LoadFBXResults *results) const; - // void fattenMeshes(vhacd::LoadFBXResults *meshes, vhacd::LoadFBXResults *results) const; + + void fattenMeshes(const FBXMesh& mesh, FBXMesh& result, + unsigned int& meshPartCount, + unsigned int startMeshIndex, unsigned int endMeshIndex) const; + bool computeVHACD(FBXGeometry& geometry, VHACD::IVHACD::Parameters params, FBXGeometry& result, int startMeshIndex, int endMeshIndex, - float minimumMeshSize, float maximumMeshSize, - bool fattenFaces); + float minimumMeshSize, float maximumMeshSize); ~VHACDUtil(); }; diff --git a/tools/vhacd/src/VHACDUtilApp.cpp b/tools/vhacd/src/VHACDUtilApp.cpp index ba3fbb94bd..242e69c363 100644 --- a/tools/vhacd/src/VHACDUtilApp.cpp +++ b/tools/vhacd/src/VHACDUtilApp.cpp @@ -33,7 +33,7 @@ QString formatFloat(double n) { } -bool writeOBJ(QString outFileName, FBXGeometry& geometry, int whichMeshPart = -1) { +bool writeOBJ(QString outFileName, FBXGeometry& geometry, bool outputCentimeters, int whichMeshPart = -1) { QFile file(outFileName); if (!file.open(QIODevice::WriteOnly)) { qDebug() << "Unable to write to " << outFileName; @@ -41,6 +41,10 @@ bool writeOBJ(QString outFileName, FBXGeometry& geometry, int whichMeshPart = -1 } QTextStream out(&file); + if (outputCentimeters) { + out << "# This file uses centimeters as units\n\n"; + } + unsigned int nth = 0; // vertex indexes in obj files span the entire file @@ -116,6 +120,9 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) : const QCommandLineOption outputFilenameOption("o", "output file", "filename.obj"); parser.addOption(outputFilenameOption); + const QCommandLineOption outputCentimetersOption("c", "output units are centimeters"); + parser.addOption(outputCentimetersOption); + const QCommandLineOption startMeshIndexOption("s", "start-mesh index", "0"); parser.addOption(startMeshIndexOption); @@ -137,7 +144,21 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) : "according the \"best\" clipping plane (range=1-32)", "20"); parser.addOption(vHacdDepthOption); - const QCommandLineOption vHacdDeltaOption("delta", "Controls the bias toward maximaxing local concavity (range=0.0-1.0)", "0.05"); + + const QCommandLineOption vHacdAlphaOption("alpha", "Controls the bias toward clipping along symmetry " + "planes (range=0.0-1.0)", "0.05"); + parser.addOption(vHacdAlphaOption); + + const QCommandLineOption vHacdBetaOption("beta", "Controls the bias toward clipping along revolution " + "axes (range=0.0-1.0)", "0.05"); + parser.addOption(vHacdBetaOption); + + const QCommandLineOption vHacdGammaOption("gamma", "Controls the maximum allowed concavity during the " + "merge stage (range=0.0-1.0)", "0.00125"); + parser.addOption(vHacdGammaOption); + + const QCommandLineOption vHacdDeltaOption("delta", "Controls the bias toward maximaxing local " + "concavity (range=0.0-1.0)", "0.05"); parser.addOption(vHacdDeltaOption); const QCommandLineOption vHacdConcavityOption("concavity", "Maximum allowed concavity (range=0.0-1.0)", "0.0025"); @@ -152,10 +173,6 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) : "plane selection stage (range=1-16)", "4"); parser.addOption(vHacdConvexhulldownsamplingOption); - // alpha - // beta - // gamma - // delta // pca // mode @@ -178,9 +195,11 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) : Q_UNREACHABLE(); } - bool fattenFaces = parser.isSet(fattenFacesOption); + bool outputCentimeters = parser.isSet(outputCentimetersOption); + bool fattenFaces = parser.isSet(fattenFacesOption); bool generateHulls = parser.isSet(generateHullsOption); + bool splitModel = parser.isSet(splitOption); QString inputFilename; @@ -236,6 +255,21 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) : vHacdDepth = parser.value(vHacdDepthOption).toInt(); } + float vHacdAlpha = 0.05; + if (parser.isSet(vHacdAlphaOption)) { + vHacdAlpha = parser.value(vHacdAlphaOption).toFloat(); + } + + float vHacdBeta = 0.05; + if (parser.isSet(vHacdBetaOption)) { + vHacdBeta = parser.value(vHacdBetaOption).toFloat(); + } + + float vHacdGamma = 0.00125; + if (parser.isSet(vHacdGammaOption)) { + vHacdGamma = parser.value(vHacdGammaOption).toFloat(); + } + float vHacdDelta = 0.05; if (parser.isSet(vHacdDeltaOption)) { vHacdDelta = parser.value(vHacdDeltaOption).toFloat(); @@ -261,8 +295,8 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) : vHacdMaxVerticesPerCH = parser.value(vHacdMaxVerticesPerCHOption).toInt(); } - if (!parser.isSet(splitOption) && !generateHulls) { - cerr << "\nNothing to do! Use -g or --split\n\n"; + if (!splitModel && !generateHulls && !fattenFaces) { + cerr << "\nNothing to do! Use -g or -f or --split\n\n"; parser.showHelp(); Q_UNREACHABLE(); } @@ -279,14 +313,14 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) : auto loadDuration = std::chrono::duration_cast(end - begin).count(); - if (parser.isSet(splitOption)) { + if (splitModel) { QVector infileExtensions = {"fbx", "obj"}; QString baseFileName = fileNameWithoutExtension(inputFilename, infileExtensions); int count = 0; foreach (const FBXMesh& mesh, fbx.meshes) { foreach (const FBXMeshPart &meshPart, mesh.parts) { QString outputFileName = baseFileName + "-" + QString::number(count) + ".obj"; - writeOBJ(outputFileName, fbx, count); + writeOBJ(outputFileName, fbx, outputCentimeters, count); count++; } } @@ -304,9 +338,9 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) : params.m_delta = vHacdDelta; params.m_planeDownsampling = vHacdPlanedownsampling; params.m_convexhullDownsampling = vHacdConvexhulldownsampling; - params.m_alpha = 0.05; // 0.05 // controls the bias toward clipping along symmetry planes - params.m_beta = 0.05; // 0.05 - params.m_gamma = 0.0005; // 0.0005 + params.m_alpha = vHacdAlpha; + params.m_beta = vHacdBeta; + params.m_gamma = vHacdGamma; params.m_pca = 0; // 0 enable/disable normalizing the mesh before applying the convex decomposition params.m_mode = 0; // 0: voxel-based (recommended), 1: tetrahedron-based params.m_maxNumVerticesPerCH = vHacdMaxVerticesPerCH; @@ -321,7 +355,7 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) : FBXGeometry result; if (!vUtil.computeVHACD(fbx, params, result, startMeshIndex, endMeshIndex, - minimumMeshSize, maximumMeshSize, fattenFaces)) { + minimumMeshSize, maximumMeshSize)) { cout << "Compute Failed..."; } end = std::chrono::high_resolution_clock::now(); @@ -350,7 +384,34 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) : cout << "Total FBX load time: " << (double)loadDuration / 1000000000.00 << " seconds" << endl; cout << "V-HACD Compute time: " << (double)computeDuration / 1000000000.00 << " seconds" << endl; - writeOBJ(outputFilename, result); + writeOBJ(outputFilename, result, outputCentimeters); + } + + if (fattenFaces) { + FBXGeometry newFbx; + FBXMesh result; + + // count the mesh-parts + unsigned int meshCount = 0; + foreach (const FBXMesh& mesh, fbx.meshes) { + meshCount += mesh.parts.size(); + } + + if (startMeshIndex < 0) { + startMeshIndex = 0; + } + if (endMeshIndex < 0) { + endMeshIndex = meshCount; + } + + unsigned int meshPartCount = 0; + result.modelTransform = glm::mat4(); // Identity matrix + foreach (const FBXMesh& mesh, fbx.meshes) { + vUtil.fattenMeshes(mesh, result, meshPartCount, startMeshIndex, endMeshIndex); + } + + newFbx.meshes.append(result); + writeOBJ(outputFilename, newFbx, outputCentimeters); } }