[{"data":1,"prerenderedAt":1600},["ShallowReactive",2],{"tutorial-fast-variadic-mesh-booleans-in-cpp":3},{"id":4,"title":5,"author":6,"body":7,"date":1577,"description":1578,"extension":1579,"keywords":1580,"meta":1590,"navigation":444,"path":1591,"published":444,"seo":1592,"stem":1593,"tags":1594,"__hash__":1599},"tutorials\u002Ftutorials\u002Ffast-variadic-mesh-booleans-in-cpp.md","Fast Variadic Mesh Booleans in C++","Žiga Sajovic",{"type":8,"value":9,"toc":1557},"minimark",[10,23,30,34,57,64,68,71,92,97,100,121,124,232,235,239,242,578,582,585,970,973,977,980,1007,1010,1043,1054,1058,1082,1093,1100,1191,1197,1236,1243,1282,1286,1293,1395,1398,1402,1418,1461,1464,1491,1494,1498,1507,1511,1516,1519,1523,1526,1530,1533,1549,1553],[11,12,13],"note",{},[14,15,16,17,22],"p",{},"Pairwise booleans? See ",[18,19,21],"a",{"href":20},"\u002Ftutorials\u002Ffast-mesh-booleans-in-cpp","Fast Mesh Booleans in C++",".",[14,24,25,29],{},[26,27,28],"code",{},"trueform"," extends mesh booleans to variadic CSG: boolean expressions over N meshes evaluated against a single implicit arrangement. Build the arrangement once, extract any union, intersection, or difference of any subset of operands. Adding another expression on the same N meshes pays only the extraction cost.",[31,32],"article-cta",{":buttons":33},"[{\"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\u002Fcsg\",\"variant\":\"soft\",\"color\":\"neutral\"}]",[14,35,36,37,41,42,48,49,52,53,56],{},"We follow the ",[38,39,40],"strong",{},"Lévy 2025 Fibonacci-bunny scene"," (cover figure of ",[18,43,47],{"href":44,"rel":45},"https:\u002F\u002Farxiv.org\u002Fabs\u002F2405.12949",[46],"nofollow","arXiv:2405.12949","): a sphere with N Stanford Bunnies arranged on its surface via the Fibonacci lattice, oriented +Z radially outward. At N = 200 that's ~22.5M input triangles. From one CSG graph we extract both the variadic union ",[26,50,51],{},"sphere ∪ (⋃ bunnies)"," (11.7M triangles out) and the variadic difference ",[26,54,55],{},"sphere ∖ (⋃ bunnies)"," (7.9M triangles out).",[14,58,59],{},[60,61],"img",{"alt":62,"src":63},"Variadic union of a sphere with 200 Stanford Bunnies","\u002Fimg\u002Ftutorials\u002Fbunnies_union.png",[65,66],"headline-numbers",{":items":67},"[{\"value\":\"613ms\",\"label\":\"Build CSG graph\",\"detail\":\"201 meshes · 22.5M tris\"},{\"value\":\"69ms\",\"label\":\"Extract union\",\"detail\":\"11.7M output tris\"},{\"value\":\"682ms\",\"label\":\"Total\",\"detail\":\"build + extract\"}]",[14,69,70],{},"One graph. Many extractions.",[14,72,73,79,80,79,84,79,88],{},[74,75,78],"span",{"className":76},[77],"tag-pill","Apple M4 Max"," ",[74,81,83],{"className":82},[77],"16 threads",[74,85,87],{"className":86},[77],"Clang -O3",[74,89,91],{"className":90},[77],"mimalloc",[93,94,96],"h2",{"id":95},"loading-the-meshes","Loading the meshes",[14,98,99],{},"Include trueform:",[101,102,107],"pre",{"className":103,"code":104,"language":105,"meta":106,"style":106},"language-cpp shiki shiki-themes github-dark","#include \u003Ctrueform\u002Ftrueform.hpp>\n","cpp","",[26,108,109],{"__ignoreMap":106},[74,110,113,117],{"class":111,"line":112},"line",1,[74,114,116],{"class":115},"snl16","#include",[74,118,120],{"class":119},"sU2Wk"," \u003Ctrueform\u002Ftrueform.hpp>\n",[14,122,123],{},"Load the canonical sphere and bunny:",[101,125,127],{"className":103,"code":126,"language":105,"meta":106,"style":106},"auto sphere = tf::make_sphere_mesh(2.5f, 32, 32);\nauto bunny  = tf::cleaned(\n    tf::read_stl(\"Stanford_Bunny.stl\").polygons(),\n    tf::epsilon\u003Cfloat>);\n",[26,128,129,174,194,219],{"__ignoreMap":106},[74,130,131,134,138,141,145,148,151,154,158,161,164,167,169,171],{"class":111,"line":112},[74,132,133],{"class":115},"auto",[74,135,137],{"class":136},"s95oV"," sphere ",[74,139,140],{"class":115},"=",[74,142,144],{"class":143},"svObZ"," tf",[74,146,147],{"class":136},"::",[74,149,150],{"class":143},"make_sphere_mesh",[74,152,153],{"class":136},"(",[74,155,157],{"class":156},"sDLfK","2.5",[74,159,160],{"class":115},"f",[74,162,163],{"class":136},", ",[74,165,166],{"class":156},"32",[74,168,163],{"class":136},[74,170,166],{"class":156},[74,172,173],{"class":136},");\n",[74,175,177,179,182,184,186,188,191],{"class":111,"line":176},2,[74,178,133],{"class":115},[74,180,181],{"class":136}," bunny  ",[74,183,140],{"class":115},[74,185,144],{"class":143},[74,187,147],{"class":136},[74,189,190],{"class":143},"cleaned",[74,192,193],{"class":136},"(\n",[74,195,197,200,202,205,207,210,213,216],{"class":111,"line":196},3,[74,198,199],{"class":143},"    tf",[74,201,147],{"class":136},[74,203,204],{"class":143},"read_stl",[74,206,153],{"class":136},[74,208,209],{"class":119},"\"Stanford_Bunny.stl\"",[74,211,212],{"class":136},").",[74,214,215],{"class":143},"polygons",[74,217,218],{"class":136},"(),\n",[74,220,222,224,227,230],{"class":111,"line":221},4,[74,223,199],{"class":143},[74,225,226],{"class":136},"::epsilon",[74,228,229],{"class":115},"\u003Cfloat>",[74,231,173],{"class":136},[14,233,234],{},"Each mesh is read once. The bunny is instanced N times via tagged transformations — no geometry is duplicated.",[93,236,238],{"id":237},"tagging-the-canonical-meshes","Tagging the canonical meshes",[14,240,241],{},"Pre-tag each canonical mesh with face membership, manifold edge link, and an AABB tree. Every instance shares the same tagged structures — the arrangement build reuses them across all N instances:",[101,243,245],{"className":103,"code":244,"language":105,"meta":106,"style":106},"auto tag_all = [](auto &m) {\n  auto fm  = tf::make_face_membership(m.polygons());\n  auto mel = tf::make_manifold_edge_link(m.polygons() | tf::tag(fm));\n  auto tr  = tf::aabb_tree\u003Cint, float, 3>(m.polygons(), tf::config_tree(4, 4));\n  return std::tuple{std::move(fm), std::move(mel), std::move(tr)};\n};\n\nauto [b_fm, b_mel, b_tr] = tag_all(bunny);\nauto [s_fm, s_mel, s_tr] = tag_all(sphere);\nauto bunny_tagged  = bunny.polygons()  | tf::tag(b_fm) | tf::tag(b_mel) | tf::tag(b_tr);\nauto sphere_tagged = sphere.polygons() | tf::tag(s_fm) | tf::tag(s_mel) | tf::tag(s_tr);\n",[26,246,247,271,296,332,392,433,439,446,462,477,528],{"__ignoreMap":106},[74,248,249,251,254,256,259,261,264,268],{"class":111,"line":112},[74,250,133],{"class":115},[74,252,253],{"class":136}," tag_all ",[74,255,140],{"class":115},[74,257,258],{"class":136}," [](",[74,260,133],{"class":115},[74,262,263],{"class":115}," &",[74,265,267],{"class":266},"s9osk","m",[74,269,270],{"class":136},") {\n",[74,272,273,276,279,281,283,285,288,291,293],{"class":111,"line":176},[74,274,275],{"class":115},"  auto",[74,277,278],{"class":136}," fm  ",[74,280,140],{"class":115},[74,282,144],{"class":143},[74,284,147],{"class":136},[74,286,287],{"class":143},"make_face_membership",[74,289,290],{"class":136},"(m.",[74,292,215],{"class":143},[74,294,295],{"class":136},"());\n",[74,297,298,300,303,305,307,309,312,314,316,319,322,324,326,329],{"class":111,"line":196},[74,299,275],{"class":115},[74,301,302],{"class":136}," mel ",[74,304,140],{"class":115},[74,306,144],{"class":143},[74,308,147],{"class":136},[74,310,311],{"class":143},"make_manifold_edge_link",[74,313,290],{"class":136},[74,315,215],{"class":143},[74,317,318],{"class":136},"() ",[74,320,321],{"class":115},"|",[74,323,144],{"class":143},[74,325,147],{"class":136},[74,327,328],{"class":143},"tag",[74,330,331],{"class":136},"(fm));\n",[74,333,334,336,339,341,343,345,348,351,354,356,359,361,364,367,369,372,375,377,380,382,385,387,389],{"class":111,"line":221},[74,335,275],{"class":115},[74,337,338],{"class":136}," tr  ",[74,340,140],{"class":115},[74,342,144],{"class":143},[74,344,147],{"class":136},[74,346,347],{"class":143},"aabb_tree",[74,349,350],{"class":136},"\u003C",[74,352,353],{"class":115},"int",[74,355,163],{"class":136},[74,357,358],{"class":115},"float",[74,360,163],{"class":136},[74,362,363],{"class":156},"3",[74,365,366],{"class":136},">(m.",[74,368,215],{"class":143},[74,370,371],{"class":136},"(), ",[74,373,374],{"class":143},"tf",[74,376,147],{"class":136},[74,378,379],{"class":143},"config_tree",[74,381,153],{"class":136},[74,383,384],{"class":156},"4",[74,386,163],{"class":136},[74,388,384],{"class":156},[74,390,391],{"class":136},"));\n",[74,393,395,398,401,404,407,409,412,415,417,419,421,424,426,428,430],{"class":111,"line":394},5,[74,396,397],{"class":115},"  return",[74,399,400],{"class":143}," std",[74,402,403],{"class":136},"::tuple{",[74,405,406],{"class":143},"std",[74,408,147],{"class":136},[74,410,411],{"class":143},"move",[74,413,414],{"class":136},"(fm), ",[74,416,406],{"class":143},[74,418,147],{"class":136},[74,420,411],{"class":143},[74,422,423],{"class":136},"(mel), ",[74,425,406],{"class":143},[74,427,147],{"class":136},[74,429,411],{"class":143},[74,431,432],{"class":136},"(tr)};\n",[74,434,436],{"class":111,"line":435},6,[74,437,438],{"class":136},"};\n",[74,440,442],{"class":111,"line":441},7,[74,443,445],{"emptyLinePlaceholder":444},true,"\n",[74,447,449,451,454,456,459],{"class":111,"line":448},8,[74,450,133],{"class":115},[74,452,453],{"class":136}," [b_fm, b_mel, b_tr] ",[74,455,140],{"class":115},[74,457,458],{"class":143}," tag_all",[74,460,461],{"class":136},"(bunny);\n",[74,463,465,467,470,472,474],{"class":111,"line":464},9,[74,466,133],{"class":115},[74,468,469],{"class":136}," [s_fm, s_mel, s_tr] ",[74,471,140],{"class":115},[74,473,458],{"class":143},[74,475,476],{"class":136},"(sphere);\n",[74,478,480,482,485,487,490,492,495,497,499,501,503,506,508,510,512,514,517,519,521,523,525],{"class":111,"line":479},10,[74,481,133],{"class":115},[74,483,484],{"class":136}," bunny_tagged  ",[74,486,140],{"class":115},[74,488,489],{"class":136}," bunny.",[74,491,215],{"class":143},[74,493,494],{"class":136},"()  ",[74,496,321],{"class":115},[74,498,144],{"class":143},[74,500,147],{"class":136},[74,502,328],{"class":143},[74,504,505],{"class":136},"(b_fm) ",[74,507,321],{"class":115},[74,509,144],{"class":143},[74,511,147],{"class":136},[74,513,328],{"class":143},[74,515,516],{"class":136},"(b_mel) ",[74,518,321],{"class":115},[74,520,144],{"class":143},[74,522,147],{"class":136},[74,524,328],{"class":143},[74,526,527],{"class":136},"(b_tr);\n",[74,529,531,533,536,538,541,543,545,547,549,551,553,556,558,560,562,564,567,569,571,573,575],{"class":111,"line":530},11,[74,532,133],{"class":115},[74,534,535],{"class":136}," sphere_tagged ",[74,537,140],{"class":115},[74,539,540],{"class":136}," sphere.",[74,542,215],{"class":143},[74,544,318],{"class":136},[74,546,321],{"class":115},[74,548,144],{"class":143},[74,550,147],{"class":136},[74,552,328],{"class":143},[74,554,555],{"class":136},"(s_fm) ",[74,557,321],{"class":115},[74,559,144],{"class":143},[74,561,147],{"class":136},[74,563,328],{"class":143},[74,565,566],{"class":136},"(s_mel) ",[74,568,321],{"class":115},[74,570,144],{"class":143},[74,572,147],{"class":136},[74,574,328],{"class":143},[74,576,577],{"class":136},"(s_tr);\n",[93,579,581],{"id":580},"distributing-instances","Distributing instances",[14,583,584],{},"Distribute N frames over the sphere using the Fibonacci lattice. Each instance is the canonical bunny with a tagged frame — same geometry, different pose:",[101,586,588],{"className":103,"code":587,"language":105,"meta":106,"style":106},"constexpr int N = 200;\nconst float golden = (1.0f + std::sqrt(5.0f)) \u002F 2.0f;\n\nstd::vector forms{\n    sphere_tagged | tf::tag(tf::transformation\u003Cfloat, 3>{}),\n};\nfor (int i = 0; i \u003C N; ++i) {\n  const float z  = 1.0f - (2.0f * i + 1.0f) \u002F N;\n  const float r  = std::sqrt(1.0f - z * z);\n  const float th = 2.0f * tf::pi\u003Cfloat> * i \u002F golden;\n  tf::vector\u003Cfloat, 3> pos{2.5f * r * std::cos(th),\n                           2.5f * r * std::sin(th),\n                           2.5f * z};\n  forms.push_back(bunny_tagged | tf::tag(\n      tf::make_transformation_from_translation(pos)));\n}\n",[26,589,590,609,660,664,671,704,708,739,786,820,853,893,916,928,950,964],{"__ignoreMap":106},[74,591,592,595,598,601,603,606],{"class":111,"line":112},[74,593,594],{"class":115},"constexpr",[74,596,597],{"class":115}," int",[74,599,600],{"class":136}," N ",[74,602,140],{"class":115},[74,604,605],{"class":156}," 200",[74,607,608],{"class":136},";\n",[74,610,611,614,617,620,622,625,628,630,633,635,637,640,642,645,647,650,653,656,658],{"class":111,"line":176},[74,612,613],{"class":115},"const",[74,615,616],{"class":115}," float",[74,618,619],{"class":136}," golden ",[74,621,140],{"class":115},[74,623,624],{"class":136}," (",[74,626,627],{"class":156},"1.0",[74,629,160],{"class":115},[74,631,632],{"class":115}," +",[74,634,400],{"class":143},[74,636,147],{"class":136},[74,638,639],{"class":143},"sqrt",[74,641,153],{"class":136},[74,643,644],{"class":156},"5.0",[74,646,160],{"class":115},[74,648,649],{"class":136},")) ",[74,651,652],{"class":115},"\u002F",[74,654,655],{"class":156}," 2.0",[74,657,160],{"class":115},[74,659,608],{"class":136},[74,661,662],{"class":111,"line":196},[74,663,445],{"emptyLinePlaceholder":444},[74,665,666,668],{"class":111,"line":221},[74,667,406],{"class":143},[74,669,670],{"class":136},"::vector forms{\n",[74,672,673,676,678,680,682,684,686,688,691,694,696,698,701],{"class":111,"line":394},[74,674,675],{"class":136},"    sphere_tagged ",[74,677,321],{"class":115},[74,679,144],{"class":143},[74,681,147],{"class":136},[74,683,328],{"class":143},[74,685,153],{"class":136},[74,687,374],{"class":143},[74,689,690],{"class":136},"::transformation",[74,692,693],{"class":115},"\u003Cfloat",[74,695,163],{"class":136},[74,697,363],{"class":156},[74,699,700],{"class":115},">",[74,702,703],{"class":136},"{}),\n",[74,705,706],{"class":111,"line":435},[74,707,438],{"class":136},[74,709,710,713,715,717,720,722,725,728,730,733,736],{"class":111,"line":441},[74,711,712],{"class":115},"for",[74,714,624],{"class":136},[74,716,353],{"class":115},[74,718,719],{"class":136}," i ",[74,721,140],{"class":115},[74,723,724],{"class":156}," 0",[74,726,727],{"class":136},"; i ",[74,729,350],{"class":115},[74,731,732],{"class":136}," N; ",[74,734,735],{"class":115},"++",[74,737,738],{"class":136},"i) {\n",[74,740,741,744,746,749,751,754,756,759,761,764,766,769,771,774,776,778,781,783],{"class":111,"line":448},[74,742,743],{"class":115},"  const",[74,745,616],{"class":115},[74,747,748],{"class":136}," z  ",[74,750,140],{"class":115},[74,752,753],{"class":156}," 1.0",[74,755,160],{"class":115},[74,757,758],{"class":115}," -",[74,760,624],{"class":136},[74,762,763],{"class":156},"2.0",[74,765,160],{"class":115},[74,767,768],{"class":115}," *",[74,770,719],{"class":136},[74,772,773],{"class":115},"+",[74,775,753],{"class":156},[74,777,160],{"class":115},[74,779,780],{"class":136},") ",[74,782,652],{"class":115},[74,784,785],{"class":136}," N;\n",[74,787,788,790,792,795,797,799,801,803,805,807,809,811,814,817],{"class":111,"line":464},[74,789,743],{"class":115},[74,791,616],{"class":115},[74,793,794],{"class":136}," r  ",[74,796,140],{"class":115},[74,798,400],{"class":143},[74,800,147],{"class":136},[74,802,639],{"class":143},[74,804,153],{"class":136},[74,806,627],{"class":156},[74,808,160],{"class":115},[74,810,758],{"class":115},[74,812,813],{"class":136}," z ",[74,815,816],{"class":115},"*",[74,818,819],{"class":136}," z);\n",[74,821,822,824,826,829,831,833,835,837,839,842,844,846,848,850],{"class":111,"line":479},[74,823,743],{"class":115},[74,825,616],{"class":115},[74,827,828],{"class":136}," th ",[74,830,140],{"class":115},[74,832,655],{"class":156},[74,834,160],{"class":115},[74,836,768],{"class":115},[74,838,144],{"class":143},[74,840,841],{"class":136},"::pi",[74,843,229],{"class":115},[74,845,768],{"class":115},[74,847,719],{"class":136},[74,849,652],{"class":115},[74,851,852],{"class":136}," golden;\n",[74,854,855,858,861,863,865,867,869,872,874,876,878,881,883,885,887,890],{"class":111,"line":530},[74,856,857],{"class":143},"  tf",[74,859,860],{"class":136},"::vector",[74,862,693],{"class":115},[74,864,163],{"class":136},[74,866,363],{"class":156},[74,868,700],{"class":115},[74,870,871],{"class":136}," pos{",[74,873,157],{"class":156},[74,875,160],{"class":115},[74,877,768],{"class":115},[74,879,880],{"class":136}," r ",[74,882,816],{"class":115},[74,884,400],{"class":143},[74,886,147],{"class":136},[74,888,889],{"class":143},"cos",[74,891,892],{"class":136},"(th),\n",[74,894,896,899,901,903,905,907,909,911,914],{"class":111,"line":895},12,[74,897,898],{"class":156},"                           2.5",[74,900,160],{"class":115},[74,902,768],{"class":115},[74,904,880],{"class":136},[74,906,816],{"class":115},[74,908,400],{"class":143},[74,910,147],{"class":136},[74,912,913],{"class":143},"sin",[74,915,892],{"class":136},[74,917,919,921,923,925],{"class":111,"line":918},13,[74,920,898],{"class":156},[74,922,160],{"class":115},[74,924,768],{"class":115},[74,926,927],{"class":136}," z};\n",[74,929,931,934,937,940,942,944,946,948],{"class":111,"line":930},14,[74,932,933],{"class":136},"  forms.",[74,935,936],{"class":143},"push_back",[74,938,939],{"class":136},"(bunny_tagged ",[74,941,321],{"class":115},[74,943,144],{"class":143},[74,945,147],{"class":136},[74,947,328],{"class":143},[74,949,193],{"class":136},[74,951,953,956,958,961],{"class":111,"line":952},15,[74,954,955],{"class":143},"      tf",[74,957,147],{"class":136},[74,959,960],{"class":143},"make_transformation_from_translation",[74,962,963],{"class":136},"(pos)));\n",[74,965,967],{"class":111,"line":966},16,[74,968,969],{"class":136},"}\n",[14,971,972],{},"One canonical bunny + N frames: instancing without copying geometry.",[93,974,976],{"id":975},"building-the-csg-graph","Building the CSG graph",[14,978,979],{},"A CSG computation has three pieces:",[981,982,983,995,1001],"ul",{},[984,985,986,987,994],"li",{},"A ",[18,988,991],{"href":989,"rel":990},"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fmodules\u002Fcsg#overview",[46],[26,992,993],{},"tf::csg_graph"," — the implicit arrangement of N forms, built once",[984,996,986,997,1000],{},[26,998,999],{},"tf::csg::expr"," — a runtime boolean expression over operand ids",[984,1002,1003,1004],{},"An extraction call: ",[26,1005,1006],{},"tf::make_csg_mesh(graph, expr)",[14,1008,1009],{},"Build the graph over the forms range:",[101,1011,1013],{"className":103,"code":1012,"language":105,"meta":106,"style":106},"auto graph = tf::make_csg_graph(tf::make_range(forms));\n",[26,1014,1015],{"__ignoreMap":106},[74,1016,1017,1019,1022,1024,1026,1028,1031,1033,1035,1037,1040],{"class":111,"line":112},[74,1018,133],{"class":115},[74,1020,1021],{"class":136}," graph ",[74,1023,140],{"class":115},[74,1025,144],{"class":143},[74,1027,147],{"class":136},[74,1029,1030],{"class":143},"make_csg_graph",[74,1032,153],{"class":136},[74,1034,374],{"class":143},[74,1036,147],{"class":136},[74,1038,1039],{"class":143},"make_range",[74,1041,1042],{"class":136},"(forms));\n",[14,1044,1045,1046,1049,1050,1053],{},"Operand ids are positions in the forms range — ",[26,1047,1048],{},"0"," is the sphere, ",[26,1051,1052],{},"1..N"," are the bunnies.",[93,1055,1057],{"id":1056},"boolean-expressions","Boolean expressions",[14,1059,1060,1061,163,1064,163,1067,163,1070,1073,1074,1077,1078,1081],{},"Expressions are built from ",[26,1062,1063],{},"tf::csg::merge",[26,1065,1066],{},"tf::csg::intersection",[26,1068,1069],{},"tf::csg::difference",[26,1071,1072],{},"tf::csg::complement",", and ",[26,1075,1076],{},"tf::csg::any_of"," \u002F ",[26,1079,1080],{},"tf::csg::all_of"," for ranges. Integers auto-promote to leaves, so the algebra reads like algebra.",[14,1083,1084,1085,1088,1089,1092],{},"For N operands, ",[26,1086,1087],{},"csg::any_of"," over a sequence range encodes ⋃; ",[26,1090,1091],{},"csg::all_of"," encodes ⋂.",[1094,1095,1097,1098],"h3",{"id":1096},"union-sphere-bunnies","Union: ",[26,1099,51],{},[101,1101,1103],{"className":103,"code":1102,"language":105,"meta":106,"style":106},"auto bunnies = tf::csg::any_of(tf::make_sequence_range(1, N + 1));\n\nauto union_mesh = tf::make_csg_mesh(graph, tf::csg::merge(0, bunnies));\n",[26,1104,1105,1150,1154],{"__ignoreMap":106},[74,1106,1107,1109,1112,1114,1116,1118,1121,1123,1126,1128,1130,1132,1135,1137,1140,1143,1145,1148],{"class":111,"line":112},[74,1108,133],{"class":115},[74,1110,1111],{"class":136}," bunnies ",[74,1113,140],{"class":115},[74,1115,144],{"class":143},[74,1117,147],{"class":136},[74,1119,1120],{"class":143},"csg",[74,1122,147],{"class":136},[74,1124,1125],{"class":143},"any_of",[74,1127,153],{"class":136},[74,1129,374],{"class":143},[74,1131,147],{"class":136},[74,1133,1134],{"class":143},"make_sequence_range",[74,1136,153],{"class":136},[74,1138,1139],{"class":156},"1",[74,1141,1142],{"class":136},", N ",[74,1144,773],{"class":115},[74,1146,1147],{"class":156}," 1",[74,1149,391],{"class":136},[74,1151,1152],{"class":111,"line":176},[74,1153,445],{"emptyLinePlaceholder":444},[74,1155,1156,1158,1161,1163,1165,1167,1170,1173,1175,1177,1179,1181,1184,1186,1188],{"class":111,"line":196},[74,1157,133],{"class":115},[74,1159,1160],{"class":136}," union_mesh ",[74,1162,140],{"class":115},[74,1164,144],{"class":143},[74,1166,147],{"class":136},[74,1168,1169],{"class":143},"make_csg_mesh",[74,1171,1172],{"class":136},"(graph, ",[74,1174,374],{"class":143},[74,1176,147],{"class":136},[74,1178,1120],{"class":143},[74,1180,147],{"class":136},[74,1182,1183],{"class":143},"merge",[74,1185,153],{"class":136},[74,1187,1048],{"class":156},[74,1189,1190],{"class":136},", bunnies));\n",[1094,1192,1194,1195],{"id":1193},"difference-sphere-bunnies","Difference: ",[26,1196,55],{},[101,1198,1200],{"className":103,"code":1199,"language":105,"meta":106,"style":106},"auto diff_mesh = tf::make_csg_mesh(graph, tf::csg::difference(0, bunnies));\n",[26,1201,1202],{"__ignoreMap":106},[74,1203,1204,1206,1209,1211,1213,1215,1217,1219,1221,1223,1225,1227,1230,1232,1234],{"class":111,"line":112},[74,1205,133],{"class":115},[74,1207,1208],{"class":136}," diff_mesh ",[74,1210,140],{"class":115},[74,1212,144],{"class":143},[74,1214,147],{"class":136},[74,1216,1169],{"class":143},[74,1218,1172],{"class":136},[74,1220,374],{"class":143},[74,1222,147],{"class":136},[74,1224,1120],{"class":143},[74,1226,147],{"class":136},[74,1228,1229],{"class":143},"difference",[74,1231,153],{"class":136},[74,1233,1048],{"class":156},[74,1235,1190],{"class":136},[1094,1237,1239,1240],{"id":1238},"intersection-sphere-bunnies","Intersection: ",[26,1241,1242],{},"sphere ∩ (⋃ bunnies)",[101,1244,1246],{"className":103,"code":1245,"language":105,"meta":106,"style":106},"auto inter_mesh = tf::make_csg_mesh(graph, tf::csg::intersection(0, bunnies));\n",[26,1247,1248],{"__ignoreMap":106},[74,1249,1250,1252,1255,1257,1259,1261,1263,1265,1267,1269,1271,1273,1276,1278,1280],{"class":111,"line":112},[74,1251,133],{"class":115},[74,1253,1254],{"class":136}," inter_mesh ",[74,1256,140],{"class":115},[74,1258,144],{"class":143},[74,1260,147],{"class":136},[74,1262,1169],{"class":143},[74,1264,1172],{"class":136},[74,1266,374],{"class":143},[74,1268,147],{"class":136},[74,1270,1120],{"class":143},[74,1272,147],{"class":136},[74,1274,1275],{"class":143},"intersection",[74,1277,153],{"class":136},[74,1279,1048],{"class":156},[74,1281,1190],{"class":136},[93,1283,1285],{"id":1284},"many-expressions-one-graph","Many expressions, one graph",[14,1287,1288,1289,1292],{},"The arrangement is the cost. Once ",[26,1290,1291],{},"graph"," is built, every additional expression runs without re-arranging:",[101,1294,1296],{"className":103,"code":1295,"language":105,"meta":106,"style":106},"auto union_mesh = tf::make_csg_mesh(graph, tf::csg::merge(0, bunnies));\nauto diff_mesh  = tf::make_csg_mesh(graph, tf::csg::difference(0, bunnies));\nauto inter_mesh = tf::make_csg_mesh(graph, tf::csg::intersection(0, bunnies));\n",[26,1297,1298,1330,1363],{"__ignoreMap":106},[74,1299,1300,1302,1304,1306,1308,1310,1312,1314,1316,1318,1320,1322,1324,1326,1328],{"class":111,"line":112},[74,1301,133],{"class":115},[74,1303,1160],{"class":136},[74,1305,140],{"class":115},[74,1307,144],{"class":143},[74,1309,147],{"class":136},[74,1311,1169],{"class":143},[74,1313,1172],{"class":136},[74,1315,374],{"class":143},[74,1317,147],{"class":136},[74,1319,1120],{"class":143},[74,1321,147],{"class":136},[74,1323,1183],{"class":143},[74,1325,153],{"class":136},[74,1327,1048],{"class":156},[74,1329,1190],{"class":136},[74,1331,1332,1334,1337,1339,1341,1343,1345,1347,1349,1351,1353,1355,1357,1359,1361],{"class":111,"line":176},[74,1333,133],{"class":115},[74,1335,1336],{"class":136}," diff_mesh  ",[74,1338,140],{"class":115},[74,1340,144],{"class":143},[74,1342,147],{"class":136},[74,1344,1169],{"class":143},[74,1346,1172],{"class":136},[74,1348,374],{"class":143},[74,1350,147],{"class":136},[74,1352,1120],{"class":143},[74,1354,147],{"class":136},[74,1356,1229],{"class":143},[74,1358,153],{"class":136},[74,1360,1048],{"class":156},[74,1362,1190],{"class":136},[74,1364,1365,1367,1369,1371,1373,1375,1377,1379,1381,1383,1385,1387,1389,1391,1393],{"class":111,"line":196},[74,1366,133],{"class":115},[74,1368,1254],{"class":136},[74,1370,140],{"class":115},[74,1372,144],{"class":143},[74,1374,147],{"class":136},[74,1376,1169],{"class":143},[74,1378,1172],{"class":136},[74,1380,374],{"class":143},[74,1382,147],{"class":136},[74,1384,1120],{"class":143},[74,1386,147],{"class":136},[74,1388,1275],{"class":143},[74,1390,153],{"class":136},[74,1392,1048],{"class":156},[74,1394,1190],{"class":136},[14,1396,1397],{},"N-ary boolean trees that would otherwise chain pairwise extractions evaluate in one pass.",[93,1399,1401],{"id":1400},"higher-precision","Higher precision",[14,1403,1404,1405,1407,1408,163,1411,1407,1414,1417],{},"The lattice resolution resolves automatically from input scalar (",[26,1406,358],{}," → ",[26,1409,1410],{},"int32",[26,1412,1413],{},"double",[26,1415,1416],{},"int64","). Override for meshes spanning very large coordinate ranges:",[101,1419,1421],{"className":103,"code":1420,"language":105,"meta":106,"style":106},"auto graph = tf::make_csg_graph\u003Ctf::exact::int64>(tf::make_range(forms));\n",[26,1422,1423],{"__ignoreMap":106},[74,1424,1425,1427,1429,1431,1433,1435,1437,1439,1441,1443,1446,1448,1450,1453,1455,1457,1459],{"class":111,"line":112},[74,1426,133],{"class":115},[74,1428,1021],{"class":136},[74,1430,140],{"class":115},[74,1432,144],{"class":143},[74,1434,147],{"class":136},[74,1436,1030],{"class":143},[74,1438,350],{"class":136},[74,1440,374],{"class":143},[74,1442,147],{"class":136},[74,1444,1445],{"class":143},"exact",[74,1447,147],{"class":136},[74,1449,1416],{"class":143},[74,1451,1452],{"class":136},">(",[74,1454,374],{"class":143},[74,1456,147],{"class":136},[74,1458,1039],{"class":143},[74,1460,1042],{"class":136},[14,1462,1463],{},"Output coordinate type is independent:",[101,1465,1467],{"className":103,"code":1466,"language":105,"meta":106,"style":106},"auto out = tf::make_csg_mesh\u003Cdouble>(graph, expr);\n",[26,1468,1469],{"__ignoreMap":106},[74,1470,1471,1473,1476,1478,1480,1482,1484,1486,1488],{"class":111,"line":112},[74,1472,133],{"class":115},[74,1474,1475],{"class":136}," out ",[74,1477,140],{"class":115},[74,1479,144],{"class":143},[74,1481,147],{"class":136},[74,1483,1169],{"class":143},[74,1485,350],{"class":136},[74,1487,1413],{"class":115},[74,1489,1490],{"class":136},">(graph, expr);\n",[14,1492,1493],{},"The arrangement carries exact intersection points regardless of the output type. Materialisation to IEEE float crosses the precision boundary exactly once.",[93,1495,1497],{"id":1496},"performance","Performance",[14,1499,1500,1501,1503,1504,1506],{},"Lévy Fibonacci-bunny benchmark. Stanford bunnies arranged Fibonacci-style on a sphere (R = 2.5), oriented +Z radially outward; ",[26,1502,55],{}," (difference) and ",[26,1505,51],{}," (union).",[1094,1508,1510],{"id":1509},"comparison","Comparison",[1512,1513],"data-table",{":headers":1514,":highlight":1048,":rows":1515},"[\"Library\",\"Op\",\"N = 50\",\"N = 100\",\"N = 200\"]","[[\"trueform\",\"diff\",\"194 ms (1×)\",\"347 ms (1×)\",\"673 ms (1×)\"],[\"trueform\",\"union\",\"194 ms (1×)\",\"351 ms (1×)\",\"682 ms (1×)\"],[\"Solidean\",\"diff\",\"3,956 ms (20.4×)\",\"8,024 ms (23.1×)\",\"16,395 ms (24.4×)\"],[\"Solidean\",\"union\",\"3,755 ms (19.4×)\",\"7,896 ms (22.5×)\",\"16,524 ms (24.2×)\"],[\"MeshLib\",\"diff\",\"4,486 ms (23.1×)\",\"20,318 ms (58.6×)\",\"84,639 ms (125.8×)\"],[\"MeshLib\",\"union\",\"6,957 ms (35.9×)\",\"26,517 ms (75.5×)\",\"105,102 ms (154.1×)\"]]",[14,1517,1518],{},"Output meshes are closed and 2-manifold on every cell; volumes agree to within ~0.05% across libraries.",[1094,1520,1522],{"id":1521},"breakdown","Breakdown",[14,1524,1525],{},"The arrangement is the cost. Each extraction is ~10% of the build.",[1512,1527],{":headers":1528,":highlight":1048,":rows":1529},"[\"N\",\"Arrangement\",\"+ Difference\",\"+ Union\",\"Total (both)\"]","[[\"50\",\"176 ms\",\"18 ms\",\"18 ms\",\"212 ms\"],[\"100\",\"316 ms\",\"31 ms\",\"35 ms\",\"382 ms\"],[\"200\",\"613 ms\",\"60 ms\",\"69 ms\",\"742 ms\"]]",[14,1531,1532],{},"From one graph, both booleans cost the arrangement plus small extraction overhead. Additional expressions on the same N meshes pay only the extraction cost. Three expressions on 200 meshes run in ~810 ms; rebuilding the arrangement three times would cost over 2,000 ms.",[14,1534,1535,1540,1541,1540,1545],{},[18,1536,1539],{"href":1537,"rel":1538},"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fbenchmarks",[46],"Full benchmarks and methodology"," · ",[18,1542,1544],{"href":1543},"\u002Falgorithms\u002Ffast-and-exact-mesh-booleans-and-arrangements","How the algorithm works",[18,1546,1548],{"href":1547},"\u002Falgorithms\u002Fthe-stl-for-geometry","The STL for Geometry",[1550,1551],"cite-as",{"author":1552,"title":5},"Sajovic, {\\v{Z}}iga",[1554,1555,1556],"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 .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":106,"searchDepth":176,"depth":176,"links":1558},[1559,1560,1561,1562,1563,1571,1572,1573],{"id":95,"depth":176,"text":96},{"id":237,"depth":176,"text":238},{"id":580,"depth":176,"text":581},{"id":975,"depth":176,"text":976},{"id":1056,"depth":176,"text":1057,"children":1564},[1565,1567,1569],{"id":1096,"depth":196,"text":1566},"Union: sphere ∪ (⋃ bunnies)",{"id":1193,"depth":196,"text":1568},"Difference: sphere ∖ (⋃ bunnies)",{"id":1238,"depth":196,"text":1570},"Intersection: sphere ∩ (⋃ bunnies)",{"id":1284,"depth":176,"text":1285},{"id":1400,"depth":176,"text":1401},{"id":1496,"depth":176,"text":1497,"children":1574},[1575,1576],{"id":1509,"depth":196,"text":1510},{"id":1521,"depth":196,"text":1522},"2026-05-08","Variadic mesh boolean operations in C++. Build one arrangement over N meshes, extract any boolean expression from it. 201 meshes (~22.5M input triangles) in 0.6 seconds. 125× faster than MeshLib on the Lévy benchmark.","md",[1581,1582,1583,1584,1585,1586,1587,1588,1589],"C++ variadic mesh boolean","variadic CSG C++","N-mesh boolean C++","multi-mesh CSG","mesh CSG library","libigl variadic alternative","exact CSG C++","mesh arrangement C++","fast mesh boolean C++",{},"\u002Ftutorials\u002Ffast-variadic-mesh-booleans-in-cpp",{"title":5,"description":1578},"tutorials\u002Ffast-variadic-mesh-booleans-in-cpp",[1595,1120,1596,1597,1598],"c++","booleans","tutorial","variadic","E5Ufb2OOHc4UzRI2cL8M6h8Kxwqqh3lF4MpoLt4kRTY",1786950767604]