mirror of
https://github.com/overte-org/overte.git
synced 2025-04-16 17:26:24 +02:00
Merge pull request #7961 from AndrewMeadows/convexification
vhacd-util improvements
This commit is contained in:
commit
4b2e2ca10a
5 changed files with 362 additions and 209 deletions
|
@ -9,10 +9,12 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QVector>
|
||||
#include "VHACDUtil.h"
|
||||
|
||||
const float COLLISION_TETRAHEDRON_SCALE = 0.25f;
|
||||
#include <unordered_map>
|
||||
#include <QVector>
|
||||
|
||||
#include <NumericalConstants.h>
|
||||
|
||||
|
||||
// FBXReader jumbles the order of the meshes by reading them back out of a hashtable. This will put
|
||||
|
@ -27,13 +29,16 @@ void reSortFBXGeometryMeshes(FBXGeometry& geometry) {
|
|||
|
||||
// Read all the meshes from provided FBX file
|
||||
bool vhacd::VHACDUtil::loadFBX(const QString filename, FBXGeometry& result) {
|
||||
if (_verbose) {
|
||||
qDebug() << "reading FBX file =" << filename << "...";
|
||||
}
|
||||
|
||||
// open the fbx file
|
||||
QFile fbx(filename);
|
||||
if (!fbx.open(QIODevice::ReadOnly)) {
|
||||
qWarning() << "unable to open FBX file =" << filename;
|
||||
return false;
|
||||
}
|
||||
std::cout << "Reading FBX.....\n";
|
||||
try {
|
||||
QByteArray fbxContents = fbx.readAll();
|
||||
FBXGeometry* geom;
|
||||
|
@ -42,14 +47,14 @@ bool vhacd::VHACDUtil::loadFBX(const QString filename, FBXGeometry& result) {
|
|||
} else if (filename.toLower().endsWith(".fbx")) {
|
||||
geom = readFBX(fbxContents, QVariantHash(), filename);
|
||||
} else {
|
||||
qDebug() << "unknown file extension";
|
||||
qWarning() << "file has unknown extension" << filename;
|
||||
return false;
|
||||
}
|
||||
result = *geom;
|
||||
|
||||
reSortFBXGeometryMeshes(result);
|
||||
} catch (const QString& error) {
|
||||
qDebug() << "Error reading " << filename << ": " << error;
|
||||
qWarning() << "error reading" << filename << ":" << error;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -57,68 +62,62 @@ 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());
|
||||
|
||||
// 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;
|
||||
void getTrianglesInMeshPart(const FBXMeshPart &meshPart, std::vector<int>& triangleIndices) {
|
||||
// append triangle indices
|
||||
triangleIndices.reserve(triangleIndices.size() + (size_t)meshPart.triangleIndices.size());
|
||||
for (auto index : meshPart.triangleIndices) {
|
||||
triangleIndices.push_back(index);
|
||||
}
|
||||
|
||||
return triangleCount;
|
||||
// convert quads to triangles
|
||||
const uint32_t QUAD_STRIDE = 4;
|
||||
uint32_t numIndices = (uint32_t)meshPart.quadIndices.size();
|
||||
for (uint32_t i = 0; i < numIndices; i += QUAD_STRIDE) {
|
||||
uint32_t p0Index = meshPart.quadIndices[i];
|
||||
uint32_t p1Index = meshPart.quadIndices[i + 1];
|
||||
uint32_t p2Index = meshPart.quadIndices[i + 2];
|
||||
uint32_t p3Index = meshPart.quadIndices[i + 3];
|
||||
// split each quad into two triangles
|
||||
triangleIndices.push_back(p0Index);
|
||||
triangleIndices.push_back(p1Index);
|
||||
triangleIndices.push_back(p2Index);
|
||||
triangleIndices.push_back(p0Index);
|
||||
triangleIndices.push_back(p2Index);
|
||||
triangleIndices.push_back(p3Index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void vhacd::VHACDUtil::fattenMeshes(const FBXMesh& mesh, FBXMesh& result,
|
||||
unsigned int& meshPartCount,
|
||||
unsigned int startMeshIndex, unsigned int endMeshIndex) const {
|
||||
void vhacd::VHACDUtil::fattenMesh(const FBXMesh& mesh, const glm::mat4& geometryOffset, FBXMesh& result) 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.
|
||||
|
||||
std::vector<int> triangles;
|
||||
std::vector<int> triangleIndices;
|
||||
foreach (const FBXMeshPart &meshPart, mesh.parts) {
|
||||
if (meshPartCount < startMeshIndex || meshPartCount >= endMeshIndex) {
|
||||
meshPartCount++;
|
||||
continue;
|
||||
}
|
||||
getTrianglesInMeshPart(meshPart, triangles);
|
||||
getTrianglesInMeshPart(meshPart, triangleIndices);
|
||||
}
|
||||
|
||||
auto triangleCount = triangles.size() / 3;
|
||||
if (triangleCount == 0) {
|
||||
if (triangleIndices.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int indexStartOffset = result.vertices.size();
|
||||
|
||||
// new mesh gets the transformed points from the original
|
||||
glm::mat4 totalTransform = geometryOffset * mesh.modelTransform;
|
||||
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);
|
||||
glm::vec4 v = totalTransform * glm::vec4(mesh.vertices[i], 1.0f);
|
||||
result.vertices += glm::vec3(v);
|
||||
}
|
||||
|
||||
// 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;
|
||||
const uint32_t TRIANGLE_STRIDE = 3;
|
||||
const float COLLISION_TETRAHEDRON_SCALE = 0.25f;
|
||||
for (uint32_t i = 0; i < triangleIndices.size(); i += TRIANGLE_STRIDE) {
|
||||
int index0 = triangleIndices[i] + indexStartOffset;
|
||||
int index1 = triangleIndices[i + 1] + indexStartOffset;
|
||||
int index2 = triangleIndices[i + 2] + indexStartOffset;
|
||||
|
||||
// TODO: skip triangles with a normal that points more negative-y than positive-y
|
||||
|
||||
|
@ -155,156 +154,304 @@ void vhacd::VHACDUtil::fattenMeshes(const FBXMesh& mesh, FBXMesh& result,
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
AABox getAABoxForMeshPart(const FBXMesh& mesh, const FBXMeshPart &meshPart) {
|
||||
AABox aaBox;
|
||||
unsigned int triangleCount = meshPart.triangleIndices.size() / 3;
|
||||
for (unsigned int i = 0; i < triangleCount; ++i) {
|
||||
aaBox += mesh.vertices[meshPart.triangleIndices[i * 3]];
|
||||
aaBox += mesh.vertices[meshPart.triangleIndices[i * 3 + 1]];
|
||||
aaBox += mesh.vertices[meshPart.triangleIndices[i * 3 + 2]];
|
||||
const int TRIANGLE_STRIDE = 3;
|
||||
for (int i = 0; i < meshPart.triangleIndices.size(); i += TRIANGLE_STRIDE) {
|
||||
aaBox += mesh.vertices[meshPart.triangleIndices[i]];
|
||||
aaBox += mesh.vertices[meshPart.triangleIndices[i + 1]];
|
||||
aaBox += mesh.vertices[meshPart.triangleIndices[i + 2]];
|
||||
}
|
||||
|
||||
unsigned int quadCount = meshPart.quadIndices.size() / 4;
|
||||
for (unsigned int i = 0; i < quadCount; ++i) {
|
||||
aaBox += mesh.vertices[meshPart.quadIndices[i * 4]];
|
||||
aaBox += mesh.vertices[meshPart.quadIndices[i * 4 + 1]];
|
||||
aaBox += mesh.vertices[meshPart.quadIndices[i * 4 + 2]];
|
||||
aaBox += mesh.vertices[meshPart.quadIndices[i * 4 + 3]];
|
||||
const int QUAD_STRIDE = 4;
|
||||
for (int i = 0; i < meshPart.quadIndices.size(); i += QUAD_STRIDE) {
|
||||
aaBox += mesh.vertices[meshPart.quadIndices[i]];
|
||||
aaBox += mesh.vertices[meshPart.quadIndices[i + 1]];
|
||||
aaBox += mesh.vertices[meshPart.quadIndices[i + 2]];
|
||||
aaBox += mesh.vertices[meshPart.quadIndices[i + 3]];
|
||||
}
|
||||
|
||||
return aaBox;
|
||||
}
|
||||
|
||||
class TriangleEdge {
|
||||
public:
|
||||
TriangleEdge() {}
|
||||
TriangleEdge(uint32_t A, uint32_t B) {
|
||||
setIndices(A, B);
|
||||
}
|
||||
void setIndices(uint32_t A, uint32_t B) {
|
||||
if (A < B) {
|
||||
_indexA = A;
|
||||
_indexB = B;
|
||||
} else {
|
||||
_indexA = B;
|
||||
_indexB = A;
|
||||
}
|
||||
}
|
||||
bool operator==(const TriangleEdge& other) const {
|
||||
return _indexA == other._indexA && _indexB == other._indexB;
|
||||
}
|
||||
|
||||
uint32_t getIndexA() const { return _indexA; }
|
||||
uint32_t getIndexB() const { return _indexB; }
|
||||
private:
|
||||
uint32_t _indexA { (uint32_t)(-1) };
|
||||
uint32_t _indexB { (uint32_t)(-1) };
|
||||
};
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<TriangleEdge> {
|
||||
std::size_t operator()(const TriangleEdge& edge) const {
|
||||
// use Cantor's pairing function to generate a hash of ZxZ --> Z
|
||||
uint32_t ab = edge.getIndexA() + edge.getIndexB();
|
||||
return hash<int>()((ab * (ab + 1)) / 2 + edge.getIndexB());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// returns false if any edge has only one adjacent triangle
|
||||
bool isClosedManifold(const std::vector<int>& triangleIndices) {
|
||||
using EdgeList = std::unordered_map<TriangleEdge, int>;
|
||||
EdgeList edges;
|
||||
|
||||
// count the triangles for each edge
|
||||
const uint32_t TRIANGLE_STRIDE = 3;
|
||||
for (uint32_t i = 0; i < triangleIndices.size(); i += TRIANGLE_STRIDE) {
|
||||
TriangleEdge edge;
|
||||
// the triangles indices are stored in sequential order
|
||||
for (uint32_t j = 0; j < 3; ++j) {
|
||||
edge.setIndices(triangleIndices[i + j], triangleIndices[i + ((j + 1) % 3)]);
|
||||
|
||||
EdgeList::iterator edgeEntry = edges.find(edge);
|
||||
if (edgeEntry == edges.end()) {
|
||||
edges.insert(std::pair<TriangleEdge, uint32_t>(edge, 1));
|
||||
} else {
|
||||
edgeEntry->second += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
// scan for outside edge
|
||||
for (auto& edgeEntry : edges) {
|
||||
if (edgeEntry.second == 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void vhacd::VHACDUtil::getConvexResults(VHACD::IVHACD* convexifier, FBXMesh& resultMesh) const {
|
||||
// Number of hulls for this input meshPart
|
||||
uint32_t numHulls = convexifier->GetNConvexHulls();
|
||||
if (_verbose) {
|
||||
qDebug() << " hulls =" << numHulls;
|
||||
}
|
||||
|
||||
// create an output meshPart for each convex hull
|
||||
const uint32_t TRIANGLE_STRIDE = 3;
|
||||
const uint32_t POINT_STRIDE = 3;
|
||||
for (uint32_t j = 0; j < numHulls; j++) {
|
||||
VHACD::IVHACD::ConvexHull hull;
|
||||
convexifier->GetConvexHull(j, hull);
|
||||
|
||||
resultMesh.parts.append(FBXMeshPart());
|
||||
FBXMeshPart& resultMeshPart = resultMesh.parts.last();
|
||||
|
||||
int hullIndexStart = resultMesh.vertices.size();
|
||||
resultMesh.vertices.reserve(hullIndexStart + hull.m_nPoints);
|
||||
uint32_t numIndices = hull.m_nPoints * POINT_STRIDE;
|
||||
for (uint32_t i = 0; i < numIndices; i += POINT_STRIDE) {
|
||||
float x = hull.m_points[i];
|
||||
float y = hull.m_points[i + 1];
|
||||
float z = hull.m_points[i + 2];
|
||||
resultMesh.vertices.append(glm::vec3(x, y, z));
|
||||
}
|
||||
|
||||
numIndices = hull.m_nTriangles * TRIANGLE_STRIDE;
|
||||
resultMeshPart.triangleIndices.reserve(resultMeshPart.triangleIndices.size() + numIndices);
|
||||
for (uint32_t i = 0; i < numIndices; i += TRIANGLE_STRIDE) {
|
||||
resultMeshPart.triangleIndices.append(hull.m_triangles[i] + hullIndexStart);
|
||||
resultMeshPart.triangleIndices.append(hull.m_triangles[i + 1] + hullIndexStart);
|
||||
resultMeshPart.triangleIndices.append(hull.m_triangles[i + 2] + hullIndexStart);
|
||||
}
|
||||
if (_verbose) {
|
||||
qDebug() << " hull" << j << " vertices =" << hull.m_nPoints
|
||||
<< " triangles =" << hull.m_nTriangles
|
||||
<< " FBXMeshVertices =" << resultMesh.vertices.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float computeDt(uint64_t start) {
|
||||
return (float)(usecTimestampNow() - start) / (float)USECS_PER_SECOND;
|
||||
}
|
||||
|
||||
bool vhacd::VHACDUtil::computeVHACD(FBXGeometry& geometry,
|
||||
VHACD::IVHACD::Parameters params,
|
||||
FBXGeometry& result,
|
||||
int startMeshIndex,
|
||||
int endMeshIndex,
|
||||
float minimumMeshSize, float maximumMeshSize) {
|
||||
if (_verbose) {
|
||||
qDebug() << "meshes =" << geometry.meshes.size();
|
||||
}
|
||||
|
||||
// count the mesh-parts
|
||||
int meshCount = 0;
|
||||
int numParts = 0;
|
||||
foreach (const FBXMesh& mesh, geometry.meshes) {
|
||||
meshCount += mesh.parts.size();
|
||||
numParts += mesh.parts.size();
|
||||
}
|
||||
if (_verbose) {
|
||||
qDebug() << "total parts =" << numParts;
|
||||
}
|
||||
|
||||
VHACD::IVHACD * interfaceVHACD = VHACD::CreateVHACD();
|
||||
|
||||
if (startMeshIndex < 0) {
|
||||
startMeshIndex = 0;
|
||||
}
|
||||
if (endMeshIndex < 0) {
|
||||
endMeshIndex = meshCount;
|
||||
}
|
||||
|
||||
std::cout << "Performing V-HACD computation on " << endMeshIndex - startMeshIndex << " meshes ..... " << std::endl;
|
||||
VHACD::IVHACD * convexifier = VHACD::CreateVHACD();
|
||||
|
||||
result.meshExtents.reset();
|
||||
result.meshes.append(FBXMesh());
|
||||
FBXMesh &resultMesh = result.meshes.last();
|
||||
|
||||
int count = 0;
|
||||
const uint32_t POINT_STRIDE = 3;
|
||||
const uint32_t TRIANGLE_STRIDE = 3;
|
||||
|
||||
int meshIndex = 0;
|
||||
int validPartsFound = 0;
|
||||
foreach (const FBXMesh& mesh, geometry.meshes) {
|
||||
|
||||
// find duplicate points
|
||||
int numDupes = 0;
|
||||
std::vector<int> dupeIndexMap;
|
||||
dupeIndexMap.reserve(mesh.vertices.size());
|
||||
for (int i = 0; i < mesh.vertices.size(); ++i) {
|
||||
dupeIndexMap.push_back(i);
|
||||
for (int j = 0; j < i; ++j) {
|
||||
float distance = glm::distance2(mesh.vertices[i], mesh.vertices[j]);
|
||||
const float MAX_DUPE_DISTANCE_SQUARED = 0.000001f;
|
||||
if (distance < MAX_DUPE_DISTANCE_SQUARED) {
|
||||
dupeIndexMap[i] = j;
|
||||
++numDupes;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// each mesh has its own transform to move it to model-space
|
||||
std::vector<glm::vec3> vertices;
|
||||
glm::mat4 totalTransform = geometry.offset * mesh.modelTransform;
|
||||
foreach (glm::vec3 vertex, mesh.vertices) {
|
||||
vertices.push_back(glm::vec3(mesh.modelTransform * glm::vec4(vertex, 1.0f)));
|
||||
vertices.push_back(glm::vec3(totalTransform * glm::vec4(vertex, 1.0f)));
|
||||
}
|
||||
uint32_t numVertices = (uint32_t)vertices.size();
|
||||
|
||||
if (_verbose) {
|
||||
qDebug() << "mesh" << meshIndex << ": "
|
||||
<< " parts =" << mesh.parts.size() << " clusters =" << mesh.clusters.size()
|
||||
<< " vertices =" << numVertices;
|
||||
}
|
||||
++meshIndex;
|
||||
|
||||
std::vector<int> openParts;
|
||||
|
||||
int partIndex = 0;
|
||||
std::vector<int> triangleIndices;
|
||||
foreach (const FBXMeshPart &meshPart, mesh.parts) {
|
||||
|
||||
if (count < startMeshIndex || count >= endMeshIndex) {
|
||||
count ++;
|
||||
continue;
|
||||
}
|
||||
|
||||
qDebug() << "--------------------";
|
||||
|
||||
std::vector<int> triangles;
|
||||
unsigned int triangleCount = getTrianglesInMeshPart(meshPart, triangles);
|
||||
triangleIndices.clear();
|
||||
getTrianglesInMeshPart(meshPart, triangleIndices);
|
||||
|
||||
// only process meshes with triangles
|
||||
if (triangles.size() <= 0) {
|
||||
qDebug() << " Skipping (no triangles)...";
|
||||
count++;
|
||||
if (triangleIndices.size() <= 0) {
|
||||
if (_verbose) {
|
||||
qDebug() << " skip part" << partIndex << "(zero triangles)";
|
||||
}
|
||||
++partIndex;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto nPoints = vertices.size();
|
||||
// collapse dupe indices
|
||||
for (auto& index : triangleIndices) {
|
||||
index = dupeIndexMap[index];
|
||||
}
|
||||
|
||||
AABox aaBox = getAABoxForMeshPart(mesh, meshPart);
|
||||
const float largestDimension = aaBox.getLargestDimension();
|
||||
|
||||
qDebug() << "Mesh " << count << " -- " << nPoints << " points, " << triangleCount << " triangles, "
|
||||
<< "size =" << largestDimension;
|
||||
|
||||
if (largestDimension < minimumMeshSize) {
|
||||
qDebug() << " Skipping (too small)...";
|
||||
count++;
|
||||
if (_verbose) {
|
||||
qDebug() << " skip part" << partIndex << ": dimension =" << largestDimension << "(too small)";
|
||||
}
|
||||
++partIndex;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (maximumMeshSize > 0.0f && largestDimension > maximumMeshSize) {
|
||||
qDebug() << " Skipping (too large)...";
|
||||
count++;
|
||||
if (_verbose) {
|
||||
qDebug() << " skip part" << partIndex << ": dimension =" << largestDimension << "(too large)";
|
||||
}
|
||||
++partIndex;
|
||||
continue;
|
||||
}
|
||||
|
||||
// figure out if the mesh is a closed manifold or not
|
||||
bool closed = isClosedManifold(triangleIndices);
|
||||
if (closed) {
|
||||
uint32_t triangleCount = (uint32_t)(triangleIndices.size()) / TRIANGLE_STRIDE;
|
||||
if (_verbose) {
|
||||
qDebug() << " process closed part" << partIndex << ": " << " triangles =" << triangleCount;
|
||||
}
|
||||
|
||||
// compute approximate convex decomposition
|
||||
bool success = convexifier->Compute(&vertices[0].x, POINT_STRIDE, numVertices,
|
||||
&triangleIndices[0], TRIANGLE_STRIDE, triangleCount, params);
|
||||
if (success) {
|
||||
getConvexResults(convexifier, resultMesh);
|
||||
} else if (_verbose) {
|
||||
qDebug() << " failed to convexify";
|
||||
}
|
||||
} else {
|
||||
if (_verbose) {
|
||||
qDebug() << " postpone open part" << partIndex;
|
||||
}
|
||||
openParts.push_back(partIndex);
|
||||
}
|
||||
++partIndex;
|
||||
++validPartsFound;
|
||||
}
|
||||
if (! openParts.empty()) {
|
||||
// combine open meshes in an attempt to produce a closed mesh
|
||||
|
||||
triangleIndices.clear();
|
||||
for (auto index : openParts) {
|
||||
const FBXMeshPart &meshPart = mesh.parts[index];
|
||||
getTrianglesInMeshPart(meshPart, triangleIndices);
|
||||
}
|
||||
|
||||
// collapse dupe indices
|
||||
for (auto& index : triangleIndices) {
|
||||
index = dupeIndexMap[index];
|
||||
}
|
||||
|
||||
// this time we don't care if the parts are closed or not
|
||||
uint32_t triangleCount = (uint32_t)(triangleIndices.size()) / TRIANGLE_STRIDE;
|
||||
if (_verbose) {
|
||||
qDebug() << " process remaining open parts =" << openParts.size() << ": "
|
||||
<< " triangles =" << triangleCount;
|
||||
}
|
||||
|
||||
// compute approximate convex decomposition
|
||||
bool res = interfaceVHACD->Compute(&vertices[0].x, 3, (uint)nPoints, &triangles[0], 3, triangleCount, params);
|
||||
if (!res){
|
||||
qDebug() << "V-HACD computation failed for Mesh : " << count;
|
||||
count++;
|
||||
continue;
|
||||
bool success = convexifier->Compute(&vertices[0].x, POINT_STRIDE, numVertices,
|
||||
&triangleIndices[0], TRIANGLE_STRIDE, triangleCount, params);
|
||||
if (success) {
|
||||
getConvexResults(convexifier, resultMesh);
|
||||
} else if (_verbose) {
|
||||
qDebug() << " failed to convexify";
|
||||
}
|
||||
|
||||
// Number of hulls for this input meshPart
|
||||
unsigned int nConvexHulls = interfaceVHACD->GetNConvexHulls();
|
||||
|
||||
// create an output meshPart for each convex hull
|
||||
for (unsigned int j = 0; j < nConvexHulls; j++) {
|
||||
VHACD::IVHACD::ConvexHull hull;
|
||||
interfaceVHACD->GetConvexHull(j, hull);
|
||||
|
||||
resultMesh.parts.append(FBXMeshPart());
|
||||
FBXMeshPart &resultMeshPart = resultMesh.parts.last();
|
||||
|
||||
int hullIndexStart = resultMesh.vertices.size();
|
||||
for (unsigned int i = 0; i < hull.m_nPoints; i++) {
|
||||
float x = hull.m_points[i * 3];
|
||||
float y = hull.m_points[i * 3 + 1];
|
||||
float z = hull.m_points[i * 3 + 2];
|
||||
resultMesh.vertices.append(glm::vec3(x, y, z));
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < hull.m_nTriangles; i++) {
|
||||
int index0 = hull.m_triangles[i * 3] + hullIndexStart;
|
||||
int index1 = hull.m_triangles[i * 3 + 1] + hullIndexStart;
|
||||
int index2 = hull.m_triangles[i * 3 + 2] + hullIndexStart;
|
||||
resultMeshPart.triangleIndices.append(index0);
|
||||
resultMeshPart.triangleIndices.append(index1);
|
||||
resultMeshPart.triangleIndices.append(index2);
|
||||
}
|
||||
}
|
||||
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
//release memory
|
||||
interfaceVHACD->Clean();
|
||||
interfaceVHACD->Release();
|
||||
convexifier->Clean();
|
||||
convexifier->Release();
|
||||
|
||||
if (count > 0){
|
||||
return true;
|
||||
}
|
||||
else{
|
||||
return false;
|
||||
}
|
||||
return validPartsFound > 0;
|
||||
}
|
||||
|
||||
vhacd::VHACDUtil:: ~VHACDUtil(){
|
||||
|
@ -319,16 +466,9 @@ void vhacd::ProgressCallback::Update(const double overallProgress,
|
|||
const char* const operation) {
|
||||
int progress = (int)(overallProgress + 0.5);
|
||||
|
||||
if (progress < 10){
|
||||
std::cout << "\b\b";
|
||||
}
|
||||
else{
|
||||
std::cout << "\b\b\b";
|
||||
}
|
||||
|
||||
std::cout << progress << "%";
|
||||
|
||||
if (progress >= 100){
|
||||
std::cout << "\b\b\b";
|
||||
std::cout << progress << "%" << std::flush;
|
||||
if (progress >= 100) {
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,18 +25,23 @@
|
|||
namespace vhacd {
|
||||
class VHACDUtil {
|
||||
public:
|
||||
void setVerbose(bool verbose) { _verbose = verbose; }
|
||||
|
||||
bool loadFBX(const QString filename, FBXGeometry& result);
|
||||
|
||||
void fattenMeshes(const FBXMesh& mesh, FBXMesh& result,
|
||||
unsigned int& meshPartCount,
|
||||
unsigned int startMeshIndex, unsigned int endMeshIndex) const;
|
||||
void fattenMesh(const FBXMesh& mesh, const glm::mat4& gometryOffset, FBXMesh& result) const;
|
||||
|
||||
bool computeVHACD(FBXGeometry& geometry,
|
||||
VHACD::IVHACD::Parameters params,
|
||||
FBXGeometry& result,
|
||||
int startMeshIndex, int endMeshIndex,
|
||||
float minimumMeshSize, float maximumMeshSize);
|
||||
|
||||
void getConvexResults(VHACD::IVHACD* convexifier, FBXMesh& resultMesh) const;
|
||||
|
||||
~VHACDUtil();
|
||||
|
||||
private:
|
||||
bool _verbose { false };
|
||||
};
|
||||
|
||||
class ProgressCallback : public VHACD::IVHACD::IUserCallback {
|
||||
|
@ -45,7 +50,7 @@ namespace vhacd {
|
|||
~ProgressCallback();
|
||||
|
||||
// Couldn't follow coding guideline here due to virtual function declared in IUserCallback
|
||||
void Update(const double overallProgress, const double stageProgress, const double operationProgress,
|
||||
void Update(const double overallProgress, const double stageProgress, const double operationProgress,
|
||||
const char * const stage, const char * const operation);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ using namespace std;
|
|||
using namespace VHACD;
|
||||
|
||||
|
||||
|
||||
QString formatFloat(double n) {
|
||||
// limit precision to 6, but don't output trailing zeros.
|
||||
QString s = QString::number(n, 'f', 6);
|
||||
|
@ -33,14 +32,15 @@ QString formatFloat(double n) {
|
|||
}
|
||||
|
||||
|
||||
bool writeOBJ(QString outFileName, FBXGeometry& geometry, bool outputCentimeters, int whichMeshPart = -1) {
|
||||
bool VHACDUtilApp::writeOBJ(QString outFileName, FBXGeometry& geometry, bool outputCentimeters, int whichMeshPart) {
|
||||
QFile file(outFileName);
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
qDebug() << "Unable to write to " << outFileName;
|
||||
qWarning() << "unable to write to" << outFileName;
|
||||
_returnCode = VHACD_RETURN_CODE_FAILURE_TO_WRITE;
|
||||
return false;
|
||||
}
|
||||
QTextStream out(&file);
|
||||
|
||||
QTextStream out(&file);
|
||||
if (outputCentimeters) {
|
||||
out << "# This file uses centimeters as units\n\n";
|
||||
}
|
||||
|
@ -105,6 +105,9 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) :
|
|||
|
||||
const QCommandLineOption helpOption = parser.addHelpOption();
|
||||
|
||||
const QCommandLineOption verboseOutput("v", "verbose output");
|
||||
parser.addOption(verboseOutput);
|
||||
|
||||
const QCommandLineOption splitOption("split", "split input-file into one mesh per output-file");
|
||||
parser.addOption(splitOption);
|
||||
|
||||
|
@ -123,12 +126,6 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) :
|
|||
const QCommandLineOption outputCentimetersOption("c", "output units are centimeters");
|
||||
parser.addOption(outputCentimetersOption);
|
||||
|
||||
const QCommandLineOption startMeshIndexOption("s", "start-mesh index", "0");
|
||||
parser.addOption(startMeshIndexOption);
|
||||
|
||||
const QCommandLineOption endMeshIndexOption("e", "end-mesh index", "0");
|
||||
parser.addOption(endMeshIndexOption);
|
||||
|
||||
const QCommandLineOption minimumMeshSizeOption("m", "minimum mesh (diagonal) size to consider", "0");
|
||||
parser.addOption(minimumMeshSizeOption);
|
||||
|
||||
|
@ -195,8 +192,10 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) :
|
|||
Q_UNREACHABLE();
|
||||
}
|
||||
|
||||
bool outputCentimeters = parser.isSet(outputCentimetersOption);
|
||||
bool verbose = parser.isSet(verboseOutput);
|
||||
vUtil.setVerbose(verbose);
|
||||
|
||||
bool outputCentimeters = parser.isSet(outputCentimetersOption);
|
||||
bool fattenFaces = parser.isSet(fattenFacesOption);
|
||||
bool generateHulls = parser.isSet(generateHullsOption);
|
||||
bool splitModel = parser.isSet(splitOption);
|
||||
|
@ -225,16 +224,6 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) :
|
|||
Q_UNREACHABLE();
|
||||
}
|
||||
|
||||
int startMeshIndex = -1;
|
||||
if (parser.isSet(startMeshIndexOption)) {
|
||||
startMeshIndex = parser.value(startMeshIndexOption).toInt();
|
||||
}
|
||||
|
||||
int endMeshIndex = -1;
|
||||
if (parser.isSet(endMeshIndexOption)) {
|
||||
endMeshIndex = parser.value(endMeshIndexOption).toInt();
|
||||
}
|
||||
|
||||
float minimumMeshSize = 0.0f;
|
||||
if (parser.isSet(minimumMeshSizeOption)) {
|
||||
minimumMeshSize = parser.value(minimumMeshSizeOption).toFloat();
|
||||
|
@ -301,17 +290,20 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) :
|
|||
Q_UNREACHABLE();
|
||||
}
|
||||
|
||||
|
||||
// load the mesh
|
||||
|
||||
// load the mesh
|
||||
FBXGeometry fbx;
|
||||
auto begin = std::chrono::high_resolution_clock::now();
|
||||
if (!vUtil.loadFBX(inputFilename, fbx)){
|
||||
cout << "Error in opening FBX file....";
|
||||
_returnCode = VHACD_RETURN_CODE_FAILURE_TO_READ;
|
||||
return;
|
||||
}
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
auto loadDuration = std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin).count();
|
||||
|
||||
if (verbose) {
|
||||
auto loadDuration = std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin).count();
|
||||
const double NANOSECS_PER_SECOND = 1.0e9;
|
||||
qDebug() << "load time =" << (double)loadDuration / NANOSECS_PER_SECOND << "seconds";
|
||||
}
|
||||
|
||||
if (splitModel) {
|
||||
QVector<QString> infileExtensions = {"fbx", "obj"};
|
||||
|
@ -329,10 +321,14 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) :
|
|||
|
||||
if (generateHulls) {
|
||||
VHACD::IVHACD::Parameters params;
|
||||
vhacd::ProgressCallback pCallBack;
|
||||
vhacd::ProgressCallback progressCallback;
|
||||
|
||||
//set parameters for V-HACD
|
||||
params.m_callback = &pCallBack; //progress callback
|
||||
if (verbose) {
|
||||
params.m_callback = &progressCallback; //progress callback
|
||||
} else {
|
||||
params.m_callback = nullptr;
|
||||
}
|
||||
params.m_resolution = vHacdResolution;
|
||||
params.m_depth = vHacdDepth;
|
||||
params.m_concavity = vHacdConcavity;
|
||||
|
@ -346,44 +342,51 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) :
|
|||
params.m_mode = 0; // 0: voxel-based (recommended), 1: tetrahedron-based
|
||||
params.m_maxNumVerticesPerCH = vHacdMaxVerticesPerCH;
|
||||
params.m_minVolumePerCH = 0.0001; // 0.0001
|
||||
params.m_callback = 0; // 0
|
||||
params.m_logger = 0; // 0
|
||||
params.m_logger = nullptr;
|
||||
params.m_convexhullApproximation = true; // true
|
||||
params.m_oclAcceleration = true; // true
|
||||
|
||||
//perform vhacd computation
|
||||
if (verbose) {
|
||||
qDebug() << "running V-HACD algorithm ...";
|
||||
}
|
||||
begin = std::chrono::high_resolution_clock::now();
|
||||
|
||||
FBXGeometry result;
|
||||
if (!vUtil.computeVHACD(fbx, params, result, startMeshIndex, endMeshIndex,
|
||||
minimumMeshSize, maximumMeshSize)) {
|
||||
cout << "Compute Failed...";
|
||||
}
|
||||
bool success = vUtil.computeVHACD(fbx, params, result, minimumMeshSize, maximumMeshSize);
|
||||
|
||||
end = std::chrono::high_resolution_clock::now();
|
||||
auto computeDuration = std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin).count();
|
||||
if (verbose) {
|
||||
qDebug() << "run time =" << (double)computeDuration / 1000000000.00 << " seconds";
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
if (verbose) {
|
||||
qDebug() << "failed to convexify model";
|
||||
}
|
||||
_returnCode = VHACD_RETURN_CODE_FAILURE_TO_CONVEXIFY;
|
||||
return;
|
||||
}
|
||||
|
||||
int totalVertices = 0;
|
||||
int totalTriangles = 0;
|
||||
int totalMeshParts = 0;
|
||||
foreach (const FBXMesh& mesh, result.meshes) {
|
||||
totalVertices += mesh.vertices.size();
|
||||
foreach (const FBXMeshPart &meshPart, mesh.parts) {
|
||||
totalTriangles += meshPart.triangleIndices.size() / 3;
|
||||
// each quad was made into two triangles
|
||||
totalTriangles += 2 * meshPart.quadIndices.size() / 4;
|
||||
totalMeshParts++;
|
||||
}
|
||||
}
|
||||
|
||||
int totalHulls = result.meshes[0].parts.size();
|
||||
cout << endl << "Summary of V-HACD Computation..................." << endl;
|
||||
cout << "File Path : " << inputFilename.toStdString() << endl;
|
||||
cout << "Number Of Meshes : " << totalMeshParts << endl;
|
||||
cout << "Total vertices : " << totalVertices << endl;
|
||||
cout << "Total Triangles : " << totalTriangles << endl;
|
||||
cout << "Total Convex Hulls : " << totalHulls << endl;
|
||||
cout << "Total FBX load time: " << (double)loadDuration / 1000000000.00 << " seconds" << endl;
|
||||
cout << "V-HACD Compute time: " << (double)computeDuration / 1000000000.00 << " seconds" << endl;
|
||||
if (verbose) {
|
||||
int totalHulls = result.meshes[0].parts.size();
|
||||
qDebug() << "output file =" << outputFilename;
|
||||
qDebug() << "vertices =" << totalVertices;
|
||||
qDebug() << "triangles =" << totalTriangles;
|
||||
qDebug() << "hulls =" << totalHulls;
|
||||
}
|
||||
|
||||
writeOBJ(outputFilename, result, outputCentimeters);
|
||||
}
|
||||
|
@ -398,17 +401,9 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) :
|
|||
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);
|
||||
vUtil.fattenMesh(mesh, fbx.offset, result);
|
||||
}
|
||||
|
||||
newFbx.meshes.append(result);
|
||||
|
|
|
@ -15,12 +15,25 @@
|
|||
|
||||
#include <QApplication>
|
||||
|
||||
#include <FBXReader.h>
|
||||
|
||||
const int VHACD_RETURN_CODE_FAILURE_TO_READ = 1;
|
||||
const int VHACD_RETURN_CODE_FAILURE_TO_WRITE = 2;
|
||||
const int VHACD_RETURN_CODE_FAILURE_TO_CONVEXIFY = 3;
|
||||
|
||||
|
||||
class VHACDUtilApp : public QCoreApplication {
|
||||
Q_OBJECT
|
||||
public:
|
||||
public:
|
||||
VHACDUtilApp(int argc, char* argv[]);
|
||||
~VHACDUtilApp();
|
||||
|
||||
bool writeOBJ(QString outFileName, FBXGeometry& geometry, bool outputCentimeters, int whichMeshPart = -1);
|
||||
|
||||
int getReturnCode() const { return _returnCode; }
|
||||
|
||||
private:
|
||||
int _returnCode { 0 };
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -23,5 +23,5 @@ using namespace VHACD;
|
|||
|
||||
int main(int argc, char * argv[]) {
|
||||
VHACDUtilApp app(argc, argv);
|
||||
return 0;
|
||||
return app.getReturnCode();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue