[{"data":1,"prerenderedAt":1788},["ShallowReactive",2],{"tutorial-mesh-intersection-curves-in-cpp":3},{"id":4,"title":5,"author":6,"body":7,"date":1764,"description":1765,"extension":1766,"keywords":1767,"meta":1778,"navigation":148,"ogImage":1779,"path":1780,"published":148,"seo":1781,"stem":1782,"tags":1783,"__hash__":1787},"tutorials\u002Ftutorials\u002Fmesh-intersection-curves-in-cpp.md","Fast and Exact Mesh Intersection Curves in C++","Žiga Sajovic",{"type":8,"value":9,"toc":1753},"minimark",[10,28,36,40,47,52,55,114,124,128,235,257,260,351,355,369,418,424,431,434,473,477,480,587,593,609,613,616,1052,1055,1059,1062,1313,1319,1325,1329,1332,1485,1488,1555,1558,1601,1611,1642,1649,1653,1691,1695,1699,1711,1714,1720,1723,1729,1745,1749],[11,12,13],"note",{},[14,15,16,17,22,23,27],"p",{},"Also available in ",[18,19,21],"a",{"href":20},"\u002Ftutorials\u002Fmesh-intersection-curves-in-python","Python"," and ",[18,24,26],{"href":25},"\u002Ftutorials\u002Fmesh-intersection-curves-in-javascript","JavaScript",".",[14,29,30,31,35],{},"An intersection curve is the line where two surfaces cross. You need it to inspect a fit, to trim a part, to mark a seam, or to check a boolean before you run it. ",[32,33,34],"code",{},"trueform"," computes these curves exactly, between two meshes or between many, and returns them as polylines. It is header-only C++17. This tutorial covers two meshes, moved meshes, reused structures, N meshes, and how to use the curves.",[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\u002Fintersect#intersection-curves\",\"variant\":\"soft\",\"color\":\"neutral\"}]",[14,41,42],{},[43,44],"img",{"alt":45,"src":46},"The Stanford dragon and a moved copy, with the curves where they intersect","\u002Fimg\u002Ftutorials\u002Fintersection-curves.png",[48,49,51],"h2",{"id":50},"install","Install",[14,53,54],{},"Header-only. It needs C++17 and oneTBB. Add it to your project via CMake FetchContent:",[56,57,62],"pre",{"className":58,"code":59,"language":60,"meta":61,"style":61},"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,63,64,73,79,85,91,97],{"__ignoreMap":61},[65,66,69],"span",{"class":67,"line":68},"line",1,[65,70,72],{"class":71},"s95oV","FetchContent_Declare(\n",[65,74,76],{"class":67,"line":75},2,[65,77,78],{"class":71},"  trueform\n",[65,80,82],{"class":67,"line":81},3,[65,83,84],{"class":71},"  GIT_REPOSITORY https:\u002F\u002Fgithub.com\u002Fpolydera\u002Ftrueform\n",[65,86,88],{"class":67,"line":87},4,[65,89,90],{"class":71},")\n",[65,92,94],{"class":67,"line":93},5,[65,95,96],{"class":71},"FetchContent_MakeAvailable(trueform)\n",[65,98,100,104,107,111],{"class":67,"line":99},6,[65,101,103],{"class":102},"snl16","target_link_libraries",[65,105,106],{"class":71},"(my_app ",[65,108,110],{"class":109},"svObZ","PRIVATE",[65,112,113],{"class":71}," tf::trueform)\n",[14,115,116,117,123],{},"Also available via conan, nuget, and pip. See the ",[18,118,122],{"href":119,"rel":120},"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fgetting-started\u002Finstallation",[121],"nofollow","installation guide"," for all options.",[48,125,127],{"id":126},"loading-the-meshes","Loading the meshes",[56,129,133],{"className":130,"code":131,"language":132,"meta":61,"style":61},"language-cpp shiki shiki-themes github-dark","#include \u003Ctrueform\u002Ftrueform.hpp>\n\nauto sphere_buffer = tf::read_stl(\"sphere.stl\");\nauto box_buffer = tf::read_stl(\"box.stl\");\nauto sphere = sphere_buffer.polygons();\nauto box = box_buffer.polygons();\n","cpp",[32,134,135,144,150,179,201,219],{"__ignoreMap":61},[65,136,137,140],{"class":67,"line":68},[65,138,139],{"class":102},"#include",[65,141,143],{"class":142},"sU2Wk"," \u003Ctrueform\u002Ftrueform.hpp>\n",[65,145,146],{"class":67,"line":75},[65,147,149],{"emptyLinePlaceholder":148},true,"\n",[65,151,152,155,158,161,164,167,170,173,176],{"class":67,"line":81},[65,153,154],{"class":102},"auto",[65,156,157],{"class":71}," sphere_buffer ",[65,159,160],{"class":102},"=",[65,162,163],{"class":109}," tf",[65,165,166],{"class":71},"::",[65,168,169],{"class":109},"read_stl",[65,171,172],{"class":71},"(",[65,174,175],{"class":142},"\"sphere.stl\"",[65,177,178],{"class":71},");\n",[65,180,181,183,186,188,190,192,194,196,199],{"class":67,"line":87},[65,182,154],{"class":102},[65,184,185],{"class":71}," box_buffer ",[65,187,160],{"class":102},[65,189,163],{"class":109},[65,191,166],{"class":71},[65,193,169],{"class":109},[65,195,172],{"class":71},[65,197,198],{"class":142},"\"box.stl\"",[65,200,178],{"class":71},[65,202,203,205,208,210,213,216],{"class":67,"line":93},[65,204,154],{"class":102},[65,206,207],{"class":71}," sphere ",[65,209,160],{"class":102},[65,211,212],{"class":71}," sphere_buffer.",[65,214,215],{"class":109},"polygons",[65,217,218],{"class":71},"();\n",[65,220,221,223,226,228,231,233],{"class":67,"line":99},[65,222,154],{"class":102},[65,224,225],{"class":71}," box ",[65,227,160],{"class":102},[65,229,230],{"class":71}," box_buffer.",[65,232,215],{"class":109},[65,234,218],{"class":71},[14,236,237,240,241,244,245,248,249,252,253,256],{},[32,238,239],{},"tf::read_stl"," returns a ",[32,242,243],{},"tf::polygons_buffer"," that owns the data. ",[32,246,247],{},".polygons()"," is a view on it. ",[32,250,251],{},"tf::read_obj"," works the same way. If your faces and points already live in your own arrays, wrap them with ",[32,254,255],{},"tf::make_polygons(tf::make_faces\u003C3>(faces), tf::make_points\u003C3>(points))",". No copy is made.",[14,258,259],{},"To follow along without files, make the two shapes. The box is larger than the sphere's inner cube and smaller than its outer one, so each box face cuts the sphere:",[56,261,263],{"className":130,"code":262,"language":132,"meta":61,"style":61},"auto sphere_buffer = tf::make_sphere_mesh(1.f, 40, 60);\nauto box_buffer = tf::make_box_mesh(1.5f, 1.5f, 1.5f, 6, 6, 6);\n",[32,264,265,302],{"__ignoreMap":61},[65,266,267,269,271,273,275,277,280,282,286,289,292,295,297,300],{"class":67,"line":68},[65,268,154],{"class":102},[65,270,157],{"class":71},[65,272,160],{"class":102},[65,274,163],{"class":109},[65,276,166],{"class":71},[65,278,279],{"class":109},"make_sphere_mesh",[65,281,172],{"class":71},[65,283,285],{"class":284},"sDLfK","1.",[65,287,288],{"class":102},"f",[65,290,291],{"class":71},", ",[65,293,294],{"class":284},"40",[65,296,291],{"class":71},[65,298,299],{"class":284},"60",[65,301,178],{"class":71},[65,303,304,306,308,310,312,314,317,319,322,324,326,328,330,332,334,336,338,341,343,345,347,349],{"class":67,"line":75},[65,305,154],{"class":102},[65,307,185],{"class":71},[65,309,160],{"class":102},[65,311,163],{"class":109},[65,313,166],{"class":71},[65,315,316],{"class":109},"make_box_mesh",[65,318,172],{"class":71},[65,320,321],{"class":284},"1.5",[65,323,288],{"class":102},[65,325,291],{"class":71},[65,327,321],{"class":284},[65,329,288],{"class":102},[65,331,291],{"class":71},[65,333,321],{"class":284},[65,335,288],{"class":102},[65,337,291],{"class":71},[65,339,340],{"class":284},"6",[65,342,291],{"class":71},[65,344,340],{"class":284},[65,346,291],{"class":71},[65,348,340],{"class":284},[65,350,178],{"class":71},[48,352,354],{"id":353},"curves-between-two-meshes","Curves between two meshes",[14,356,357,364,365,368],{},[18,358,361],{"href":359,"rel":360},"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fmodules\u002Fintersect#between-two-meshes",[121],[32,362,363],{},"tf::make_intersection_curves"," takes two meshes and returns a ",[32,366,367],{},"tf::curves_buffer",":",[56,370,372],{"className":130,"code":371,"language":132,"meta":61,"style":61},"auto curves = tf::make_intersection_curves(sphere, box);\n\ncurves.paths().size();   \u002F\u002F 6\n",[32,373,374,393,397],{"__ignoreMap":61},[65,375,376,378,381,383,385,387,390],{"class":67,"line":68},[65,377,154],{"class":102},[65,379,380],{"class":71}," curves ",[65,382,160],{"class":102},[65,384,163],{"class":109},[65,386,166],{"class":71},[65,388,389],{"class":109},"make_intersection_curves",[65,391,392],{"class":71},"(sphere, box);\n",[65,394,395],{"class":67,"line":75},[65,396,149],{"emptyLinePlaceholder":148},[65,398,399,402,405,408,411,414],{"class":67,"line":81},[65,400,401],{"class":71},"curves.",[65,403,404],{"class":109},"paths",[65,406,407],{"class":71},"().",[65,409,410],{"class":109},"size",[65,412,413],{"class":71},"();",[65,415,417],{"class":416},"sAwPA","   \u002F\u002F 6\n",[14,419,420],{},[43,421],{"alt":422,"src":423},"Step: curves between two meshes. Two Thingi10K parts from the benchmark corpus, a lattice slab and a scanned shape, with their intersection curves in pink","\u002Fimg\u002Ftutorials\u002Fsteps\u002Fmesh-intersection-curves-1-two-meshes.png",[14,425,426,427,430],{},"Six closed curves, one per box face. If the meshes do not touch, ",[32,428,429],{},"paths()"," is empty.",[14,432,433],{},"The computation is exact. Coordinates go onto an integer lattice and every predicate runs in exact integer arithmetic. Shared edges, shared vertices and coplanar faces are classified, not guessed. Open meshes, non-manifold edges and self-intersecting inputs are accepted. For meshes over a very large coordinate range, use the wider lattice:",[56,435,437],{"className":130,"code":436,"language":132,"meta":61,"style":61},"auto fine = tf::make_intersection_curves\u003Ctf::exact::int64>(sphere, box);\n",[32,438,439],{"__ignoreMap":61},[65,440,441,443,446,448,450,452,454,457,460,462,465,467,470],{"class":67,"line":68},[65,442,154],{"class":102},[65,444,445],{"class":71}," fine ",[65,447,160],{"class":102},[65,449,163],{"class":109},[65,451,166],{"class":71},[65,453,389],{"class":109},[65,455,456],{"class":71},"\u003C",[65,458,459],{"class":109},"tf",[65,461,166],{"class":71},[65,463,464],{"class":109},"exact",[65,466,166],{"class":71},[65,468,469],{"class":109},"int64",[65,471,472],{"class":71},">(sphere, box);\n",[48,474,476],{"id":475},"curves-between-moved-meshes","Curves between moved meshes",[14,478,479],{},"Most real cases have one part in a different pose. Tag a transformation onto the form. The points are not copied or rewritten.",[56,481,483],{"className":130,"code":482,"language":132,"meta":61,"style":61},"auto T = tf::make_transformation_from_translation(\n    tf::make_vector(1.f, 0.f, 0.f));\nauto moved = sphere | tf::tag(T);\n\nauto circle = tf::make_intersection_curves(sphere, moved);\n\u002F\u002F one closed circle in the plane x = 0.5\n",[32,484,485,504,536,560,564,582],{"__ignoreMap":61},[65,486,487,489,492,494,496,498,501],{"class":67,"line":68},[65,488,154],{"class":102},[65,490,491],{"class":71}," T ",[65,493,160],{"class":102},[65,495,163],{"class":109},[65,497,166],{"class":71},[65,499,500],{"class":109},"make_transformation_from_translation",[65,502,503],{"class":71},"(\n",[65,505,506,509,511,514,516,518,520,522,525,527,529,531,533],{"class":67,"line":75},[65,507,508],{"class":109},"    tf",[65,510,166],{"class":71},[65,512,513],{"class":109},"make_vector",[65,515,172],{"class":71},[65,517,285],{"class":284},[65,519,288],{"class":102},[65,521,291],{"class":71},[65,523,524],{"class":284},"0.",[65,526,288],{"class":102},[65,528,291],{"class":71},[65,530,524],{"class":284},[65,532,288],{"class":102},[65,534,535],{"class":71},"));\n",[65,537,538,540,543,545,547,550,552,554,557],{"class":67,"line":81},[65,539,154],{"class":102},[65,541,542],{"class":71}," moved ",[65,544,160],{"class":102},[65,546,207],{"class":71},[65,548,549],{"class":102},"|",[65,551,163],{"class":109},[65,553,166],{"class":71},[65,555,556],{"class":109},"tag",[65,558,559],{"class":71},"(T);\n",[65,561,562],{"class":67,"line":87},[65,563,149],{"emptyLinePlaceholder":148},[65,565,566,568,571,573,575,577,579],{"class":67,"line":93},[65,567,154],{"class":102},[65,569,570],{"class":71}," circle ",[65,572,160],{"class":102},[65,574,163],{"class":109},[65,576,166],{"class":71},[65,578,389],{"class":109},[65,580,581],{"class":71},"(sphere, moved);\n",[65,583,584],{"class":67,"line":99},[65,585,586],{"class":416},"\u002F\u002F one closed circle in the plane x = 0.5\n",[14,588,589],{},[43,590],{"alt":591,"src":592},"Step: curves between moved meshes. The Stanford bunny and a moved, turned copy of it (a shared view with a transformation), with the closed intersection curve in pink","\u002Fimg\u002Ftutorials\u002Fsteps\u002Fmesh-intersection-curves-2-moved.png",[14,594,595,598,599,602,603,608],{},[32,596,597],{},"moved"," keeps the full API of ",[32,600,601],{},"sphere"," through the ",[18,604,607],{"href":605,"rel":606},"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fmodules\u002Fcore#policies",[121],"policy system",". The same mesh data serves both sides.",[48,610,612],{"id":611},"reusing-structures","Reusing structures",[14,614,615],{},"The call needs a spatial tree, the face membership and the manifold edge link for each mesh. It builds them when they are missing. When one mesh moves and you recompute many times, build them once and tag them on:",[56,617,619],{"className":130,"code":618,"language":132,"meta":61,"style":61},"tf::aabb_tree\u003Cint, float, 3> sphere_tree(sphere, tf::config_tree(4, 4));\ntf::aabb_tree\u003Cint, float, 3> box_tree(box, tf::config_tree(4, 4));\nauto sphere_fm = tf::make_face_membership(sphere);\nauto box_fm = tf::make_face_membership(box);\nauto sphere_mel = tf::make_manifold_edge_link(sphere | tf::tag(sphere_fm));\nauto box_mel = tf::make_manifold_edge_link(box | tf::tag(box_fm));\n\nauto sphere_form = sphere | tf::tag(sphere_tree)\n                          | tf::tag(sphere_fm) | tf::tag(sphere_mel);\nauto box_form = box | tf::tag(box_tree)\n                    | tf::tag(box_fm) | tf::tag(box_mel);\n\nfor (float x = 0.f; x \u003C= 1.f; x += 0.1f) {\n  auto Tx = tf::make_transformation_from_translation(\n      tf::make_vector(x, 0.f, 0.f));\n  auto c = tf::make_intersection_curves(sphere_form, box_form | tf::tag(Tx));\n}\n",[32,620,621,675,723,742,760,790,819,824,847,873,896,922,927,972,991,1016,1046],{"__ignoreMap":61},[65,622,623,625,627,630,632,635,637,640,642,645,648,651,653,655,657,659,661,664,666,669,671,673],{"class":67,"line":68},[65,624,459],{"class":109},[65,626,166],{"class":71},[65,628,629],{"class":109},"aabb_tree",[65,631,456],{"class":71},[65,633,634],{"class":102},"int",[65,636,291],{"class":71},[65,638,639],{"class":102},"float",[65,641,291],{"class":71},[65,643,644],{"class":284},"3",[65,646,647],{"class":71},"> ",[65,649,650],{"class":109},"sphere_tree",[65,652,172],{"class":71},[65,654,601],{"class":109},[65,656,291],{"class":71},[65,658,459],{"class":109},[65,660,166],{"class":71},[65,662,663],{"class":109},"config_tree",[65,665,172],{"class":71},[65,667,668],{"class":284},"4",[65,670,291],{"class":71},[65,672,668],{"class":284},[65,674,535],{"class":71},[65,676,677,679,681,683,685,687,689,691,693,695,697,700,702,705,707,709,711,713,715,717,719,721],{"class":67,"line":75},[65,678,459],{"class":109},[65,680,166],{"class":71},[65,682,629],{"class":109},[65,684,456],{"class":71},[65,686,634],{"class":102},[65,688,291],{"class":71},[65,690,639],{"class":102},[65,692,291],{"class":71},[65,694,644],{"class":284},[65,696,647],{"class":71},[65,698,699],{"class":109},"box_tree",[65,701,172],{"class":71},[65,703,704],{"class":109},"box",[65,706,291],{"class":71},[65,708,459],{"class":109},[65,710,166],{"class":71},[65,712,663],{"class":109},[65,714,172],{"class":71},[65,716,668],{"class":284},[65,718,291],{"class":71},[65,720,668],{"class":284},[65,722,535],{"class":71},[65,724,725,727,730,732,734,736,739],{"class":67,"line":81},[65,726,154],{"class":102},[65,728,729],{"class":71}," sphere_fm ",[65,731,160],{"class":102},[65,733,163],{"class":109},[65,735,166],{"class":71},[65,737,738],{"class":109},"make_face_membership",[65,740,741],{"class":71},"(sphere);\n",[65,743,744,746,749,751,753,755,757],{"class":67,"line":87},[65,745,154],{"class":102},[65,747,748],{"class":71}," box_fm ",[65,750,160],{"class":102},[65,752,163],{"class":109},[65,754,166],{"class":71},[65,756,738],{"class":109},[65,758,759],{"class":71},"(box);\n",[65,761,762,764,767,769,771,773,776,779,781,783,785,787],{"class":67,"line":93},[65,763,154],{"class":102},[65,765,766],{"class":71}," sphere_mel ",[65,768,160],{"class":102},[65,770,163],{"class":109},[65,772,166],{"class":71},[65,774,775],{"class":109},"make_manifold_edge_link",[65,777,778],{"class":71},"(sphere ",[65,780,549],{"class":102},[65,782,163],{"class":109},[65,784,166],{"class":71},[65,786,556],{"class":109},[65,788,789],{"class":71},"(sphere_fm));\n",[65,791,792,794,797,799,801,803,805,808,810,812,814,816],{"class":67,"line":99},[65,793,154],{"class":102},[65,795,796],{"class":71}," box_mel ",[65,798,160],{"class":102},[65,800,163],{"class":109},[65,802,166],{"class":71},[65,804,775],{"class":109},[65,806,807],{"class":71},"(box ",[65,809,549],{"class":102},[65,811,163],{"class":109},[65,813,166],{"class":71},[65,815,556],{"class":109},[65,817,818],{"class":71},"(box_fm));\n",[65,820,822],{"class":67,"line":821},7,[65,823,149],{"emptyLinePlaceholder":148},[65,825,827,829,832,834,836,838,840,842,844],{"class":67,"line":826},8,[65,828,154],{"class":102},[65,830,831],{"class":71}," sphere_form ",[65,833,160],{"class":102},[65,835,207],{"class":71},[65,837,549],{"class":102},[65,839,163],{"class":109},[65,841,166],{"class":71},[65,843,556],{"class":109},[65,845,846],{"class":71},"(sphere_tree)\n",[65,848,850,853,855,857,859,862,864,866,868,870],{"class":67,"line":849},9,[65,851,852],{"class":102},"                          |",[65,854,163],{"class":109},[65,856,166],{"class":71},[65,858,556],{"class":109},[65,860,861],{"class":71},"(sphere_fm) ",[65,863,549],{"class":102},[65,865,163],{"class":109},[65,867,166],{"class":71},[65,869,556],{"class":109},[65,871,872],{"class":71},"(sphere_mel);\n",[65,874,876,878,881,883,885,887,889,891,893],{"class":67,"line":875},10,[65,877,154],{"class":102},[65,879,880],{"class":71}," box_form ",[65,882,160],{"class":102},[65,884,225],{"class":71},[65,886,549],{"class":102},[65,888,163],{"class":109},[65,890,166],{"class":71},[65,892,556],{"class":109},[65,894,895],{"class":71},"(box_tree)\n",[65,897,899,902,904,906,908,911,913,915,917,919],{"class":67,"line":898},11,[65,900,901],{"class":102},"                    |",[65,903,163],{"class":109},[65,905,166],{"class":71},[65,907,556],{"class":109},[65,909,910],{"class":71},"(box_fm) ",[65,912,549],{"class":102},[65,914,163],{"class":109},[65,916,166],{"class":71},[65,918,556],{"class":109},[65,920,921],{"class":71},"(box_mel);\n",[65,923,925],{"class":67,"line":924},12,[65,926,149],{"emptyLinePlaceholder":148},[65,928,930,933,936,938,941,943,946,948,951,954,957,959,961,964,967,969],{"class":67,"line":929},13,[65,931,932],{"class":102},"for",[65,934,935],{"class":71}," (",[65,937,639],{"class":102},[65,939,940],{"class":71}," x ",[65,942,160],{"class":102},[65,944,945],{"class":284}," 0.",[65,947,288],{"class":102},[65,949,950],{"class":71},"; x ",[65,952,953],{"class":102},"\u003C=",[65,955,956],{"class":284}," 1.",[65,958,288],{"class":102},[65,960,950],{"class":71},[65,962,963],{"class":102},"+=",[65,965,966],{"class":284}," 0.1",[65,968,288],{"class":102},[65,970,971],{"class":71},") {\n",[65,973,975,978,981,983,985,987,989],{"class":67,"line":974},14,[65,976,977],{"class":102},"  auto",[65,979,980],{"class":71}," Tx ",[65,982,160],{"class":102},[65,984,163],{"class":109},[65,986,166],{"class":71},[65,988,500],{"class":109},[65,990,503],{"class":71},[65,992,994,997,999,1001,1004,1006,1008,1010,1012,1014],{"class":67,"line":993},15,[65,995,996],{"class":109},"      tf",[65,998,166],{"class":71},[65,1000,513],{"class":109},[65,1002,1003],{"class":71},"(x, ",[65,1005,524],{"class":284},[65,1007,288],{"class":102},[65,1009,291],{"class":71},[65,1011,524],{"class":284},[65,1013,288],{"class":102},[65,1015,535],{"class":71},[65,1017,1019,1021,1024,1026,1028,1030,1032,1035,1037,1039,1041,1043],{"class":67,"line":1018},16,[65,1020,977],{"class":102},[65,1022,1023],{"class":71}," c ",[65,1025,160],{"class":102},[65,1027,163],{"class":109},[65,1029,166],{"class":71},[65,1031,389],{"class":109},[65,1033,1034],{"class":71},"(sphere_form, box_form ",[65,1036,549],{"class":102},[65,1038,163],{"class":109},[65,1040,166],{"class":71},[65,1042,556],{"class":109},[65,1044,1045],{"class":71},"(Tx));\n",[65,1047,1049],{"class":67,"line":1048},17,[65,1050,1051],{"class":71},"}\n",[14,1053,1054],{},"The trees stay valid under the transformation. Moving the box does not rebuild anything.",[48,1056,1058],{"id":1057},"curves-between-n-meshes","Curves between N meshes",[14,1060,1061],{},"Pass a range of forms to get all pairwise curves in one call. The forms must share one type, so give each one a transformation, even the one that stays put:",[56,1063,1065],{"className":130,"code":1064,"language":132,"meta":61,"style":61},"auto f0 = sphere | tf::tag(tf::make_transformation_from_translation(\n                       tf::make_vector(0.f, 0.f, 0.f)));\nauto f1 = sphere | tf::tag(tf::make_transformation_from_translation(\n                       tf::make_vector(1.f, 0.f, 0.f)));\nauto f2 = sphere | tf::tag(tf::make_transformation_from_translation(\n                       tf::make_vector(0.5f, 0.8f, 0.f)));\ndecltype(f0) forms[] = {f0, f1, f2};\n\nauto arcs = tf::make_intersection_curves(tf::make_range(forms, forms + 3));\n\narcs.paths().size();   \u002F\u002F 6\n",[32,1066,1067,1096,1126,1155,1183,1212,1242,1255,1259,1294,1298],{"__ignoreMap":61},[65,1068,1069,1071,1074,1076,1078,1080,1082,1084,1086,1088,1090,1092,1094],{"class":67,"line":68},[65,1070,154],{"class":102},[65,1072,1073],{"class":71}," f0 ",[65,1075,160],{"class":102},[65,1077,207],{"class":71},[65,1079,549],{"class":102},[65,1081,163],{"class":109},[65,1083,166],{"class":71},[65,1085,556],{"class":109},[65,1087,172],{"class":71},[65,1089,459],{"class":109},[65,1091,166],{"class":71},[65,1093,500],{"class":109},[65,1095,503],{"class":71},[65,1097,1098,1101,1103,1105,1107,1109,1111,1113,1115,1117,1119,1121,1123],{"class":67,"line":75},[65,1099,1100],{"class":109},"                       tf",[65,1102,166],{"class":71},[65,1104,513],{"class":109},[65,1106,172],{"class":71},[65,1108,524],{"class":284},[65,1110,288],{"class":102},[65,1112,291],{"class":71},[65,1114,524],{"class":284},[65,1116,288],{"class":102},[65,1118,291],{"class":71},[65,1120,524],{"class":284},[65,1122,288],{"class":102},[65,1124,1125],{"class":71},")));\n",[65,1127,1128,1130,1133,1135,1137,1139,1141,1143,1145,1147,1149,1151,1153],{"class":67,"line":81},[65,1129,154],{"class":102},[65,1131,1132],{"class":71}," f1 ",[65,1134,160],{"class":102},[65,1136,207],{"class":71},[65,1138,549],{"class":102},[65,1140,163],{"class":109},[65,1142,166],{"class":71},[65,1144,556],{"class":109},[65,1146,172],{"class":71},[65,1148,459],{"class":109},[65,1150,166],{"class":71},[65,1152,500],{"class":109},[65,1154,503],{"class":71},[65,1156,1157,1159,1161,1163,1165,1167,1169,1171,1173,1175,1177,1179,1181],{"class":67,"line":87},[65,1158,1100],{"class":109},[65,1160,166],{"class":71},[65,1162,513],{"class":109},[65,1164,172],{"class":71},[65,1166,285],{"class":284},[65,1168,288],{"class":102},[65,1170,291],{"class":71},[65,1172,524],{"class":284},[65,1174,288],{"class":102},[65,1176,291],{"class":71},[65,1178,524],{"class":284},[65,1180,288],{"class":102},[65,1182,1125],{"class":71},[65,1184,1185,1187,1190,1192,1194,1196,1198,1200,1202,1204,1206,1208,1210],{"class":67,"line":93},[65,1186,154],{"class":102},[65,1188,1189],{"class":71}," f2 ",[65,1191,160],{"class":102},[65,1193,207],{"class":71},[65,1195,549],{"class":102},[65,1197,163],{"class":109},[65,1199,166],{"class":71},[65,1201,556],{"class":109},[65,1203,172],{"class":71},[65,1205,459],{"class":109},[65,1207,166],{"class":71},[65,1209,500],{"class":109},[65,1211,503],{"class":71},[65,1213,1214,1216,1218,1220,1222,1225,1227,1229,1232,1234,1236,1238,1240],{"class":67,"line":99},[65,1215,1100],{"class":109},[65,1217,166],{"class":71},[65,1219,513],{"class":109},[65,1221,172],{"class":71},[65,1223,1224],{"class":284},"0.5",[65,1226,288],{"class":102},[65,1228,291],{"class":71},[65,1230,1231],{"class":284},"0.8",[65,1233,288],{"class":102},[65,1235,291],{"class":71},[65,1237,524],{"class":284},[65,1239,288],{"class":102},[65,1241,1125],{"class":71},[65,1243,1244,1247,1250,1252],{"class":67,"line":821},[65,1245,1246],{"class":102},"decltype",[65,1248,1249],{"class":71},"(f0) forms[] ",[65,1251,160],{"class":102},[65,1253,1254],{"class":71}," {f0, f1, f2};\n",[65,1256,1257],{"class":67,"line":826},[65,1258,149],{"emptyLinePlaceholder":148},[65,1260,1261,1263,1266,1268,1270,1272,1274,1276,1278,1280,1283,1286,1289,1292],{"class":67,"line":849},[65,1262,154],{"class":102},[65,1264,1265],{"class":71}," arcs ",[65,1267,160],{"class":102},[65,1269,163],{"class":109},[65,1271,166],{"class":71},[65,1273,389],{"class":109},[65,1275,172],{"class":71},[65,1277,459],{"class":109},[65,1279,166],{"class":71},[65,1281,1282],{"class":109},"make_range",[65,1284,1285],{"class":71},"(forms, forms ",[65,1287,1288],{"class":102},"+",[65,1290,1291],{"class":284}," 3",[65,1293,535],{"class":71},[65,1295,1296],{"class":67,"line":875},[65,1297,149],{"emptyLinePlaceholder":148},[65,1299,1300,1303,1305,1307,1309,1311],{"class":67,"line":898},[65,1301,1302],{"class":71},"arcs.",[65,1304,404],{"class":109},[65,1306,407],{"class":71},[65,1308,410],{"class":109},[65,1310,413],{"class":71},[65,1312,417],{"class":416},[14,1314,1315],{},[43,1316],{"alt":1317,"src":1318},"Step: curves between N meshes. Three posed copies of the Stanford bunny in one call; the pink curves are split into arcs where they cross, with the split points marked","\u002Fimg\u002Ftutorials\u002Fsteps\u002Fmesh-intersection-curves-3-n-meshes.png",[14,1320,1321,1322,1324],{},"Three spheres make three pairwise circles. The circles cross each other on the surfaces. ",[32,1323,34],{}," splits the curves at every crossing point, so here you get six open arcs instead of three circles.",[48,1326,1328],{"id":1327},"working-with-results","Working with results",[14,1330,1331],{},"Each path is one curve, as indices into the points. A closed curve repeats its first index at the end.",[56,1333,1335],{"className":130,"code":1334,"language":132,"meta":61,"style":61},"for (auto path : curves.paths()) {\n  bool closed = path.front() == path.back();\n  float length = 0.f;\n  for (std::size_t i = 1; i \u003C path.size(); ++i)\n    length += tf::distance(curves.points()[path[i - 1]],\n                           curves.points()[path[i]]);\n}\n",[32,1336,1337,1353,1382,1399,1440,1471,1481],{"__ignoreMap":61},[65,1338,1339,1341,1343,1345,1348,1350],{"class":67,"line":68},[65,1340,932],{"class":102},[65,1342,935],{"class":71},[65,1344,154],{"class":102},[65,1346,1347],{"class":71}," path : curves.",[65,1349,404],{"class":109},[65,1351,1352],{"class":71},"()) {\n",[65,1354,1355,1358,1361,1363,1366,1369,1372,1375,1377,1380],{"class":67,"line":75},[65,1356,1357],{"class":102},"  bool",[65,1359,1360],{"class":71}," closed ",[65,1362,160],{"class":102},[65,1364,1365],{"class":71}," path.",[65,1367,1368],{"class":109},"front",[65,1370,1371],{"class":71},"() ",[65,1373,1374],{"class":102},"==",[65,1376,1365],{"class":71},[65,1378,1379],{"class":109},"back",[65,1381,218],{"class":71},[65,1383,1384,1387,1390,1392,1394,1396],{"class":67,"line":81},[65,1385,1386],{"class":102},"  float",[65,1388,1389],{"class":71}," length ",[65,1391,160],{"class":102},[65,1393,945],{"class":284},[65,1395,288],{"class":102},[65,1397,1398],{"class":71},";\n",[65,1400,1401,1404,1406,1409,1411,1414,1417,1419,1422,1425,1427,1429,1431,1434,1437],{"class":67,"line":87},[65,1402,1403],{"class":102},"  for",[65,1405,935],{"class":71},[65,1407,1408],{"class":109},"std",[65,1410,166],{"class":71},[65,1412,1413],{"class":102},"size_t",[65,1415,1416],{"class":71}," i ",[65,1418,160],{"class":102},[65,1420,1421],{"class":284}," 1",[65,1423,1424],{"class":71},"; i ",[65,1426,456],{"class":102},[65,1428,1365],{"class":71},[65,1430,410],{"class":109},[65,1432,1433],{"class":71},"(); ",[65,1435,1436],{"class":102},"++",[65,1438,1439],{"class":71},"i)\n",[65,1441,1442,1445,1447,1449,1451,1454,1457,1460,1463,1466,1468],{"class":67,"line":93},[65,1443,1444],{"class":71},"    length ",[65,1446,963],{"class":102},[65,1448,163],{"class":109},[65,1450,166],{"class":71},[65,1452,1453],{"class":109},"distance",[65,1455,1456],{"class":71},"(curves.",[65,1458,1459],{"class":109},"points",[65,1461,1462],{"class":71},"()[path[i ",[65,1464,1465],{"class":102},"-",[65,1467,1421],{"class":284},[65,1469,1470],{"class":71},"]],\n",[65,1472,1473,1476,1478],{"class":67,"line":99},[65,1474,1475],{"class":71},"                           curves.",[65,1477,1459],{"class":109},[65,1479,1480],{"class":71},"()[path[i]]);\n",[65,1482,1483],{"class":67,"line":821},[65,1484,1051],{"class":71},[14,1486,1487],{},"The curves lie on both surfaces. You can check against the tagged tree:",[56,1489,1491],{"className":130,"code":1490,"language":132,"meta":61,"style":61},"float worst = 0.f;   \u002F\u002F close to 0\nfor (auto pt : curves.points())\n  worst = std::max(worst, tf::distance(sphere_form, pt));\n",[32,1492,1493,1512,1528],{"__ignoreMap":61},[65,1494,1495,1497,1500,1502,1504,1506,1509],{"class":67,"line":68},[65,1496,639],{"class":102},[65,1498,1499],{"class":71}," worst ",[65,1501,160],{"class":102},[65,1503,945],{"class":284},[65,1505,288],{"class":102},[65,1507,1508],{"class":71},";",[65,1510,1511],{"class":416},"   \u002F\u002F close to 0\n",[65,1513,1514,1516,1518,1520,1523,1525],{"class":67,"line":75},[65,1515,932],{"class":102},[65,1517,935],{"class":71},[65,1519,154],{"class":102},[65,1521,1522],{"class":71}," pt : curves.",[65,1524,1459],{"class":109},[65,1526,1527],{"class":71},"())\n",[65,1529,1530,1533,1535,1538,1540,1543,1546,1548,1550,1552],{"class":67,"line":81},[65,1531,1532],{"class":71},"  worst ",[65,1534,160],{"class":102},[65,1536,1537],{"class":109}," std",[65,1539,166],{"class":71},[65,1541,1542],{"class":109},"max",[65,1544,1545],{"class":71},"(worst, ",[65,1547,459],{"class":109},[65,1549,166],{"class":71},[65,1551,1453],{"class":109},[65,1553,1554],{"class":71},"(sphere_form, pt));\n",[14,1556,1557],{},"If you also want the boolean, ask for the curves on the same call:",[56,1559,1561],{"className":130,"code":1560,"language":132,"meta":61,"style":61},"auto [result, labels, face_labels, boolean_curves] = tf::make_boolean(\n    sphere, box, tf::boolean_op::merge, tf::return_curves);\n",[32,1562,1563,1581],{"__ignoreMap":61},[65,1564,1565,1567,1570,1572,1574,1576,1579],{"class":67,"line":68},[65,1566,154],{"class":102},[65,1568,1569],{"class":71}," [result, labels, face_labels, boolean_curves] ",[65,1571,160],{"class":102},[65,1573,163],{"class":109},[65,1575,166],{"class":71},[65,1577,1578],{"class":109},"make_boolean",[65,1580,503],{"class":71},[65,1582,1583,1586,1588,1590,1593,1596,1598],{"class":67,"line":75},[65,1584,1585],{"class":71},"    sphere, box, ",[65,1587,459],{"class":109},[65,1589,166],{"class":71},[65,1591,1592],{"class":109},"boolean_op",[65,1594,1595],{"class":71},"::merge, ",[65,1597,459],{"class":109},[65,1599,1600],{"class":71},"::return_curves);\n",[14,1602,1603,1604,368],{},"To split faces along the curves instead, so the curves become edges, use ",[18,1605,1608],{"href":1606,"rel":1607},"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fmodules\u002Farrangement#mesh-arrangements",[121],[32,1609,1610],{},"tf::make_mesh_arrangements",[56,1612,1614],{"className":130,"code":1613,"language":132,"meta":61,"style":61},"auto [split, tag_labels, source_faces] =\n    tf::make_mesh_arrangements(sphere, box);\n\u002F\u002F tag_labels: which mesh each face came from; source_faces: its source face\n",[32,1615,1616,1626,1637],{"__ignoreMap":61},[65,1617,1618,1620,1623],{"class":67,"line":68},[65,1619,154],{"class":102},[65,1621,1622],{"class":71}," [split, tag_labels, source_faces] ",[65,1624,1625],{"class":102},"=\n",[65,1627,1628,1630,1632,1635],{"class":67,"line":75},[65,1629,508],{"class":109},[65,1631,166],{"class":71},[65,1633,1634],{"class":109},"make_mesh_arrangements",[65,1636,392],{"class":71},[65,1638,1639],{"class":67,"line":81},[65,1640,1641],{"class":416},"\u002F\u002F tag_labels: which mesh each face came from; source_faces: its source face\n",[14,1643,1644,1645,27],{},"To cut a mesh by a plane or a scalar field instead of another mesh, see ",[18,1646,1648],{"href":1647},"\u002Ftutorials\u002Fmesh-slicing-and-cross-sections-in-cpp","mesh slicing and cross-sections",[48,1650,1652],{"id":1651},"related-tutorials","Related tutorials",[1654,1655,1656,1664,1671,1678,1684],"ul",{},[1657,1658,1659,1663],"li",{},[18,1660,1662],{"href":1661},"\u002Ftutorials\u002Ffast-mesh-booleans-in-cpp","Fast Mesh Booleans in C++"," — exact union, intersection and difference of two meshes.",[1657,1665,1666,1670],{},[18,1667,1669],{"href":1668},"\u002Ftutorials\u002Ffast-variadic-mesh-booleans-in-cpp","Fast Variadic Mesh Booleans in C++"," — one arrangement, many boolean expressions over N meshes.",[1657,1672,1673,1677],{},[18,1674,1676],{"href":1675},"\u002Ftutorials\u002Ffast-mesh-collision-detection-in-cpp","Fast and Robust Mesh Collision Detection in C++"," — do two meshes touch, how far apart, and where.",[1657,1679,1680,1683],{},[18,1681,1682],{"href":1647},"Fast and Robust Mesh Slicing and Cross-Sections in C++"," — plane sections, layers and slabs of a mesh.",[1657,1685,1686,1690],{},[18,1687,1689],{"href":1688},"\u002Ftutorials\u002Fisocontours-on-a-triangle-mesh-in-cpp","Fast and Robust Isocontours on a Triangle Mesh in C++"," — curves and bands of equal value on a scalar field.",[48,1692,1694],{"id":1693},"performance","Performance",[1696,1697],"headline-numbers",{":items":1698},"[{\"value\":\"3.5ms\",\"label\":\"Intersection curves\",\"detail\":\"2×500K polygons\"},{\"value\":\"14ms\",\"label\":\"Boolean union\",\"detail\":\"2×500K polygons\"},{\"value\":\"266.5ms\",\"label\":\"Polygon arrangements\",\"detail\":\"2,051,532 polygons (libigl: 6,168 ms)\"}]",[14,1700,1701,1706,1707],{},[65,1702,1705],{"className":1703},[1704],"tag-pill","Apple M4 Max"," ",[65,1708,1710],{"className":1709},[1704],"16 threads",[14,1712,1713],{},"The curve and boolean timings are on two 500K-polygon meshes. The arrangement benchmark splits all faces along all intersection curves, against libigl and CGAL:",[1715,1716],"data-table",{":headers":1717,":highlight":1718,":rows":1719},"[\"Operation\",\"Mesh\",\"trueform\",\"libigl\",\"CGAL\"]","2","[[\"Polygon arrangements\",\"2,051,532 polygons\",\"266.5 ms\",\"6,168 ms (23×)\",\"6,631 ms (25×)\"]]",[14,1721,1722],{},"Building the spatial tree is the up-front cost of every query here. On 1,025,766 polygons it takes 24.9 ms, against 76.8 ms for MeshLib and 248.5 ms for CGAL. A tagged transformation does not rebuild it.",[14,1724,1725],{},[43,1726],{"alt":1727,"src":1728},"Benchmark chart: trueform against other C++ libraries, polygon arrangements","\u002Fimg\u002Falgorithms\u002Fbenchmark-polygon-arrangements-cpp-2026.png",[14,1730,1731,1735,1736,1735,1741],{},[18,1732,1734],{"href":1733},"\u002Ftrueform\u002Fbenchmarks","Full benchmarks"," · ",[18,1737,1740],{"href":1738,"rel":1739},"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fmodules\u002Fintersect",[121],"Documentation",[18,1742,1744],{"href":1743},"\u002Falgorithms\u002Ffast-and-exact-mesh-booleans-and-arrangements","How the algorithm works",[1746,1747],"cite-as",{"author":1748,"title":5},"Sajovic, {\\v{Z}}iga",[1750,1751,1752],"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 .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}",{"title":61,"searchDepth":75,"depth":75,"links":1754},[1755,1756,1757,1758,1759,1760,1761,1762,1763],{"id":50,"depth":75,"text":51},{"id":126,"depth":75,"text":127},{"id":353,"depth":75,"text":354},{"id":475,"depth":75,"text":476},{"id":611,"depth":75,"text":612},{"id":1057,"depth":75,"text":1058},{"id":1327,"depth":75,"text":1328},{"id":1651,"depth":75,"text":1652},{"id":1693,"depth":75,"text":1694},"2026-10-31","Compute the exact curves where two or more triangle meshes intersect in C++. Polylines out, with tagged transformations, precomputed structures and N-mesh input. 3.5 ms on 2×500K polygons. Header-only, C++17.","md",[1768,1769,1770,1771,1772,1773,1774,1775,1776,1777],"mesh intersection curves C++","mesh mesh intersection C++","intersection polyline mesh C++","surface intersection curve C++","exact mesh intersection C++","CGAL Polygon_mesh_processing surface_intersection alternative","libigl intersect_other alternative","VTK vtkIntersectionPolyDataFilter alternative","MeshLib intersection alternative","mesh intersection library C++",{},"\u002Fimg\u002Fog\u002Ftutorials\u002Fmesh-intersection-curves-in-cpp.png","\u002Ftutorials\u002Fmesh-intersection-curves-in-cpp",{"title":5,"description":1765},"tutorials\u002Fmesh-intersection-curves-in-cpp",[1784,1785,1786],"c++","intersections","tutorial","n6SVizSqkXsbDYLNe9fdxhFvtyejN_woj1fEhayKsng",1793517812008]