[{"data":1,"prerenderedAt":1959},["ShallowReactive",2],{"tutorial-fast-geodesic-distances-in-cpp":3},{"id":4,"title":5,"author":6,"body":7,"date":1935,"description":1936,"extension":1937,"keywords":1938,"meta":1949,"navigation":152,"ogImage":1950,"path":1951,"published":152,"seo":1952,"stem":1953,"tags":1954,"__hash__":1958},"tutorials\u002Ftutorials\u002Ffast-geodesic-distances-in-cpp.md","Fast and Accurate Geodesic Distances in C++","Žiga Sajovic",{"type":8,"value":9,"toc":1922},"minimark",[10,28,40,44,51,56,59,118,128,132,201,223,226,284,288,291,341,347,350,353,533,536,539,718,722,725,728,731,1088,1094,1097,1100,1182,1186,1189,1298,1304,1311,1315,1318,1351,1354,1358,1361,1510,1514,1521,1529,1646,1652,1655,1778,1785,1797,1801,1807,1811,1841,1845,1857,1861,1877,1882,1885,1889,1896,1899,1902,1914,1918],[11,12,13],"note",{},[14,15,16,17,22,23,27],"p",{},"Also available in ",[18,19,21],"a",{"href":20},"\u002Ftutorials\u002Ffast-geodesic-distances-in-python","Python"," and ",[18,24,26],{"href":25},"\u002Ftutorials\u002Ffast-geodesic-distances-in-javascript","JavaScript",".",[14,29,30,31,35,36,39],{},"A geodesic distance is the distance from a point to a seed, measured along the surface of a mesh. Straight-line distance cuts through the object. Geodesic distance walks over it. ",[32,33,34],"code",{},"trueform"," computes it with one call, ",[32,37,38],{},"tf::make_geodesic_distances",", new in version 0.10.8. It is header-only C++17. This tutorial covers one seed, many seeds, a bounded radius, float and double output, and turning distances into rings and patches.",[41,42],"article-cta",{":buttons":43},"[{\"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\u002Fgeometry#geodesic-distances\",\"variant\":\"soft\",\"color\":\"neutral\"}]",[14,45,46],{},[47,48],"img",{"alt":49,"src":50},"Geodesic distance from one seed on the Stanford dragon, teal to orange, with iso-distance lines","\u002Fimg\u002Ftutorials\u002Fgeodesic-distances.png",[52,53,55],"h2",{"id":54},"install","Install",[14,57,58],{},"Header-only. It needs C++17 and oneTBB. Add it to your project via CMake FetchContent:",[60,61,66],"pre",{"className":62,"code":63,"language":64,"meta":65,"style":65},"language-cmake shiki shiki-themes github-dark","FetchContent_Declare(\n  trueform\n  GIT_REPOSITORY https:\u002F\u002Fgithub.com\u002Fpolydera\u002Ftrueform\n)\nFetchContent_MakeAvailable(trueform)\ntarget_link_libraries(my_app PRIVATE tf::trueform)\n","cmake","",[32,67,68,77,83,89,95,101],{"__ignoreMap":65},[69,70,73],"span",{"class":71,"line":72},"line",1,[69,74,76],{"class":75},"s95oV","FetchContent_Declare(\n",[69,78,80],{"class":71,"line":79},2,[69,81,82],{"class":75},"  trueform\n",[69,84,86],{"class":71,"line":85},3,[69,87,88],{"class":75},"  GIT_REPOSITORY https:\u002F\u002Fgithub.com\u002Fpolydera\u002Ftrueform\n",[69,90,92],{"class":71,"line":91},4,[69,93,94],{"class":75},")\n",[69,96,98],{"class":71,"line":97},5,[69,99,100],{"class":75},"FetchContent_MakeAvailable(trueform)\n",[69,102,104,108,111,115],{"class":71,"line":103},6,[69,105,107],{"class":106},"snl16","target_link_libraries",[69,109,110],{"class":75},"(my_app ",[69,112,114],{"class":113},"svObZ","PRIVATE",[69,116,117],{"class":75}," tf::trueform)\n",[14,119,120,121,127],{},"Also available via conan, nuget, and pip. You need version 0.10.8 or later. See the ",[18,122,126],{"href":123,"rel":124},"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fgetting-started\u002Finstallation",[125],"nofollow","installation guide"," for all options.",[52,129,131],{"id":130},"loading-the-mesh","Loading the mesh",[60,133,137],{"className":134,"code":135,"language":136,"meta":65,"style":65},"language-cpp shiki shiki-themes github-dark","#include \u003Ctrueform\u002Ftrueform.hpp>\n\nauto mesh_buffer = tf::read_stl(\"part.stl\");\nauto mesh = mesh_buffer.polygons();\n","cpp",[32,138,139,148,154,183],{"__ignoreMap":65},[69,140,141,144],{"class":71,"line":72},[69,142,143],{"class":106},"#include",[69,145,147],{"class":146},"sU2Wk"," \u003Ctrueform\u002Ftrueform.hpp>\n",[69,149,150],{"class":71,"line":79},[69,151,153],{"emptyLinePlaceholder":152},true,"\n",[69,155,156,159,162,165,168,171,174,177,180],{"class":71,"line":85},[69,157,158],{"class":106},"auto",[69,160,161],{"class":75}," mesh_buffer ",[69,163,164],{"class":106},"=",[69,166,167],{"class":113}," tf",[69,169,170],{"class":75},"::",[69,172,173],{"class":113},"read_stl",[69,175,176],{"class":75},"(",[69,178,179],{"class":146},"\"part.stl\"",[69,181,182],{"class":75},");\n",[69,184,185,187,190,192,195,198],{"class":71,"line":91},[69,186,158],{"class":106},[69,188,189],{"class":75}," mesh ",[69,191,164],{"class":106},[69,193,194],{"class":75}," mesh_buffer.",[69,196,197],{"class":113},"polygons",[69,199,200],{"class":75},"();\n",[14,202,203,206,207,210,211,214,215,218,219,222],{},[32,204,205],{},"tf::read_stl"," returns a ",[32,208,209],{},"tf::polygons_buffer"," that owns the data. ",[32,212,213],{},".polygons()"," is a view on it. ",[32,216,217],{},"tf::read_obj"," works the same way. If your faces and points already live in your own arrays, wrap them with ",[32,220,221],{},"tf::make_polygons(tf::make_faces\u003C3>(faces), tf::make_points\u003C3>(points))",". No copy is made.",[14,224,225],{},"To follow along without a file, make a unit sphere:",[60,227,229],{"className":134,"code":228,"language":136,"meta":65,"style":65},"auto sphere_buffer = tf::make_sphere_mesh(1.f, 200, 400);\nauto mesh = sphere_buffer.polygons();\n",[32,230,231,269],{"__ignoreMap":65},[69,232,233,235,238,240,242,244,247,249,253,256,259,262,264,267],{"class":71,"line":72},[69,234,158],{"class":106},[69,236,237],{"class":75}," sphere_buffer ",[69,239,164],{"class":106},[69,241,167],{"class":113},[69,243,170],{"class":75},[69,245,246],{"class":113},"make_sphere_mesh",[69,248,176],{"class":75},[69,250,252],{"class":251},"sDLfK","1.",[69,254,255],{"class":106},"f",[69,257,258],{"class":75},", ",[69,260,261],{"class":251},"200",[69,263,258],{"class":75},[69,265,266],{"class":251},"400",[69,268,182],{"class":75},[69,270,271,273,275,277,280,282],{"class":71,"line":79},[69,272,158],{"class":106},[69,274,189],{"class":75},[69,276,164],{"class":106},[69,278,279],{"class":75}," sphere_buffer.",[69,281,197],{"class":113},[69,283,200],{"class":75},[52,285,287],{"id":286},"distance-from-one-seed","Distance from one seed",[14,289,290],{},"A seed is a point id. The result has one distance per point.",[60,292,294],{"className":134,"code":293,"language":136,"meta":65,"style":65},"std::vector\u003Cint> seeds{0};\nauto d = tf::make_geodesic_distances(mesh, seeds);\n\u002F\u002F d: tf::buffer\u003Cfloat>, one distance per point; d[0] == 0\n",[32,295,296,316,335],{"__ignoreMap":65},[69,297,298,301,304,307,310,313],{"class":71,"line":72},[69,299,300],{"class":113},"std",[69,302,303],{"class":75},"::vector",[69,305,306],{"class":106},"\u003Cint>",[69,308,309],{"class":75}," seeds{",[69,311,312],{"class":251},"0",[69,314,315],{"class":75},"};\n",[69,317,318,320,323,325,327,329,332],{"class":71,"line":79},[69,319,158],{"class":106},[69,321,322],{"class":75}," d ",[69,324,164],{"class":106},[69,326,167],{"class":113},[69,328,170],{"class":75},[69,330,331],{"class":113},"make_geodesic_distances",[69,333,334],{"class":75},"(mesh, seeds);\n",[69,336,337],{"class":71,"line":85},[69,338,340],{"class":339},"sAwPA","\u002F\u002F d: tf::buffer\u003Cfloat>, one distance per point; d[0] == 0\n",[14,342,343],{},[47,344],{"alt":345,"src":346},"Distance from one seed, computed on the Stanford bunny: geodesic distance from point 0 at the nose, teal near to orange far, with pink rings of equal distance","\u002Fimg\u002Ftutorials\u002Fsteps\u002Ffast-geodesic-distances-1-seed.png",[14,348,349],{},"The distance is zero at the seed. On the unit sphere, the farthest point is the opposite pole, at a distance close to π.",[14,351,352],{},"You can check the result on this sphere. The exact geodesic distance on a unit sphere is the great-circle angle:",[60,354,356],{"className":134,"code":355,"language":136,"meta":65,"style":65},"auto p0 = mesh.points()[0];\nfloat max_error = 0.f;\nfor (auto [pt, dist] : tf::zip(mesh.points(), d)) {\n  float c = std::clamp(\n      tf::dot(pt.as_vector_view(), p0.as_vector_view()), -1.f, 1.f);\n  max_error = std::max(max_error, std::abs(dist - std::acos(c)));\n}\n",[32,357,358,381,399,428,449,488,527],{"__ignoreMap":65},[69,359,360,362,365,367,370,373,376,378],{"class":71,"line":72},[69,361,158],{"class":106},[69,363,364],{"class":75}," p0 ",[69,366,164],{"class":106},[69,368,369],{"class":75}," mesh.",[69,371,372],{"class":113},"points",[69,374,375],{"class":75},"()[",[69,377,312],{"class":251},[69,379,380],{"class":75},"];\n",[69,382,383,386,389,391,394,396],{"class":71,"line":79},[69,384,385],{"class":106},"float",[69,387,388],{"class":75}," max_error ",[69,390,164],{"class":106},[69,392,393],{"class":251}," 0.",[69,395,255],{"class":106},[69,397,398],{"class":75},";\n",[69,400,401,404,407,409,412,415,417,420,423,425],{"class":71,"line":85},[69,402,403],{"class":106},"for",[69,405,406],{"class":75}," (",[69,408,158],{"class":106},[69,410,411],{"class":75}," [pt, dist] : ",[69,413,414],{"class":113},"tf",[69,416,170],{"class":75},[69,418,419],{"class":113},"zip",[69,421,422],{"class":75},"(mesh.",[69,424,372],{"class":113},[69,426,427],{"class":75},"(), d)) {\n",[69,429,430,433,436,438,441,443,446],{"class":71,"line":91},[69,431,432],{"class":106},"  float",[69,434,435],{"class":75}," c ",[69,437,164],{"class":106},[69,439,440],{"class":113}," std",[69,442,170],{"class":75},[69,444,445],{"class":113},"clamp",[69,447,448],{"class":75},"(\n",[69,450,451,454,456,459,462,465,468,470,473,476,478,480,482,484,486],{"class":71,"line":97},[69,452,453],{"class":113},"      tf",[69,455,170],{"class":75},[69,457,458],{"class":113},"dot",[69,460,461],{"class":75},"(pt.",[69,463,464],{"class":113},"as_vector_view",[69,466,467],{"class":75},"(), p0.",[69,469,464],{"class":113},[69,471,472],{"class":75},"()), ",[69,474,475],{"class":106},"-",[69,477,252],{"class":251},[69,479,255],{"class":106},[69,481,258],{"class":75},[69,483,252],{"class":251},[69,485,255],{"class":106},[69,487,182],{"class":75},[69,489,490,493,495,497,499,502,505,507,509,512,515,517,519,521,524],{"class":71,"line":103},[69,491,492],{"class":75},"  max_error ",[69,494,164],{"class":106},[69,496,440],{"class":113},[69,498,170],{"class":75},[69,500,501],{"class":113},"max",[69,503,504],{"class":75},"(max_error, ",[69,506,300],{"class":113},[69,508,170],{"class":75},[69,510,511],{"class":113},"abs",[69,513,514],{"class":75},"(dist ",[69,516,475],{"class":106},[69,518,440],{"class":113},[69,520,170],{"class":75},[69,522,523],{"class":113},"acos",[69,525,526],{"class":75},"(c)));\n",[69,528,530],{"class":71,"line":529},7,[69,531,532],{"class":75},"}\n",[14,534,535],{},"The difference is small, and it shrinks as the sphere gets finer.",[14,537,538],{},"Most of the time the seed is a position, not an id. Find the nearest mesh point first, with a tree on the points:",[60,540,542],{"className":134,"code":541,"language":136,"meta":65,"style":65},"tf::aabb_tree\u003Cint, float, 3> point_tree(mesh.points(), tf::config_tree(4, 4));\nauto cloud = mesh.points() | tf::tag(point_tree);\n\nauto [seed, metric_point] =\n    tf::neighbor_search(cloud, tf::make_point(0.f, 0.f, -1.f));\nauto from_south = tf::make_geodesic_distances(mesh, std::vector\u003Cint>{seed});\n",[32,543,544,605,634,638,648,691],{"__ignoreMap":65},[69,545,546,548,550,553,556,559,561,563,565,568,571,574,576,579,581,583,586,588,590,593,595,598,600,602],{"class":71,"line":72},[69,547,414],{"class":113},[69,549,170],{"class":75},[69,551,552],{"class":113},"aabb_tree",[69,554,555],{"class":75},"\u003C",[69,557,558],{"class":106},"int",[69,560,258],{"class":75},[69,562,385],{"class":106},[69,564,258],{"class":75},[69,566,567],{"class":251},"3",[69,569,570],{"class":75},"> ",[69,572,573],{"class":113},"point_tree",[69,575,176],{"class":75},[69,577,578],{"class":113},"mesh",[69,580,27],{"class":75},[69,582,372],{"class":113},[69,584,585],{"class":75},"(), ",[69,587,414],{"class":113},[69,589,170],{"class":75},[69,591,592],{"class":113},"config_tree",[69,594,176],{"class":75},[69,596,597],{"class":251},"4",[69,599,258],{"class":75},[69,601,597],{"class":251},[69,603,604],{"class":75},"));\n",[69,606,607,609,612,614,616,618,621,624,626,628,631],{"class":71,"line":79},[69,608,158],{"class":106},[69,610,611],{"class":75}," cloud ",[69,613,164],{"class":106},[69,615,369],{"class":75},[69,617,372],{"class":113},[69,619,620],{"class":75},"() ",[69,622,623],{"class":106},"|",[69,625,167],{"class":113},[69,627,170],{"class":75},[69,629,630],{"class":113},"tag",[69,632,633],{"class":75},"(point_tree);\n",[69,635,636],{"class":71,"line":85},[69,637,153],{"emptyLinePlaceholder":152},[69,639,640,642,645],{"class":71,"line":91},[69,641,158],{"class":106},[69,643,644],{"class":75}," [seed, metric_point] ",[69,646,647],{"class":106},"=\n",[69,649,650,653,655,658,661,663,665,668,670,673,675,677,679,681,683,685,687,689],{"class":71,"line":97},[69,651,652],{"class":113},"    tf",[69,654,170],{"class":75},[69,656,657],{"class":113},"neighbor_search",[69,659,660],{"class":75},"(cloud, ",[69,662,414],{"class":113},[69,664,170],{"class":75},[69,666,667],{"class":113},"make_point",[69,669,176],{"class":75},[69,671,672],{"class":251},"0.",[69,674,255],{"class":106},[69,676,258],{"class":75},[69,678,672],{"class":251},[69,680,255],{"class":106},[69,682,258],{"class":75},[69,684,475],{"class":106},[69,686,252],{"class":251},[69,688,255],{"class":106},[69,690,604],{"class":75},[69,692,693,695,698,700,702,704,706,709,711,713,715],{"class":71,"line":103},[69,694,158],{"class":106},[69,696,697],{"class":75}," from_south ",[69,699,164],{"class":106},[69,701,167],{"class":113},[69,703,170],{"class":75},[69,705,331],{"class":113},[69,707,708],{"class":75},"(mesh, ",[69,710,300],{"class":113},[69,712,303],{"class":75},[69,714,306],{"class":106},[69,716,717],{"class":75},"{seed});\n",[52,719,721],{"id":720},"distance-from-many-seeds","Distance from many seeds",[14,723,724],{},"Pass several ids. Each point gets the distance to its nearest seed.",[14,726,727],{},"The seeds can be a whole curve. Then the field is the distance from that curve, measured along the surface. That is the distance from an edge: an offset from a trim line, a falloff from a seam.",[14,729,730],{},"Take the bunny and cut it in half. The cut is the boundary of the half. Its points are the seeds:",[60,732,734],{"className":134,"code":733,"language":136,"meta":65,"style":65},"auto bunny_buffer = tf::read_stl(\"bunny.stl\");\nauto bunny = bunny_buffer.polygons();\n\n\u002F\u002F cut it at the middle height; the bunny stands along y\ntf::buffer\u003Cfloat> height;\nheight.allocate(bunny.points().size());\ntf::parallel_transform(bunny.points(), height,\n                       [](auto pt) { return pt[1]; });\nauto [lo, hi] = std::minmax_element(height.begin(), height.end());\nstd::array\u003Cfloat, 1> middle{0.5f * (*lo + *hi)};\nstd::array\u003Cint, 1> top{1};\nauto [half_buffer, labels, face_labels] = tf::make_isobands(\n    bunny, tf::make_range(height), tf::make_range(middle),\n    tf::make_range(top));\nauto half = half_buffer.polygons();\n\n\u002F\u002F the cut is the open boundary of the half\nauto cut = tf::make_boundary_paths(half);\nauto from_cut = tf::make_geodesic_distances(half, cut.data_buffer());\n",[32,735,736,758,774,778,783,796,818,834,861,892,937,960,979,1004,1016,1033,1038,1044,1064],{"__ignoreMap":65},[69,737,738,740,743,745,747,749,751,753,756],{"class":71,"line":72},[69,739,158],{"class":106},[69,741,742],{"class":75}," bunny_buffer ",[69,744,164],{"class":106},[69,746,167],{"class":113},[69,748,170],{"class":75},[69,750,173],{"class":113},[69,752,176],{"class":75},[69,754,755],{"class":146},"\"bunny.stl\"",[69,757,182],{"class":75},[69,759,760,762,765,767,770,772],{"class":71,"line":79},[69,761,158],{"class":106},[69,763,764],{"class":75}," bunny ",[69,766,164],{"class":106},[69,768,769],{"class":75}," bunny_buffer.",[69,771,197],{"class":113},[69,773,200],{"class":75},[69,775,776],{"class":71,"line":85},[69,777,153],{"emptyLinePlaceholder":152},[69,779,780],{"class":71,"line":91},[69,781,782],{"class":339},"\u002F\u002F cut it at the middle height; the bunny stands along y\n",[69,784,785,787,790,793],{"class":71,"line":97},[69,786,414],{"class":113},[69,788,789],{"class":75},"::buffer",[69,791,792],{"class":106},"\u003Cfloat>",[69,794,795],{"class":75}," height;\n",[69,797,798,801,804,807,809,812,815],{"class":71,"line":103},[69,799,800],{"class":75},"height.",[69,802,803],{"class":113},"allocate",[69,805,806],{"class":75},"(bunny.",[69,808,372],{"class":113},[69,810,811],{"class":75},"().",[69,813,814],{"class":113},"size",[69,816,817],{"class":75},"());\n",[69,819,820,822,824,827,829,831],{"class":71,"line":529},[69,821,414],{"class":113},[69,823,170],{"class":75},[69,825,826],{"class":113},"parallel_transform",[69,828,806],{"class":75},[69,830,372],{"class":113},[69,832,833],{"class":75},"(), height,\n",[69,835,837,840,842,846,849,852,855,858],{"class":71,"line":836},8,[69,838,839],{"class":75},"                       [](",[69,841,158],{"class":106},[69,843,845],{"class":844},"s9osk"," pt",[69,847,848],{"class":75},") { ",[69,850,851],{"class":106},"return",[69,853,854],{"class":75}," pt[",[69,856,857],{"class":251},"1",[69,859,860],{"class":75},"]; });\n",[69,862,864,866,869,871,873,875,878,881,884,887,890],{"class":71,"line":863},9,[69,865,158],{"class":106},[69,867,868],{"class":75}," [lo, hi] ",[69,870,164],{"class":106},[69,872,440],{"class":113},[69,874,170],{"class":75},[69,876,877],{"class":113},"minmax_element",[69,879,880],{"class":75},"(height.",[69,882,883],{"class":113},"begin",[69,885,886],{"class":75},"(), height.",[69,888,889],{"class":113},"end",[69,891,817],{"class":75},[69,893,895,897,900,903,905,907,910,913,916,918,921,923,926,929,932,934],{"class":71,"line":894},10,[69,896,300],{"class":113},[69,898,899],{"class":75},"::array",[69,901,902],{"class":106},"\u003Cfloat",[69,904,258],{"class":75},[69,906,857],{"class":251},[69,908,909],{"class":106},">",[69,911,912],{"class":75}," middle{",[69,914,915],{"class":251},"0.5",[69,917,255],{"class":106},[69,919,920],{"class":106}," *",[69,922,406],{"class":75},[69,924,925],{"class":106},"*",[69,927,928],{"class":75},"lo ",[69,930,931],{"class":106},"+",[69,933,920],{"class":106},[69,935,936],{"class":75},"hi)};\n",[69,938,940,942,944,947,949,951,953,956,958],{"class":71,"line":939},11,[69,941,300],{"class":113},[69,943,899],{"class":75},[69,945,946],{"class":106},"\u003Cint",[69,948,258],{"class":75},[69,950,857],{"class":251},[69,952,909],{"class":106},[69,954,955],{"class":75}," top{",[69,957,857],{"class":251},[69,959,315],{"class":75},[69,961,963,965,968,970,972,974,977],{"class":71,"line":962},12,[69,964,158],{"class":106},[69,966,967],{"class":75}," [half_buffer, labels, face_labels] ",[69,969,164],{"class":106},[69,971,167],{"class":113},[69,973,170],{"class":75},[69,975,976],{"class":113},"make_isobands",[69,978,448],{"class":75},[69,980,982,985,987,989,992,995,997,999,1001],{"class":71,"line":981},13,[69,983,984],{"class":75},"    bunny, ",[69,986,414],{"class":113},[69,988,170],{"class":75},[69,990,991],{"class":113},"make_range",[69,993,994],{"class":75},"(height), ",[69,996,414],{"class":113},[69,998,170],{"class":75},[69,1000,991],{"class":113},[69,1002,1003],{"class":75},"(middle),\n",[69,1005,1007,1009,1011,1013],{"class":71,"line":1006},14,[69,1008,652],{"class":113},[69,1010,170],{"class":75},[69,1012,991],{"class":113},[69,1014,1015],{"class":75},"(top));\n",[69,1017,1019,1021,1024,1026,1029,1031],{"class":71,"line":1018},15,[69,1020,158],{"class":106},[69,1022,1023],{"class":75}," half ",[69,1025,164],{"class":106},[69,1027,1028],{"class":75}," half_buffer.",[69,1030,197],{"class":113},[69,1032,200],{"class":75},[69,1034,1036],{"class":71,"line":1035},16,[69,1037,153],{"emptyLinePlaceholder":152},[69,1039,1041],{"class":71,"line":1040},17,[69,1042,1043],{"class":339},"\u002F\u002F the cut is the open boundary of the half\n",[69,1045,1047,1049,1052,1054,1056,1058,1061],{"class":71,"line":1046},18,[69,1048,158],{"class":106},[69,1050,1051],{"class":75}," cut ",[69,1053,164],{"class":106},[69,1055,167],{"class":113},[69,1057,170],{"class":75},[69,1059,1060],{"class":113},"make_boundary_paths",[69,1062,1063],{"class":75},"(half);\n",[69,1065,1067,1069,1072,1074,1076,1078,1080,1083,1086],{"class":71,"line":1066},19,[69,1068,158],{"class":106},[69,1070,1071],{"class":75}," from_cut ",[69,1073,164],{"class":106},[69,1075,167],{"class":113},[69,1077,170],{"class":75},[69,1079,331],{"class":113},[69,1081,1082],{"class":75},"(half, cut.",[69,1084,1085],{"class":113},"data_buffer",[69,1087,817],{"class":75},[14,1089,1090],{},[47,1091],{"alt":1092,"src":1093},"Distance from many seeds on the Stanford bunny: the bunny cut in half at mid-height, with every point on the cut as a seed. The half is colored by geodesic distance from the cut, teal at the cut to orange at the ear tips, with pink rings parallel to the cut and the cut itself as a thick pink line","\u002Fimg\u002Ftutorials\u002Fsteps\u002Ffast-geodesic-distances-2-seeds.png",[14,1095,1096],{},"Keep the top band. The bunny has holes in its base, so the top half has one boundary: the cut. The ids index the half's own points, so nothing maps back. The distance is zero on the cut and grows toward the ear tips.",[14,1098,1099],{},"One call handles all the seeds, here a few hundred. You do not loop over them. Isocontours of the field are curves parallel to the cut:",[60,1101,1103],{"className":134,"code":1102,"language":136,"meta":65,"style":65},"std::array\u003Cfloat, 3> levels{1.f, 2.f, 3.f};\nauto rings = tf::make_isocontours(half, tf::make_range(from_cut),\n                                  tf::make_range(levels));\n",[32,1104,1105,1142,1170],{"__ignoreMap":65},[69,1106,1107,1109,1111,1113,1115,1117,1119,1122,1124,1126,1128,1131,1133,1135,1138,1140],{"class":71,"line":72},[69,1108,300],{"class":113},[69,1110,899],{"class":75},[69,1112,902],{"class":106},[69,1114,258],{"class":75},[69,1116,567],{"class":251},[69,1118,909],{"class":106},[69,1120,1121],{"class":75}," levels{",[69,1123,252],{"class":251},[69,1125,255],{"class":106},[69,1127,258],{"class":75},[69,1129,1130],{"class":251},"2.",[69,1132,255],{"class":106},[69,1134,258],{"class":75},[69,1136,1137],{"class":251},"3.",[69,1139,255],{"class":106},[69,1141,315],{"class":75},[69,1143,1144,1146,1149,1151,1153,1155,1158,1161,1163,1165,1167],{"class":71,"line":79},[69,1145,158],{"class":106},[69,1147,1148],{"class":75}," rings ",[69,1150,164],{"class":106},[69,1152,167],{"class":113},[69,1154,170],{"class":75},[69,1156,1157],{"class":113},"make_isocontours",[69,1159,1160],{"class":75},"(half, ",[69,1162,414],{"class":113},[69,1164,170],{"class":75},[69,1166,991],{"class":113},[69,1168,1169],{"class":75},"(from_cut),\n",[69,1171,1172,1175,1177,1179],{"class":71,"line":85},[69,1173,1174],{"class":113},"                                  tf",[69,1176,170],{"class":75},[69,1178,991],{"class":113},[69,1180,1181],{"class":75},"(levels));\n",[52,1183,1185],{"id":1184},"bounded-by-a-radius","Bounded by a radius",[14,1187,1188],{},"Often you only care about the neighborhood of a seed. Pass a radius as the third argument. The front stops there.",[60,1190,1192],{"className":134,"code":1191,"language":136,"meta":65,"style":65},"auto near = tf::make_geodesic_distances(mesh, seeds, 0.5f);\n\nstd::vector\u003Cint> region;   \u002F\u002F point ids within 0.5 of the seed\nfor (int i = 0; i \u003C (int)near.size(); ++i)\n  if (std::isfinite(near[i])) region.push_back(i);\n",[32,1193,1194,1218,1222,1236,1275],{"__ignoreMap":65},[69,1195,1196,1198,1201,1203,1205,1207,1209,1212,1214,1216],{"class":71,"line":72},[69,1197,158],{"class":106},[69,1199,1200],{"class":75}," near ",[69,1202,164],{"class":106},[69,1204,167],{"class":113},[69,1206,170],{"class":75},[69,1208,331],{"class":113},[69,1210,1211],{"class":75},"(mesh, seeds, ",[69,1213,915],{"class":251},[69,1215,255],{"class":106},[69,1217,182],{"class":75},[69,1219,1220],{"class":71,"line":79},[69,1221,153],{"emptyLinePlaceholder":152},[69,1223,1224,1226,1228,1230,1233],{"class":71,"line":85},[69,1225,300],{"class":113},[69,1227,303],{"class":75},[69,1229,306],{"class":106},[69,1231,1232],{"class":75}," region;",[69,1234,1235],{"class":339},"   \u002F\u002F point ids within 0.5 of the seed\n",[69,1237,1238,1240,1242,1244,1247,1249,1252,1255,1257,1259,1261,1264,1266,1269,1272],{"class":71,"line":91},[69,1239,403],{"class":106},[69,1241,406],{"class":75},[69,1243,558],{"class":106},[69,1245,1246],{"class":75}," i ",[69,1248,164],{"class":106},[69,1250,1251],{"class":251}," 0",[69,1253,1254],{"class":75},"; i ",[69,1256,555],{"class":106},[69,1258,406],{"class":75},[69,1260,558],{"class":106},[69,1262,1263],{"class":75},")near.",[69,1265,814],{"class":113},[69,1267,1268],{"class":75},"(); ",[69,1270,1271],{"class":106},"++",[69,1273,1274],{"class":75},"i)\n",[69,1276,1277,1280,1282,1284,1286,1289,1292,1295],{"class":71,"line":97},[69,1278,1279],{"class":106},"  if",[69,1281,406],{"class":75},[69,1283,300],{"class":113},[69,1285,170],{"class":75},[69,1287,1288],{"class":113},"isfinite",[69,1290,1291],{"class":75},"(near[i])) region.",[69,1293,1294],{"class":113},"push_back",[69,1296,1297],{"class":75},"(i);\n",[14,1299,1300],{},[47,1301],{"alt":1302,"src":1303},"Bounded by a radius on the Stanford bunny: only points within geodesic distance 0.5 of the seed get a distance; the rest stay dark, and the pink ring marks the radius","\u002Fimg\u002Ftutorials\u002Fsteps\u002Ffast-geodesic-distances-3-radius.png",[14,1305,1306,1307,1310],{},"Points past the radius get infinity. So do points on a piece of the mesh the seed is not connected to, with or without a radius. Test with ",[32,1308,1309],{},"std::isfinite",", not with a comparison to a large number. A small radius on a large mesh only costs what the front touches.",[52,1312,1314],{"id":1313},"float-or-double","float or double",[14,1316,1317],{},"The result has the mesh's coordinate type. A float mesh gives float distances. To get double distances from a float mesh, state the type:",[60,1319,1321],{"className":134,"code":1320,"language":136,"meta":65,"style":65},"auto wide = tf::make_geodesic_distances\u003Cdouble>(mesh, seeds);\n\u002F\u002F wide: tf::buffer\u003Cdouble>\n",[32,1322,1323,1346],{"__ignoreMap":65},[69,1324,1325,1327,1330,1332,1334,1336,1338,1340,1343],{"class":71,"line":72},[69,1326,158],{"class":106},[69,1328,1329],{"class":75}," wide ",[69,1331,164],{"class":106},[69,1333,167],{"class":113},[69,1335,170],{"class":75},[69,1337,331],{"class":113},[69,1339,555],{"class":75},[69,1341,1342],{"class":106},"double",[69,1344,1345],{"class":75},">(mesh, seeds);\n",[69,1347,1348],{"class":71,"line":79},[69,1349,1350],{"class":339},"\u002F\u002F wide: tf::buffer\u003Cdouble>\n",[14,1352,1353],{},"The front itself runs in double precision either way. Distances are measured in the stored coordinates. A transformation tagged onto the mesh does not change them.",[52,1355,1357],{"id":1356},"reusing-topology","Reusing topology",[14,1359,1360],{},"The call needs the face membership and the vertex link. It builds them when they are missing. For many calls on one mesh, build them once and tag them on:",[60,1362,1364],{"className":134,"code":1363,"language":136,"meta":65,"style":65},"auto fm = tf::make_face_membership(mesh);\nauto vl = tf::make_vertex_link(mesh | tf::tag(fm));\nauto tagged = mesh | tf::tag(fm) | tf::tag(vl);\n\nfor (int s : {0, 100, 1000}) {\n  auto ds = tf::make_geodesic_distances(tagged, std::vector\u003Cint>{s});\n}\n",[32,1365,1366,1385,1415,1448,1452,1478,1506],{"__ignoreMap":65},[69,1367,1368,1370,1373,1375,1377,1379,1382],{"class":71,"line":72},[69,1369,158],{"class":106},[69,1371,1372],{"class":75}," fm ",[69,1374,164],{"class":106},[69,1376,167],{"class":113},[69,1378,170],{"class":75},[69,1380,1381],{"class":113},"make_face_membership",[69,1383,1384],{"class":75},"(mesh);\n",[69,1386,1387,1389,1392,1394,1396,1398,1401,1404,1406,1408,1410,1412],{"class":71,"line":79},[69,1388,158],{"class":106},[69,1390,1391],{"class":75}," vl ",[69,1393,164],{"class":106},[69,1395,167],{"class":113},[69,1397,170],{"class":75},[69,1399,1400],{"class":113},"make_vertex_link",[69,1402,1403],{"class":75},"(mesh ",[69,1405,623],{"class":106},[69,1407,167],{"class":113},[69,1409,170],{"class":75},[69,1411,630],{"class":113},[69,1413,1414],{"class":75},"(fm));\n",[69,1416,1417,1419,1422,1424,1426,1428,1430,1432,1434,1437,1439,1441,1443,1445],{"class":71,"line":85},[69,1418,158],{"class":106},[69,1420,1421],{"class":75}," tagged ",[69,1423,164],{"class":106},[69,1425,189],{"class":75},[69,1427,623],{"class":106},[69,1429,167],{"class":113},[69,1431,170],{"class":75},[69,1433,630],{"class":113},[69,1435,1436],{"class":75},"(fm) ",[69,1438,623],{"class":106},[69,1440,167],{"class":113},[69,1442,170],{"class":75},[69,1444,630],{"class":113},[69,1446,1447],{"class":75},"(vl);\n",[69,1449,1450],{"class":71,"line":91},[69,1451,153],{"emptyLinePlaceholder":152},[69,1453,1454,1456,1458,1460,1463,1465,1467,1470,1472,1475],{"class":71,"line":97},[69,1455,403],{"class":106},[69,1457,406],{"class":75},[69,1459,558],{"class":106},[69,1461,1462],{"class":75}," s : {",[69,1464,312],{"class":251},[69,1466,258],{"class":75},[69,1468,1469],{"class":251},"100",[69,1471,258],{"class":75},[69,1473,1474],{"class":251},"1000",[69,1476,1477],{"class":75},"}) {\n",[69,1479,1480,1483,1486,1488,1490,1492,1494,1497,1499,1501,1503],{"class":71,"line":103},[69,1481,1482],{"class":106},"  auto",[69,1484,1485],{"class":75}," ds ",[69,1487,164],{"class":106},[69,1489,167],{"class":113},[69,1491,170],{"class":75},[69,1493,331],{"class":113},[69,1495,1496],{"class":75},"(tagged, ",[69,1498,300],{"class":113},[69,1500,303],{"class":75},[69,1502,306],{"class":106},[69,1504,1505],{"class":75},"{s});\n",[69,1507,1508],{"class":71,"line":529},[69,1509,532],{"class":75},[52,1511,1513],{"id":1512},"working-with-results","Working with results",[14,1515,1516,1517,1520],{},"The distance field is a plain ",[32,1518,1519],{},"tf::buffer",", one value per point. Any scalar-field tool takes it.",[14,1522,1523,1524,1528],{},"Draw rings of equal distance with ",[18,1525,1527],{"href":1526},"\u002Ftutorials\u002Fisocontours-on-a-triangle-mesh-in-cpp","isocontours",":",[60,1530,1532],{"className":134,"code":1531,"language":136,"meta":65,"style":65},"std::vector\u003Cfloat> levels{0.25f, 0.5f, 1.0f};\nauto rings = tf::make_isocontours(mesh, tf::make_range(d),\n                                  tf::make_range(levels));\n\nfor (auto ring : rings.curves()) {\n  for (auto pt : ring) {\n    \u002F\u002F points along one ring\n  }\n}\n",[32,1533,1534,1564,1589,1599,1603,1620,1632,1637,1642],{"__ignoreMap":65},[69,1535,1536,1538,1540,1542,1544,1547,1549,1551,1553,1555,1557,1560,1562],{"class":71,"line":72},[69,1537,300],{"class":113},[69,1539,303],{"class":75},[69,1541,792],{"class":106},[69,1543,1121],{"class":75},[69,1545,1546],{"class":251},"0.25",[69,1548,255],{"class":106},[69,1550,258],{"class":75},[69,1552,915],{"class":251},[69,1554,255],{"class":106},[69,1556,258],{"class":75},[69,1558,1559],{"class":251},"1.0",[69,1561,255],{"class":106},[69,1563,315],{"class":75},[69,1565,1566,1568,1570,1572,1574,1576,1578,1580,1582,1584,1586],{"class":71,"line":79},[69,1567,158],{"class":106},[69,1569,1148],{"class":75},[69,1571,164],{"class":106},[69,1573,167],{"class":113},[69,1575,170],{"class":75},[69,1577,1157],{"class":113},[69,1579,708],{"class":75},[69,1581,414],{"class":113},[69,1583,170],{"class":75},[69,1585,991],{"class":113},[69,1587,1588],{"class":75},"(d),\n",[69,1590,1591,1593,1595,1597],{"class":71,"line":85},[69,1592,1174],{"class":113},[69,1594,170],{"class":75},[69,1596,991],{"class":113},[69,1598,1181],{"class":75},[69,1600,1601],{"class":71,"line":91},[69,1602,153],{"emptyLinePlaceholder":152},[69,1604,1605,1607,1609,1611,1614,1617],{"class":71,"line":97},[69,1606,403],{"class":106},[69,1608,406],{"class":75},[69,1610,158],{"class":106},[69,1612,1613],{"class":75}," ring : rings.",[69,1615,1616],{"class":113},"curves",[69,1618,1619],{"class":75},"()) {\n",[69,1621,1622,1625,1627,1629],{"class":71,"line":103},[69,1623,1624],{"class":106},"  for",[69,1626,406],{"class":75},[69,1628,158],{"class":106},[69,1630,1631],{"class":75}," pt : ring) {\n",[69,1633,1634],{"class":71,"line":529},[69,1635,1636],{"class":339},"    \u002F\u002F points along one ring\n",[69,1638,1639],{"class":71,"line":836},[69,1640,1641],{"class":75},"  }\n",[69,1643,1644],{"class":71,"line":863},[69,1645,532],{"class":75},[14,1647,1648],{},[47,1649],{"alt":1650,"src":1651},"Working with results on the Stanford bunny: pink rings at distances 0.25, 0.5 and 1.0, and the patch within 0.5 of the seeds cut out with isobands in orange","\u002Fimg\u002Ftutorials\u002Fsteps\u002Ffast-geodesic-distances-4-patch.png",[14,1653,1654],{},"Cut out the patch within a distance as its own mesh with isobands:",[60,1656,1658],{"className":134,"code":1657,"language":136,"meta":65,"style":65},"std::array\u003Cfloat, 1> cut{0.5f};\nstd::array\u003Cint, 1> keep{0};\nauto [patch, labels, face_labels] = tf::make_isobands(\n    mesh, tf::make_range(d), tf::make_range(cut), tf::make_range(keep));\n\ntf::write_obj(patch.polygons(), \"patch.obj\");\n",[32,1659,1660,1683,1704,1721,1753,1757],{"__ignoreMap":65},[69,1661,1662,1664,1666,1668,1670,1672,1674,1677,1679,1681],{"class":71,"line":72},[69,1663,300],{"class":113},[69,1665,899],{"class":75},[69,1667,902],{"class":106},[69,1669,258],{"class":75},[69,1671,857],{"class":251},[69,1673,909],{"class":106},[69,1675,1676],{"class":75}," cut{",[69,1678,915],{"class":251},[69,1680,255],{"class":106},[69,1682,315],{"class":75},[69,1684,1685,1687,1689,1691,1693,1695,1697,1700,1702],{"class":71,"line":79},[69,1686,300],{"class":113},[69,1688,899],{"class":75},[69,1690,946],{"class":106},[69,1692,258],{"class":75},[69,1694,857],{"class":251},[69,1696,909],{"class":106},[69,1698,1699],{"class":75}," keep{",[69,1701,312],{"class":251},[69,1703,315],{"class":75},[69,1705,1706,1708,1711,1713,1715,1717,1719],{"class":71,"line":85},[69,1707,158],{"class":106},[69,1709,1710],{"class":75}," [patch, labels, face_labels] ",[69,1712,164],{"class":106},[69,1714,167],{"class":113},[69,1716,170],{"class":75},[69,1718,976],{"class":113},[69,1720,448],{"class":75},[69,1722,1723,1726,1728,1730,1732,1735,1737,1739,1741,1744,1746,1748,1750],{"class":71,"line":91},[69,1724,1725],{"class":75},"    mesh, ",[69,1727,414],{"class":113},[69,1729,170],{"class":75},[69,1731,991],{"class":113},[69,1733,1734],{"class":75},"(d), ",[69,1736,414],{"class":113},[69,1738,170],{"class":75},[69,1740,991],{"class":113},[69,1742,1743],{"class":75},"(cut), ",[69,1745,414],{"class":113},[69,1747,170],{"class":75},[69,1749,991],{"class":113},[69,1751,1752],{"class":75},"(keep));\n",[69,1754,1755],{"class":71,"line":97},[69,1756,153],{"emptyLinePlaceholder":152},[69,1758,1759,1761,1763,1766,1769,1771,1773,1776],{"class":71,"line":103},[69,1760,414],{"class":113},[69,1762,170],{"class":75},[69,1764,1765],{"class":113},"write_obj",[69,1767,1768],{"class":75},"(patch.",[69,1770,197],{"class":113},[69,1772,585],{"class":75},[69,1774,1775],{"class":146},"\"patch.obj\"",[69,1777,182],{"class":75},[14,1779,1780,1781,1784],{},"Band 0 is everything below 0.5. The patch boundary is cut exactly at the 0.5 ring. ",[32,1782,1783],{},"face_labels"," maps each patch face back to its source face.",[14,1786,1787,1788,22,1792,1796],{},"The field also feeds the ",[18,1789,1791],{"href":1790},"\u002Ftutorials\u002Fprincipal-curvatures-of-a-mesh-in-cpp","curvature",[18,1793,1795],{"href":1794},"\u002Ftutorials\u002Ffast-mesh-booleans-in-cpp","boolean"," tools in the same library.",[52,1798,1800],{"id":1799},"about-other-methods","About other methods",[14,1802,1803,1804,1806],{},"People search for the heat method, fast marching, or exact MMP geodesics. They all answer the same question: distance along the surface from seeds. ",[32,1805,38],{}," runs a label-correcting front with a triangle-unfolding update. It is not the heat method. On a planar mesh it gives the straight line. On a curved one it beats the shortest edge path.",[52,1808,1810],{"id":1809},"related-tutorials","Related tutorials",[1812,1813,1814,1821,1827,1834],"ul",{},[1815,1816,1817,1820],"li",{},[18,1818,1819],{"href":1526},"Fast and Robust Isocontours on a Triangle Mesh in C++"," — curves and bands of equal value on a scalar field.",[1815,1822,1823,1826],{},[18,1824,1825],{"href":1790},"Fast and Robust Principal Curvatures of a Mesh in C++"," — curvature values and directions at every vertex.",[1815,1828,1829,1833],{},[18,1830,1832],{"href":1831},"\u002Ftutorials\u002Fmesh-slicing-and-cross-sections-in-cpp","Fast and Robust Mesh Slicing and Cross-Sections in C++"," — plane sections, layers and slabs of a mesh.",[1815,1835,1836,1840],{},[18,1837,1839],{"href":1838},"\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.",[52,1842,1844],{"id":1843},"performance","Performance",[14,1846,1847,1848,1852,1853,1856],{},"We measured the same 28 Thingi10K meshes and the same seeds as the ",[18,1849,1851],{"href":1850},"\u002Ftutorials\u002Ffast-geodesic-distances-in-python#performance","Python page",", from 10K to 3.2M faces, in C++. The peers are MeshLib's ",[32,1854,1855],{},"computeSurfaceDistances",", libigl's heat geodesics and CGAL's heat method. Each time is the best of the timed runs, from arrays in to distances out. The mesh, its topology and any factorization are built inside the clock.",[1858,1859],"headline-numbers",{":items":1860},"[{\"value\":\"4.4×\",\"label\":\"Faster than MeshLib\",\"detail\":\"One seed, geometric mean, 28 meshes\"},{\"value\":\"6.5×\",\"label\":\"Faster than MeshLib\",\"detail\":\"32 seeds, geometric mean, 28 meshes\"},{\"value\":\"135ms\",\"label\":\"Geodesic distances\",\"detail\":\"3,154,110 faces, one seed\"}]",[14,1862,1863,1868,1869,1868,1873],{},[69,1864,1867],{"className":1865},[1866],"tag-pill","Apple M4 Max"," ",[69,1870,1872],{"className":1871},[1866],"16 threads",[69,1874,1876],{"className":1875},[1866],"Apple clang 17 · C++17 -O3",[1878,1879],"data-table",{":headers":1880,":highlight":857,":rows":1881},"[\"Meshes (geometric mean)\",\"trueform\",\"MeshLib\",\"Speedup\",\"libigl heat\",\"CGAL heat\"]","[[\"10K–100K faces, one seed\",\"1.5 ms\",\"5.4 ms\",\"3.5×\",\"40.6 ms\",\"55.2 ms\"],[\"100K–1M faces, one seed\",\"8.0 ms\",\"36.7 ms\",\"4.6×\",\"675 ms\",\"1,105 ms\"],[\"1M–3.2M faces, one seed\",\"65.0 ms\",\"373 ms\",\"5.7×\",\"not measured at full size: 72 s per run at 2.45M faces\",\"not measured at this size: 5.8 s per run at 939K faces\"],[\"All 28 meshes, one seed\",\"8.1 ms\",\"35.9 ms\",\"4.4× (1.7×–9.1×)\",\"not measured on all 28: see 1M–3.2M\",\"not measured on all 28: see 1M–3.2M\"],[\"10K–100K faces, 32 seeds\",\"1.0 ms\",\"5.6 ms\",\"5.7×\",\"40.6 ms\",\"56.0 ms\"],[\"100K–1M faces, 32 seeds\",\"6.5 ms\",\"41.5 ms\",\"6.4×\",\"679 ms\",\"1,116 ms\"],[\"1M–3.2M faces, 32 seeds\",\"53.3 ms\",\"418 ms\",\"7.8×\",\"not measured at full size: 72 s per run at 2.45M faces\",\"not measured at this size: 5.8 s per run at 939K faces\"],[\"All 28 meshes, 32 seeds\",\"6.0 ms\",\"39.4 ms\",\"6.5× (4.3×–9.3×)\",\"not measured on all 28: see 1M–3.2M\",\"not measured on all 28: see 1M–3.2M\"]]",[14,1883,1884],{},"On the Stanford dragon, one seed and 32 seeds:",[1878,1886],{":headers":1887,":highlight":857,":rows":1888},"[\"Dragon\",\"trueform\",\"MeshLib\",\"Speedup\",\"libigl heat\",\"CGAL heat\"]","[[\"58,298 faces, one seed\",\"2.7 ms\",\"12.5 ms\",\"4.6×\",\"98.7 ms\",\"failed: non-manifold face\"],[\"141,966 faces, one seed\",\"6.2 ms\",\"28.3 ms\",\"4.6×\",\"not measured: over 10× trueform\",\"not measured: over 10× trueform\"],[\"244,026 faces, one seed\",\"7.9 ms\",\"49.3 ms\",\"6.2×\",\"not measured: over 10× trueform\",\"not measured: over 10× trueform\"],[\"480,868 faces, one seed\",\"17.5 ms\",\"96.7 ms\",\"5.5×\",\"not measured: over 10× trueform\",\"not measured: over 10× trueform\"],[\"1,025,766 faces, one seed\",\"34.7 ms\",\"208 ms\",\"6.0×\",\"not measured: over 10× trueform\",\"failed: non-manifold face\"],[\"58,298 faces, 32 seeds\",\"1.7 ms\",\"13.6 ms\",\"7.8×\",\"102 ms\",\"failed: non-manifold face\"],[\"141,966 faces, 32 seeds\",\"4.3 ms\",\"30.2 ms\",\"7.0×\",\"not measured: over 10× trueform\",\"not measured: over 10× trueform\"],[\"244,026 faces, 32 seeds\",\"8.2 ms\",\"50.6 ms\",\"6.2×\",\"not measured: over 10× trueform\",\"not measured: over 10× trueform\"],[\"480,868 faces, 32 seeds\",\"16.9 ms\",\"105 ms\",\"6.2×\",\"not measured: over 10× trueform\",\"not measured: over 10× trueform\"],[\"1,025,766 faces, 32 seeds\",\"32.0 ms\",\"235 ms\",\"7.3×\",\"not measured: over 10× trueform\",\"failed: non-manifold face\"]]",[14,1890,1891,1892,1895],{},"The heat methods spend most of their time on a sparse factorization, on one thread. Above 100K faces they run more than 10× slower than trueform, so we did not time them at every size. CGAL's ",[32,1893,1894],{},"Surface_mesh"," rejects the 58K and 1M dragons at a non-manifold face. MeshLib runs faster from C++ than from Python, so the gap here is smaller than on the Python page.",[14,1897,1898],{},"Error is the relative error per point against exact MMP distances, on the 24 meshes (up to 1.3M faces) that have an exact reference. Each mesh's median error, averaged over the meshes, is 0.57 % from one seed and 0.20 % from 32 seeds. MeshLib's is 2.3 % from one seed and from 32 seeds. libigl heat's is 36 % and 30 %. CGAL heat's is 13 % and 31 %, on the 20 meshes up to 939K faces.",[14,1900,1901],{},"Best of the timed runs after warm-ups (trueform 15 after 3, 2026-11-01; peers 7 after 2, 2026-10-31), trueform 0.10.8; harness and raw results: benchmarks\u002Ftutorials\u002Fcpp-geodesics.",[14,1903,1904,1908,1909],{},[18,1905,1907],{"href":1906},"\u002Ftrueform\u002Fbenchmarks","Full benchmarks"," · ",[18,1910,1913],{"href":1911,"rel":1912},"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fmodules\u002Fgeometry#geodesic-distances",[125],"Documentation",[1915,1916],"cite-as",{"author":1917,"title":5},"Sajovic, {\\v{Z}}iga",[1919,1920,1921],"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}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":65,"searchDepth":79,"depth":79,"links":1923},[1924,1925,1926,1927,1928,1929,1930,1931,1932,1933,1934],{"id":54,"depth":79,"text":55},{"id":130,"depth":79,"text":131},{"id":286,"depth":79,"text":287},{"id":720,"depth":79,"text":721},{"id":1184,"depth":79,"text":1185},{"id":1313,"depth":79,"text":1314},{"id":1356,"depth":79,"text":1357},{"id":1512,"depth":79,"text":1513},{"id":1799,"depth":79,"text":1800},{"id":1809,"depth":79,"text":1810},{"id":1843,"depth":79,"text":1844},"2026-10-31","Compute geodesic distances on a triangle mesh in C++. Distance along the surface from one seed or many, bounded by a radius, in float or double. Header-only, C++17.","md",[1939,1940,1941,1942,1943,1944,1945,1946,1947,1948],"geodesic distance C++","geodesic distance mesh C++","surface distance triangle mesh C++","heat method geodesic C++","fast marching mesh C++","MeshLib computeSurfaceDistances alternative","libigl exact_geodesic alternative","CGAL Surface_mesh_shortest_path alternative","geometry-central geodesic alternative","mesh geodesics library C++",{},"\u002Fimg\u002Fog\u002Ftutorials\u002Ffast-geodesic-distances-in-cpp.png","\u002Ftutorials\u002Ffast-geodesic-distances-in-cpp",{"title":5,"description":1936},"tutorials\u002Ffast-geodesic-distances-in-cpp",[1955,1956,1957],"c++","geodesics","tutorial","D-afR5Nm2HQFsC8UbBlNEsjyUP7uL600mKRVAD9XGYo",1793517812008]