in Meshconvert/Meshconvert.cpp [497:1081]
int __cdecl wmain(_In_ int argc, _In_z_count_(argc) wchar_t* argv[])
{
// Parameters and defaults
DXGI_FORMAT normalFormat = DXGI_FORMAT_R32G32B32_FLOAT;
DXGI_FORMAT uvFormat = DXGI_FORMAT_R32G32_FLOAT;
DXGI_FORMAT colorFormat = DXGI_FORMAT_B8G8R8A8_UNORM;
wchar_t szOutputFile[MAX_PATH] = {};
// Set locale for output since GetErrorDesc can get localized strings.
std::locale::global(std::locale(""));
// Process command line
uint32_t dwOptions = 0;
std::list<SConversion> conversion;
for (int iArg = 1; iArg < argc; iArg++)
{
PWSTR pArg = argv[iArg];
if (('-' == pArg[0]) || ('/' == pArg[0]))
{
pArg++;
PWSTR pValue;
for (pValue = pArg; *pValue && (':' != *pValue); pValue++);
if (*pValue)
*pValue++ = 0;
uint32_t dwOption = LookupByName(pArg, g_pOptions);
if (!dwOption || (dwOptions & (1 << dwOption)))
{
wprintf(L"ERROR: unknown command-line option '%ls'\n\n", pArg);
PrintUsage();
return 1;
}
dwOptions |= (1 << dwOption);
// Handle options with additional value parameter
switch (dwOption)
{
case OPT_OUTPUTFILE:
case OPT_VERT_NORMAL_FORMAT:
case OPT_VERT_UV_FORMAT:
case OPT_VERT_COLOR_FORMAT:
case OPT_FILELIST:
if (!*pValue)
{
if ((iArg + 1 >= argc))
{
wprintf(L"ERROR: missing value for command-line option '%ls'\n\n", pArg);
PrintUsage();
return 1;
}
iArg++;
pValue = argv[iArg];
}
break;
}
switch (dwOption)
{
case OPT_OPTIMIZE_LRU:
dwOptions |= (1 << OPT_OPTIMIZE);
break;
case OPT_WEIGHT_BY_AREA:
if (dwOptions & (1 << OPT_WEIGHT_BY_EQUAL))
{
wprintf(L"Cannot use both na and ne at the same time\n");
return 1;
}
dwOptions |= (1 << OPT_NORMALS);
break;
case OPT_WEIGHT_BY_EQUAL:
if (dwOptions & (1 << OPT_WEIGHT_BY_AREA))
{
wprintf(L"Cannot use both na and ne at the same time\n");
return 1;
}
dwOptions |= (1 << OPT_NORMALS);
break;
case OPT_OUTPUTFILE:
wcscpy_s(szOutputFile, MAX_PATH, pValue);
break;
case OPT_TOPOLOGICAL_ADJ:
if (dwOptions & (1 << OPT_GEOMETRIC_ADJ))
{
wprintf(L"Cannot use both ta and ga at the same time\n");
return 1;
}
break;
case OPT_GEOMETRIC_ADJ:
if (dwOptions & (1 << OPT_TOPOLOGICAL_ADJ))
{
wprintf(L"Cannot use both ta and ga at the same time\n");
return 1;
}
break;
case OPT_SDKMESH:
case OPT_SDKMESH_V2:
if (dwOptions & ((1 << OPT_VBO) | (1 << OPT_CMO) | (1 << OPT_WAVEFRONT_OBJ)))
{
wprintf(L"Can only use one of sdkmesh, cmo, vbo, or wf\n");
return 1;
}
if (dwOption == OPT_SDKMESH_V2)
{
dwOptions |= (1 << OPT_SDKMESH);
}
break;
case OPT_CMO:
if (dwOptions & ((1 << OPT_VBO) | (1 << OPT_SDKMESH) | (1 << OPT_WAVEFRONT_OBJ)))
{
wprintf(L"Can only use one of sdkmesh, cmo, vbo, or wf\n");
return 1;
}
break;
case OPT_VBO:
if (dwOptions & ((1 << OPT_SDKMESH) | (1 << OPT_CMO) | (1 << OPT_WAVEFRONT_OBJ)))
{
wprintf(L"Can only use one of sdkmesh, cmo, vbo, or wf\n");
return 1;
}
break;
case OPT_WAVEFRONT_OBJ:
if (dwOptions & ((1 << OPT_VBO) | (1 << OPT_SDKMESH) | (1 << OPT_CMO)))
{
wprintf(L"Can only use one of sdkmesh, cmo, vbo, or wf\n");
return 1;
}
break;
case OPT_VERT_NORMAL_FORMAT:
normalFormat = static_cast<DXGI_FORMAT>(LookupByName(pValue, g_vertexNormalFormats));
if (!normalFormat)
{
wprintf(L"Invalid value specified with -fn (%ls)\n", pValue);
wprintf(L"\n");
PrintUsage();
return 1;
}
break;
case OPT_VERT_UV_FORMAT:
uvFormat = static_cast<DXGI_FORMAT>(LookupByName(pValue, g_vertexUVFormats));
if (!uvFormat)
{
wprintf(L"Invalid value specified with -fuv (%ls)\n", pValue);
wprintf(L"\n");
PrintUsage();
return 1;
}
break;
case OPT_VERT_COLOR_FORMAT:
colorFormat = static_cast<DXGI_FORMAT>(LookupByName(pValue, g_vertexColorFormats));
if (!colorFormat)
{
wprintf(L"Invalid value specified with -fc (%ls)\n", pValue);
wprintf(L"\n");
PrintUsage();
return 1;
}
break;
case OPT_FILELIST:
{
std::wifstream inFile(pValue);
if (!inFile)
{
wprintf(L"Error opening -flist file %ls\n", pValue);
return 1;
}
inFile.imbue(std::locale::classic());
ProcessFileList(inFile, conversion);
}
break;
}
}
else if (wcspbrk(pArg, L"?*") != nullptr)
{
size_t count = conversion.size();
SearchForFiles(pArg, conversion, (dwOptions & (1 << OPT_RECURSIVE)) != 0);
if (conversion.size() <= count)
{
wprintf(L"No matching files found for %ls\n", pArg);
return 1;
}
}
else
{
SConversion conv = {};
wcscpy_s(conv.szSrc, MAX_PATH, pArg);
conversion.push_back(conv);
}
}
if (conversion.empty())
{
PrintUsage();
return 0;
}
if (*szOutputFile && conversion.size() > 1)
{
wprintf(L"Cannot use -o with multiple input files\n");
return 1;
}
if (~dwOptions & (1 << OPT_NOLOGO))
PrintLogo();
// Process files
for (auto pConv = conversion.begin(); pConv != conversion.end(); ++pConv)
{
wchar_t ext[_MAX_EXT] = {};
wchar_t fname[_MAX_FNAME] = {};
_wsplitpath_s(pConv->szSrc, nullptr, 0, nullptr, 0, fname, _MAX_FNAME, ext, _MAX_EXT);
if (pConv != conversion.begin())
wprintf(L"\n");
wprintf(L"reading %ls", pConv->szSrc);
fflush(stdout);
std::unique_ptr<Mesh> inMesh;
std::vector<Mesh::Material> inMaterial;
HRESULT hr = E_NOTIMPL;
if (_wcsicmp(ext, L".vbo") == 0)
{
hr = Mesh::CreateFromVBO(pConv->szSrc, inMesh);
}
else if (_wcsicmp(ext, L".sdkmesh") == 0)
{
wprintf(L"\nERROR: Importing SDKMESH files not supported\n");
return 1;
}
else if (_wcsicmp(ext, L".cmo") == 0)
{
wprintf(L"\nERROR: Importing Visual Studio CMO files not supported\n");
return 1;
}
else if (_wcsicmp(ext, L".x") == 0)
{
wprintf(L"\nERROR: Legacy Microsoft X files not supported\n");
return 1;
}
else if (_wcsicmp(ext, L".fbx") == 0)
{
wprintf(L"\nERROR: Autodesk FBX files not supported\n");
return 1;
}
else
{
hr = LoadFromOBJ(pConv->szSrc, inMesh, inMaterial,
(dwOptions & (1 << OPT_CLOCKWISE)) ? false : true,
(dwOptions & (1 << OPT_NODDS)) ? false : true);
}
if (FAILED(hr))
{
wprintf(L" FAILED (%08X%ls)\n", static_cast<unsigned int>(hr), GetErrorDesc(hr));
return 1;
}
size_t nVerts = inMesh->GetVertexCount();
size_t nFaces = inMesh->GetFaceCount();
if (!nVerts || !nFaces)
{
wprintf(L"\nERROR: Invalid mesh\n");
return 1;
}
assert(inMesh->GetPositionBuffer() != nullptr);
assert(inMesh->GetIndexBuffer() != nullptr);
wprintf(L"\n%zu vertices, %zu faces", nVerts, nFaces);
if (dwOptions & (1 << OPT_FLIPU))
{
hr = inMesh->InvertUTexCoord();
if (FAILED(hr))
{
wprintf(L"\nERROR: Failed inverting u texcoord (%08X%ls)\n",
static_cast<unsigned int>(hr), GetErrorDesc(hr));
return 1;
}
}
if (dwOptions & (1 << OPT_FLIPV))
{
hr = inMesh->InvertVTexCoord();
if (FAILED(hr))
{
wprintf(L"\nERROR: Failed inverting v texcoord (%08X%ls)\n",
static_cast<unsigned int>(hr), GetErrorDesc(hr));
return 1;
}
}
if (dwOptions & (1 << OPT_FLIPZ))
{
hr = inMesh->ReverseHandedness();
if (FAILED(hr))
{
wprintf(L"\nERROR: Failed reversing handedness (%08X%ls)\n",
static_cast<unsigned int>(hr), GetErrorDesc(hr));
return 1;
}
}
// Prepare mesh for processing
if (dwOptions & ((1 << OPT_OPTIMIZE) | (1 << OPT_CLEAN)))
{
// Adjacency
float epsilon = (dwOptions & (1 << OPT_GEOMETRIC_ADJ)) ? 1e-5f : 0.f;
hr = inMesh->GenerateAdjacency(epsilon);
if (FAILED(hr))
{
wprintf(L"\nERROR: Failed generating adjacency (%08X%ls)\n",
static_cast<unsigned int>(hr), GetErrorDesc(hr));
return 1;
}
// Validation
std::wstring msgs;
hr = inMesh->Validate(VALIDATE_BACKFACING, &msgs);
if (!msgs.empty())
{
wprintf(L"\nWARNING: \n");
wprintf(L"%ls", msgs.c_str());
}
// Clean (also handles attribute reuse split if needed)
hr = inMesh->Clean();
if (FAILED(hr))
{
wprintf(L"\nERROR: Failed mesh clean (%08X%ls)\n",
static_cast<unsigned int>(hr), GetErrorDesc(hr));
return 1;
}
else
{
size_t nNewVerts = inMesh->GetVertexCount();
if (nVerts != nNewVerts)
{
wprintf(L" [%zu vertex dups] ", nNewVerts - nVerts);
nVerts = nNewVerts;
}
}
}
if (!inMesh->GetNormalBuffer())
{
dwOptions |= 1 << OPT_NORMALS;
}
if (!inMesh->GetTangentBuffer() && (dwOptions & (1 << OPT_CMO)))
{
dwOptions |= 1 << OPT_TANGENTS;
}
// Compute vertex normals from faces
if ((dwOptions & (1 << OPT_NORMALS))
|| ((dwOptions & ((1 << OPT_TANGENTS) | (1 << OPT_CTF))) && !inMesh->GetNormalBuffer()))
{
CNORM_FLAGS flags = CNORM_DEFAULT;
if (dwOptions & (1 << OPT_WEIGHT_BY_EQUAL))
{
flags |= CNORM_WEIGHT_EQUAL;
}
else if (dwOptions & (1 << OPT_WEIGHT_BY_AREA))
{
flags |= CNORM_WEIGHT_BY_AREA;
}
if (dwOptions & (1 << OPT_CLOCKWISE))
{
flags |= CNORM_WIND_CW;
}
hr = inMesh->ComputeNormals(flags);
if (FAILED(hr))
{
wprintf(L"\nERROR: Failed computing normals (flags:%lX, %08X%ls)\n",
flags, static_cast<unsigned int>(hr), GetErrorDesc(hr));
return 1;
}
}
// Compute tangents and bitangents
if (dwOptions & ((1 << OPT_TANGENTS) | (1 << OPT_CTF)))
{
if (!inMesh->GetTexCoordBuffer())
{
wprintf(L"\nERROR: Computing tangents/bi-tangents requires texture coordinates\n");
return 1;
}
hr = inMesh->ComputeTangentFrame((dwOptions & (1 << OPT_CTF)) ? true : false);
if (FAILED(hr))
{
wprintf(L"\nERROR: Failed computing tangent frame (%08X%ls)\n",
static_cast<unsigned int>(hr), GetErrorDesc(hr));
return 1;
}
}
// Perform attribute and vertex-cache optimization
if (dwOptions & (1 << OPT_OPTIMIZE))
{
assert(inMesh->GetAdjacencyBuffer() != nullptr);
float acmr, atvr;
ComputeVertexCacheMissRate(inMesh->GetIndexBuffer(), nFaces, nVerts, OPTFACES_V_DEFAULT, acmr, atvr);
wprintf(L" [ACMR %f, ATVR %f] ", acmr, atvr);
hr = inMesh->Optimize((dwOptions & (1 << OPT_OPTIMIZE_LRU)) ? true : false);
if (FAILED(hr))
{
wprintf(L"\nERROR: Failed vertex-cache optimization (%08X%ls)\n",
static_cast<unsigned int>(hr), GetErrorDesc(hr));
return 1;
}
}
if (dwOptions & (1 << OPT_FLIP))
{
hr = inMesh->ReverseWinding();
if (FAILED(hr))
{
wprintf(L"\nERROR: Failed reversing winding (%08X%ls)\n",
static_cast<unsigned int>(hr), GetErrorDesc(hr));
return 1;
}
}
// Write results
wprintf(L"\n\t->\n");
if (dwOptions & (1 << OPT_OPTIMIZE))
{
float acmr, atvr;
ComputeVertexCacheMissRate(inMesh->GetIndexBuffer(), nFaces, nVerts, OPTFACES_V_DEFAULT, acmr, atvr);
wprintf(L" [ACMR %f, ATVR %f] ", acmr, atvr);
}
wchar_t outputPath[MAX_PATH] = {};
wchar_t outputExt[_MAX_EXT] = {};
if (*szOutputFile)
{
wcscpy_s(outputPath, szOutputFile);
_wsplitpath_s(szOutputFile, nullptr, 0, nullptr, 0, nullptr, 0, outputExt, _MAX_EXT);
}
else
{
if (dwOptions & (1 << OPT_VBO))
{
wcscpy_s(outputExt, L".vbo");
}
else if (dwOptions & (1 << OPT_CMO))
{
wcscpy_s(outputExt, L".cmo");
}
else if (dwOptions & (1 << OPT_WAVEFRONT_OBJ))
{
wcscpy_s(outputExt, L".obj");
}
else
{
wcscpy_s(outputExt, L".sdkmesh");
}
wchar_t outFilename[_MAX_FNAME] = {};
wcscpy_s(outFilename, fname);
_wmakepath_s(outputPath, nullptr, nullptr, outFilename, outputExt);
}
if (dwOptions & (1 << OPT_TOLOWER))
{
std::ignore = _wcslwr_s(outputPath);
}
if (~dwOptions & (1 << OPT_OVERWRITE))
{
if (GetFileAttributesW(outputPath) != INVALID_FILE_ATTRIBUTES)
{
wprintf(L"\nERROR: Output file already exists, use -y to overwrite:\n'%ls'\n", outputPath);
return 1;
}
}
if (!_wcsicmp(outputExt, L".vbo"))
{
if (!inMesh->GetNormalBuffer() || !inMesh->GetTexCoordBuffer())
{
wprintf(L"\nERROR: VBO requires position, normal, and texcoord\n");
return 1;
}
if (!inMesh->Is16BitIndexBuffer() || (dwOptions & (1 << OPT_FORCE_32BIT_IB)))
{
wprintf(L"\nERROR: VBO only supports 16-bit indices\n");
return 1;
}
hr = inMesh->ExportToVBO(outputPath);
}
else if (!_wcsicmp(outputExt, L".sdkmesh"))
{
hr = inMesh->ExportToSDKMESH(
outputPath,
inMaterial.size(), inMaterial.empty() ? nullptr : inMaterial.data(),
(dwOptions & (1 << OPT_FORCE_32BIT_IB)) ? true : false,
(dwOptions & (1 << OPT_SDKMESH_V2)) ? true : false,
normalFormat,
uvFormat,
colorFormat);
}
else if (!_wcsicmp(outputExt, L".cmo"))
{
if (!inMesh->GetNormalBuffer() || !inMesh->GetTexCoordBuffer() || !inMesh->GetTangentBuffer())
{
wprintf(L"\nERROR: Visual Studio CMO requires position, normal, tangents, and texcoord\n");
return 1;
}
if (!inMesh->Is16BitIndexBuffer() || (dwOptions & (1 << OPT_FORCE_32BIT_IB)))
{
wprintf(L"\nERROR: Visual Studio CMO only supports 16-bit indices\n");
return 1;
}
hr = inMesh->ExportToCMO(outputPath, inMaterial.size(), inMaterial.empty() ? nullptr : inMaterial.data());
}
else if (!_wcsicmp(outputExt, L".obj") || !_wcsicmp(outputExt, L"._obj"))
{
hr = inMesh->ExportToOBJ(outputPath, inMaterial.size(), inMaterial.empty() ? nullptr : inMaterial.data());
}
else if (!_wcsicmp(outputExt, L".x"))
{
wprintf(L"\nERROR: Legacy Microsoft X files not supported\n");
return 1;
}
else
{
wprintf(L"\nERROR: Unknown output file type '%ls'\n", outputExt);
return 1;
}
if (FAILED(hr))
{
wprintf(L"\nERROR: Failed write (%08X%ls):-> '%ls'\n",
static_cast<unsigned int>(hr), GetErrorDesc(hr), outputPath);
return 1;
}
wprintf(L" %zu vertices, %zu faces written:\n'%ls'\n", nVerts, nFaces, outputPath);
}
return 0;
}