[{"data":1,"prerenderedAt":1359},["ShallowReactive",2],{"tutorial-mesh-intersection-curves-in-javascript":3},{"id":4,"title":5,"author":6,"body":7,"date":1334,"description":1335,"extension":1336,"keywords":1337,"meta":1348,"navigation":151,"ogImage":1349,"path":1350,"published":151,"seo":1351,"stem":1352,"tags":1353,"__hash__":1358},"tutorials\u002Ftutorials\u002Fmesh-intersection-curves-in-javascript.md","Fast and Exact Mesh Intersection Curves in JavaScript","Žiga Sajovic",{"type":8,"value":9,"toc":1324},"minimark",[10,28,40,44,51,56,81,94,98,211,218,284,287,291,305,350,356,370,373,377,386,470,476,486,495,562,565,569,572,743,749,755,759,772,1021,1024,1055,1062,1069,1105,1108,1168,1179,1213,1220,1224,1256,1260,1264,1284,1294,1300,1316,1320],[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-cpp","C++",".",[14,29,30,31,35,36,39],{},"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. One ",[32,37,38],{},"npm install",". It runs as WebAssembly in Node.js and the browser, with TypeScript types. This tutorial covers two meshes, moved meshes, N meshes, and how to use the curves.",[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\u002Fts\u002Fmodules\u002Fintersect#intersection-curves\",\"variant\":\"soft\",\"color\":\"neutral\"}]",[14,45,46],{},[47,48],"img",{"alt":49,"src":50},"The Stanford dragon and a moved copy, with the curves where they intersect","\u002Fimg\u002Ftutorials\u002Fintersection-curves.png",[52,53,55],"h2",{"id":54},"install","Install",[57,58,63],"pre",{"className":59,"code":60,"language":61,"meta":62,"style":62},"language-bash shiki shiki-themes github-dark","npm install @polydera\u002Ftrueform\n","bash","",[32,64,65],{"__ignoreMap":62},[66,67,70,74,78],"span",{"class":68,"line":69},"line",1,[66,71,73],{"class":72},"svObZ","npm",[66,75,77],{"class":76},"sU2Wk"," install",[66,79,80],{"class":76}," @polydera\u002Ftrueform\n",[14,82,83,84,87,88,27],{},"The WebAssembly binary loads on first use. In the browser, multithreading needs ",[32,85,86],{},"SharedArrayBuffer"," headers. See the ",[18,89,93],{"href":90,"rel":91},"https:\u002F\u002Ftrueform.polydera.com\u002Fts\u002Fgetting-started\u002Finstallation",[92],"nofollow","installation guide",[52,95,97],{"id":96},"loading-the-meshes","Loading the meshes",[57,99,103],{"className":100,"code":101,"language":102,"meta":62,"style":62},"language-typescript shiki shiki-themes github-dark","import * as tf from \"@polydera\u002Ftrueform\";\nimport { readFileSync } from \"node:fs\";\n\nconst mesh0 = tf.readStl(readFileSync(\"part.stl\"));\nconst mesh1 = tf.readObj(readFileSync(\"tool.obj\"));\n","typescript",[32,104,105,131,146,153,185],{"__ignoreMap":62},[66,106,107,111,115,118,122,125,128],{"class":68,"line":69},[66,108,110],{"class":109},"snl16","import",[66,112,114],{"class":113},"sDLfK"," *",[66,116,117],{"class":109}," as",[66,119,121],{"class":120},"s95oV"," tf ",[66,123,124],{"class":109},"from",[66,126,127],{"class":76}," \"@polydera\u002Ftrueform\"",[66,129,130],{"class":120},";\n",[66,132,134,136,139,141,144],{"class":68,"line":133},2,[66,135,110],{"class":109},[66,137,138],{"class":120}," { readFileSync } ",[66,140,124],{"class":109},[66,142,143],{"class":76}," \"node:fs\"",[66,145,130],{"class":120},[66,147,149],{"class":68,"line":148},3,[66,150,152],{"emptyLinePlaceholder":151},true,"\n",[66,154,156,159,162,165,168,171,174,177,179,182],{"class":68,"line":155},4,[66,157,158],{"class":109},"const",[66,160,161],{"class":113}," mesh0",[66,163,164],{"class":109}," =",[66,166,167],{"class":120}," tf.",[66,169,170],{"class":72},"readStl",[66,172,173],{"class":120},"(",[66,175,176],{"class":72},"readFileSync",[66,178,173],{"class":120},[66,180,181],{"class":76},"\"part.stl\"",[66,183,184],{"class":120},"));\n",[66,186,188,190,193,195,197,200,202,204,206,209],{"class":68,"line":187},5,[66,189,158],{"class":109},[66,191,192],{"class":113}," mesh1",[66,194,164],{"class":109},[66,196,167],{"class":120},[66,198,199],{"class":72},"readObj",[66,201,173],{"class":120},[66,203,176],{"class":72},[66,205,173],{"class":120},[66,207,208],{"class":76},"\"tool.obj\"",[66,210,184],{"class":120},[14,212,213,214,217],{},"In the browser, pass the bytes from ",[32,215,216],{},"fetch"," instead. To follow along without files, build a sphere and a box:",[57,219,221],{"className":100,"code":220,"language":102,"meta":62,"style":62},"const sphere = tf.sphereMesh(1.0, 40, 40);\nconst box = tf.boxMesh(1.5, 1.5, 1.5);\n",[32,222,223,255],{"__ignoreMap":62},[66,224,225,227,230,232,234,237,239,242,245,248,250,252],{"class":68,"line":69},[66,226,158],{"class":109},[66,228,229],{"class":113}," sphere",[66,231,164],{"class":109},[66,233,167],{"class":120},[66,235,236],{"class":72},"sphereMesh",[66,238,173],{"class":120},[66,240,241],{"class":113},"1.0",[66,243,244],{"class":120},", ",[66,246,247],{"class":113},"40",[66,249,244],{"class":120},[66,251,247],{"class":113},[66,253,254],{"class":120},");\n",[66,256,257,259,262,264,266,269,271,274,276,278,280,282],{"class":68,"line":133},[66,258,158],{"class":109},[66,260,261],{"class":113}," box",[66,263,164],{"class":109},[66,265,167],{"class":120},[66,267,268],{"class":72},"boxMesh",[66,270,173],{"class":120},[66,272,273],{"class":113},"1.5",[66,275,244],{"class":120},[66,277,273],{"class":113},[66,279,244],{"class":120},[66,281,273],{"class":113},[66,283,254],{"class":120},[14,285,286],{},"The box is smaller than the sphere, but its corners poke out. Each of its six faces cuts the sphere in a circle.",[52,288,290],{"id":289},"curves-between-two-meshes","Curves between two meshes",[14,292,293,300,301,304],{},[18,294,297],{"href":295,"rel":296},"https:\u002F\u002Ftrueform.polydera.com\u002Fts\u002Fmodules\u002Fintersect#between-two-meshes",[92],[32,298,299],{},"tf.intersectionCurves"," takes two meshes and returns a ",[32,302,303],{},"Curves"," object:",[57,306,308],{"className":100,"code":307,"language":102,"meta":62,"style":62},"const curves = tf.intersectionCurves(sphere, box);\ncurves.length;        \u002F\u002F 6\ncurves.points.shape;  \u002F\u002F [562, 3]\n",[32,309,310,327,342],{"__ignoreMap":62},[66,311,312,314,317,319,321,324],{"class":68,"line":69},[66,313,158],{"class":109},[66,315,316],{"class":113}," curves",[66,318,164],{"class":109},[66,320,167],{"class":120},[66,322,323],{"class":72},"intersectionCurves",[66,325,326],{"class":120},"(sphere, box);\n",[66,328,329,332,335,338],{"class":68,"line":133},[66,330,331],{"class":120},"curves.",[66,333,334],{"class":113},"length",[66,336,337],{"class":120},";        ",[66,339,341],{"class":340},"sAwPA","\u002F\u002F 6\n",[66,343,344,347],{"class":68,"line":148},[66,345,346],{"class":120},"curves.points.shape;  ",[66,348,349],{"class":340},"\u002F\u002F [562, 3]\n",[14,351,352],{},[47,353],{"alt":354,"src":355},"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,357,358,359,362,363,366,367,27],{},"Six closed curves, one per box face. If the meshes do not touch, ",[32,360,361],{},"curves.length"," is 0 and ",[32,364,365],{},"curves.points"," has shape ",[32,368,369],{},"[0, 3]",[14,371,372],{},"The computation is exact. Shared edges, shared vertices and coplanar faces are classified, not guessed. Open meshes, non-manifold edges and self-intersecting inputs are accepted.",[52,374,376],{"id":375},"curves-between-moved-meshes","Curves between moved meshes",[14,378,379,380,385],{},"Most real cases have one part in a different pose. Set a 4×4 ",[18,381,384],{"href":382,"rel":383},"https:\u002F\u002Ftrueform.polydera.com\u002Fts\u002Fmodules\u002Fspatial#transformations-on-forms",[92],"transformation"," on the mesh. The points are not copied or rewritten.",[57,387,389],{"className":100,"code":388,"language":102,"meta":62,"style":62},"const moved = tf.sphereMesh(1.0, 40, 40);\nmoved.transformation = tf.makeTranslation(1.0, 0, 0);\n\nconst curves = tf.intersectionCurves(sphere, moved);\n\u002F\u002F one closed circle in the plane x = 0.5\n",[32,390,391,418,446,450,465],{"__ignoreMap":62},[66,392,393,395,398,400,402,404,406,408,410,412,414,416],{"class":68,"line":69},[66,394,158],{"class":109},[66,396,397],{"class":113}," moved",[66,399,164],{"class":109},[66,401,167],{"class":120},[66,403,236],{"class":72},[66,405,173],{"class":120},[66,407,241],{"class":113},[66,409,244],{"class":120},[66,411,247],{"class":113},[66,413,244],{"class":120},[66,415,247],{"class":113},[66,417,254],{"class":120},[66,419,420,423,426,428,431,433,435,437,440,442,444],{"class":68,"line":133},[66,421,422],{"class":120},"moved.transformation ",[66,424,425],{"class":109},"=",[66,427,167],{"class":120},[66,429,430],{"class":72},"makeTranslation",[66,432,173],{"class":120},[66,434,241],{"class":113},[66,436,244],{"class":120},[66,438,439],{"class":113},"0",[66,441,244],{"class":120},[66,443,439],{"class":113},[66,445,254],{"class":120},[66,447,448],{"class":68,"line":148},[66,449,152],{"emptyLinePlaceholder":151},[66,451,452,454,456,458,460,462],{"class":68,"line":155},[66,453,158],{"class":109},[66,455,316],{"class":113},[66,457,164],{"class":109},[66,459,167],{"class":120},[66,461,323],{"class":72},[66,463,464],{"class":120},"(sphere, moved);\n",[66,466,467],{"class":68,"line":187},[66,468,469],{"class":340},"\u002F\u002F one closed circle in the plane x = 0.5\n",[14,471,472],{},[47,473],{"alt":474,"src":475},"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,477,478,479,482,483,27],{},"Matrices are row-major. A Three.js ",[32,480,481],{},"Matrix4"," is column-major, so transpose it: ",[32,484,485],{},"tf.ndarray(m.elements, [4, 4]).T",[14,487,488,489,494],{},"To intersect a mesh with a moved copy of itself, use a ",[18,490,493],{"href":491,"rel":492},"https:\u002F\u002Ftrueform.polydera.com\u002Fts\u002Fmodules\u002Fspatial#shallow-copies",[92],"shallow copy",". It shares the points and faces. Only the transformation differs:",[57,496,498],{"className":100,"code":497,"language":102,"meta":62,"style":62},"const view = sphere.shallowCopy();\nview.transformation = tf.makeTranslation(1.0, 0, 0);\n\nconst curves = tf.intersectionCurves(sphere, view);\n",[32,499,500,518,543,547],{"__ignoreMap":62},[66,501,502,504,507,509,512,515],{"class":68,"line":69},[66,503,158],{"class":109},[66,505,506],{"class":113}," view",[66,508,164],{"class":109},[66,510,511],{"class":120}," sphere.",[66,513,514],{"class":72},"shallowCopy",[66,516,517],{"class":120},"();\n",[66,519,520,523,525,527,529,531,533,535,537,539,541],{"class":68,"line":133},[66,521,522],{"class":120},"view.transformation ",[66,524,425],{"class":109},[66,526,167],{"class":120},[66,528,430],{"class":72},[66,530,173],{"class":120},[66,532,241],{"class":113},[66,534,244],{"class":120},[66,536,439],{"class":113},[66,538,244],{"class":120},[66,540,439],{"class":113},[66,542,254],{"class":120},[66,544,545],{"class":68,"line":148},[66,546,152],{"emptyLinePlaceholder":151},[66,548,549,551,553,555,557,559],{"class":68,"line":155},[66,550,158],{"class":109},[66,552,316],{"class":113},[66,554,164],{"class":109},[66,556,167],{"class":120},[66,558,323],{"class":72},[66,560,561],{"class":120},"(sphere, view);\n",[14,563,564],{},"The spatial tree is built once per mesh. Changing a transformation does not rebuild it.",[52,566,568],{"id":567},"curves-between-n-meshes","Curves between N meshes",[14,570,571],{},"Pass an array to get all pairwise curves in one call:",[57,573,575],{"className":100,"code":574,"language":102,"meta":62,"style":62},"const m0 = tf.sphereMesh(1.0, 40, 40);\nconst m1 = tf.sphereMesh(1.0, 40, 40);\nconst m2 = tf.sphereMesh(1.0, 40, 40);\nm1.transformation = tf.makeTranslation(1.0, 0, 0);\nm2.transformation = tf.makeTranslation(0.5, 0.8, 0);\n\nconst curves = tf.intersectionCurves([m0, m1, m2]);\ncurves.length;  \u002F\u002F 6\n",[32,576,577,604,631,658,683,710,715,731],{"__ignoreMap":62},[66,578,579,581,584,586,588,590,592,594,596,598,600,602],{"class":68,"line":69},[66,580,158],{"class":109},[66,582,583],{"class":113}," m0",[66,585,164],{"class":109},[66,587,167],{"class":120},[66,589,236],{"class":72},[66,591,173],{"class":120},[66,593,241],{"class":113},[66,595,244],{"class":120},[66,597,247],{"class":113},[66,599,244],{"class":120},[66,601,247],{"class":113},[66,603,254],{"class":120},[66,605,606,608,611,613,615,617,619,621,623,625,627,629],{"class":68,"line":133},[66,607,158],{"class":109},[66,609,610],{"class":113}," m1",[66,612,164],{"class":109},[66,614,167],{"class":120},[66,616,236],{"class":72},[66,618,173],{"class":120},[66,620,241],{"class":113},[66,622,244],{"class":120},[66,624,247],{"class":113},[66,626,244],{"class":120},[66,628,247],{"class":113},[66,630,254],{"class":120},[66,632,633,635,638,640,642,644,646,648,650,652,654,656],{"class":68,"line":148},[66,634,158],{"class":109},[66,636,637],{"class":113}," m2",[66,639,164],{"class":109},[66,641,167],{"class":120},[66,643,236],{"class":72},[66,645,173],{"class":120},[66,647,241],{"class":113},[66,649,244],{"class":120},[66,651,247],{"class":113},[66,653,244],{"class":120},[66,655,247],{"class":113},[66,657,254],{"class":120},[66,659,660,663,665,667,669,671,673,675,677,679,681],{"class":68,"line":155},[66,661,662],{"class":120},"m1.transformation ",[66,664,425],{"class":109},[66,666,167],{"class":120},[66,668,430],{"class":72},[66,670,173],{"class":120},[66,672,241],{"class":113},[66,674,244],{"class":120},[66,676,439],{"class":113},[66,678,244],{"class":120},[66,680,439],{"class":113},[66,682,254],{"class":120},[66,684,685,688,690,692,694,696,699,701,704,706,708],{"class":68,"line":187},[66,686,687],{"class":120},"m2.transformation ",[66,689,425],{"class":109},[66,691,167],{"class":120},[66,693,430],{"class":72},[66,695,173],{"class":120},[66,697,698],{"class":113},"0.5",[66,700,244],{"class":120},[66,702,703],{"class":113},"0.8",[66,705,244],{"class":120},[66,707,439],{"class":113},[66,709,254],{"class":120},[66,711,713],{"class":68,"line":712},6,[66,714,152],{"emptyLinePlaceholder":151},[66,716,718,720,722,724,726,728],{"class":68,"line":717},7,[66,719,158],{"class":109},[66,721,316],{"class":113},[66,723,164],{"class":109},[66,725,167],{"class":120},[66,727,323],{"class":72},[66,729,730],{"class":120},"([m0, m1, m2]);\n",[66,732,734,736,738,741],{"class":68,"line":733},8,[66,735,331],{"class":120},[66,737,334],{"class":113},[66,739,740],{"class":120},";  ",[66,742,341],{"class":340},[14,744,745],{},[47,746],{"alt":747,"src":748},"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,750,751,752,754],{},"Three spheres make three pairwise circles. The circles cross each other on the surfaces. ",[32,753,34],{}," splits the curves at every crossing point, so here you get six open arcs instead of three circles.",[52,756,758],{"id":757},"working-with-results","Working with results",[14,760,761,764,765,768,769,771],{},[32,762,763],{},"curves.paths"," is an ",[32,766,767],{},"OffsetBlockedBuffer",". Each block is one curve, as indices into ",[32,770,365],{},". A closed curve repeats its first index at the end.",[57,773,775],{"className":100,"code":774,"language":102,"meta":62,"style":62},"const p = curves.points.data;\n\nfor (const pathIds of curves.paths) {\n  const ids = pathIds.data;\n  const closed = ids[0] === ids[ids.length - 1];\n  let length = 0;\n  for (let k = 1; k \u003C ids.length; k++) {\n    const a = 3 * ids[k - 1], b = 3 * ids[k];\n    length += Math.hypot(p[b] - p[a], p[b + 1] - p[a + 1], p[b + 2] - p[a + 2]);\n  }\n}\n",[32,776,777,789,793,812,825,859,874,911,949,1009,1015],{"__ignoreMap":62},[66,778,779,781,784,786],{"class":68,"line":69},[66,780,158],{"class":109},[66,782,783],{"class":113}," p",[66,785,164],{"class":109},[66,787,788],{"class":120}," curves.points.data;\n",[66,790,791],{"class":68,"line":133},[66,792,152],{"emptyLinePlaceholder":151},[66,794,795,798,801,803,806,809],{"class":68,"line":148},[66,796,797],{"class":109},"for",[66,799,800],{"class":120}," (",[66,802,158],{"class":109},[66,804,805],{"class":113}," pathIds",[66,807,808],{"class":109}," of",[66,810,811],{"class":120}," curves.paths) {\n",[66,813,814,817,820,822],{"class":68,"line":155},[66,815,816],{"class":109},"  const",[66,818,819],{"class":113}," ids",[66,821,164],{"class":109},[66,823,824],{"class":120}," pathIds.data;\n",[66,826,827,829,832,834,837,839,842,845,848,850,853,856],{"class":68,"line":187},[66,828,816],{"class":109},[66,830,831],{"class":113}," closed",[66,833,164],{"class":109},[66,835,836],{"class":120}," ids[",[66,838,439],{"class":113},[66,840,841],{"class":120},"] ",[66,843,844],{"class":109},"===",[66,846,847],{"class":120}," ids[ids.",[66,849,334],{"class":113},[66,851,852],{"class":109}," -",[66,854,855],{"class":113}," 1",[66,857,858],{"class":120},"];\n",[66,860,861,864,867,869,872],{"class":68,"line":712},[66,862,863],{"class":109},"  let",[66,865,866],{"class":120}," length ",[66,868,425],{"class":109},[66,870,871],{"class":113}," 0",[66,873,130],{"class":120},[66,875,876,879,881,884,887,889,891,894,897,900,902,905,908],{"class":68,"line":717},[66,877,878],{"class":109},"  for",[66,880,800],{"class":120},[66,882,883],{"class":109},"let",[66,885,886],{"class":120}," k ",[66,888,425],{"class":109},[66,890,855],{"class":113},[66,892,893],{"class":120},"; k ",[66,895,896],{"class":109},"\u003C",[66,898,899],{"class":120}," ids.",[66,901,334],{"class":113},[66,903,904],{"class":120},"; k",[66,906,907],{"class":109},"++",[66,909,910],{"class":120},") {\n",[66,912,913,916,919,921,924,926,929,932,934,937,940,942,944,946],{"class":68,"line":733},[66,914,915],{"class":109},"    const",[66,917,918],{"class":113}," a",[66,920,164],{"class":109},[66,922,923],{"class":113}," 3",[66,925,114],{"class":109},[66,927,928],{"class":120}," ids[k ",[66,930,931],{"class":109},"-",[66,933,855],{"class":113},[66,935,936],{"class":120},"], ",[66,938,939],{"class":113},"b",[66,941,164],{"class":109},[66,943,923],{"class":113},[66,945,114],{"class":109},[66,947,948],{"class":120}," ids[k];\n",[66,950,952,955,958,961,964,967,969,972,975,977,979,981,984,986,988,991,993,996,998,1000,1002,1004,1006],{"class":68,"line":951},9,[66,953,954],{"class":120},"    length ",[66,956,957],{"class":109},"+=",[66,959,960],{"class":120}," Math.",[66,962,963],{"class":72},"hypot",[66,965,966],{"class":120},"(p[b] ",[66,968,931],{"class":109},[66,970,971],{"class":120}," p[a], p[b ",[66,973,974],{"class":109},"+",[66,976,855],{"class":113},[66,978,841],{"class":120},[66,980,931],{"class":109},[66,982,983],{"class":120}," p[a ",[66,985,974],{"class":109},[66,987,855],{"class":113},[66,989,990],{"class":120},"], p[b ",[66,992,974],{"class":109},[66,994,995],{"class":113}," 2",[66,997,841],{"class":120},[66,999,931],{"class":109},[66,1001,983],{"class":120},[66,1003,974],{"class":109},[66,1005,995],{"class":113},[66,1007,1008],{"class":120},"]);\n",[66,1010,1012],{"class":68,"line":1011},10,[66,1013,1014],{"class":120},"  }\n",[66,1016,1018],{"class":68,"line":1017},11,[66,1019,1020],{"class":120},"}\n",[14,1022,1023],{},"The curves lie on both surfaces. Check it with a batch distance query:",[57,1025,1027],{"className":100,"code":1026,"language":102,"meta":62,"style":62},"tf.distance(sphere, tf.point(curves.points)).max();  \u002F\u002F close to 0\n",[32,1028,1029],{"__ignoreMap":62},[66,1030,1031,1034,1037,1040,1043,1046,1049,1052],{"class":68,"line":69},[66,1032,1033],{"class":120},"tf.",[66,1035,1036],{"class":72},"distance",[66,1038,1039],{"class":120},"(sphere, tf.",[66,1041,1042],{"class":72},"point",[66,1044,1045],{"class":120},"(curves.points)).",[66,1047,1048],{"class":72},"max",[66,1050,1051],{"class":120},"();  ",[66,1053,1054],{"class":340},"\u002F\u002F close to 0\n",[14,1056,1057,1058,1061],{},"Put the mesh first. In 0.10.8, ",[32,1059,1060],{},"tf.distance(points, mesh)"," with the batch first throws.",[14,1063,1064,1065,1068],{},"To draw the curves, build an index buffer of segments for ",[32,1066,1067],{},"THREE.LineSegments",", or sweep them into a tube mesh:",[57,1070,1072],{"className":100,"code":1071,"language":102,"meta":62,"style":62},"const tubes = tf.tubeMesh(curves, 0.01, 12);  \u002F\u002F radius, radial segments\n",[32,1073,1074],{"__ignoreMap":62},[66,1075,1076,1078,1081,1083,1085,1088,1091,1094,1096,1099,1102],{"class":68,"line":69},[66,1077,158],{"class":109},[66,1079,1080],{"class":113}," tubes",[66,1082,164],{"class":109},[66,1084,167],{"class":120},[66,1086,1087],{"class":72},"tubeMesh",[66,1089,1090],{"class":120},"(curves, ",[66,1092,1093],{"class":113},"0.01",[66,1095,244],{"class":120},[66,1097,1098],{"class":113},"12",[66,1100,1101],{"class":120},");  ",[66,1103,1104],{"class":340},"\u002F\u002F radius, radial segments\n",[14,1106,1107],{},"If you also want the boolean, ask for the curves on the same call:",[57,1109,1111],{"className":100,"code":1110,"language":102,"meta":62,"style":62},"const { mesh, labels, faceLabels, curves: seam } =\n  tf.booleanUnion(sphere, moved, { returnCurves: true });\n",[32,1112,1113,1151],{"__ignoreMap":62},[66,1114,1115,1117,1120,1123,1125,1128,1130,1133,1135,1139,1142,1145,1148],{"class":68,"line":69},[66,1116,158],{"class":109},[66,1118,1119],{"class":120}," { ",[66,1121,1122],{"class":113},"mesh",[66,1124,244],{"class":120},[66,1126,1127],{"class":113},"labels",[66,1129,244],{"class":120},[66,1131,1132],{"class":113},"faceLabels",[66,1134,244],{"class":120},[66,1136,1138],{"class":1137},"s9osk","curves",[66,1140,1141],{"class":120},": ",[66,1143,1144],{"class":113},"seam",[66,1146,1147],{"class":120}," } ",[66,1149,1150],{"class":109},"=\n",[66,1152,1153,1156,1159,1162,1165],{"class":68,"line":133},[66,1154,1155],{"class":120},"  tf.",[66,1157,1158],{"class":72},"booleanUnion",[66,1160,1161],{"class":120},"(sphere, moved, { returnCurves: ",[66,1163,1164],{"class":113},"true",[66,1166,1167],{"class":120}," });\n",[14,1169,1170,1171,1178],{},"To split faces along the curves instead, use ",[18,1172,1175],{"href":1173,"rel":1174},"https:\u002F\u002Ftrueform.polydera.com\u002Fts\u002Fmodules\u002Farrangement",[92],[32,1176,1177],{},"tf.meshArrangements",":",[57,1180,1182],{"className":100,"code":1181,"language":102,"meta":62,"style":62},"const { mesh, tagLabels, faceLabels } = tf.meshArrangements([sphere, moved]);\n",[32,1183,1184],{"__ignoreMap":62},[66,1185,1186,1188,1190,1192,1194,1197,1199,1201,1203,1205,1207,1210],{"class":68,"line":69},[66,1187,158],{"class":109},[66,1189,1119],{"class":120},[66,1191,1122],{"class":113},[66,1193,244],{"class":120},[66,1195,1196],{"class":113},"tagLabels",[66,1198,244],{"class":120},[66,1200,1132],{"class":113},[66,1202,1147],{"class":120},[66,1204,425],{"class":109},[66,1206,167],{"class":120},[66,1208,1209],{"class":72},"meshArrangements",[66,1211,1212],{"class":120},"([sphere, moved]);\n",[14,1214,1215,1216,1219],{},"In the browser, ",[32,1217,1218],{},"tf.async.intersectionCurves"," runs the same call off the main thread.",[52,1221,1223],{"id":1222},"related-tutorials","Related tutorials",[1225,1226,1227,1235,1242,1249],"ul",{},[1228,1229,1230,1234],"li",{},[18,1231,1233],{"href":1232},"\u002Ftutorials\u002Ffast-mesh-booleans-in-javascript","Fast Mesh Booleans in JavaScript"," — exact union, intersection and difference of two meshes.",[1228,1236,1237,1241],{},[18,1238,1240],{"href":1239},"\u002Ftutorials\u002Ffast-mesh-collision-detection-in-javascript","Fast and Robust Mesh Collision Detection in JavaScript"," — do two meshes touch, how far apart, and where.",[1228,1243,1244,1248],{},[18,1245,1247],{"href":1246},"\u002Ftutorials\u002Fmesh-slicing-and-cross-sections-in-javascript","Fast and Robust Mesh Slicing and Cross-Sections in JavaScript"," — plane sections, layers and slabs of a mesh.",[1228,1250,1251,1255],{},[18,1252,1254],{"href":1253},"\u002Ftutorials\u002Fisocontours-on-a-triangle-mesh-in-javascript","Fast and Robust Isocontours on a Triangle Mesh in JavaScript"," — curves and bands of equal value on a scalar field.",[52,1257,1259],{"id":1258},"performance","Performance",[1261,1262],"headline-numbers",{":items":1263},"[{\"value\":\"15.8ms\",\"label\":\"Spatial tree build\",\"detail\":\"1,025,766 polygons, vs 259.9 ms three-mesh-bvh\"},{\"value\":\"16×\",\"label\":\"Faster tree build\",\"detail\":\"than three-mesh-bvh\"},{\"value\":\"467.9ms\",\"label\":\"Polygon arrangements\",\"detail\":\"2,051,532 polygons\"}]",[14,1265,1266,1271,1272,1271,1276,1271,1280],{},[66,1267,1270],{"className":1268},[1269],"tag-pill","Apple M4 Max"," ",[66,1273,1275],{"className":1274},[1269],"16 threads",[66,1277,1279],{"className":1278},[1269],"Node.js",[66,1281,1283],{"className":1282},[1269],"WebAssembly",[14,1285,1286,1287,1289,1290,1293],{},"Every intersection query starts from a spatial tree. On a 1,025,766-polygon mesh, ",[32,1288,34],{}," builds it in 15.8 ms in Node.js, against 259.9 ms for three-mesh-bvh. Build it ahead of time with ",[32,1291,1292],{},"mesh.buildTree()",". Polygon arrangements, which find every crossing among the faces of a mesh and split the faces along the curves, take 467.9 ms on 2,051,532 polygons. That benchmark has no JavaScript competitor.",[14,1295,1296],{},[47,1297],{"alt":1298,"src":1299},"Benchmark chart: trueform against other TypeScript libraries, polygon arrangements","\u002Fimg\u002Falgorithms\u002Fbenchmark-polygon-arrangements-typescript-2026.png",[14,1301,1302,1306,1307,1306,1312],{},[18,1303,1305],{"href":1304},"\u002Ftrueform\u002Fbenchmarks","Full benchmarks"," · ",[18,1308,1311],{"href":1309,"rel":1310},"https:\u002F\u002Ftrueform.polydera.com\u002Fts\u002Fmodules\u002Fintersect",[92],"Documentation",[18,1313,1315],{"href":1314},"\u002Falgorithms\u002Ffast-and-exact-mesh-booleans-and-arrangements","How the algorithm works",[1317,1318],"cite-as",{"author":1319,"title":5},"Sajovic, {\\v{Z}}iga",[1321,1322,1323],"style",{},"html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}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":62,"searchDepth":133,"depth":133,"links":1325},[1326,1327,1328,1329,1330,1331,1332,1333],{"id":54,"depth":133,"text":55},{"id":96,"depth":133,"text":97},{"id":289,"depth":133,"text":290},{"id":375,"depth":133,"text":376},{"id":567,"depth":133,"text":568},{"id":757,"depth":133,"text":758},{"id":1222,"depth":133,"text":1223},{"id":1258,"depth":133,"text":1259},"2026-10-31","Compute the exact curves where two or more triangle meshes intersect in JavaScript and TypeScript. Polylines out, with transformations and N-mesh input. WebAssembly, runs in Node.js and the browser.","md",[1338,1339,1340,1341,1342,1343,1344,1345,1346,1347],"mesh intersection curves JavaScript","mesh mesh intersection TypeScript","intersection polyline mesh browser","surface intersection curve JavaScript","WebAssembly mesh intersection","three.js mesh intersection","three-mesh-bvh intersection alternative","npm mesh intersection","exact mesh intersection JavaScript","CGAL alternative JavaScript",{},"\u002Fimg\u002Fog\u002Ftutorials\u002Fmesh-intersection-curves-in-javascript.png","\u002Ftutorials\u002Fmesh-intersection-curves-in-javascript",{"title":5,"description":1335},"tutorials\u002Fmesh-intersection-curves-in-javascript",[1354,102,1355,1356,1357],"javascript","intersections","tutorial","webassembly","b3M_6xG_bWYvy5GlXqri_OxTAt8wvhkpfL6PyzE4dEI",1793517812008]