mirror of
https://github.com/overte-org/overte.git
synced 2025-04-20 04:44:11 +02:00
add fatten-faces mode to convex-hull tool for use on meshes which were derived from height-fields
This commit is contained in:
parent
66ea6f4eb4
commit
efa5c473d2
5 changed files with 152 additions and 78 deletions
|
@ -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<glm::vec3>& faceNormals, QVector<int>& 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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -50,42 +50,92 @@ bool vhacd::VHACDUtil::loadFBX(const QString filename, FBXGeometry& result) {
|
|||
}
|
||||
|
||||
|
||||
unsigned int getTrianglesInMeshPart(const FBXMeshPart &meshPart, std::vector<int>& triangles) {
|
||||
// append all the triangles (and converted quads) from this mesh-part to triangles
|
||||
std::vector<int> 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<glm::vec3> vertices = meshes->perMeshVertices.at(i);
|
||||
// QVector<int> 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<int> 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<int> 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<int> 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, "
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
@ -188,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;
|
||||
|
@ -286,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();
|
||||
}
|
||||
|
@ -304,14 +313,14 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) :
|
|||
auto loadDuration = std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin).count();
|
||||
|
||||
|
||||
if (parser.isSet(splitOption)) {
|
||||
if (splitModel) {
|
||||
QVector<QString> 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++;
|
||||
}
|
||||
}
|
||||
|
@ -331,7 +340,7 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) :
|
|||
params.m_convexhullDownsampling = vHacdConvexhulldownsampling;
|
||||
params.m_alpha = vHacdAlpha;
|
||||
params.m_beta = vHacdBeta;
|
||||
params.m_gamma = 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;
|
||||
|
@ -346,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();
|
||||
|
@ -375,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue