[{"data":1,"prerenderedAt":1320},["ShallowReactive",2],{"tutorial-fast-stl-and-obj-reading-in-cpp":3},{"id":4,"title":5,"author":6,"body":7,"date":1291,"description":1292,"extension":1293,"keywords":1294,"meta":1308,"navigation":141,"ogImage":1309,"path":1310,"published":141,"seo":1311,"stem":1312,"tags":1313,"__hash__":1319},"tutorials\u002Ftutorials\u002Ffast-stl-and-obj-reading-in-cpp.md","Fast STL and OBJ Reading in C++","Žiga Sajovic",{"type":8,"value":9,"toc":1280},"minimark",[10,28,36,40,45,48,107,117,121,243,250,270,273,306,310,321,380,383,447,450,454,457,589,596,651,655,662,777,780,784,787,969,983,986,1078,1082,1085,1148,1172,1176,1212,1216,1219,1223,1239,1244,1248,1254,1260,1272,1276],[11,12,13],"note",{},[14,15,16,17,22,23,27],"p",{},"Also available in ",[18,19,21],"a",{"href":20},"\u002Ftutorials\u002Ffast-stl-and-obj-reading-in-python","Python"," and ",[18,24,26],{"href":25},"\u002Ftutorials\u002Ffast-stl-and-obj-reading-in-javascript","JavaScript",".",[14,29,30,31,35],{},"Most mesh work starts with a file. ",[32,33,34],"code",{},"trueform"," reads STL (binary and ASCII) and OBJ into indexed buffers: one flat array of points, one of face indices. STL corners are welded on load, so the result is a connected mesh. It is header-only C++17. This page covers reading STL and OBJ, quads and mixed polygons, normals, texture coordinates and groups, writing files, and handing the data to your own code.",[37,38],"article-cta",{":buttons":39},"[{\"label\":\"trueform\",\"to\":\"\u002Ftrueform\",\"icon\":\"i-lucide-cpu\",\"variant\":\"soft\",\"color\":\"neutral\"},{\"label\":\"GitHub\",\"to\":\"https:\u002F\u002Fgithub.com\u002Fpolydera\u002Ftrueform\",\"icon\":\"i-simple-icons-github\",\"variant\":\"soft\",\"color\":\"neutral\"},{\"label\":\"Documentation\",\"to\":\"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fmodules\u002Fio\",\"variant\":\"soft\",\"color\":\"neutral\"}]",[41,42,44],"h2",{"id":43},"install","Install",[14,46,47],{},"Header-only. It needs C++17 and oneTBB. Add it to your project via CMake FetchContent:",[49,50,55],"pre",{"className":51,"code":52,"language":53,"meta":54,"style":54},"language-cmake shiki shiki-themes github-dark","FetchContent_Declare(\n  trueform\n  GIT_REPOSITORY https:\u002F\u002Fgithub.com\u002Fpolydera\u002Ftrueform\n)\nFetchContent_MakeAvailable(trueform)\ntarget_link_libraries(my_app PRIVATE tf::trueform)\n","cmake","",[32,56,57,66,72,78,84,90],{"__ignoreMap":54},[58,59,62],"span",{"class":60,"line":61},"line",1,[58,63,65],{"class":64},"s95oV","FetchContent_Declare(\n",[58,67,69],{"class":60,"line":68},2,[58,70,71],{"class":64},"  trueform\n",[58,73,75],{"class":60,"line":74},3,[58,76,77],{"class":64},"  GIT_REPOSITORY https:\u002F\u002Fgithub.com\u002Fpolydera\u002Ftrueform\n",[58,79,81],{"class":60,"line":80},4,[58,82,83],{"class":64},")\n",[58,85,87],{"class":60,"line":86},5,[58,88,89],{"class":64},"FetchContent_MakeAvailable(trueform)\n",[58,91,93,97,100,104],{"class":60,"line":92},6,[58,94,96],{"class":95},"snl16","target_link_libraries",[58,98,99],{"class":64},"(my_app ",[58,101,103],{"class":102},"svObZ","PRIVATE",[58,105,106],{"class":64}," tf::trueform)\n",[14,108,109,110,116],{},"Also available via conan, nuget, and pip. See the ",[18,111,115],{"href":112,"rel":113},"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fgetting-started\u002Finstallation",[114],"nofollow","installation guide"," for all options.",[41,118,120],{"id":119},"reading-stl","Reading STL",[49,122,126],{"className":123,"code":124,"language":125,"meta":54,"style":54},"language-cpp shiki shiki-themes github-dark","#include \u003Ctrueform\u002Ftrueform.hpp>\n\nauto polygons = tf::read_stl(\"model.stl\");\n\u002F\u002F tf::polygons_buffer\u003Cint, float, 3, 3>\n\nauto faces = polygons.faces();    \u002F\u002F (N, 3) vertex ids\nauto points = polygons.points();  \u002F\u002F (M, 3) positions\nauto mesh = polygons.polygons();  \u002F\u002F the mesh view the rest of the library takes\n","cpp",[32,127,128,137,143,172,178,182,203,223],{"__ignoreMap":54},[58,129,130,133],{"class":60,"line":61},[58,131,132],{"class":95},"#include",[58,134,136],{"class":135},"sU2Wk"," \u003Ctrueform\u002Ftrueform.hpp>\n",[58,138,139],{"class":60,"line":68},[58,140,142],{"emptyLinePlaceholder":141},true,"\n",[58,144,145,148,151,154,157,160,163,166,169],{"class":60,"line":74},[58,146,147],{"class":95},"auto",[58,149,150],{"class":64}," polygons ",[58,152,153],{"class":95},"=",[58,155,156],{"class":102}," tf",[58,158,159],{"class":64},"::",[58,161,162],{"class":102},"read_stl",[58,164,165],{"class":64},"(",[58,167,168],{"class":135},"\"model.stl\"",[58,170,171],{"class":64},");\n",[58,173,174],{"class":60,"line":80},[58,175,177],{"class":176},"sAwPA","\u002F\u002F tf::polygons_buffer\u003Cint, float, 3, 3>\n",[58,179,180],{"class":60,"line":86},[58,181,142],{"emptyLinePlaceholder":141},[58,183,184,186,189,191,194,197,200],{"class":60,"line":92},[58,185,147],{"class":95},[58,187,188],{"class":64}," faces ",[58,190,153],{"class":95},[58,192,193],{"class":64}," polygons.",[58,195,196],{"class":102},"faces",[58,198,199],{"class":64},"();",[58,201,202],{"class":176},"    \u002F\u002F (N, 3) vertex ids\n",[58,204,206,208,211,213,215,218,220],{"class":60,"line":205},7,[58,207,147],{"class":95},[58,209,210],{"class":64}," points ",[58,212,153],{"class":95},[58,214,193],{"class":64},[58,216,217],{"class":102},"points",[58,219,199],{"class":64},[58,221,222],{"class":176},"  \u002F\u002F (M, 3) positions\n",[58,224,226,228,231,233,235,238,240],{"class":60,"line":225},8,[58,227,147],{"class":95},[58,229,230],{"class":64}," mesh ",[58,232,153],{"class":95},[58,234,193],{"class":64},[58,236,237],{"class":102},"polygons",[58,239,199],{"class":64},[58,241,242],{"class":176},"  \u002F\u002F the mesh view the rest of the library takes\n",[14,244,245],{},[246,247],"img",{"alt":248,"src":249},"Reading STL: the triangles as an STL file stores them, each with its own three corners, next to the welded mesh read_stl returns, one connected smooth surface","\u002Fimg\u002Ftutorials\u002Fsteps\u002Ffast-stl-and-obj-reading-1-weld.png",[14,251,252,259,260,262,263,265,266,27],{},[18,253,256],{"href":254,"rel":255},"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fmodules\u002Fio#stl-files",[114],[32,257,258],{},"tf::read_stl"," detects binary or ASCII. An STL file stores three corners per triangle with no sharing. The reader merges corners at the same position, so ",[32,261,217],{}," holds each vertex once and ",[32,264,196],{}," indexes into it. The mesh is ready for topology queries such as ",[18,267,269],{"href":268},"\u002Ftutorials\u002Ffast-connected-components-in-cpp","connected components",[14,271,272],{},"For meshes with more than 2 billion vertices, ask for 64-bit indices:",[49,274,276],{"className":123,"code":275,"language":125,"meta":54,"style":54},"auto large = tf::read_stl\u003Cint64_t>(\"model.stl\");\n",[32,277,278],{"__ignoreMap":54},[58,279,280,282,285,287,289,291,293,296,299,302,304],{"class":60,"line":61},[58,281,147],{"class":95},[58,283,284],{"class":64}," large ",[58,286,153],{"class":95},[58,288,156],{"class":102},[58,290,159],{"class":64},[58,292,162],{"class":102},[58,294,295],{"class":64},"\u003C",[58,297,298],{"class":95},"int64_t",[58,300,301],{"class":64},">(",[58,303,168],{"class":135},[58,305,171],{"class":64},[41,307,309],{"id":308},"reading-obj","Reading OBJ",[14,311,312,313,320],{},"OBJ files may mix triangles, quads and larger polygons. By default ",[18,314,317],{"href":315,"rel":316},"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fmodules\u002Fio#obj-files",[114],[32,318,319],{},"tf::read_obj"," returns faces of any length:",[49,322,324],{"className":123,"code":323,"language":125,"meta":54,"style":54},"auto mesh = tf::read_obj(\"model.obj\");\n\u002F\u002F tf::polygons_buffer\u003Cint, float, 3, tf::dynamic_size>\nauto first = mesh.faces()[0];     \u002F\u002F vertex ids of face 0, any length\n",[32,325,326,348,353],{"__ignoreMap":54},[58,327,328,330,332,334,336,338,341,343,346],{"class":60,"line":61},[58,329,147],{"class":95},[58,331,230],{"class":64},[58,333,153],{"class":95},[58,335,156],{"class":102},[58,337,159],{"class":64},[58,339,340],{"class":102},"read_obj",[58,342,165],{"class":64},[58,344,345],{"class":135},"\"model.obj\"",[58,347,171],{"class":64},[58,349,350],{"class":60,"line":68},[58,351,352],{"class":176},"\u002F\u002F tf::polygons_buffer\u003Cint, float, 3, tf::dynamic_size>\n",[58,354,355,357,360,362,365,367,370,374,377],{"class":60,"line":74},[58,356,147],{"class":95},[58,358,359],{"class":64}," first ",[58,361,153],{"class":95},[58,363,364],{"class":64}," mesh.",[58,366,196],{"class":102},[58,368,369],{"class":64},"()[",[58,371,373],{"class":372},"sDLfK","0",[58,375,376],{"class":64},"];",[58,378,379],{"class":176},"     \u002F\u002F vertex ids of face 0, any length\n",[14,381,382],{},"If you know the face size, state it. The faces then come back as a fixed-width block:",[49,384,386],{"className":123,"code":385,"language":125,"meta":54,"style":54},"auto triangles = tf::read_obj\u003C3>(\"model.obj\");\n\u002F\u002F tf::polygons_buffer\u003Cint, float, 3, 3>\nauto large = tf::read_obj\u003Cint64_t, 3>(\"model.obj\");\n",[32,387,388,414,418],{"__ignoreMap":54},[58,389,390,392,395,397,399,401,403,405,408,410,412],{"class":60,"line":61},[58,391,147],{"class":95},[58,393,394],{"class":64}," triangles ",[58,396,153],{"class":95},[58,398,156],{"class":102},[58,400,159],{"class":64},[58,402,340],{"class":102},[58,404,295],{"class":64},[58,406,407],{"class":372},"3",[58,409,301],{"class":64},[58,411,345],{"class":135},[58,413,171],{"class":64},[58,415,416],{"class":60,"line":68},[58,417,177],{"class":176},[58,419,420,422,424,426,428,430,432,434,436,439,441,443,445],{"class":60,"line":74},[58,421,147],{"class":95},[58,423,284],{"class":64},[58,425,153],{"class":95},[58,427,156],{"class":102},[58,429,159],{"class":64},[58,431,340],{"class":102},[58,433,295],{"class":64},[58,435,298],{"class":95},[58,437,438],{"class":64},", ",[58,440,407],{"class":372},[58,442,301],{"class":64},[58,444,345],{"class":135},[58,446,171],{"class":64},[14,448,449],{},"The reader maps the file instead of copying it, so do not modify the file while it is read. OBJ indices start at 1; the buffers start at 0. The default mode reads positions only.",[41,451,453],{"id":452},"quads-and-mixed-polygons","Quads and mixed polygons",[14,455,456],{},"A small quad file shows the two forms:",[49,458,460],{"className":123,"code":459,"language":125,"meta":54,"style":54},"std::ofstream(\"quads.obj\") \u003C\u003C \"v 0 0 0\\nv 1 0 0\\nv 1 1 0\\nv 0 1 0\\nv 2 0 0\\nv 2 1 0\\n\"\n                              \"f 1 2 3 4\\nf 2 5 6 3\\n\";\n\nauto quads = tf::read_obj\u003C4>(\"quads.obj\");\nauto [a, b, c, d] = quads.faces()[1];  \u002F\u002F 1, 4, 5, 2\n",[32,461,462,517,535,539,565],{"__ignoreMap":54},[58,463,464,467,469,472,474,477,480,483,486,489,492,494,497,499,502,504,507,509,512,514],{"class":60,"line":61},[58,465,466],{"class":102},"std",[58,468,159],{"class":64},[58,470,471],{"class":102},"ofstream",[58,473,165],{"class":64},[58,475,476],{"class":135},"\"quads.obj\"",[58,478,479],{"class":64},") ",[58,481,482],{"class":95},"\u003C\u003C",[58,484,485],{"class":135}," \"v 0 0 0",[58,487,488],{"class":372},"\\n",[58,490,491],{"class":135},"v 1 0 0",[58,493,488],{"class":372},[58,495,496],{"class":135},"v 1 1 0",[58,498,488],{"class":372},[58,500,501],{"class":135},"v 0 1 0",[58,503,488],{"class":372},[58,505,506],{"class":135},"v 2 0 0",[58,508,488],{"class":372},[58,510,511],{"class":135},"v 2 1 0",[58,513,488],{"class":372},[58,515,516],{"class":135},"\"\n",[58,518,519,522,524,527,529,532],{"class":60,"line":68},[58,520,521],{"class":135},"                              \"f 1 2 3 4",[58,523,488],{"class":372},[58,525,526],{"class":135},"f 2 5 6 3",[58,528,488],{"class":372},[58,530,531],{"class":135},"\"",[58,533,534],{"class":64},";\n",[58,536,537],{"class":60,"line":74},[58,538,142],{"emptyLinePlaceholder":141},[58,540,541,543,546,548,550,552,554,556,559,561,563],{"class":60,"line":80},[58,542,147],{"class":95},[58,544,545],{"class":64}," quads ",[58,547,153],{"class":95},[58,549,156],{"class":102},[58,551,159],{"class":64},[58,553,340],{"class":102},[58,555,295],{"class":64},[58,557,558],{"class":372},"4",[58,560,301],{"class":64},[58,562,476],{"class":135},[58,564,171],{"class":64},[58,566,567,569,572,574,577,579,581,584,586],{"class":60,"line":86},[58,568,147],{"class":95},[58,570,571],{"class":64}," [a, b, c, d] ",[58,573,153],{"class":95},[58,575,576],{"class":64}," quads.",[58,578,196],{"class":102},[58,580,369],{"class":64},[58,582,583],{"class":372},"1",[58,585,376],{"class":64},[58,587,588],{"class":176},"  \u002F\u002F 1, 4, 5, 2\n",[14,590,591,592,595],{},"To get triangles from polygons, use ",[32,593,594],{},"tf::triangulated",":",[49,597,599],{"className":123,"code":598,"language":125,"meta":54,"style":54},"auto poly = tf::read_obj(\"quads.obj\");\nauto tris = tf::triangulated(poly.polygons());\n\u002F\u002F tris.faces().size() == 4\n",[32,600,601,622,646],{"__ignoreMap":54},[58,602,603,605,608,610,612,614,616,618,620],{"class":60,"line":61},[58,604,147],{"class":95},[58,606,607],{"class":64}," poly ",[58,609,153],{"class":95},[58,611,156],{"class":102},[58,613,159],{"class":64},[58,615,340],{"class":102},[58,617,165],{"class":64},[58,619,476],{"class":135},[58,621,171],{"class":64},[58,623,624,626,629,631,633,635,638,641,643],{"class":60,"line":68},[58,625,147],{"class":95},[58,627,628],{"class":64}," tris ",[58,630,153],{"class":95},[58,632,156],{"class":102},[58,634,159],{"class":64},[58,636,637],{"class":102},"triangulated",[58,639,640],{"class":64},"(poly.",[58,642,237],{"class":102},[58,644,645],{"class":64},"());\n",[58,647,648],{"class":60,"line":74},[58,649,650],{"class":176},"\u002F\u002F tris.faces().size() == 4\n",[41,652,654],{"id":653},"normals-texture-coordinates-and-groups","Normals, texture coordinates and groups",[14,656,657,658,661],{},"Pass ",[32,659,660],{},"tf::complete"," to read everything the file carries. Each unique position, texture and normal triplet becomes one vertex, so all per-vertex buffers line up:",[49,663,665],{"className":123,"code":664,"language":125,"meta":54,"style":54},"auto f = tf::read_obj(\"textured.obj\", tf::complete);\n\nauto points = f.polygons.points();\nauto normals = f.normals;              \u002F\u002F aligned with points, or empty\nauto textures = f.textures;            \u002F\u002F aligned with points, or empty\nauto &face_groups = f.face_groups;     \u002F\u002F per face, index into group_names\nauto &group_names = f.group_names;\n",[32,666,667,695,699,715,730,745,763],{"__ignoreMap":54},[58,668,669,671,674,676,678,680,682,684,687,689,692],{"class":60,"line":61},[58,670,147],{"class":95},[58,672,673],{"class":64}," f ",[58,675,153],{"class":95},[58,677,156],{"class":102},[58,679,159],{"class":64},[58,681,340],{"class":102},[58,683,165],{"class":64},[58,685,686],{"class":135},"\"textured.obj\"",[58,688,438],{"class":64},[58,690,691],{"class":102},"tf",[58,693,694],{"class":64},"::complete);\n",[58,696,697],{"class":60,"line":68},[58,698,142],{"emptyLinePlaceholder":141},[58,700,701,703,705,707,710,712],{"class":60,"line":74},[58,702,147],{"class":95},[58,704,210],{"class":64},[58,706,153],{"class":95},[58,708,709],{"class":64}," f.polygons.",[58,711,217],{"class":102},[58,713,714],{"class":64},"();\n",[58,716,717,719,722,724,727],{"class":60,"line":80},[58,718,147],{"class":95},[58,720,721],{"class":64}," normals ",[58,723,153],{"class":95},[58,725,726],{"class":64}," f.normals;",[58,728,729],{"class":176},"              \u002F\u002F aligned with points, or empty\n",[58,731,732,734,737,739,742],{"class":60,"line":86},[58,733,147],{"class":95},[58,735,736],{"class":64}," textures ",[58,738,153],{"class":95},[58,740,741],{"class":64}," f.textures;",[58,743,744],{"class":176},"            \u002F\u002F aligned with points, or empty\n",[58,746,747,749,752,755,757,760],{"class":60,"line":92},[58,748,147],{"class":95},[58,750,751],{"class":95}," &",[58,753,754],{"class":64},"face_groups ",[58,756,153],{"class":95},[58,758,759],{"class":64}," f.face_groups;",[58,761,762],{"class":176},"     \u002F\u002F per face, index into group_names\n",[58,764,765,767,769,772,774],{"class":60,"line":205},[58,766,147],{"class":95},[58,768,751],{"class":95},[58,770,771],{"class":64},"group_names ",[58,773,153],{"class":95},[58,775,776],{"class":64}," f.group_names;\n",[14,778,779],{},"A position with two different normals or texture coordinates becomes two vertices, as at a texture seam or a sharp edge. Negative (relative) indices are not supported in this mode; such a file returns an empty result.",[41,781,783],{"id":782},"writing-stl-and-obj","Writing STL and OBJ",[14,785,786],{},"The writers take any mesh view. Your own arrays work without a copy:",[49,788,790],{"className":123,"code":789,"language":125,"meta":54,"style":54},"std::vector\u003Cfloat> flat_points = {0, 0, 0, 1, 0, 0, 0, 1, 0};\nstd::vector\u003Cint> flat_triangles = {0, 1, 2};\n\nauto mesh = tf::make_polygons(tf::make_faces\u003C3>(flat_triangles),\n                              tf::make_points\u003C3>(flat_points));\nbool ok = tf::write_stl(mesh, \"triangle.stl\");\ntf::write_obj(mesh, \"triangle.obj\");\n",[32,791,792,847,876,880,911,928,953],{"__ignoreMap":54},[58,793,794,796,799,802,805,807,810,812,814,816,818,820,822,824,826,828,830,832,834,836,838,840,842,844],{"class":60,"line":61},[58,795,466],{"class":102},[58,797,798],{"class":64},"::vector",[58,800,801],{"class":95},"\u003Cfloat>",[58,803,804],{"class":64}," flat_points ",[58,806,153],{"class":95},[58,808,809],{"class":64}," {",[58,811,373],{"class":372},[58,813,438],{"class":64},[58,815,373],{"class":372},[58,817,438],{"class":64},[58,819,373],{"class":372},[58,821,438],{"class":64},[58,823,583],{"class":372},[58,825,438],{"class":64},[58,827,373],{"class":372},[58,829,438],{"class":64},[58,831,373],{"class":372},[58,833,438],{"class":64},[58,835,373],{"class":372},[58,837,438],{"class":64},[58,839,583],{"class":372},[58,841,438],{"class":64},[58,843,373],{"class":372},[58,845,846],{"class":64},"};\n",[58,848,849,851,853,856,859,861,863,865,867,869,871,874],{"class":60,"line":68},[58,850,466],{"class":102},[58,852,798],{"class":64},[58,854,855],{"class":95},"\u003Cint>",[58,857,858],{"class":64}," flat_triangles ",[58,860,153],{"class":95},[58,862,809],{"class":64},[58,864,373],{"class":372},[58,866,438],{"class":64},[58,868,583],{"class":372},[58,870,438],{"class":64},[58,872,873],{"class":372},"2",[58,875,846],{"class":64},[58,877,878],{"class":60,"line":74},[58,879,142],{"emptyLinePlaceholder":141},[58,881,882,884,886,888,890,892,895,897,899,901,904,906,908],{"class":60,"line":80},[58,883,147],{"class":95},[58,885,230],{"class":64},[58,887,153],{"class":95},[58,889,156],{"class":102},[58,891,159],{"class":64},[58,893,894],{"class":102},"make_polygons",[58,896,165],{"class":64},[58,898,691],{"class":102},[58,900,159],{"class":64},[58,902,903],{"class":102},"make_faces",[58,905,295],{"class":64},[58,907,407],{"class":372},[58,909,910],{"class":64},">(flat_triangles),\n",[58,912,913,916,918,921,923,925],{"class":60,"line":86},[58,914,915],{"class":102},"                              tf",[58,917,159],{"class":64},[58,919,920],{"class":102},"make_points",[58,922,295],{"class":64},[58,924,407],{"class":372},[58,926,927],{"class":64},">(flat_points));\n",[58,929,930,933,936,938,940,942,945,948,951],{"class":60,"line":92},[58,931,932],{"class":95},"bool",[58,934,935],{"class":64}," ok ",[58,937,153],{"class":95},[58,939,156],{"class":102},[58,941,159],{"class":64},[58,943,944],{"class":102},"write_stl",[58,946,947],{"class":64},"(mesh, ",[58,949,950],{"class":135},"\"triangle.stl\"",[58,952,171],{"class":64},[58,954,955,957,959,962,964,967],{"class":60,"line":205},[58,956,691],{"class":102},[58,958,159],{"class":64},[58,960,961],{"class":102},"write_obj",[58,963,947],{"class":64},[58,965,966],{"class":135},"\"triangle.obj\"",[58,968,171],{"class":64},[14,970,971,974,975,978,979,982],{},[32,972,973],{},"tf::write_stl"," writes binary STL and needs triangles. ",[32,976,977],{},"tf::write_obj"," keeps quads and mixed polygons as they are. Both return ",[32,980,981],{},"true"," on success.",[14,984,985],{},"To place a mesh as it is written, tag a transformation on the view. The data is not changed:",[49,987,989],{"className":123,"code":988,"language":125,"meta":54,"style":54},"auto T = tf::make_transformation_from_translation(\n    tf::make_vector(0.f, 0.f, 5.f));\ntf::write_stl(polygons.polygons() | tf::tag(T), \"moved.stl\");\n",[32,990,991,1010,1044],{"__ignoreMap":54},[58,992,993,995,998,1000,1002,1004,1007],{"class":60,"line":61},[58,994,147],{"class":95},[58,996,997],{"class":64}," T ",[58,999,153],{"class":95},[58,1001,156],{"class":102},[58,1003,159],{"class":64},[58,1005,1006],{"class":102},"make_transformation_from_translation",[58,1008,1009],{"class":64},"(\n",[58,1011,1012,1015,1017,1020,1022,1025,1028,1030,1032,1034,1036,1039,1041],{"class":60,"line":68},[58,1013,1014],{"class":102},"    tf",[58,1016,159],{"class":64},[58,1018,1019],{"class":102},"make_vector",[58,1021,165],{"class":64},[58,1023,1024],{"class":372},"0.",[58,1026,1027],{"class":95},"f",[58,1029,438],{"class":64},[58,1031,1024],{"class":372},[58,1033,1027],{"class":95},[58,1035,438],{"class":64},[58,1037,1038],{"class":372},"5.",[58,1040,1027],{"class":95},[58,1042,1043],{"class":64},"));\n",[58,1045,1046,1048,1050,1052,1055,1057,1060,1063,1065,1067,1070,1073,1076],{"class":60,"line":74},[58,1047,691],{"class":102},[58,1049,159],{"class":64},[58,1051,944],{"class":102},[58,1053,1054],{"class":64},"(polygons.",[58,1056,237],{"class":102},[58,1058,1059],{"class":64},"() ",[58,1061,1062],{"class":95},"|",[58,1064,156],{"class":102},[58,1066,159],{"class":64},[58,1068,1069],{"class":102},"tag",[58,1071,1072],{"class":64},"(T), ",[58,1074,1075],{"class":135},"\"moved.stl\"",[58,1077,171],{"class":64},[41,1079,1081],{"id":1080},"working-with-results","Working with results",[14,1083,1084],{},"The buffers own flat memory. Hand it to a renderer, a GPU upload or another library directly:",[49,1086,1088],{"className":123,"code":1087,"language":125,"meta":54,"style":54},"float *point_data = polygons.points_buffer().data_buffer().data();\nint *face_data = polygons.faces_buffer().data_buffer().data();\n",[32,1089,1090,1121],{"__ignoreMap":54},[58,1091,1092,1095,1098,1101,1103,1105,1108,1111,1114,1116,1119],{"class":60,"line":61},[58,1093,1094],{"class":95},"float",[58,1096,1097],{"class":95}," *",[58,1099,1100],{"class":64},"point_data ",[58,1102,153],{"class":95},[58,1104,193],{"class":64},[58,1106,1107],{"class":102},"points_buffer",[58,1109,1110],{"class":64},"().",[58,1112,1113],{"class":102},"data_buffer",[58,1115,1110],{"class":64},[58,1117,1118],{"class":102},"data",[58,1120,714],{"class":64},[58,1122,1123,1126,1128,1131,1133,1135,1138,1140,1142,1144,1146],{"class":60,"line":68},[58,1124,1125],{"class":95},"int",[58,1127,1097],{"class":95},[58,1129,1130],{"class":64},"face_data ",[58,1132,153],{"class":95},[58,1134,193],{"class":64},[58,1136,1137],{"class":102},"faces_buffer",[58,1139,1110],{"class":64},[58,1141,1113],{"class":102},[58,1143,1110],{"class":64},[58,1145,1118],{"class":102},[58,1147,714],{"class":64},[14,1149,1150,1153,1154,1156,1157,438,1161,438,1166,438,1168,27],{},[32,1151,1152],{},"polygons.polygons()"," goes straight into the rest of ",[32,1155,34],{},": ",[18,1158,1160],{"href":1159},"\u002Ftutorials\u002Ffast-closest-point-on-a-mesh-in-cpp","closest point",[18,1162,1165],{"href":1163,"rel":1164},"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fmodules\u002Fremesh",[114],"decimation",[18,1167,269],{"href":268},[18,1169,1171],{"href":1170},"\u002Ftutorials\u002Ffast-mesh-booleans-in-cpp","booleans",[41,1173,1175],{"id":1174},"related-tutorials","Related tutorials",[1177,1178,1179,1186,1192,1198,1205],"ul",{},[1180,1181,1182,1185],"li",{},[18,1183,1184],{"href":1170},"Fast Mesh Booleans in C++"," — exact union, intersection and difference of two meshes.",[1180,1187,1188,1191],{},[18,1189,1190],{"href":1159},"Fast and Robust Closest Point on a Mesh in C++"," — nearest surface point for a batch of queries.",[1180,1193,1194,1197],{},[18,1195,1196],{"href":268},"Fast Connected Components in C++"," — split a mesh into parts and find its boundary loops.",[1180,1199,1200,1204],{},[18,1201,1203],{"href":1202},"\u002Ftutorials\u002Fprincipal-curvatures-of-a-mesh-in-cpp","Fast and Robust Principal Curvatures of a Mesh in C++"," — curvature values and directions at every vertex.",[1180,1206,1207,1211],{},[18,1208,1210],{"href":1209},"\u002Ftutorials\u002Fconstrained-delaunay-triangulation-in-cpp","Fast and Exact Constrained Delaunay Triangulation in C++"," — triangulate points and outlines, with holes and constraints.",[41,1213,1215],{"id":1214},"performance","Performance",[14,1217,1218],{},"Read times on a 1,025,766-triangle mesh, in C++. OBJ is a triangulated file read into float32 points and int32 faces. Binary STL is read and welded exactly into shared vertices. Times include opening the file, parsing, and building the output.",[1220,1221],"headline-numbers",{":items":1222},"[{\"value\":\"6.5ms\",\"label\":\"OBJ read\",\"detail\":\"1,025,766 triangles\"},{\"value\":\"42.5ms\",\"label\":\"STL read and weld\",\"detail\":\"1,025,766 triangles\"},{\"value\":\"5.4×\",\"label\":\"faster STL than stl_reader\",\"detail\":\"229.1 ms\"}]",[14,1224,1225,1230,1231,1230,1235],{},[58,1226,1229],{"className":1227},[1228],"tag-pill","Apple M4 Max"," ",[58,1232,1234],{"className":1233},[1228],"16 threads",[58,1236,1238],{"className":1237},[1228],"mimalloc",[1240,1241],"data-table",{":headers":1242,":highlight":373,":rows":1243},"[\"OBJ reader\",\"1,025,766 triangles\",\"vs trueform\"]","[[\"trueform\",\"6.5 ms\",\"1.0×\"],[\"RapidObj\",\"13.3 ms\",\"2.0×\"],[\"tinyobjloader\",\"66.6 ms\",\"10×\"],[\"CGAL\",\"736.1 ms\",\"112×\"],[\"libigl\",\"952.3 ms\",\"146×\"]]",[1240,1245],{":headers":1246,":highlight":373,":rows":1247},"[\"STL reader\",\"1,025,766 triangles\",\"vs trueform\"]","[[\"trueform\",\"42.5 ms\",\"1.0×\"],[\"stl_reader\",\"229.1 ms\",\"5.4×\"],[\"VTK\",\"239.2 ms\",\"5.6×\"],[\"libigl\",\"294.1 ms\",\"6.9×\"],[\"CGAL\",\"392.8 ms\",\"9.2×\"]]",[14,1249,1250,1251,1253],{},"Of the readers measured, ",[32,1252,34],{}," is the fastest on both formats. Median of three fresh-process medians, seven samples per process. Benchmark run with RapidObj 1.1.0, tinyobjloader 2.0.0-rc.13, VTK 9.5.2, CGAL 6.2 and libigl 2.5.0.",[14,1255,1256],{},[246,1257],{"alt":1258,"src":1259},"Benchmark chart: trueform against other C++ libraries, io","\u002Fimg\u002Falgorithms\u002Fbenchmark-io-cpp-2026.png",[14,1261,1262,1266,1267],{},[18,1263,1265],{"href":1264},"\u002Ftrueform\u002Fbenchmarks","Full benchmarks"," · ",[18,1268,1271],{"href":1269,"rel":1270},"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fmodules\u002Fio",[114],"Documentation",[1273,1274],"cite-as",{"author":1275,"title":5},"Sajovic, {\\v{Z}}iga",[1277,1278,1279],"style",{},"html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}",{"title":54,"searchDepth":68,"depth":68,"links":1281},[1282,1283,1284,1285,1286,1287,1288,1289,1290],{"id":43,"depth":68,"text":44},{"id":119,"depth":68,"text":120},{"id":308,"depth":68,"text":309},{"id":452,"depth":68,"text":453},{"id":653,"depth":68,"text":654},{"id":782,"depth":68,"text":783},{"id":1080,"depth":68,"text":1081},{"id":1174,"depth":68,"text":1175},{"id":1214,"depth":68,"text":1215},"2026-10-31","Read and write STL and OBJ mesh files in C++ into indexed float\u002Fint buffers. A 1M-triangle OBJ in 6.5 ms and a welded binary STL in 42.5 ms, measured against RapidObj, tinyobjloader, stl_reader, VTK, libigl and CGAL.","md",[1295,1296,1297,1298,1299,1300,1301,1302,1303,1304,1305,1306,1307],"read STL C++","read OBJ C++","fast OBJ loader C++","fast STL reader C++","load mesh C++","write STL C++","write OBJ C++","tinyobjloader alternative","RapidObj alternative","stl_reader alternative","libigl readOBJ alternative","CGAL read_polygon_mesh alternative","VTK STL reader alternative",{},"\u002Fimg\u002Fog\u002Ftutorials\u002Ffast-stl-and-obj-reading-in-cpp.png","\u002Ftutorials\u002Ffast-stl-and-obj-reading-in-cpp",{"title":5,"description":1292},"tutorials\u002Ffast-stl-and-obj-reading-in-cpp",[1314,1315,1316,1317,1318],"c++","io","stl","obj","tutorial","PN-s020BmACMXVe7Lldk-DQAySTicb4Mhte43hRHSBg",1793517811885]