[{"data":1,"prerenderedAt":1449},["ShallowReactive",2],{"tutorial-fast-mesh-booleans-in-cpp":3},{"id":4,"title":5,"author":6,"body":7,"date":1430,"description":1431,"extension":1432,"keywords":1433,"meta":1440,"navigation":254,"path":1441,"published":254,"seo":1442,"stem":1443,"tags":1444,"__hash__":1448},"tutorials\u002Ftutorials\u002Ffast-mesh-booleans-in-cpp.md","Fast Mesh Booleans in C++","Žiga Sajovic",{"type":8,"value":9,"toc":1415},"minimark",[10,28,35,39,42,46,51,56,59,118,128,132,135,151,158,216,219,310,317,417,425,429,432,517,537,541,549,554,593,597,631,641,645,679,683,706,709,741,744,800,804,830,990,993,1139,1142,1146,1153,1235,1244,1268,1272,1293,1345,1348,1352,1355,1359,1379,1385,1390,1395,1407,1411],[11,12,13],"note",{},[14,15,16,17,22,23,27],"p",{},"Also available in ",[18,19,21],"a",{"href":20},"\u002Ftutorials\u002Ffast-mesh-booleans-in-python","Python"," and ",[18,24,26],{"href":25},"\u002Ftutorials\u002Ffast-mesh-booleans-in-javascript","JavaScript",".",[14,29,30,34],{},[31,32,33],"code",{},"trueform"," is the fastest C++ mesh boolean library for real-world meshes — performing exact boolean union, intersection, and difference at interactive speed. This tutorial covers the basics: loading meshes, running booleans, and working with results. It then shows how to precompute spatial and topological structures and apply transformations to avoid rebuilding them, enabling boolean operations on moving geometry at real-time rates.",[36,37],"article-cta",{":buttons":38},"[{\"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\u002Fgetting-started\u002Finstallation\",\"variant\":\"soft\",\"color\":\"neutral\"},{\"label\":\"Try it live\",\"to\":\"https:\u002F\u002Ftrueform.polydera.com\u002Flive-examples\u002Fboolean\",\"icon\":\"i-lucide-play\"}]",[14,40,41],{},"We will use the Stanford Dragon to demonstrate boolean union, intersection, and difference between two offset copies of the same mesh.",[43,44],"image-grid",{":images":45},"[{\"src\":\"\u002Fimg\u002Ftutorials\u002Fdragon_original.png\",\"label\":\"Dragon\",\"detail\":\"500K triangles\"},{\"src\":\"\u002Fimg\u002Ftutorials\u002Fdragon_union.png\",\"label\":\"Union\",\"detail\":\"A ∪ B\"},{\"src\":\"\u002Fimg\u002Ftutorials\u002Fdragon_difference.png\",\"label\":\"Difference\",\"detail\":\"A \\\\ B\"},{\"src\":\"\u002Fimg\u002Ftutorials\u002Fdragon_intersection.png\",\"label\":\"Intersection\",\"detail\":\"A ∩ B\"}]",[14,47,48,49,27],{},"Each boolean under 14ms on 2×500K polygons. Let's begin by installing ",[31,50,33],{},[52,53,55],"h2",{"id":54},"install","Install",[14,57,58],{},"Header-only. Add it to your project via CMake FetchContent:",[60,61,66],"pre",{"className":62,"code":63,"language":64,"meta":65,"style":65},"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","",[31,67,68,77,83,89,95,101],{"__ignoreMap":65},[69,70,73],"span",{"class":71,"line":72},"line",1,[69,74,76],{"class":75},"s95oV","FetchContent_Declare(\n",[69,78,80],{"class":71,"line":79},2,[69,81,82],{"class":75},"  trueform\n",[69,84,86],{"class":71,"line":85},3,[69,87,88],{"class":75},"  GIT_REPOSITORY https:\u002F\u002Fgithub.com\u002Fpolydera\u002Ftrueform\n",[69,90,92],{"class":71,"line":91},4,[69,93,94],{"class":75},")\n",[69,96,98],{"class":71,"line":97},5,[69,99,100],{"class":75},"FetchContent_MakeAvailable(trueform)\n",[69,102,104,108,111,115],{"class":71,"line":103},6,[69,105,107],{"class":106},"snl16","target_link_libraries",[69,109,110],{"class":75},"(my_app ",[69,112,114],{"class":113},"svObZ","PRIVATE",[69,116,117],{"class":75}," tf::trueform)\n",[14,119,120,121,127],{},"Also available via conan, nuget, and pip. See the ",[18,122,126],{"href":123,"rel":124},"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fgetting-started\u002Finstallation",[125],"nofollow","installation guide"," for all options.",[52,129,131],{"id":130},"loading-the-mesh","Loading the mesh",[14,133,134],{},"Include the library:",[60,136,140],{"className":137,"code":138,"language":139,"meta":65,"style":65},"language-cpp shiki shiki-themes github-dark","#include \u003Ctrueform\u002Ftrueform.hpp>\n","cpp",[31,141,142],{"__ignoreMap":65},[69,143,144,147],{"class":71,"line":72},[69,145,146],{"class":106},"#include",[69,148,150],{"class":149},"sU2Wk"," \u003Ctrueform\u002Ftrueform.hpp>\n",[14,152,153,154,157],{},"All functions live in the ",[31,155,156],{},"tf::"," namespace. To load a mesh from a file:",[60,159,161],{"className":137,"code":160,"language":139,"meta":65,"style":65},"auto dragon_buffer = tf::read_stl(\"stanford_dragon.stl\");\n\u002F\u002F semantic view into the data\nauto dragon = dragon_buffer.polygons();\n",[31,162,163,192,198],{"__ignoreMap":65},[69,164,165,168,171,174,177,180,183,186,189],{"class":71,"line":72},[69,166,167],{"class":106},"auto",[69,169,170],{"class":75}," dragon_buffer ",[69,172,173],{"class":106},"=",[69,175,176],{"class":113}," tf",[69,178,179],{"class":75},"::",[69,181,182],{"class":113},"read_stl",[69,184,185],{"class":75},"(",[69,187,188],{"class":149},"\"stanford_dragon.stl\"",[69,190,191],{"class":75},");\n",[69,193,194],{"class":71,"line":79},[69,195,197],{"class":196},"sAwPA","\u002F\u002F semantic view into the data\n",[69,199,200,202,205,207,210,213],{"class":71,"line":85},[69,201,167],{"class":106},[69,203,204],{"class":75}," dragon ",[69,206,173],{"class":106},[69,208,209],{"class":75}," dragon_buffer.",[69,211,212],{"class":113},"polygons",[69,214,215],{"class":75},"();\n",[14,217,218],{},"If you already have vertex and face data in memory, construct views directly — no copy required:",[60,220,222],{"className":137,"code":221,"language":139,"meta":65,"style":65},"std::vector\u003Cfloat> flat_points;\nstd::vector\u003Cint> flat_triangles;\n\nauto dragon = tf::make_polygons(\n    tf::make_faces\u003C3>(flat_triangles),\n    tf::make_points\u003C3>(flat_points));\n",[31,223,224,238,250,256,274,294],{"__ignoreMap":65},[69,225,226,229,232,235],{"class":71,"line":72},[69,227,228],{"class":113},"std",[69,230,231],{"class":75},"::vector",[69,233,234],{"class":106},"\u003Cfloat>",[69,236,237],{"class":75}," flat_points;\n",[69,239,240,242,244,247],{"class":71,"line":79},[69,241,228],{"class":113},[69,243,231],{"class":75},[69,245,246],{"class":106},"\u003Cint>",[69,248,249],{"class":75}," flat_triangles;\n",[69,251,252],{"class":71,"line":85},[69,253,255],{"emptyLinePlaceholder":254},true,"\n",[69,257,258,260,262,264,266,268,271],{"class":71,"line":91},[69,259,167],{"class":106},[69,261,204],{"class":75},[69,263,173],{"class":106},[69,265,176],{"class":113},[69,267,179],{"class":75},[69,269,270],{"class":113},"make_polygons",[69,272,273],{"class":75},"(\n",[69,275,276,279,281,284,287,291],{"class":71,"line":97},[69,277,278],{"class":113},"    tf",[69,280,179],{"class":75},[69,282,283],{"class":113},"make_faces",[69,285,286],{"class":75},"\u003C",[69,288,290],{"class":289},"sDLfK","3",[69,292,293],{"class":75},">(flat_triangles),\n",[69,295,296,298,300,303,305,307],{"class":71,"line":103},[69,297,278],{"class":113},[69,299,179],{"class":75},[69,301,302],{"class":113},"make_points",[69,304,286],{"class":75},[69,306,290],{"class":289},[69,308,309],{"class":75},">(flat_points));\n",[14,311,312,313,316],{},"The resulting ",[31,314,315],{},"dragon"," gives you full mesh semantics on the views:",[60,318,320],{"className":137,"code":319,"language":139,"meta":65,"style":65},"auto [id0, id1, id2] = dragon.faces().front();\nauto [pt0, pt1, pt2] = dragon.front();\nauto pt = dragon.points().front();\n\nauto pt3 = pt0 + (pt2 - pt1) \u002F 2;\n",[31,321,322,345,360,380,384],{"__ignoreMap":65},[69,323,324,326,329,331,334,337,340,343],{"class":71,"line":72},[69,325,167],{"class":106},[69,327,328],{"class":75}," [id0, id1, id2] ",[69,330,173],{"class":106},[69,332,333],{"class":75}," dragon.",[69,335,336],{"class":113},"faces",[69,338,339],{"class":75},"().",[69,341,342],{"class":113},"front",[69,344,215],{"class":75},[69,346,347,349,352,354,356,358],{"class":71,"line":79},[69,348,167],{"class":106},[69,350,351],{"class":75}," [pt0, pt1, pt2] ",[69,353,173],{"class":106},[69,355,333],{"class":75},[69,357,342],{"class":113},[69,359,215],{"class":75},[69,361,362,364,367,369,371,374,376,378],{"class":71,"line":85},[69,363,167],{"class":106},[69,365,366],{"class":75}," pt ",[69,368,173],{"class":106},[69,370,333],{"class":75},[69,372,373],{"class":113},"points",[69,375,339],{"class":75},[69,377,342],{"class":113},[69,379,215],{"class":75},[69,381,382],{"class":71,"line":91},[69,383,255],{"emptyLinePlaceholder":254},[69,385,386,388,391,393,396,399,402,405,408,411,414],{"class":71,"line":97},[69,387,167],{"class":106},[69,389,390],{"class":75}," pt3 ",[69,392,173],{"class":106},[69,394,395],{"class":75}," pt0 ",[69,397,398],{"class":106},"+",[69,400,401],{"class":75}," (pt2 ",[69,403,404],{"class":106},"-",[69,406,407],{"class":75}," pt1) ",[69,409,410],{"class":106},"\u002F",[69,412,413],{"class":289}," 2",[69,415,416],{"class":75},";\n",[14,418,419,420,27],{},"For details on working with geometric ranges, see the ",[18,421,424],{"href":422,"rel":423},"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fexamples\u002Fmesh-assembly",[125],"Geometry Walkthrough",[52,426,428],{"id":427},"translated-views","Translated views",[14,430,431],{},"To perform a boolean between two copies of the dragon at different positions, we tag a transformation onto the form. The underlying data is shared — no vertices are copied.",[60,433,435],{"className":137,"code":434,"language":139,"meta":65,"style":65},"auto T = tf::make_transformation_from_translation(\n    tf::make_vector(0.f, 1.f, 0.f));\n\nauto translated = dragon | tf::tag(T);\n",[31,436,437,455,489,493],{"__ignoreMap":65},[69,438,439,441,444,446,448,450,453],{"class":71,"line":72},[69,440,167],{"class":106},[69,442,443],{"class":75}," T ",[69,445,173],{"class":106},[69,447,176],{"class":113},[69,449,179],{"class":75},[69,451,452],{"class":113},"make_transformation_from_translation",[69,454,273],{"class":75},[69,456,457,459,461,464,466,469,472,475,478,480,482,484,486],{"class":71,"line":79},[69,458,278],{"class":113},[69,460,179],{"class":75},[69,462,463],{"class":113},"make_vector",[69,465,185],{"class":75},[69,467,468],{"class":289},"0.",[69,470,471],{"class":106},"f",[69,473,474],{"class":75},", ",[69,476,477],{"class":289},"1.",[69,479,471],{"class":106},[69,481,474],{"class":75},[69,483,468],{"class":289},[69,485,471],{"class":106},[69,487,488],{"class":75},"));\n",[69,490,491],{"class":71,"line":85},[69,492,255],{"emptyLinePlaceholder":254},[69,494,495,497,500,502,504,507,509,511,514],{"class":71,"line":91},[69,496,167],{"class":106},[69,498,499],{"class":75}," translated ",[69,501,173],{"class":106},[69,503,204],{"class":75},[69,505,506],{"class":106},"|",[69,508,176],{"class":113},[69,510,179],{"class":75},[69,512,513],{"class":113},"tag",[69,515,516],{"class":75},"(T);\n",[14,518,519,522,523,525,526,531,532,534,535,27],{},[31,520,521],{},"translated"," retains the full API of ",[31,524,315],{}," through the ",[18,527,530],{"href":528,"rel":529},"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fmodules\u002Fcore#policies",[125],"policy system",". Any operation that accepts ",[31,533,315],{}," also accepts ",[31,536,521],{},[52,538,540],{"id":539},"boolean-operations","Boolean operations",[14,542,543,544,27],{},"With the two forms ready, a boolean is a single call. ",[18,545,548],{"href":546,"rel":547},"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fmodules\u002Fcut#boolean-operations",[125],"Full documentation",[550,551,553],"h3",{"id":552},"union","Union",[60,555,557],{"className":137,"code":556,"language":139,"meta":65,"style":65},"auto [result_buffer, labels, face_labels] = tf::make_boolean(\n    dragon, translated, tf::boolean_op::merge);\n",[31,558,559,577],{"__ignoreMap":65},[69,560,561,563,566,568,570,572,575],{"class":71,"line":72},[69,562,167],{"class":106},[69,564,565],{"class":75}," [result_buffer, labels, face_labels] ",[69,567,173],{"class":106},[69,569,176],{"class":113},[69,571,179],{"class":75},[69,573,574],{"class":113},"make_boolean",[69,576,273],{"class":75},[69,578,579,582,585,587,590],{"class":71,"line":79},[69,580,581],{"class":75},"    dragon, translated, ",[69,583,584],{"class":113},"tf",[69,586,179],{"class":75},[69,588,589],{"class":113},"boolean_op",[69,591,592],{"class":75},"::merge);\n",[550,594,596],{"id":595},"difference","Difference",[60,598,600],{"className":137,"code":599,"language":139,"meta":65,"style":65},"auto [result_buffer, labels, face_labels] = tf::make_boolean(\n    dragon, translated, tf::boolean_op::left_difference);\n",[31,601,602,618],{"__ignoreMap":65},[69,603,604,606,608,610,612,614,616],{"class":71,"line":72},[69,605,167],{"class":106},[69,607,565],{"class":75},[69,609,173],{"class":106},[69,611,176],{"class":113},[69,613,179],{"class":75},[69,615,574],{"class":113},[69,617,273],{"class":75},[69,619,620,622,624,626,628],{"class":71,"line":79},[69,621,581],{"class":75},[69,623,584],{"class":113},[69,625,179],{"class":75},[69,627,589],{"class":113},[69,629,630],{"class":75},"::left_difference);\n",[14,632,633,636,637,640],{},[31,634,635],{},"left_difference"," keeps the left mesh minus the right. ",[31,638,639],{},"right_difference"," does the opposite.",[550,642,644],{"id":643},"intersection","Intersection",[60,646,648],{"className":137,"code":647,"language":139,"meta":65,"style":65},"auto [result_buffer, labels, face_labels] = tf::make_boolean(\n    dragon, translated, tf::boolean_op::intersection);\n",[31,649,650,666],{"__ignoreMap":65},[69,651,652,654,656,658,660,662,664],{"class":71,"line":72},[69,653,167],{"class":106},[69,655,565],{"class":75},[69,657,173],{"class":106},[69,659,176],{"class":113},[69,661,179],{"class":75},[69,663,574],{"class":113},[69,665,273],{"class":75},[69,667,668,670,672,674,676],{"class":71,"line":79},[69,669,581],{"class":75},[69,671,584],{"class":113},[69,673,179],{"class":75},[69,675,589],{"class":113},[69,677,678],{"class":75},"::intersection);\n",[550,680,682],{"id":681},"working-with-results","Working with results",[14,684,685,686,689,690,693,694,697,698,701,702,705],{},"Every boolean returns three values. ",[31,687,688],{},"result_buffer"," holds the output mesh. ",[31,691,692],{},"labels"," marks each output face — ",[31,695,696],{},"0"," for faces originating from the first input, ",[31,699,700],{},"1"," from the second. ",[31,703,704],{},"face_labels"," maps each output face back to the index of its source face in the input, enabling attribute transfer.",[14,707,708],{},"To split the result into separate meshes by label:",[60,710,712],{"className":137,"code":711,"language":139,"meta":65,"style":65},"auto [components, component_labels] =\n    tf::split_into_components(result_buffer.polygons(), labels);\n",[31,713,714,724],{"__ignoreMap":65},[69,715,716,718,721],{"class":71,"line":72},[69,717,167],{"class":106},[69,719,720],{"class":75}," [components, component_labels] ",[69,722,723],{"class":106},"=\n",[69,725,726,728,730,733,736,738],{"class":71,"line":79},[69,727,278],{"class":113},[69,729,179],{"class":75},[69,731,732],{"class":113},"split_into_components",[69,734,735],{"class":75},"(result_buffer.",[69,737,212],{"class":113},[69,739,740],{"class":75},"(), labels);\n",[14,742,743],{},"Write the two halves to file:",[60,745,747],{"className":137,"code":746,"language":139,"meta":65,"style":65},"tf::write_stl(components[0].polygons(), \"first.stl\");\ntf::write_obj(components[1].polygons(), \"second.obj\");\n",[31,748,749,776],{"__ignoreMap":65},[69,750,751,753,755,758,761,763,766,768,771,774],{"class":71,"line":72},[69,752,584],{"class":113},[69,754,179],{"class":75},[69,756,757],{"class":113},"write_stl",[69,759,760],{"class":75},"(components[",[69,762,696],{"class":289},[69,764,765],{"class":75},"].",[69,767,212],{"class":113},[69,769,770],{"class":75},"(), ",[69,772,773],{"class":149},"\"first.stl\"",[69,775,191],{"class":75},[69,777,778,780,782,785,787,789,791,793,795,798],{"class":71,"line":79},[69,779,584],{"class":113},[69,781,179],{"class":75},[69,783,784],{"class":113},"write_obj",[69,786,760],{"class":75},[69,788,700],{"class":289},[69,790,765],{"class":75},[69,792,212],{"class":113},[69,794,770],{"class":75},[69,796,797],{"class":149},"\"second.obj\"",[69,799,191],{"class":75},[52,801,803],{"id":802},"precomputed-structures","Precomputed structures",[14,805,806,807,474,814,821,822,829],{},"When running multiple booleans on the same geometry — for example, subtracting a tool at different positions along a path — rebuilding spatial and topological structures every time is wasteful. Build the ",[18,808,811],{"href":809,"rel":810},"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fmodules\u002Fspatial#tftree",[125],[31,812,813],{},"aabb_tree",[18,815,818],{"href":816,"rel":817},"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fmodules\u002Ftopology#face-membership",[125],[31,819,820],{},"face_membership",", and ",[18,823,826],{"href":824,"rel":825},"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fmodules\u002Ftopology#manifold-edge-link",[125],[31,827,828],{},"manifold_edge_link"," once, then tag them onto the form:",[60,831,833],{"className":137,"code":832,"language":139,"meta":65,"style":65},"tf::aabb_tree\u003Cint, float, 3> tree;\ntree.build(dragon, tf::config_tree(4, 4));\n\nauto fm = tf::make_face_membership(dragon);\nauto mel = tf::make_manifold_edge_link(dragon);\n\nauto tagged = dragon\n    | tf::tag(tree)\n    | tf::tag(fm)\n    | tf::tag(mel);\n",[31,834,835,860,889,893,912,930,934,947,962,976],{"__ignoreMap":65},[69,836,837,839,842,845,847,850,852,854,857],{"class":71,"line":72},[69,838,584],{"class":113},[69,840,841],{"class":75},"::aabb_tree",[69,843,844],{"class":106},"\u003Cint",[69,846,474],{"class":75},[69,848,849],{"class":106},"float",[69,851,474],{"class":75},[69,853,290],{"class":289},[69,855,856],{"class":106},">",[69,858,859],{"class":75}," tree;\n",[69,861,862,865,868,871,873,875,878,880,883,885,887],{"class":71,"line":79},[69,863,864],{"class":75},"tree.",[69,866,867],{"class":113},"build",[69,869,870],{"class":75},"(dragon, ",[69,872,584],{"class":113},[69,874,179],{"class":75},[69,876,877],{"class":113},"config_tree",[69,879,185],{"class":75},[69,881,882],{"class":289},"4",[69,884,474],{"class":75},[69,886,882],{"class":289},[69,888,488],{"class":75},[69,890,891],{"class":71,"line":85},[69,892,255],{"emptyLinePlaceholder":254},[69,894,895,897,900,902,904,906,909],{"class":71,"line":91},[69,896,167],{"class":106},[69,898,899],{"class":75}," fm ",[69,901,173],{"class":106},[69,903,176],{"class":113},[69,905,179],{"class":75},[69,907,908],{"class":113},"make_face_membership",[69,910,911],{"class":75},"(dragon);\n",[69,913,914,916,919,921,923,925,928],{"class":71,"line":97},[69,915,167],{"class":106},[69,917,918],{"class":75}," mel ",[69,920,173],{"class":106},[69,922,176],{"class":113},[69,924,179],{"class":75},[69,926,927],{"class":113},"make_manifold_edge_link",[69,929,911],{"class":75},[69,931,932],{"class":71,"line":103},[69,933,255],{"emptyLinePlaceholder":254},[69,935,937,939,942,944],{"class":71,"line":936},7,[69,938,167],{"class":106},[69,940,941],{"class":75}," tagged ",[69,943,173],{"class":106},[69,945,946],{"class":75}," dragon\n",[69,948,950,953,955,957,959],{"class":71,"line":949},8,[69,951,952],{"class":106},"    |",[69,954,176],{"class":113},[69,956,179],{"class":75},[69,958,513],{"class":113},[69,960,961],{"class":75},"(tree)\n",[69,963,965,967,969,971,973],{"class":71,"line":964},9,[69,966,952],{"class":106},[69,968,176],{"class":113},[69,970,179],{"class":75},[69,972,513],{"class":113},[69,974,975],{"class":75},"(fm)\n",[69,977,979,981,983,985,987],{"class":71,"line":978},10,[69,980,952],{"class":106},[69,982,176],{"class":113},[69,984,179],{"class":75},[69,986,513],{"class":113},[69,988,989],{"class":75},"(mel);\n",[14,991,992],{},"Now boolean at different positions. The structures are reused — only the transformation changes:",[60,994,996],{"className":137,"code":995,"language":139,"meta":65,"style":65},"for (float t = 0.1f; t \u003C= 1.0f; t += 0.1f) {\n    auto T = tf::make_transformation_from_translation(\n        tf::make_vector(0.f, t, 0.f));\n\n    auto [result, labels, face_labels] = tf::make_boolean(\n        tagged,\n        tagged | tf::tag(T),\n        tf::boolean_op::left_difference);\n}\n",[31,997,998,1041,1058,1082,1086,1103,1108,1124,1134],{"__ignoreMap":65},[69,999,1000,1003,1006,1008,1011,1013,1016,1018,1021,1024,1027,1029,1031,1034,1036,1038],{"class":71,"line":72},[69,1001,1002],{"class":106},"for",[69,1004,1005],{"class":75}," (",[69,1007,849],{"class":106},[69,1009,1010],{"class":75}," t ",[69,1012,173],{"class":106},[69,1014,1015],{"class":289}," 0.1",[69,1017,471],{"class":106},[69,1019,1020],{"class":75},"; t ",[69,1022,1023],{"class":106},"\u003C=",[69,1025,1026],{"class":289}," 1.0",[69,1028,471],{"class":106},[69,1030,1020],{"class":75},[69,1032,1033],{"class":106},"+=",[69,1035,1015],{"class":289},[69,1037,471],{"class":106},[69,1039,1040],{"class":75},") {\n",[69,1042,1043,1046,1048,1050,1052,1054,1056],{"class":71,"line":79},[69,1044,1045],{"class":106},"    auto",[69,1047,443],{"class":75},[69,1049,173],{"class":106},[69,1051,176],{"class":113},[69,1053,179],{"class":75},[69,1055,452],{"class":113},[69,1057,273],{"class":75},[69,1059,1060,1063,1065,1067,1069,1071,1073,1076,1078,1080],{"class":71,"line":85},[69,1061,1062],{"class":113},"        tf",[69,1064,179],{"class":75},[69,1066,463],{"class":113},[69,1068,185],{"class":75},[69,1070,468],{"class":289},[69,1072,471],{"class":106},[69,1074,1075],{"class":75},", t, ",[69,1077,468],{"class":289},[69,1079,471],{"class":106},[69,1081,488],{"class":75},[69,1083,1084],{"class":71,"line":91},[69,1085,255],{"emptyLinePlaceholder":254},[69,1087,1088,1090,1093,1095,1097,1099,1101],{"class":71,"line":97},[69,1089,1045],{"class":106},[69,1091,1092],{"class":75}," [result, labels, face_labels] ",[69,1094,173],{"class":106},[69,1096,176],{"class":113},[69,1098,179],{"class":75},[69,1100,574],{"class":113},[69,1102,273],{"class":75},[69,1104,1105],{"class":71,"line":103},[69,1106,1107],{"class":75},"        tagged,\n",[69,1109,1110,1113,1115,1117,1119,1121],{"class":71,"line":936},[69,1111,1112],{"class":75},"        tagged ",[69,1114,506],{"class":106},[69,1116,176],{"class":113},[69,1118,179],{"class":75},[69,1120,513],{"class":113},[69,1122,1123],{"class":75},"(T),\n",[69,1125,1126,1128,1130,1132],{"class":71,"line":949},[69,1127,1062],{"class":113},[69,1129,179],{"class":75},[69,1131,589],{"class":113},[69,1133,630],{"class":75},[69,1135,1136],{"class":71,"line":964},[69,1137,1138],{"class":75},"}\n",[14,1140,1141],{},"10 booleans on 2×500K polygon meshes in under 140ms total. Spatial and topological structures computed once.",[52,1143,1145],{"id":1144},"extracting-intersection-curves","Extracting intersection curves",[14,1147,1148,1149,1152],{},"Any boolean can also return the intersection boundary as polylines embedded on the surface. Pass ",[31,1150,1151],{},"tf::return_curves"," as an additional argument:",[60,1154,1156],{"className":137,"code":1155,"language":139,"meta":65,"style":65},"auto [result, labels, face_labels, curves] = tf::make_boolean(\n    dragon, translated,\n    tf::boolean_op::left_difference,\n    tf::return_curves);\n\nauto paths = curves.paths_buffer();\nauto points = curves.points_buffer();\n",[31,1157,1158,1175,1180,1191,1198,1202,1219],{"__ignoreMap":65},[69,1159,1160,1162,1165,1167,1169,1171,1173],{"class":71,"line":72},[69,1161,167],{"class":106},[69,1163,1164],{"class":75}," [result, labels, face_labels, curves] ",[69,1166,173],{"class":106},[69,1168,176],{"class":113},[69,1170,179],{"class":75},[69,1172,574],{"class":113},[69,1174,273],{"class":75},[69,1176,1177],{"class":71,"line":79},[69,1178,1179],{"class":75},"    dragon, translated,\n",[69,1181,1182,1184,1186,1188],{"class":71,"line":85},[69,1183,278],{"class":113},[69,1185,179],{"class":75},[69,1187,589],{"class":113},[69,1189,1190],{"class":75},"::left_difference,\n",[69,1192,1193,1195],{"class":71,"line":91},[69,1194,278],{"class":113},[69,1196,1197],{"class":75},"::return_curves);\n",[69,1199,1200],{"class":71,"line":97},[69,1201,255],{"emptyLinePlaceholder":254},[69,1203,1204,1206,1209,1211,1214,1217],{"class":71,"line":103},[69,1205,167],{"class":106},[69,1207,1208],{"class":75}," paths ",[69,1210,173],{"class":106},[69,1212,1213],{"class":75}," curves.",[69,1215,1216],{"class":113},"paths_buffer",[69,1218,215],{"class":75},[69,1220,1221,1223,1226,1228,1230,1233],{"class":71,"line":936},[69,1222,167],{"class":106},[69,1224,1225],{"class":75}," points ",[69,1227,173],{"class":106},[69,1229,1213],{"class":75},[69,1231,1232],{"class":113},"points_buffer",[69,1234,215],{"class":75},[14,1236,1237,1238,1243],{},"If only the ",[18,1239,1242],{"href":1240,"rel":1241},"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fmodules\u002Fintersect#intersection-curves",[125],"intersection curves"," are needed without the boolean itself:",[60,1245,1247],{"className":137,"code":1246,"language":139,"meta":65,"style":65},"auto curves_buffer = tf::make_intersection_curves(dragon, translated);\n",[31,1248,1249],{"__ignoreMap":65},[69,1250,1251,1253,1256,1258,1260,1262,1265],{"class":71,"line":72},[69,1252,167],{"class":106},[69,1254,1255],{"class":75}," curves_buffer ",[69,1257,173],{"class":106},[69,1259,176],{"class":113},[69,1261,179],{"class":75},[69,1263,1264],{"class":113},"make_intersection_curves",[69,1266,1267],{"class":75},"(dragon, translated);\n",[52,1269,1271],{"id":1270},"higher-precision","Higher precision",[14,1273,1274,1275,1277,1278,1281,1282,1285,1286,1289,1290,1292],{},"By default, ",[31,1276,33],{}," uses ",[31,1279,1280],{},"int32"," coordinates with ",[31,1283,1284],{},"int64"," intermediates and ",[31,1287,1288],{},"int128"," predicates. For meshes spanning very large coordinate ranges, switch to ",[31,1291,1284],{}," base precision:",[60,1294,1296],{"className":137,"code":1295,"language":139,"meta":65,"style":65},"auto [result, labels, face_labels] =\n    tf::make_boolean\u003Ctf::exact::int64>(\n        dragon, translated,\n        tf::boolean_op::left_difference);\n",[31,1297,1298,1306,1330,1335],{"__ignoreMap":65},[69,1299,1300,1302,1304],{"class":71,"line":72},[69,1301,167],{"class":106},[69,1303,1092],{"class":75},[69,1305,723],{"class":106},[69,1307,1308,1310,1312,1314,1316,1318,1320,1323,1325,1327],{"class":71,"line":79},[69,1309,278],{"class":113},[69,1311,179],{"class":75},[69,1313,574],{"class":113},[69,1315,286],{"class":75},[69,1317,584],{"class":113},[69,1319,179],{"class":75},[69,1321,1322],{"class":113},"exact",[69,1324,179],{"class":75},[69,1326,1284],{"class":113},[69,1328,1329],{"class":75},">(\n",[69,1331,1332],{"class":71,"line":85},[69,1333,1334],{"class":75},"        dragon, translated,\n",[69,1336,1337,1339,1341,1343],{"class":71,"line":91},[69,1338,1062],{"class":113},[69,1340,179],{"class":75},[69,1342,589],{"class":113},[69,1344,630],{"class":75},[14,1346,1347],{},"Same API. Wider coordinate range.",[52,1349,1351],{"id":1350},"performance-and-robustness","Performance and robustness",[14,1353,1354],{},"All operations run at interactive speed on million-polygon meshes.",[1356,1357],"headline-numbers",{":items":1358},"[{\"value\":\"3.5ms\",\"label\":\"Intersection curves\",\"detail\":\"2×500K polygons\"},{\"value\":\"14ms\",\"label\":\"Boolean union\",\"detail\":\"2×500K polygons\"},{\"value\":\"86ms\",\"label\":\"Polygon arrangements\",\"detail\":\"2×500K polygons\"}]",[14,1360,1361,1366,1367,1366,1371,1366,1375],{},[69,1362,1365],{"className":1363},[1364],"tag-pill","Apple M4 Max"," ",[69,1368,1370],{"className":1369},[1364],"16 threads",[69,1372,1374],{"className":1373},[1364],"Clang -O3",[69,1376,1378],{"className":1377},[1364],"mimalloc",[14,1380,1381,1382,1384],{},"Real meshes are not ideal manifolds. ",[31,1383,33],{}," handles them directly — no preprocessing, no manifold requirement.",[1386,1387],"data-table",{":headers":1388,":highlight":696,":rows":1389},"[\"Feature\",\"Handling\"]","[[\"Convex polygons\",\"Native — not limited to triangles\"],[\"Non-manifold edges\",\"Handled directly\"],[\"Inconsistent winding\",\"Bayesian classification\"],[\"Self-intersecting input\",\"Resolved via polygon arrangements\"],[\"Coplanar primitives\",\"Exact — aligned\u002Fopposing boundary classification\"],[\"Contour crossings\",\"Resolved via indirect predicates\"]]",[14,1391,1392,1394],{},[31,1393,33],{}," provides a robust alternative to CGAL, MeshLib, and libigl for fast and exact mesh boolean operations.",[14,1396,1397,1402,1403],{},[18,1398,1401],{"href":1399,"rel":1400},"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fbenchmarks",[125],"Full benchmarks and methodology"," · ",[18,1404,1406],{"href":1405},"\u002Falgorithms\u002Ffast-and-exact-mesh-booleans-and-arrangements","How the algorithm works",[1408,1409],"cite-as",{"author":1410,"title":5},"Sajovic, {\\v{Z}}iga",[1412,1413,1414],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}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 .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}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":65,"searchDepth":79,"depth":79,"links":1416},[1417,1418,1419,1420,1426,1427,1428,1429],{"id":54,"depth":79,"text":55},{"id":130,"depth":79,"text":131},{"id":427,"depth":79,"text":428},{"id":539,"depth":79,"text":540,"children":1421},[1422,1423,1424,1425],{"id":552,"depth":85,"text":553},{"id":595,"depth":85,"text":596},{"id":643,"depth":85,"text":644},{"id":681,"depth":85,"text":682},{"id":802,"depth":79,"text":803},{"id":1144,"depth":79,"text":1145},{"id":1270,"depth":79,"text":1271},{"id":1350,"depth":79,"text":1351},"2026-04-30","Learn how to perform fast mesh boolean operations in C++. Union, intersection, and difference at interactive speed on million-polygon meshes. Exact arithmetic, with support for non-manifold topology and coplanar primitives.","md",[1434,1435,1436,1437,1438,1439],"C++ mesh boolean","mesh boolean C++","CGAL alternative C++","fast boolean C++","mesh CSG C++","mesh boolean library C++",{},"\u002Ftutorials\u002Ffast-mesh-booleans-in-cpp",{"title":5,"description":1431},"tutorials\u002Ffast-mesh-booleans-in-cpp",[1445,1446,1447],"c++","booleans","tutorial","F-ONTlzJ1bbmcEGZpFhHqudXiY2Ws7AvAsu8YIlheEs",1779353828611]