繁体   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