[英]C++//STL to OBJ Converter: How can i make my program run faster
我正在創建一個“STL 到 OBJ”格式轉換器。 該程序包含一個 header 文件,該文件從 STL 文件中讀取數據。 主程序獲取該數據並將其寫入一個新的 OBJ 文件。 一切都很好,但是對於大文件,程序需要很長時間。 我確切地知道哪個部分使程序變慢,我找不到任何替代方案。 它恰好在 For 循環中的“// 為面創建數組”部分中。
首先我想解釋一下 STL 和 OBJ 文件。 一般來說,任何 STL 格式的 3D 圖像都是由大量三角形創建的,每個三角形有 3 個頂點(每個頂點有 3 個點:x、y 和 z)。 但是有很多重復的頂點,因為三角形是相互連接的。 但在 OBJ 格式中,有兩個部分負責:一個是“頂點列表”,這里的頂點是一個接一個地排序,不重復。 第二部分是“面列表”,它是頂點的索引數。
這是我的主要代碼:
#include "Header.h"
using namespace std;
string inputFile = "Fidgit.stl"; //Import einen STL-Datei (1.6MB)
string outputFile = "Fidgit1.obj"; //Export einen OBJ-Datei (1.1MB)
int main(int argc, char** argv)
{
auto t0 = std::chrono::system_clock::now();
std::cout << "Lesen der STL-Datei" << std::endl;
std::vector<float> coords, normals;
std::vector<unsigned int> tris, solids;
stl_reader::ReadStlFile(inputFile.c_str(), coords, normals, tris, solids);
const size_t numTris = tris.size() / 3;
std::cout << " Numbers of Triangels: " << numTris << std::endl;
auto t1 = std::chrono::system_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(t1 - t0);
std::cout << " duration: " << elapsed.count() << " ms" << std::endl;
std::cout << "writing OBJ-File" << std::endl;
std::ofstream fileOBJ(outputFile.c_str(), std::ios::out);
std::cout << " Erstelle Liste der Punkte" << std::endl;
fileOBJ << "# Object name:" << std::endl;
fileOBJ << outputFile << std::endl;
fileOBJ << std::endl;
fileOBJ << "# Begin list of vertices" << std::endl;
vector<string> AllVertex;
std::ifstream inFile(outputFile.c_str(), std::ios::in);
////////////////////////////////////////////////////////////////////////////
// Find Vertiecs coordinates and write into OBJ file
for (size_t itri = 0; itri < numTris; ++itri) {
for (size_t icorner = 0; icorner < 3; ++icorner) {
float* c = &coords[3 * tris[3 * itri + icorner]];
std::string VerStr = "v " + to_string(c[2]) + " " + to_string(c[1]) + " " + to_string(c[0]) ;
AllVertex.push_back(VerStr);
}
}
// here is a vertices containing the vertices coordinates read from the STL file.
// But there are many repeated vectors that we don't need in obj format,
// so they have to be removed by next step
vector <string> OldSTLVertex = AllVertex;
//Copy of STL vectors before removing the repeated vertices
// to be able to find the faces indexes
sort(AllteVertex.begin(), AllVertex.end());
auto last = unique(AllVertex.begin(), AllVertex.end());
AllVertex.erase(last, AllVertex.end());
vector <string> OBJVertex = AllVertex;
// here are the vectors without repetitions
// ready to be able to save the vector coordinates in the created obj file:
for (auto ind : OBJVertex)
{
fileOBJ << ind << endl;
}
fileOBJ << "# End list of vertices" << std::endl;
fileOBJ << std::endl;
auto t2 = std::chrono::system_clock::now();
elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1);
std::cout << " duration: " << elapsed.count() << " ms" << std::endl;
//////////////////////////////////////////////////////////////////////////////
// Create Arry for the Faces
std::cout << " Create list of faces (triangles)" << std::endl;
vector <int> OBJFaces(numTris * 3);
fileOBJ << "# Begin list of faces" << std::endl;
int iCounter = 0;
int iPercent = 0;
int vcounter = 0;
// the point here is: which index in OBJVertiecs[] hat jeder vertiec in OldSTLVertex[]
for (int i = 0; i < OldSTLVertex.size(); i++) // in my example OldSTLVertex.size() have 99030 elements
{
bool bFound = false;
int vertexIndex = 0;
while (!bFound) // for (size_t vertexIndex = 0; vertexIndex < OBJVertex.size(); ++vertexIndex)
{
if (OldSTLVertex[i] == OBJVertex[vertexIndex]) // OBJVertex have 16523 elements
{
bFound = true;
OBJFaces[vcounter] = vertexIndex;
vcounter++;
}
vertexIndex++;
}
iCounter++;
if (iCounter % (OldSTLVertex.size() / 100) == 0) // every time 10% are done
{
iPercent = iPercent + 1;
auto t3 = std::chrono::system_clock::now();
elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(t3 - t2);
std::cout << " " << iPercent << "% done in " << elapsed.count() << " ms" << std::endl;
}
}
/////////////////////////////////////////////////////////////////////////////
// Write faces into OBJ file
unsigned count = 0;
for (auto ind : OBJFaces)
{
if (count++ % 3 == 0) fileOBJ << "f ";
fileOBJ << ind + 1 << " ";
if (count % 3 == 0) fileOBJ << std::endl;
}
fileOBJ << "# End list of faces" << std::endl;
fileOBJ << std::endl;
auto t4 = std::chrono::system_clock::now();
elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(t4 - t0);
std::cout << "OBJ file written in " << elapsed.count() << " ms." << std::endl;
return 0;
}
您當前的代碼首先將 STL 中的所有頂點映射到它們引用的頂點索引的 OBJ 字符串格式,然后使用std::unique
來減少此列表,然后在每個頂點上使用 O(n) 查找來查找原始索引。 這是 O(n*m) 並且如果 n 和 m 都很大,則非常昂貴。
相反,您可以執行以下操作:
tris
的元素,並為每個引用的頂點idx
使用std::map<std::tuple<float, float, float>, unsigned int>
對它們進行重復數據刪除。
obj_coords
並用其新索引覆蓋tris[idx]
。tris[idx]
。obj_coords
。coords
中的間接性所以,總結一下:
using Coord = std::tuple<float, float, float>;
std::map<Coord, int> coordToIndex;
std::vector<Coord> obj_coords;
for (auto &idx : tris) {
const Coord c = { coords[3*idx+0], coords[3*idx+1], coords[3*idx+2] };
if (auto it = coordToIndex.find(c); c != coordToIndex.end()) {
// We saw this vertex before
idx = it->snd;
} else {
// New vertex.
obj_coords.push_back(c);
idx = obj_coords.size()-1;
coordToIndex[c] = idx; // Also create an entry in coordToIndex
}
}
然后,生成頂點很簡單:(雖然不知道為什么交換 z 和 x)
for (const auto& coord : obj_coords) {
fileOBJ << "v " << std::get<2>(coord) << " " << std::get<1>(coord) << " " << std::get<0>(coord) << "\n";
}
最后,面孔:
for (int tri = 0; tri < tris.size(); tri += 3) {
fileOBJ << "f " << tris[tri+0] << " " << tris[tri+1] << " " << tris[tri+2] << "\n"
}
您可能已經注意到我使用"\n"
而不是std::endl
。 這是因為std::endl
意味着std::flush
,它試圖確保將數據寫入磁盤。 盡可能頻繁地調用它是一種浪費。 相反,您可以手動刷新一次,或者相信析構函數會為您完成:
fileOBJ << std::flush;
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.