mirror of
https://github.com/JulianGro/overte.git
synced 2025-05-28 00:11:43 +02:00
420 lines
15 KiB
C++
420 lines
15 KiB
C++
//
|
|
// VHACDUtil.h
|
|
// tools/vhacd/src
|
|
//
|
|
// Created by Seth Alves on 3/5/15.
|
|
// Copyright 2015 High Fidelity, Inc.
|
|
//
|
|
// Distributed under the Apache License, Version 2.0.
|
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
//
|
|
|
|
#include <QCommandLineParser>
|
|
#include <VHACD.h>
|
|
#include "VHACDUtilApp.h"
|
|
#include "VHACDUtil.h"
|
|
#include "PathUtils.h"
|
|
|
|
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);
|
|
while (s.endsWith("0")) {
|
|
s.remove(s.size() - 1, 1);
|
|
}
|
|
if (s.endsWith(".")) {
|
|
s.remove(s.size() - 1, 1);
|
|
}
|
|
return s;
|
|
}
|
|
|
|
|
|
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;
|
|
return false;
|
|
}
|
|
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
|
|
// vertex indexes in a mesh span just that mesh
|
|
|
|
int vertexIndexOffset = 0;
|
|
|
|
foreach (const FBXMesh& mesh, geometry.meshes) {
|
|
bool verticesHaveBeenOutput = false;
|
|
foreach (const FBXMeshPart &meshPart, mesh.parts) {
|
|
if (whichMeshPart >= 0 && nth != (unsigned int) whichMeshPart) {
|
|
nth++;
|
|
continue;
|
|
}
|
|
|
|
if (!verticesHaveBeenOutput) {
|
|
for (int i = 0; i < mesh.vertices.size(); i++) {
|
|
glm::vec4 v = mesh.modelTransform * glm::vec4(mesh.vertices[i], 1.0f);
|
|
out << "v ";
|
|
out << formatFloat(v[0]) << " ";
|
|
out << formatFloat(v[1]) << " ";
|
|
out << formatFloat(v[2]) << "\n";
|
|
}
|
|
verticesHaveBeenOutput = true;
|
|
}
|
|
|
|
out << "g hull-" << nth++ << "\n";
|
|
int triangleCount = meshPart.triangleIndices.size() / 3;
|
|
for (int i = 0; i < triangleCount; i++) {
|
|
out << "f ";
|
|
out << vertexIndexOffset + meshPart.triangleIndices[i*3] + 1 << " ";
|
|
out << vertexIndexOffset + meshPart.triangleIndices[i*3+1] + 1 << " ";
|
|
out << vertexIndexOffset + meshPart.triangleIndices[i*3+2] + 1 << "\n";
|
|
}
|
|
out << "\n";
|
|
}
|
|
|
|
if (verticesHaveBeenOutput) {
|
|
vertexIndexOffset += mesh.vertices.size();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) :
|
|
QCoreApplication(argc, argv)
|
|
{
|
|
vhacd::VHACDUtil vUtil;
|
|
|
|
// parse command-line
|
|
QCommandLineParser parser;
|
|
parser.setApplicationDescription("High Fidelity Object Decomposer");
|
|
parser.addHelpOption();
|
|
|
|
const QCommandLineOption helpOption = parser.addHelpOption();
|
|
|
|
const QCommandLineOption splitOption("split", "split input-file into one mesh per output-file");
|
|
parser.addOption(splitOption);
|
|
|
|
const QCommandLineOption fattenFacesOption("f", "fatten faces");
|
|
parser.addOption(fattenFacesOption);
|
|
|
|
const QCommandLineOption generateHullsOption("g", "output convex hull approximations");
|
|
parser.addOption(generateHullsOption);
|
|
|
|
const QCommandLineOption inputFilenameOption("i", "input file", "filename.fbx");
|
|
parser.addOption(inputFilenameOption);
|
|
|
|
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);
|
|
|
|
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);
|
|
|
|
const QCommandLineOption maximumMeshSizeOption("x", "maximum mesh (diagonal) size to consider", "0");
|
|
parser.addOption(maximumMeshSizeOption);
|
|
|
|
const QCommandLineOption vHacdResolutionOption("resolution", "Maximum number of voxels generated during the "
|
|
"voxelization stage (range=10,000-16,000,000)", "100000");
|
|
parser.addOption(vHacdResolutionOption);
|
|
|
|
const QCommandLineOption vHacdDepthOption("depth", "Maximum number of clipping stages. During each split stage, parts "
|
|
"with a concavity higher than the user defined threshold are clipped "
|
|
"according the \"best\" clipping plane (range=1-32)", "20");
|
|
parser.addOption(vHacdDepthOption);
|
|
|
|
|
|
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");
|
|
parser.addOption(vHacdConcavityOption);
|
|
|
|
const QCommandLineOption vHacdPlanedownsamplingOption("planedownsampling", "Controls the granularity of the search for"
|
|
" the \"best\" clipping plane (range=1-16)", "4");
|
|
parser.addOption(vHacdPlanedownsamplingOption);
|
|
|
|
const QCommandLineOption vHacdConvexhulldownsamplingOption("convexhulldownsampling", "Controls the precision of the "
|
|
"convex-hull generation process during the clipping "
|
|
"plane selection stage (range=1-16)", "4");
|
|
parser.addOption(vHacdConvexhulldownsamplingOption);
|
|
|
|
// pca
|
|
// mode
|
|
|
|
const QCommandLineOption vHacdMaxVerticesPerCHOption("maxvertices", "Controls the maximum number of triangles per "
|
|
"convex-hull (range=4-1024)", "64");
|
|
parser.addOption(vHacdMaxVerticesPerCHOption);
|
|
|
|
// minVolumePerCH
|
|
// convexhullApproximation
|
|
|
|
|
|
if (!parser.parse(QCoreApplication::arguments())) {
|
|
qCritical() << parser.errorText() << endl;
|
|
parser.showHelp();
|
|
Q_UNREACHABLE();
|
|
}
|
|
|
|
if (parser.isSet(helpOption)) {
|
|
parser.showHelp();
|
|
Q_UNREACHABLE();
|
|
}
|
|
|
|
bool outputCentimeters = parser.isSet(outputCentimetersOption);
|
|
|
|
bool fattenFaces = parser.isSet(fattenFacesOption);
|
|
bool generateHulls = parser.isSet(generateHullsOption);
|
|
bool splitModel = parser.isSet(splitOption);
|
|
|
|
|
|
QString inputFilename;
|
|
if (parser.isSet(inputFilenameOption)) {
|
|
inputFilename = parser.value(inputFilenameOption);
|
|
}
|
|
|
|
QString outputFilename;
|
|
if (parser.isSet(outputFilenameOption)) {
|
|
outputFilename = parser.value(outputFilenameOption);
|
|
}
|
|
|
|
|
|
if (inputFilename == "") {
|
|
cerr << "input filename is required.";
|
|
parser.showHelp();
|
|
Q_UNREACHABLE();
|
|
}
|
|
|
|
if (outputFilename == "") {
|
|
cerr << "output filename is required.";
|
|
parser.showHelp();
|
|
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();
|
|
}
|
|
|
|
float maximumMeshSize = 0.0f;
|
|
if (parser.isSet(maximumMeshSizeOption)) {
|
|
maximumMeshSize = parser.value(maximumMeshSizeOption).toFloat();
|
|
}
|
|
|
|
int vHacdResolution = 100000;
|
|
if (parser.isSet(vHacdResolutionOption)) {
|
|
vHacdResolution = parser.value(vHacdResolutionOption).toInt();
|
|
}
|
|
|
|
int vHacdDepth = 20;
|
|
if (parser.isSet(vHacdDepthOption)) {
|
|
vHacdDepth = parser.value(vHacdDepthOption).toInt();
|
|
}
|
|
|
|
float vHacdAlpha = 0.05f;
|
|
if (parser.isSet(vHacdAlphaOption)) {
|
|
vHacdAlpha = parser.value(vHacdAlphaOption).toFloat();
|
|
}
|
|
|
|
float vHacdBeta = 0.05f;
|
|
if (parser.isSet(vHacdBetaOption)) {
|
|
vHacdBeta = parser.value(vHacdBetaOption).toFloat();
|
|
}
|
|
|
|
float vHacdGamma = 0.00125f;
|
|
if (parser.isSet(vHacdGammaOption)) {
|
|
vHacdGamma = parser.value(vHacdGammaOption).toFloat();
|
|
}
|
|
|
|
float vHacdDelta = 0.05f;
|
|
if (parser.isSet(vHacdDeltaOption)) {
|
|
vHacdDelta = parser.value(vHacdDeltaOption).toFloat();
|
|
}
|
|
|
|
float vHacdConcavity = 0.0025f;
|
|
if (parser.isSet(vHacdConcavityOption)) {
|
|
vHacdConcavity = parser.value(vHacdConcavityOption).toFloat();
|
|
}
|
|
|
|
int vHacdPlanedownsampling = 4;
|
|
if (parser.isSet(vHacdPlanedownsamplingOption)) {
|
|
vHacdPlanedownsampling = parser.value(vHacdPlanedownsamplingOption).toInt();
|
|
}
|
|
|
|
int vHacdConvexhulldownsampling = 4;
|
|
if (parser.isSet(vHacdConvexhulldownsamplingOption)) {
|
|
vHacdConvexhulldownsampling = parser.value(vHacdConvexhulldownsamplingOption).toInt();
|
|
}
|
|
|
|
int vHacdMaxVerticesPerCH = 64;
|
|
if (parser.isSet(vHacdMaxVerticesPerCHOption)) {
|
|
vHacdMaxVerticesPerCH = parser.value(vHacdMaxVerticesPerCHOption).toInt();
|
|
}
|
|
|
|
if (!splitModel && !generateHulls && !fattenFaces) {
|
|
cerr << "\nNothing to do! Use -g or -f or --split\n\n";
|
|
parser.showHelp();
|
|
Q_UNREACHABLE();
|
|
}
|
|
|
|
|
|
// load the mesh
|
|
|
|
FBXGeometry fbx;
|
|
auto begin = std::chrono::high_resolution_clock::now();
|
|
if (!vUtil.loadFBX(inputFilename, fbx)){
|
|
cout << "Error in opening FBX file....";
|
|
}
|
|
auto end = std::chrono::high_resolution_clock::now();
|
|
auto loadDuration = std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin).count();
|
|
|
|
|
|
if (splitModel) {
|
|
QVector<QString> infileExtensions = {"fbx", "obj"};
|
|
QString baseFileName = fileNameWithoutExtension(outputFilename, 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, outputCentimeters, count);
|
|
count++;
|
|
(void)meshPart; // quiet warning
|
|
}
|
|
}
|
|
}
|
|
|
|
if (generateHulls) {
|
|
VHACD::IVHACD::Parameters params;
|
|
vhacd::ProgressCallback pCallBack;
|
|
|
|
//set parameters for V-HACD
|
|
params.m_callback = &pCallBack; //progress callback
|
|
params.m_resolution = vHacdResolution;
|
|
params.m_depth = vHacdDepth;
|
|
params.m_concavity = vHacdConcavity;
|
|
params.m_delta = vHacdDelta;
|
|
params.m_planeDownsampling = vHacdPlanedownsampling;
|
|
params.m_convexhullDownsampling = vHacdConvexhulldownsampling;
|
|
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;
|
|
params.m_minVolumePerCH = 0.0001; // 0.0001
|
|
params.m_callback = 0; // 0
|
|
params.m_logger = 0; // 0
|
|
params.m_convexhullApproximation = true; // true
|
|
params.m_oclAcceleration = true; // true
|
|
|
|
//perform vhacd computation
|
|
begin = std::chrono::high_resolution_clock::now();
|
|
|
|
FBXGeometry result;
|
|
if (!vUtil.computeVHACD(fbx, params, result, startMeshIndex, endMeshIndex,
|
|
minimumMeshSize, maximumMeshSize)) {
|
|
cout << "Compute Failed...";
|
|
}
|
|
end = std::chrono::high_resolution_clock::now();
|
|
auto computeDuration = std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin).count();
|
|
|
|
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;
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
VHACDUtilApp::~VHACDUtilApp() {
|
|
}
|