簡體   English   中英

C++//STL 到 OBJ 轉換器:如何讓我的程序運行得更快

[英]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 都很大,則非常昂貴。

相反,您可以執行以下操作:

  1. 遍歷tris的元素,並為每個引用的頂點idx使用std::map<std::tuple<float, float, float>, unsigned int>對它們進行重復數據刪除。
    1. 如果它不存在,則將坐標三元組推送到向量obj_coords並用其新索引覆蓋tris[idx]
    2. 如果它存在,您只需用現有索引覆蓋tris[idx]
  2. 渲染向量時,您幾乎可以按原樣轉儲obj_coords
  3. 渲染面時,您只需遵循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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM