[{"data":1,"prerenderedAt":2016},["ShallowReactive",2],{"tutorial-fast-mesh-collision-detection-in-cpp":3},{"id":4,"title":5,"author":6,"body":7,"date":1991,"description":1992,"extension":1993,"keywords":1994,"meta":2006,"navigation":148,"ogImage":2007,"path":2008,"published":148,"seo":2009,"stem":2010,"tags":2011,"__hash__":2015},"tutorials\u002Ftutorials\u002Ffast-mesh-collision-detection-in-cpp.md","Fast and Robust Mesh Collision Detection in C++","Žiga Sajovic",{"type":8,"value":9,"toc":1979},"minimark",[10,28,36,40,47,52,55,114,124,128,210,231,234,359,362,366,369,528,542,546,549,668,674,689,798,802,815,915,921,935,956,960,967,1063,1069,1080,1134,1143,1198,1201,1387,1391,1397,1510,1516,1519,1525,1674,1678,1681,1815,1818,1825,1886,1890,1921,1925,1929,1941,1944,1950,1953,1959,1971,1975],[11,12,13],"note",{},[14,15,16,17,22,23,27],"p",{},"Also available in ",[18,19,21],"a",{"href":20},"\u002Ftutorials\u002Ffast-mesh-collision-detection-in-python","Python"," and ",[18,24,26],{"href":25},"\u002Ftutorials\u002Ffast-mesh-collision-detection-in-javascript","JavaScript",".",[14,29,30,31,35],{},"Collision detection asks three questions about two meshes. Do they touch? If not, how far apart are they? If they do, which faces touch? ",[32,33,34],"code",{},"trueform"," answers all three on triangle meshes, with each mesh in its own pose. It is header-only C++17. This tutorial covers the test, the clearance, the contact faces, and moving a mesh without rebuilding anything.",[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\u002Fspatial#distance-and-intersection\",\"variant\":\"soft\",\"color\":\"neutral\"},{\"label\":\"Try it live\",\"to\":\"https:\u002F\u002Ftrueform.polydera.com\u002Flive-examples\u002Fcollision\",\"icon\":\"i-lucide-play\"}]",[14,41,42],{},[43,44],"img",{"alt":45,"src":46},"A sphere near the Stanford dragon with the closest pair of points between them","\u002Fimg\u002Ftutorials\u002Fcollision-detection.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 fixture_buffer = tf::read_stl(\"fixture.stl\");\nauto part_buffer = tf::read_obj\u003C3>(\"part.obj\");\n","cpp",[32,134,135,144,150,179],{"__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}," fixture_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},"\"fixture.stl\"",[65,177,178],{"class":71},");\n",[65,180,181,183,186,188,190,192,195,198,202,205,208],{"class":67,"line":87},[65,182,154],{"class":102},[65,184,185],{"class":71}," part_buffer ",[65,187,160],{"class":102},[65,189,163],{"class":109},[65,191,166],{"class":71},[65,193,194],{"class":109},"read_obj",[65,196,197],{"class":71},"\u003C",[65,199,201],{"class":200},"sDLfK","3",[65,203,204],{"class":71},">(",[65,206,207],{"class":142},"\"part.obj\"",[65,209,178],{"class":71},[14,211,212,22,215,218,219,222,223,226,227,230],{},[32,213,214],{},"tf::read_stl",[32,216,217],{},"tf::read_obj\u003C3>"," return a ",[32,220,221],{},"tf::polygons_buffer"," that owns the data. ",[32,224,225],{},".polygons()"," is a view on it. If your faces and points already live in your own arrays, wrap them with ",[32,228,229],{},"tf::make_polygons(tf::make_faces\u003C3>(faces), tf::make_points\u003C3>(points))",". No copy is made.",[14,232,233],{},"To follow along without files, use a subdivided box as the static mesh and a small sphere as the moving one:",[56,235,237],{"className":130,"code":236,"language":132,"meta":61,"style":61},"auto static_buffer = tf::make_box_mesh(2.f, 2.f, 2.f, 10, 10, 10);\nauto moving_buffer = tf::make_sphere_mesh(0.5f, 30, 30);\nauto fixture = static_buffer.polygons();\nauto part = moving_buffer.polygons();\n",[32,238,239,291,325,343],{"__ignoreMap":61},[65,240,241,243,246,248,250,252,255,257,260,263,266,268,270,272,274,276,278,281,283,285,287,289],{"class":67,"line":68},[65,242,154],{"class":102},[65,244,245],{"class":71}," static_buffer ",[65,247,160],{"class":102},[65,249,163],{"class":109},[65,251,166],{"class":71},[65,253,254],{"class":109},"make_box_mesh",[65,256,172],{"class":71},[65,258,259],{"class":200},"2.",[65,261,262],{"class":102},"f",[65,264,265],{"class":71},", ",[65,267,259],{"class":200},[65,269,262],{"class":102},[65,271,265],{"class":71},[65,273,259],{"class":200},[65,275,262],{"class":102},[65,277,265],{"class":71},[65,279,280],{"class":200},"10",[65,282,265],{"class":71},[65,284,280],{"class":200},[65,286,265],{"class":71},[65,288,280],{"class":200},[65,290,178],{"class":71},[65,292,293,295,298,300,302,304,307,309,312,314,316,319,321,323],{"class":67,"line":75},[65,294,154],{"class":102},[65,296,297],{"class":71}," moving_buffer ",[65,299,160],{"class":102},[65,301,163],{"class":109},[65,303,166],{"class":71},[65,305,306],{"class":109},"make_sphere_mesh",[65,308,172],{"class":71},[65,310,311],{"class":200},"0.5",[65,313,262],{"class":102},[65,315,265],{"class":71},[65,317,318],{"class":200},"30",[65,320,265],{"class":71},[65,322,318],{"class":200},[65,324,178],{"class":71},[65,326,327,329,332,334,337,340],{"class":67,"line":81},[65,328,154],{"class":102},[65,330,331],{"class":71}," fixture ",[65,333,160],{"class":102},[65,335,336],{"class":71}," static_buffer.",[65,338,339],{"class":109},"polygons",[65,341,342],{"class":71},"();\n",[65,344,345,347,350,352,355,357],{"class":67,"line":87},[65,346,154],{"class":102},[65,348,349],{"class":71}," part ",[65,351,160],{"class":102},[65,353,354],{"class":71}," moving_buffer.",[65,356,339],{"class":109},[65,358,342],{"class":71},[14,360,361],{},"The box spans −1 to 1 on each axis. The sphere has radius 0.5.",[48,363,365],{"id":364},"building-the-spatial-trees","Building the spatial trees",[14,367,368],{},"Every query runs on a spatial tree per mesh. Build each one once and tag it onto its mesh:",[56,370,372],{"className":130,"code":371,"language":132,"meta":61,"style":61},"tf::aabb_tree\u003Cint, float, 3> fixture_tree(fixture, tf::config_tree(4, 4));\ntf::aabb_tree\u003Cint, float, 3> part_tree(part, tf::config_tree(4, 4));\n\nauto fixture_form = fixture | tf::tag(fixture_tree);\nauto part_form = part | tf::tag(part_tree);\n",[32,373,374,430,478,482,506],{"__ignoreMap":61},[65,375,376,379,381,384,386,389,391,394,396,398,401,404,406,409,411,413,415,418,420,423,425,427],{"class":67,"line":68},[65,377,378],{"class":109},"tf",[65,380,166],{"class":71},[65,382,383],{"class":109},"aabb_tree",[65,385,197],{"class":71},[65,387,388],{"class":102},"int",[65,390,265],{"class":71},[65,392,393],{"class":102},"float",[65,395,265],{"class":71},[65,397,201],{"class":200},[65,399,400],{"class":71},"> ",[65,402,403],{"class":109},"fixture_tree",[65,405,172],{"class":71},[65,407,408],{"class":109},"fixture",[65,410,265],{"class":71},[65,412,378],{"class":109},[65,414,166],{"class":71},[65,416,417],{"class":109},"config_tree",[65,419,172],{"class":71},[65,421,422],{"class":200},"4",[65,424,265],{"class":71},[65,426,422],{"class":200},[65,428,429],{"class":71},"));\n",[65,431,432,434,436,438,440,442,444,446,448,450,452,455,457,460,462,464,466,468,470,472,474,476],{"class":67,"line":75},[65,433,378],{"class":109},[65,435,166],{"class":71},[65,437,383],{"class":109},[65,439,197],{"class":71},[65,441,388],{"class":102},[65,443,265],{"class":71},[65,445,393],{"class":102},[65,447,265],{"class":71},[65,449,201],{"class":200},[65,451,400],{"class":71},[65,453,454],{"class":109},"part_tree",[65,456,172],{"class":71},[65,458,459],{"class":109},"part",[65,461,265],{"class":71},[65,463,378],{"class":109},[65,465,166],{"class":71},[65,467,417],{"class":109},[65,469,172],{"class":71},[65,471,422],{"class":200},[65,473,265],{"class":71},[65,475,422],{"class":200},[65,477,429],{"class":71},[65,479,480],{"class":67,"line":81},[65,481,149],{"emptyLinePlaceholder":148},[65,483,484,486,489,491,493,496,498,500,503],{"class":67,"line":87},[65,485,154],{"class":102},[65,487,488],{"class":71}," fixture_form ",[65,490,160],{"class":102},[65,492,331],{"class":71},[65,494,495],{"class":102},"|",[65,497,163],{"class":109},[65,499,166],{"class":71},[65,501,502],{"class":109},"tag",[65,504,505],{"class":71},"(fixture_tree);\n",[65,507,508,510,513,515,517,519,521,523,525],{"class":67,"line":93},[65,509,154],{"class":102},[65,511,512],{"class":71}," part_form ",[65,514,160],{"class":102},[65,516,349],{"class":71},[65,518,495],{"class":102},[65,520,163],{"class":109},[65,522,166],{"class":71},[65,524,502],{"class":109},[65,526,527],{"class":71},"(part_tree);\n",[14,529,530,533,534,537,538,541],{},[32,531,532],{},"(4, 4)"," is four children per node and up to four faces per leaf. The docs recommend it for all workloads. For distance queries between two meshes, an ",[32,535,536],{},"obb_tree"," or ",[32,539,540],{},"obbrss_tree"," gives tighter culling. The calls below stay the same.",[48,543,545],{"id":544},"moving-a-mesh","Moving a mesh",[14,547,548],{},"Tag a transformation onto a form. Every query applies it on the fly. The points are not rewritten and the tree stays valid.",[56,550,552],{"className":130,"code":551,"language":132,"meta":61,"style":61},"auto at = [](float x, float y, float z) {\n  return tf::make_transformation_from_translation(tf::make_vector(x, y, z));\n};\n\nauto moved = part_form | tf::tag(at(3.f, 0.f, 0.f));\n",[32,553,554,589,613,618,622],{"__ignoreMap":61},[65,555,556,558,561,563,566,568,572,574,576,579,581,583,586],{"class":67,"line":68},[65,557,154],{"class":102},[65,559,560],{"class":71}," at ",[65,562,160],{"class":102},[65,564,565],{"class":71}," [](",[65,567,393],{"class":102},[65,569,571],{"class":570},"s9osk"," x",[65,573,265],{"class":71},[65,575,393],{"class":102},[65,577,578],{"class":570}," y",[65,580,265],{"class":71},[65,582,393],{"class":102},[65,584,585],{"class":570}," z",[65,587,588],{"class":71},") {\n",[65,590,591,594,596,598,601,603,605,607,610],{"class":67,"line":75},[65,592,593],{"class":102},"  return",[65,595,163],{"class":109},[65,597,166],{"class":71},[65,599,600],{"class":109},"make_transformation_from_translation",[65,602,172],{"class":71},[65,604,378],{"class":109},[65,606,166],{"class":71},[65,608,609],{"class":109},"make_vector",[65,611,612],{"class":71},"(x, y, z));\n",[65,614,615],{"class":67,"line":81},[65,616,617],{"class":71},"};\n",[65,619,620],{"class":67,"line":87},[65,621,149],{"emptyLinePlaceholder":148},[65,623,624,626,629,631,633,635,637,639,641,643,646,648,651,653,655,658,660,662,664,666],{"class":67,"line":93},[65,625,154],{"class":102},[65,627,628],{"class":71}," moved ",[65,630,160],{"class":102},[65,632,512],{"class":71},[65,634,495],{"class":102},[65,636,163],{"class":109},[65,638,166],{"class":71},[65,640,502],{"class":109},[65,642,172],{"class":71},[65,644,645],{"class":109},"at",[65,647,172],{"class":71},[65,649,650],{"class":200},"3.",[65,652,262],{"class":102},[65,654,265],{"class":71},[65,656,657],{"class":200},"0.",[65,659,262],{"class":102},[65,661,265],{"class":71},[65,663,657],{"class":200},[65,665,262],{"class":102},[65,667,429],{"class":71},[14,669,670],{},[43,671],{"alt":672,"src":673},"Step: moving a mesh. A Thingi10K screw moved away from a Thingi10K part by its transformation; the faint copy is where it started","\u002Fimg\u002Ftutorials\u002Fsteps\u002Ffast-mesh-collision-detection-1-moving.png",[14,675,676,677,680,681,684,685,688],{},"For a rotation, compose it with the translation. ",[32,678,679],{},"tf::transformed(R, T)"," applies ",[32,682,683],{},"R"," first, then ",[32,686,687],{},"T",":",[56,690,692],{"className":130,"code":691,"language":132,"meta":61,"style":61},"auto R = tf::make_rotation(tf::deg(30.f), tf::axis\u003C2>);\nauto posed = part_form | tf::tag(tf::transformed(R, at(3.f, 0.f, 0.f)));\n",[32,693,694,744],{"__ignoreMap":61},[65,695,696,698,701,703,705,707,710,712,714,716,719,721,724,726,729,731,734,736,739,742],{"class":67,"line":68},[65,697,154],{"class":102},[65,699,700],{"class":71}," R ",[65,702,160],{"class":102},[65,704,163],{"class":109},[65,706,166],{"class":71},[65,708,709],{"class":109},"make_rotation",[65,711,172],{"class":71},[65,713,378],{"class":109},[65,715,166],{"class":71},[65,717,718],{"class":109},"deg",[65,720,172],{"class":71},[65,722,723],{"class":200},"30.",[65,725,262],{"class":102},[65,727,728],{"class":71},"), ",[65,730,378],{"class":109},[65,732,733],{"class":71},"::axis",[65,735,197],{"class":102},[65,737,738],{"class":200},"2",[65,740,741],{"class":102},">",[65,743,178],{"class":71},[65,745,746,748,751,753,755,757,759,761,763,765,767,769,772,775,777,779,781,783,785,787,789,791,793,795],{"class":67,"line":75},[65,747,154],{"class":102},[65,749,750],{"class":71}," posed ",[65,752,160],{"class":102},[65,754,512],{"class":71},[65,756,495],{"class":102},[65,758,163],{"class":109},[65,760,166],{"class":71},[65,762,502],{"class":109},[65,764,172],{"class":71},[65,766,378],{"class":109},[65,768,166],{"class":71},[65,770,771],{"class":109},"transformed",[65,773,774],{"class":71},"(R, ",[65,776,645],{"class":109},[65,778,172],{"class":71},[65,780,650],{"class":200},[65,782,262],{"class":102},[65,784,265],{"class":71},[65,786,657],{"class":200},[65,788,262],{"class":102},[65,790,265],{"class":71},[65,792,657],{"class":200},[65,794,262],{"class":102},[65,796,797],{"class":71},")));\n",[48,799,801],{"id":800},"do-the-meshes-collide","Do the meshes collide?",[14,803,804,811,812,688],{},[18,805,808],{"href":806,"rel":807},"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fmodules\u002Fspatial#distance-and-intersection",[121],[32,809,810],{},"tf::intersects"," returns a ",[32,813,814],{},"bool",[56,816,818],{"className":130,"code":817,"language":132,"meta":61,"style":61},"tf::intersects(fixture_form, part_form | tf::tag(at(3.f, 0.f, 0.f)));   \u002F\u002F false\ntf::intersects(fixture_form, part_form | tf::tag(at(1.3f, 0.f, 0.f)));  \u002F\u002F true\n",[32,819,820,869],{"__ignoreMap":61},[65,821,822,824,826,829,832,834,836,838,840,842,844,846,848,850,852,854,856,858,860,862,865],{"class":67,"line":68},[65,823,378],{"class":109},[65,825,166],{"class":71},[65,827,828],{"class":109},"intersects",[65,830,831],{"class":71},"(fixture_form, part_form ",[65,833,495],{"class":102},[65,835,163],{"class":109},[65,837,166],{"class":71},[65,839,502],{"class":109},[65,841,172],{"class":71},[65,843,645],{"class":109},[65,845,172],{"class":71},[65,847,650],{"class":200},[65,849,262],{"class":102},[65,851,265],{"class":71},[65,853,657],{"class":200},[65,855,262],{"class":102},[65,857,265],{"class":71},[65,859,657],{"class":200},[65,861,262],{"class":102},[65,863,864],{"class":71},")));",[65,866,868],{"class":867},"sAwPA","   \u002F\u002F false\n",[65,870,871,873,875,877,879,881,883,885,887,889,891,893,896,898,900,902,904,906,908,910,912],{"class":67,"line":75},[65,872,378],{"class":109},[65,874,166],{"class":71},[65,876,828],{"class":109},[65,878,831],{"class":71},[65,880,495],{"class":102},[65,882,163],{"class":109},[65,884,166],{"class":71},[65,886,502],{"class":109},[65,888,172],{"class":71},[65,890,645],{"class":109},[65,892,172],{"class":71},[65,894,895],{"class":200},"1.3",[65,897,262],{"class":102},[65,899,265],{"class":71},[65,901,657],{"class":200},[65,903,262],{"class":102},[65,905,265],{"class":71},[65,907,657],{"class":200},[65,909,262],{"class":102},[65,911,864],{"class":71},[65,913,914],{"class":867},"  \u002F\u002F true\n",[14,916,917],{},[43,918],{"alt":919,"src":920},"Step: do the meshes collide? The screw in two poses: grey, clear of the part (False), and pink, pushed into it (True)","\u002Fimg\u002Ftutorials\u002Fsteps\u002Ffast-mesh-collision-detection-2-collide.png",[14,922,923,924,927,928,931,932,27],{},"Touching counts. With the sphere at ",[32,925,926],{},"x = 1.5",", its surface just meets the box face at ",[32,929,930],{},"x = 1",", and the answer is ",[32,933,934],{},"true",[14,936,937,938,941,942,945,946,949,950,955],{},"The test is on surfaces. A sphere entirely inside the box does not cross any box face, so ",[32,939,940],{},"tf::intersects(fixture_form, part_form)"," returns ",[32,943,944],{},"false",". To catch containment, test one vertex of the moving mesh against the closed mesh with ",[32,947,948],{},"tf::winding_number"," (see the ",[18,951,954],{"href":952,"rel":953},"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fmodules\u002Fspatial#winding-numbers",[121],"Spatial docs",").",[48,957,959],{"id":958},"how-far-apart-are-they","How far apart are they?",[14,961,962,963,966],{},"For clearance, use ",[32,964,965],{},"tf::distance",". It returns 0 when the meshes touch.",[56,968,970],{"className":130,"code":969,"language":132,"meta":61,"style":61},"auto near = part_form | tf::tag(at(1.55f, 0.f, 0.f));\n\nfloat gap = tf::distance(fixture_form, near);     \u002F\u002F 0.05\nfloat gap2 = tf::distance2(fixture_form, near);   \u002F\u002F 0.0025, squared\n",[32,971,972,1016,1020,1042],{"__ignoreMap":61},[65,973,974,976,979,981,983,985,987,989,991,993,995,997,1000,1002,1004,1006,1008,1010,1012,1014],{"class":67,"line":68},[65,975,154],{"class":102},[65,977,978],{"class":71}," near ",[65,980,160],{"class":102},[65,982,512],{"class":71},[65,984,495],{"class":102},[65,986,163],{"class":109},[65,988,166],{"class":71},[65,990,502],{"class":109},[65,992,172],{"class":71},[65,994,645],{"class":109},[65,996,172],{"class":71},[65,998,999],{"class":200},"1.55",[65,1001,262],{"class":102},[65,1003,265],{"class":71},[65,1005,657],{"class":200},[65,1007,262],{"class":102},[65,1009,265],{"class":71},[65,1011,657],{"class":200},[65,1013,262],{"class":102},[65,1015,429],{"class":71},[65,1017,1018],{"class":67,"line":75},[65,1019,149],{"emptyLinePlaceholder":148},[65,1021,1022,1024,1027,1029,1031,1033,1036,1039],{"class":67,"line":81},[65,1023,393],{"class":102},[65,1025,1026],{"class":71}," gap ",[65,1028,160],{"class":102},[65,1030,163],{"class":109},[65,1032,166],{"class":71},[65,1034,1035],{"class":109},"distance",[65,1037,1038],{"class":71},"(fixture_form, near);",[65,1040,1041],{"class":867},"     \u002F\u002F 0.05\n",[65,1043,1044,1046,1049,1051,1053,1055,1058,1060],{"class":67,"line":87},[65,1045,393],{"class":102},[65,1047,1048],{"class":71}," gap2 ",[65,1050,160],{"class":102},[65,1052,163],{"class":109},[65,1054,166],{"class":71},[65,1056,1057],{"class":109},"distance2",[65,1059,1038],{"class":71},[65,1061,1062],{"class":867},"   \u002F\u002F 0.0025, squared\n",[14,1064,1065],{},[43,1066],{"alt":1067,"src":1068},"Step: how far apart are they? The screw just short of the part, with the closest pair of points joined in pink","\u002Fimg\u002Ftutorials\u002Fsteps\u002Ffast-mesh-collision-detection-3-distance.png",[14,1070,1071,1072,1079],{},"To get the closest pair itself, call ",[18,1073,1076],{"href":1074,"rel":1075},"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fmodules\u002Fspatial#neighbor-search",[121],[32,1077,1078],{},"tf::neighbor_search"," with two forms:",[56,1081,1083],{"className":130,"code":1082,"language":132,"meta":61,"style":61},"auto [ids, metric_point_pair] = tf::neighbor_search(fixture_form, near);\nauto [face0, face1] = ids;                    \u002F\u002F closest face on each mesh\nauto [dist2, pt0, pt1] = metric_point_pair;   \u002F\u002F squared distance, world points\n",[32,1084,1085,1104,1119],{"__ignoreMap":61},[65,1086,1087,1089,1092,1094,1096,1098,1101],{"class":67,"line":68},[65,1088,154],{"class":102},[65,1090,1091],{"class":71}," [ids, metric_point_pair] ",[65,1093,160],{"class":102},[65,1095,163],{"class":109},[65,1097,166],{"class":71},[65,1099,1100],{"class":109},"neighbor_search",[65,1102,1103],{"class":71},"(fixture_form, near);\n",[65,1105,1106,1108,1111,1113,1116],{"class":67,"line":75},[65,1107,154],{"class":102},[65,1109,1110],{"class":71}," [face0, face1] ",[65,1112,160],{"class":102},[65,1114,1115],{"class":71}," ids;",[65,1117,1118],{"class":867},"                    \u002F\u002F closest face on each mesh\n",[65,1120,1121,1123,1126,1128,1131],{"class":67,"line":81},[65,1122,154],{"class":102},[65,1124,1125],{"class":71}," [dist2, pt0, pt1] ",[65,1127,160],{"class":102},[65,1129,1130],{"class":71}," metric_point_pair;",[65,1132,1133],{"class":867},"   \u002F\u002F squared distance, world points\n",[14,1135,1136,1137,1139,1140,1142],{},"Pass a radius to only look that far. The result converts to ",[32,1138,814],{}," and is ",[32,1141,944],{}," when nothing is in range. A tight radius also prunes the search:",[56,1144,1146],{"className":130,"code":1145,"language":132,"meta":61,"style":61},"if (auto within = tf::neighbor_search(fixture_form, near, 0.1f)) {\n  auto [ids, mpp] = within;\n}\n",[32,1147,1148,1180,1193],{"__ignoreMap":61},[65,1149,1150,1153,1156,1158,1161,1163,1165,1167,1169,1172,1175,1177],{"class":67,"line":68},[65,1151,1152],{"class":102},"if",[65,1154,1155],{"class":71}," (",[65,1157,154],{"class":102},[65,1159,1160],{"class":71}," within ",[65,1162,160],{"class":102},[65,1164,163],{"class":109},[65,1166,166],{"class":71},[65,1168,1100],{"class":109},[65,1170,1171],{"class":71},"(fixture_form, near, ",[65,1173,1174],{"class":200},"0.1",[65,1176,262],{"class":102},[65,1178,1179],{"class":71},")) {\n",[65,1181,1182,1185,1188,1190],{"class":67,"line":75},[65,1183,1184],{"class":102},"  auto",[65,1186,1187],{"class":71}," [ids, mpp] ",[65,1189,160],{"class":102},[65,1191,1192],{"class":71}," within;\n",[65,1194,1195],{"class":67,"line":81},[65,1196,1197],{"class":71},"}\n",[14,1199,1200],{},"A mesh against a primitive works the same way. A common case is a capsule: a segment with a radius, as a stand-in for a tool or a robot link.",[56,1202,1204],{"className":130,"code":1203,"language":132,"meta":61,"style":61},"auto axis = tf::make_segment_between_points(tf::make_point(1.3f, -0.5f, 0.f),\n                                            tf::make_point(1.3f, 0.5f, 0.f));\nfloat radius = 0.2f;\n\nauto [face_id, metric_point] = tf::neighbor_search(fixture_form, axis);\nfloat clearance = std::sqrt(metric_point.metric) - radius;   \u002F\u002F 0.1\n\n\u002F\u002F closest points between the axis and that face\nauto [d2, on_axis, on_face] =\n    tf::closest_metric_point_pair(axis, fixture[face_id]);\n",[32,1205,1206,1255,1284,1301,1305,1323,1351,1356,1362,1373],{"__ignoreMap":61},[65,1207,1208,1210,1213,1215,1217,1219,1222,1224,1226,1228,1231,1233,1235,1237,1239,1242,1244,1246,1248,1250,1252],{"class":67,"line":68},[65,1209,154],{"class":102},[65,1211,1212],{"class":71}," axis ",[65,1214,160],{"class":102},[65,1216,163],{"class":109},[65,1218,166],{"class":71},[65,1220,1221],{"class":109},"make_segment_between_points",[65,1223,172],{"class":71},[65,1225,378],{"class":109},[65,1227,166],{"class":71},[65,1229,1230],{"class":109},"make_point",[65,1232,172],{"class":71},[65,1234,895],{"class":200},[65,1236,262],{"class":102},[65,1238,265],{"class":71},[65,1240,1241],{"class":102},"-",[65,1243,311],{"class":200},[65,1245,262],{"class":102},[65,1247,265],{"class":71},[65,1249,657],{"class":200},[65,1251,262],{"class":102},[65,1253,1254],{"class":71},"),\n",[65,1256,1257,1260,1262,1264,1266,1268,1270,1272,1274,1276,1278,1280,1282],{"class":67,"line":75},[65,1258,1259],{"class":109},"                                            tf",[65,1261,166],{"class":71},[65,1263,1230],{"class":109},[65,1265,172],{"class":71},[65,1267,895],{"class":200},[65,1269,262],{"class":102},[65,1271,265],{"class":71},[65,1273,311],{"class":200},[65,1275,262],{"class":102},[65,1277,265],{"class":71},[65,1279,657],{"class":200},[65,1281,262],{"class":102},[65,1283,429],{"class":71},[65,1285,1286,1288,1291,1293,1296,1298],{"class":67,"line":81},[65,1287,393],{"class":102},[65,1289,1290],{"class":71}," radius ",[65,1292,160],{"class":102},[65,1294,1295],{"class":200}," 0.2",[65,1297,262],{"class":102},[65,1299,1300],{"class":71},";\n",[65,1302,1303],{"class":67,"line":87},[65,1304,149],{"emptyLinePlaceholder":148},[65,1306,1307,1309,1312,1314,1316,1318,1320],{"class":67,"line":93},[65,1308,154],{"class":102},[65,1310,1311],{"class":71}," [face_id, metric_point] ",[65,1313,160],{"class":102},[65,1315,163],{"class":109},[65,1317,166],{"class":71},[65,1319,1100],{"class":109},[65,1321,1322],{"class":71},"(fixture_form, axis);\n",[65,1324,1325,1327,1330,1332,1335,1337,1340,1343,1345,1348],{"class":67,"line":99},[65,1326,393],{"class":102},[65,1328,1329],{"class":71}," clearance ",[65,1331,160],{"class":102},[65,1333,1334],{"class":109}," std",[65,1336,166],{"class":71},[65,1338,1339],{"class":109},"sqrt",[65,1341,1342],{"class":71},"(metric_point.metric) ",[65,1344,1241],{"class":102},[65,1346,1347],{"class":71}," radius;",[65,1349,1350],{"class":867},"   \u002F\u002F 0.1\n",[65,1352,1354],{"class":67,"line":1353},7,[65,1355,149],{"emptyLinePlaceholder":148},[65,1357,1359],{"class":67,"line":1358},8,[65,1360,1361],{"class":867},"\u002F\u002F closest points between the axis and that face\n",[65,1363,1365,1367,1370],{"class":67,"line":1364},9,[65,1366,154],{"class":102},[65,1368,1369],{"class":71}," [d2, on_axis, on_face] ",[65,1371,1372],{"class":102},"=\n",[65,1374,1376,1379,1381,1384],{"class":67,"line":1375},10,[65,1377,1378],{"class":109},"    tf",[65,1380,166],{"class":71},[65,1382,1383],{"class":109},"closest_metric_point_pair",[65,1385,1386],{"class":71},"(axis, fixture[face_id]);\n",[48,1388,1390],{"id":1389},"which-faces-touch","Which faces touch?",[14,1392,1393,1396],{},[32,1394,1395],{},"tf::gather_ids"," with two forms collects every pair of intersecting faces:",[56,1398,1400],{"className":130,"code":1399,"language":132,"meta":61,"style":61},"auto touching = part_form | tf::tag(at(1.3f, 0.f, 0.f));\n\nstd::vector\u003Cstd::pair\u003Cint, int>> pairs;\ntf::gather_ids(fixture_form, touching, tf::intersects_f,\n               std::back_inserter(pairs));\n\u002F\u002F pairs.size() == 120\n",[32,1401,1402,1445,1449,1475,1492,1505],{"__ignoreMap":61},[65,1403,1404,1406,1409,1411,1413,1415,1417,1419,1421,1423,1425,1427,1429,1431,1433,1435,1437,1439,1441,1443],{"class":67,"line":68},[65,1405,154],{"class":102},[65,1407,1408],{"class":71}," touching ",[65,1410,160],{"class":102},[65,1412,512],{"class":71},[65,1414,495],{"class":102},[65,1416,163],{"class":109},[65,1418,166],{"class":71},[65,1420,502],{"class":109},[65,1422,172],{"class":71},[65,1424,645],{"class":109},[65,1426,172],{"class":71},[65,1428,895],{"class":200},[65,1430,262],{"class":102},[65,1432,265],{"class":71},[65,1434,657],{"class":200},[65,1436,262],{"class":102},[65,1438,265],{"class":71},[65,1440,657],{"class":200},[65,1442,262],{"class":102},[65,1444,429],{"class":71},[65,1446,1447],{"class":67,"line":75},[65,1448,149],{"emptyLinePlaceholder":148},[65,1450,1451,1454,1457,1459,1461,1464,1467,1469,1472],{"class":67,"line":81},[65,1452,1453],{"class":109},"std",[65,1455,1456],{"class":71},"::vector",[65,1458,197],{"class":102},[65,1460,1453],{"class":109},[65,1462,1463],{"class":71},"::pair",[65,1465,1466],{"class":102},"\u003Cint",[65,1468,265],{"class":71},[65,1470,1471],{"class":102},"int>>",[65,1473,1474],{"class":71}," pairs;\n",[65,1476,1477,1479,1481,1484,1487,1489],{"class":67,"line":87},[65,1478,378],{"class":109},[65,1480,166],{"class":71},[65,1482,1483],{"class":109},"gather_ids",[65,1485,1486],{"class":71},"(fixture_form, touching, ",[65,1488,378],{"class":109},[65,1490,1491],{"class":71},"::intersects_f,\n",[65,1493,1494,1497,1499,1502],{"class":67,"line":93},[65,1495,1496],{"class":109},"               std",[65,1498,166],{"class":71},[65,1500,1501],{"class":109},"back_inserter",[65,1503,1504],{"class":71},"(pairs));\n",[65,1506,1507],{"class":67,"line":99},[65,1508,1509],{"class":867},"\u002F\u002F pairs.size() == 120\n",[14,1511,1512],{},[43,1513],{"alt":1514,"src":1515},"Step: which faces touch? The screw pushed into the part, with the intersecting faces colored pink on both meshes","\u002Fimg\u002Ftutorials\u002Fsteps\u002Ffast-mesh-collision-detection-4-touching-faces.png",[14,1517,1518],{},"The first id indexes a face of the first mesh, the second a face of the second. Use them to color the contact, to transfer attributes, or to restrict a finer check.",[14,1520,1521,1522,1524],{},"For near misses, give ",[32,1523,1395],{}," your own test. The first one prunes tree nodes, the second checks faces:",[56,1526,1528],{"className":130,"code":1527,"language":132,"meta":61,"style":61},"float d = 0.1f;\nauto within_d = [d2 = d * d](const auto &a, const auto &b) {\n  return tf::distance2(a, b) \u003C= d2;\n};\n\nstd::vector\u003Cstd::pair\u003Cint, int>> close;\ntf::gather_ids(fixture_form, near, within_d, within_d,\n               std::back_inserter(close));\n\u002F\u002F close.size() == 430\n",[32,1529,1530,1546,1599,1618,1622,1626,1647,1658,1669],{"__ignoreMap":61},[65,1531,1532,1534,1537,1539,1542,1544],{"class":67,"line":68},[65,1533,393],{"class":102},[65,1535,1536],{"class":71}," d ",[65,1538,160],{"class":102},[65,1540,1541],{"class":200}," 0.1",[65,1543,262],{"class":102},[65,1545,1300],{"class":71},[65,1547,1548,1550,1553,1555,1558,1561,1564,1566,1569,1572,1575,1578,1581,1584,1586,1588,1590,1592,1594,1597],{"class":67,"line":75},[65,1549,154],{"class":102},[65,1551,1552],{"class":71}," within_d ",[65,1554,160],{"class":102},[65,1556,1557],{"class":71}," [",[65,1559,1560],{"class":570},"d2",[65,1562,1563],{"class":102}," =",[65,1565,1536],{"class":71},[65,1567,1568],{"class":102},"*",[65,1570,1571],{"class":570}," d",[65,1573,1574],{"class":71},"](",[65,1576,1577],{"class":102},"const",[65,1579,1580],{"class":102}," auto",[65,1582,1583],{"class":102}," &",[65,1585,18],{"class":570},[65,1587,265],{"class":71},[65,1589,1577],{"class":102},[65,1591,1580],{"class":102},[65,1593,1583],{"class":102},[65,1595,1596],{"class":570},"b",[65,1598,588],{"class":71},[65,1600,1601,1603,1605,1607,1609,1612,1615],{"class":67,"line":81},[65,1602,593],{"class":102},[65,1604,163],{"class":109},[65,1606,166],{"class":71},[65,1608,1057],{"class":109},[65,1610,1611],{"class":71},"(a, b) ",[65,1613,1614],{"class":102},"\u003C=",[65,1616,1617],{"class":71}," d2;\n",[65,1619,1620],{"class":67,"line":87},[65,1621,617],{"class":71},[65,1623,1624],{"class":67,"line":93},[65,1625,149],{"emptyLinePlaceholder":148},[65,1627,1628,1630,1632,1634,1636,1638,1640,1642,1644],{"class":67,"line":99},[65,1629,1453],{"class":109},[65,1631,1456],{"class":71},[65,1633,197],{"class":102},[65,1635,1453],{"class":109},[65,1637,1463],{"class":71},[65,1639,1466],{"class":102},[65,1641,265],{"class":71},[65,1643,1471],{"class":102},[65,1645,1646],{"class":71}," close;\n",[65,1648,1649,1651,1653,1655],{"class":67,"line":1353},[65,1650,378],{"class":109},[65,1652,166],{"class":71},[65,1654,1483],{"class":109},[65,1656,1657],{"class":71},"(fixture_form, near, within_d, within_d,\n",[65,1659,1660,1662,1664,1666],{"class":67,"line":1358},[65,1661,1496],{"class":109},[65,1663,166],{"class":71},[65,1665,1501],{"class":109},[65,1667,1668],{"class":71},"(close));\n",[65,1670,1671],{"class":67,"line":1364},[65,1672,1673],{"class":867},"\u002F\u002F close.size() == 430\n",[48,1675,1677],{"id":1676},"working-with-results","Working with results",[14,1679,1680],{},"In a motion loop, the trees are built once and only the transformation changes:",[56,1682,1684],{"className":130,"code":1683,"language":132,"meta":61,"style":61},"for (int i = 0; i \u003C= 6; ++i) {\n  auto step = part_form | tf::tag(at(3.f - 0.5f * i, 0.f, 0.f));\n  bool hit = tf::intersects(fixture_form, step);\n  float gap = tf::distance(fixture_form, step);\n}\n",[32,1685,1686,1720,1775,1794,1811],{"__ignoreMap":61},[65,1687,1688,1691,1693,1695,1698,1700,1703,1706,1708,1711,1714,1717],{"class":67,"line":68},[65,1689,1690],{"class":102},"for",[65,1692,1155],{"class":71},[65,1694,388],{"class":102},[65,1696,1697],{"class":71}," i ",[65,1699,160],{"class":102},[65,1701,1702],{"class":200}," 0",[65,1704,1705],{"class":71},"; i ",[65,1707,1614],{"class":102},[65,1709,1710],{"class":200}," 6",[65,1712,1713],{"class":71},"; ",[65,1715,1716],{"class":102},"++",[65,1718,1719],{"class":71},"i) {\n",[65,1721,1722,1724,1727,1729,1731,1733,1735,1737,1739,1741,1743,1745,1747,1749,1752,1755,1757,1760,1763,1765,1767,1769,1771,1773],{"class":67,"line":75},[65,1723,1184],{"class":102},[65,1725,1726],{"class":71}," step ",[65,1728,160],{"class":102},[65,1730,512],{"class":71},[65,1732,495],{"class":102},[65,1734,163],{"class":109},[65,1736,166],{"class":71},[65,1738,502],{"class":109},[65,1740,172],{"class":71},[65,1742,645],{"class":109},[65,1744,172],{"class":71},[65,1746,650],{"class":200},[65,1748,262],{"class":102},[65,1750,1751],{"class":102}," -",[65,1753,1754],{"class":200}," 0.5",[65,1756,262],{"class":102},[65,1758,1759],{"class":102}," *",[65,1761,1762],{"class":71}," i, ",[65,1764,657],{"class":200},[65,1766,262],{"class":102},[65,1768,265],{"class":71},[65,1770,657],{"class":200},[65,1772,262],{"class":102},[65,1774,429],{"class":71},[65,1776,1777,1780,1783,1785,1787,1789,1791],{"class":67,"line":81},[65,1778,1779],{"class":102},"  bool",[65,1781,1782],{"class":71}," hit ",[65,1784,160],{"class":102},[65,1786,163],{"class":109},[65,1788,166],{"class":71},[65,1790,828],{"class":109},[65,1792,1793],{"class":71},"(fixture_form, step);\n",[65,1795,1796,1799,1801,1803,1805,1807,1809],{"class":67,"line":87},[65,1797,1798],{"class":102},"  float",[65,1800,1026],{"class":71},[65,1802,160],{"class":102},[65,1804,163],{"class":109},[65,1806,166],{"class":71},[65,1808,1035],{"class":109},[65,1810,1793],{"class":71},[65,1812,1813],{"class":67,"line":93},[65,1814,1197],{"class":71},[14,1816,1817],{},"To test many copies of one part in different poses, tag one transformation per copy onto the same form. They share the data and the tree.",[14,1819,1820,1821,688],{},"When you need the contact as geometry, not just face pairs, compute the ",[18,1822,1824],{"href":1823},"\u002Ftutorials\u002Fmesh-intersection-curves-in-cpp","intersection curves",[56,1826,1828],{"className":130,"code":1827,"language":132,"meta":61,"style":61},"auto curves = tf::make_intersection_curves(\n    fixture, part | tf::tag(at(1.3f, 0.f, 0.f)));\n",[32,1829,1830,1849],{"__ignoreMap":61},[65,1831,1832,1834,1837,1839,1841,1843,1846],{"class":67,"line":68},[65,1833,154],{"class":102},[65,1835,1836],{"class":71}," curves ",[65,1838,160],{"class":102},[65,1840,163],{"class":109},[65,1842,166],{"class":71},[65,1844,1845],{"class":109},"make_intersection_curves",[65,1847,1848],{"class":71},"(\n",[65,1850,1851,1854,1856,1858,1860,1862,1864,1866,1868,1870,1872,1874,1876,1878,1880,1882,1884],{"class":67,"line":75},[65,1852,1853],{"class":71},"    fixture, part ",[65,1855,495],{"class":102},[65,1857,163],{"class":109},[65,1859,166],{"class":71},[65,1861,502],{"class":109},[65,1863,172],{"class":71},[65,1865,645],{"class":109},[65,1867,172],{"class":71},[65,1869,895],{"class":200},[65,1871,262],{"class":102},[65,1873,265],{"class":71},[65,1875,657],{"class":200},[65,1877,262],{"class":102},[65,1879,265],{"class":71},[65,1881,657],{"class":200},[65,1883,262],{"class":102},[65,1885,797],{"class":71},[48,1887,1889],{"id":1888},"related-tutorials","Related tutorials",[1891,1892,1893,1901,1907,1914],"ul",{},[1894,1895,1896,1900],"li",{},[18,1897,1899],{"href":1898},"\u002Ftutorials\u002Ffast-closest-point-on-a-mesh-in-cpp","Fast and Robust Closest Point on a Mesh in C++"," — nearest surface point for a batch of queries.",[1894,1902,1903,1906],{},[18,1904,1905],{"href":1823},"Fast and Exact Mesh Intersection Curves in C++"," — exact curves where two or more meshes cross.",[1894,1908,1909,1913],{},[18,1910,1912],{"href":1911},"\u002Ftutorials\u002Ffast-mesh-booleans-in-cpp","Fast Mesh Booleans in C++"," — exact union, intersection and difference of two meshes.",[1894,1915,1916,1920],{},[18,1917,1919],{"href":1918},"\u002Ftutorials\u002Ffast-variadic-mesh-booleans-in-cpp","Fast Variadic Mesh Booleans in C++"," — one arrangement, many boolean expressions over N meshes.",[48,1922,1924],{"id":1923},"performance","Performance",[1926,1927],"headline-numbers",{":items":1928},"[{\"value\":\"\u003C 1 ms\",\"label\":\"Mesh–mesh closest point\",\"detail\":\"1,025,766 polygons, vs 370 ms FCL\"},{\"value\":\"24.9ms\",\"label\":\"Spatial tree build\",\"detail\":\"1,025,766 polygons, vs 76.8 ms MeshLib\"},{\"value\":\"3.5ms\",\"label\":\"Intersection curves\",\"detail\":\"2×500K polygons\"}]",[14,1930,1931,1936,1937],{},[65,1932,1935],{"className":1933},[1934],"tag-pill","Apple M4 Max"," ",[65,1938,1940],{"className":1939},[1934],"16 threads",[14,1942,1943],{},"The closest pair between two meshes of 1,025,766 polygons takes under 1 ms, against 370 ms for FCL. Building the spatial tree is the up-front cost of every query on this page:",[1945,1946],"data-table",{":headers":1947,":highlight":1948,":rows":1949},"[\"Library\",\"Tree build, 1,025,766 polygons\",\"vs trueform\"]","0","[[\"trueform\",\"24.9 ms\",\"1.0×\"],[\"MeshLib\",\"76.8 ms\",\"3.1×\"],[\"CGAL\",\"248.5 ms\",\"10.0×\"]]",[14,1951,1952],{},"A tagged transformation does not rebuild the tree, so a moving mesh pays this cost once.",[14,1954,1955],{},[43,1956],{"alt":1957,"src":1958},"Benchmark chart: trueform against other C++ libraries, tree build","\u002Fimg\u002Falgorithms\u002Fbenchmark-tree-build-cpp-2026.png",[14,1960,1961,1965,1966],{},[18,1962,1964],{"href":1963},"\u002Ftrueform\u002Fbenchmarks","Full benchmarks"," · ",[18,1967,1970],{"href":1968,"rel":1969},"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fmodules\u002Fspatial",[121],"Documentation",[1972,1973],"cite-as",{"author":1974,"title":5},"Sajovic, {\\v{Z}}iga",[1976,1977,1978],"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 .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}",{"title":61,"searchDepth":75,"depth":75,"links":1980},[1981,1982,1983,1984,1985,1986,1987,1988,1989,1990],{"id":50,"depth":75,"text":51},{"id":126,"depth":75,"text":127},{"id":364,"depth":75,"text":365},{"id":544,"depth":75,"text":545},{"id":800,"depth":75,"text":801},{"id":958,"depth":75,"text":959},{"id":1389,"depth":75,"text":1390},{"id":1676,"depth":75,"text":1677},{"id":1888,"depth":75,"text":1889},{"id":1923,"depth":75,"text":1924},"2026-10-31","Test whether two triangle meshes collide, measure the clearance between them, and find which faces touch, in C++. Move meshes by tagging a transformation, no rebuilds. Header-only, C++17.","md",[1995,1996,1997,1998,1999,2000,2001,2002,2003,2004,2005],"mesh collision detection C++","collision detection C++","triangle mesh collision C++","mesh mesh distance C++","clearance check mesh C++","BVH collision C++","FCL alternative","Coal alternative","CGAL do_intersect alternative","MeshLib collision alternative","collision detection library C++",{},"\u002Fimg\u002Fog\u002Ftutorials\u002Ffast-mesh-collision-detection-in-cpp.png","\u002Ftutorials\u002Ffast-mesh-collision-detection-in-cpp",{"title":5,"description":1992},"tutorials\u002Ffast-mesh-collision-detection-in-cpp",[2012,2013,2014],"c++","collision","tutorial","zimdcbMtq2I5CTt4-DrOFGfYSXtUb2A8JvwypmaJPZY",1793517812008]