[{"data":1,"prerenderedAt":1251},["ShallowReactive",2],{"algo-mesh-booleans":3},{"id":4,"title":5,"author":6,"body":7,"date":1224,"description":1225,"extension":1226,"hub":441,"keywords":1227,"meta":1238,"navigation":441,"ogImage":1239,"path":1240,"published":441,"seo":1241,"stem":1244,"tags":1245,"updated":1249,"__hash__":1250},"algorithms\u002Falgorithms\u002Fmesh-booleans.md","Mesh booleans","Žiga Sajovic",{"type":8,"value":9,"toc":1210},"minimark",[10,20,23,28,37,41,46,54,59,62,75,78,82,91,94,97,103,106,110,116,119,130,169,172,191,197,201,204,235,241,245,255,261,267,271,274,278,300,307,311,314,372,376,379,383,387,390,486,494,497,520,528,532,535,1094,1106,1110,1193,1198,1202,1206],[11,12,13,14,19],"p",{},"Subtract the Stanford dragon from itself and the answer should be nothing. It is a hard test. Every face of one operand lies on a face of the other, so every decision lands exactly on zero. This page puts ",[15,16,18],"a",{"href":17},"\u002Ftrueform","trueform"," through that test and three more that break booleans. Then it says what trueform asks of your meshes and how fast it runs.",[11,21,22],{},"A mesh boolean returns the union (A ∪ B), the intersection (A ∩ B) or the difference (A ∖ B) of solids stored as surface meshes. It also goes by CSG, mesh subtraction and 3D boolean operations. Below, the dragon is A and a sphere is B. Every output face is coloured by the operand it came from.",[24,25],"image-grid",{":eager":26,":images":27},"true","[{\"src\":\"\u002Fimg\u002Falgorithms\u002Fbooleans\u002Fbooleans-op-input.webp\",\"label\":\"Input\",\"detail\":\"A, the dragon · B, a sphere\",\"alt\":\"The Stanford dragon in teal with a translucent orange sphere through its body\",\"width\":900,\"height\":900},{\"src\":\"\u002Fimg\u002Falgorithms\u002Fbooleans\u002Fbooleans-op-union.webp\",\"label\":\"Union\",\"detail\":\"A ∪ B\",\"alt\":\"Union: the dragon and the sphere fused into one closed surface, dragon faces teal, sphere faces orange\",\"width\":900,\"height\":900},{\"src\":\"\u002Fimg\u002Falgorithms\u002Fbooleans\u002Fbooleans-op-intersection.webp\",\"label\":\"Intersection\",\"detail\":\"A ∩ B\",\"alt\":\"Intersection: only the part of the dragon inside the sphere, capped by orange pieces of the sphere\",\"width\":900,\"height\":900},{\"src\":\"\u002Fimg\u002Falgorithms\u002Fbooleans\u002Fbooleans-op-difference.webp\",\"label\":\"Difference\",\"detail\":\"A ∖ B\",\"alt\":\"Difference: the dragon with a spherical bite taken out of its body, the bite walled by orange sphere faces\",\"width\":900,\"height\":900}]",[11,29,30,31,36],{},"Each output is one closed mesh. The orange faces are pieces of the sphere: in the union they are its outside, in the intersection and the difference they wall the cut. Run booleans on a dragon in your browser in the ",[15,32,35],{"href":33,"external":34},"\u002Ftrueform\u002Flive\u002Fboolean","","live example",". Each language has a full tutorial.",[38,39],"hub-language-cards",{":items":40},"[{\"lang\":\"C++\",\"icon\":\"i-simple-icons-cplusplus\",\"title\":\"Fast Mesh Booleans in C++\",\"to\":\"\u002Ftutorials\u002Ffast-mesh-booleans-in-cpp\",\"detail\":\"Header-only. Views over your own buffers, precomputed structures, curves, int64 precision.\"},{\"lang\":\"Python\",\"icon\":\"i-simple-icons-python\",\"title\":\"Fast Mesh Booleans in Python\",\"to\":\"\u002Ftutorials\u002Ffast-mesh-booleans-in-python\",\"detail\":\"One pip install. NumPy arrays in, NumPy arrays out.\"},{\"lang\":\"JavaScript\",\"icon\":\"i-simple-icons-typescript\",\"title\":\"Fast Mesh Booleans in JavaScript\",\"to\":\"\u002Ftutorials\u002Ffast-mesh-booleans-in-javascript\",\"detail\":\"WebAssembly. The same API in Node.js and the browser, with async variants.\"},{\"lang\":\"C++ · N meshes\",\"icon\":\"i-simple-icons-cplusplus\",\"title\":\"Fast Variadic Mesh Booleans in C++\",\"to\":\"\u002Ftutorials\u002Ffast-variadic-mesh-booleans-in-cpp\",\"detail\":\"One arrangement over N meshes. Any boolean expression read from it.\"}]",[42,43,45],"h2",{"id":44},"four-inputs-that-break-booleans","Four inputs that break booleans",[11,47,48,49,53],{},"A boolean is easy when two surfaces cross cleanly. It is hard where they touch, coincide or cross themselves. The four tests below aim at exactly that. We ran them with trueform 0.10.8 from pip, in Python. The data is the public Stanford dragon, or shapes made from it. Run your own hard cases too; ",[50,51,52],"code",{},"pip install trueform"," is one line.",[55,56,58],"h3",{"id":57},"the-same-mesh-twice","The same mesh twice",[11,60,61],{},"The dragon has 244,026 triangles. Use it as both operands. Every face now sits on a face of the other mesh, edge on edge and corner on corner.",[63,64,65,69,72],"ul",{},[66,67,68],"li",{},"A ∪ A returns the dragon: 244,026 triangles, closed.",[66,70,71],{},"A ∩ A returns the dragon: 244,026 triangles, closed.",[66,73,74],{},"A ∖ A returns an empty mesh.",[11,76,77],{},"Nothing is nudged to make the case generic. Each contact is classified for what it is, and coincident faces count as coincident.",[55,79,81],{"id":80},"flush-faces-everywhere","Flush faces everywhere",[11,83,84],{},[85,86],"img",{"alt":87,"height":88,"src":89,"width":90},"A voxel copy of the Stanford dragon minus the same voxel dragon moved by one voxel. Teal faces come from the first operand, orange faces from the moved copy, and every face is axis-aligned",800,"\u002Fimg\u002Falgorithms\u002Fbooleans\u002Fbooleans-coplanar.webp",1200,[11,92,93],{},"We turned the dragon into voxels: a closed mesh of 46,268 triangles, every vertex on a unit grid. Then we moved a copy by one voxel along each axis. Every face of one operand now lies in a plane of the other. Edges run along edges and corners land on corners. The voxel surface also has 45 non-manifold edges, where two voxels meet along an edge and nothing more.",[11,95,96],{},"This input has a known answer. Count the voxels. The volume of each output mesh must equal the count exactly.",[98,99],"data-table",{":headers":100,":rows":101,":highlight":102},"[\"Operation\",\"Voxels (expected)\",\"Volume of the output\",\"Closed\"]","[[\"Union\",\"55,743\",\"55,743.000\",\"yes\"],[\"Intersection\",\"42,473\",\"42,473.000\",\"yes\"],[\"Difference\",\"6,635\",\"6,635.000\",\"yes\"]]","2",[11,104,105],{},"The picture is the difference. The teal faces are what is left of the first dragon. The orange faces are walls of the moved copy, now cut into the first. The two colours meet only along shared edges and shared planes.",[55,107,109],{"id":108},"a-mesh-that-crosses-itself","A mesh that crosses itself",[11,111,112],{},[85,113],{"alt":114,"height":88,"src":115,"width":90},"Two overlapping Stanford dragons stored as one mesh, one copy teal and one sand, with the curves where the mesh crosses itself in pink","\u002Fimg\u002Falgorithms\u002Fbooleans\u002Fbooleans-self-input.webp",[11,117,118],{},"Real files often hold two overlapping shells as one mesh. Inside and outside are then ambiguous, and a boolean expression needs them. There are two ways through, both in the CSG module.",[11,120,121,125,126,129],{},[122,123,124],"strong",{},"Repair, then combine."," ",[50,127,128],{},"outer_shell"," keeps only the faces that bound the outside and drops everything buried in the overlap. The shell is a valid operand for any boolean.",[131,132,136],"pre",{"className":133,"code":134,"language":135,"meta":34,"style":34},"language-python shiki shiki-themes github-dark","shell = tf.outer_shell(mesh)   # C++: tf::make_outer_shell, JavaScript: tf.outerShell\nresult = tf.boolean_difference(shell, other)\n","python",[50,137,138,158],{"__ignoreMap":34},[139,140,143,147,151,154],"span",{"class":141,"line":142},"line",1,[139,144,146],{"class":145},"s95oV","shell ",[139,148,150],{"class":149},"snl16","=",[139,152,153],{"class":145}," tf.outer_shell(mesh)   ",[139,155,157],{"class":156},"sAwPA","# C++: tf::make_outer_shell, JavaScript: tf.outerShell\n",[139,159,161,164,166],{"class":141,"line":160},2,[139,162,163],{"class":145},"result ",[139,165,150],{"class":149},[139,167,168],{"class":145}," tf.boolean_difference(shell, other)\n",[11,170,171],{},"Two dragons in one mesh: 488,052 triangles that cross themselves along the pink curves. The shell has 444,856 triangles. It is closed and crosses itself nowhere.",[11,173,174,177,178,181,182,185,186,190],{},[122,175,176],{},"Keep the overlap as a region."," Build a CSG graph with ",[50,179,180],{},"within"," on. Each operand is then also cut against itself, and ",[50,183,184],{},"domains()"," returns the overlap pocket as its own closed cell. Pick the cells you need from there. Boolean expressions on the graph still read each operand as one solid, so for an expression, repair first. ",[15,187,189],{"href":188},"\u002Falgorithms\u002Fcsg-for-engineering","CSG for engineering"," works through both.",[11,192,193],{},[85,194],{"alt":195,"height":88,"src":196,"width":90},"The outer shell of the two overlapping dragons: one clean closed surface, with the former crossing curves in pink as creases","\u002Fimg\u002Falgorithms\u002Fbooleans\u002Fbooleans-outer-shell.webp",[55,198,200],{"id":199},"an-open-surface","An open surface",[11,202,203],{},"A plane, a terrain or a scanned patch bounds no volume. Declare it a sheet and it becomes a valid operand. It cuts a solid, and difference and intersection return the two halves, each capped and closed. A unit box cut by a plane gives two halves of volume 0.5.",[131,205,207],{"className":133,"code":206,"language":135,"meta":34,"style":34},"(faces, points), labels, face_labels = tf.boolean_difference(box, plane, sheets=[1])\n",[50,208,209],{"__ignoreMap":34},[139,210,211,214,216,219,223,225,228,232],{"class":141,"line":142},[139,212,213],{"class":145},"(faces, points), labels, face_labels ",[139,215,150],{"class":149},[139,217,218],{"class":145}," tf.boolean_difference(box, plane, ",[139,220,222],{"class":221},"s9osk","sheets",[139,224,150],{"class":149},[139,226,227],{"class":145},"[",[139,229,231],{"class":230},"sDLfK","1",[139,233,234],{"class":145},"])\n",[11,236,237,238,240],{},"Many sheets cutting many solids at once are the subject of ",[15,239,189],{"href":188},".",[42,242,244],{"id":243},"why-these-cases-hold","Why these cases hold",[11,246,247,250,251,240],{},[122,248,249],{},"Exact decisions."," Every geometric decision runs in integer arithmetic. Float coordinates go onto an int32 lattice, double coordinates onto int64. Each predicate is evaluated in integers wide enough to be exact. A sign test returns minus, zero or plus. Zero means touching, not \"close to touching\". The method is written up in ",[15,252,254],{"href":253},"\u002Falgorithms\u002Ffast-and-exact-mesh-booleans-and-arrangements","Fast and Exact Mesh Booleans and Arrangements",[11,256,257,260],{},[122,258,259],{},"Five kinds of contact."," Two triangles can meet vertex to vertex, vertex to edge, edge to edge, vertex to face or edge to face. The default mode states each contact as one of the five. It does not perturb them away. That is why the identical and voxel tests come out exact. Coincident faces are classified as facing the same way or opposite ways.",[11,262,263,266],{},[122,264,265],{},"One arrangement, read once."," A boolean is the two-mesh case of trueform's CSG graph. It splits both meshes along the curves where they cross, classifies every piece against the other mesh, and keeps the pieces the operation asks for.",[42,268,270],{"id":269},"how-other-libraries-handle-a-touch","How other libraries handle a touch",[11,272,273],{},"The hard cases above all land on exact contact. Libraries take different roads there, and each road has a price. The descriptions come from each library's own documentation.",[98,275],{":headers":276,":rows":277},"[\"Approach\",\"Example\",\"At an exact touch\"]","[[\"Floating point with symbolic perturbation\",\"Manifold\",\"Ties are broken by a fixed nudge that depends on the operation, so touching cubes merge and a mesh minus itself is empty. Its stated aim is manifold output, with geometry valid to within a small ε.\"],[\"Corefinement with an exact kernel\",\"CGAL\",\"Contacts are resolved exactly. Inputs must bound a volume and not cross themselves, and the output must be manifold: two cubes that share only an edge cannot be united this way.\"],[\"Floating point with Simulation of Simplicity\",\"MeshLib, mesh mode\",\"Degenerate contacts are resolved by a symbolic perturbation: an exact touch is decided as if one surface had moved by an infinitesimal amount, so it reads as a crossing or a gap, not as a touch. This is the mode in the speed comparison below.\"],[\"Voxels\",\"MeshLib, voxel mode\",\"Both solids become a grid, the operation runs on the grid, and a surface is extracted. Detail is bounded by the voxel size.\"],[\"Integer lattice, contact stated as is\",\"trueform\",\"Coordinates go onto an integer lattice once and every predicate is exact. A touch is classified as one of five contacts and kept, not nudged, so coincident faces stay coincident.\"]]",[11,279,280,281,287,288,293,294,299],{},"Sources: Manifold's ",[15,282,286],{"href":283,"rel":284},"https:\u002F\u002Fgithub.com\u002Felalish\u002Fmanifold\u002Fwiki\u002FManifold-Library",[285],"nofollow","library notes",", CGAL's ",[15,289,292],{"href":290,"rel":291},"https:\u002F\u002Fdoc.cgal.org\u002Flatest\u002FPMP_Boolean_operations\u002Findex.html",[285],"Boolean Operations on Meshes"," manual and MeshLib's ",[15,295,298],{"href":296,"rel":297},"https:\u002F\u002Fmeshlib.io\u002Ffeature\u002Fmesh-boolean\u002F",[285],"mesh boolean"," page, which describes both of its modes. Speed is a separate question, answered below.",[11,301,302,303,240],{},"trueform also takes the voxel road when it fits. Its volume module samples closed meshes into signed distance fields and combines them by union, intersection and difference. A field boolean is bounded by the grid's spacing; use it for offsets, blends and inputs that must be resampled. See ",[15,304,306],{"href":305},"\u002Falgorithms\u002Fcsg-for-engineering#fields-buffers-blends-and-slices","fields on the CSG page",[42,308,310],{"id":309},"preparing-operands","Preparing operands",[11,312,313],{},"Each condition below has a call that handles it, so a pipeline can run on files as they arrive.",[63,315,316,325,337,346,356,362],{},[66,317,318,321,322,240],{},[122,319,320],{},"Faces that disagree on their winding."," An operand must say where its inside is. Orient it first with ",[50,323,324],{},"orient_faces_consistently",[66,326,327,330,331,333,334,336],{},[122,328,329],{},"An operand that overlaps itself."," Repair it with ",[50,332,128],{},", or keep the overlap as a region with a ",[50,335,180],{}," CSG graph, as above.",[66,338,339,342,343,345],{},[122,340,341],{},"An open surface."," List it in ",[50,344,222],{}," and it cuts as a sheet. An open mesh not listed is read as a volume.",[66,347,348,351,352,355],{},[122,349,350],{},"Coordinates far apart."," Pass double coordinates, or ask for ",[50,353,354],{},"tf::exact::int64"," in C++. Decisions then run on the int64 lattice. The API is the same.",[66,357,358,361],{},[122,359,360],{},"CAD solids."," B-rep and NURBS go in tessellated, as triangle meshes.",[66,363,364,367,368,371],{},[122,365,366],{},"A long chain of booleans."," The points created where two surfaces cross are written back in your input's type, float in, float out, and the next boolean reads them as fresh input. Build one ",[15,369,370],{"href":188},"CSG graph"," instead: one arrangement over every operand, every result read from it.",[42,373,375],{"id":374},"how-fast","How fast",[11,377,378],{},"The same corpus runs in every language: 1,000 pairs of solid Thingi10K meshes, 200K to 1.5M polygons per pair, each pair randomly rotated and overlapped. Arrays in, arrays out. Apple M4 Max, 16 threads, trueform 0.9.8.",[98,380],{":headers":381,":rows":382,":highlight":231},"[\"Language\",\"trueform median\",\"Compared against\",\"Fastest on\"]","[[\"C++ (native)\",\"15.7 ms\",\"MeshLib 5.5×, Manifold 7.5×, Solidean 9.2×, CGAL 31×, Geogram 105× (geomean)\",\"1000 \u002F 1000\"],[\"Python\",\"18.0 ms\",\"MeshLib 4.9×, Manifold 6.9× (geomean)\",\"1000 \u002F 1000\"],[\"Browser (WebAssembly)\",\"21.9 ms\",\"Manifold WASM 12.6×, three-bvh-csg 41.2× (geomean)\",\"1000 \u002F 1000\"]]",[384,385],"hub-benchmark-figures",{"category":386},"mesh-booleans",[11,388,389],{},"Repeated booleans on the same mesh go faster still. Hold the meshes and move the operand with a transformation. The first boolean builds the spatial tree and the topology; every later one reuses them.",[131,391,393],{"className":133,"code":392,"language":135,"meta":34,"style":34},"dragon = tf.Mesh(*tf.read_stl(\"dragon.stl\"))\ntool = tf.Mesh(*tf.read_stl(\"tool.stl\"))\n\nfor T in poses:                  # 4×4 transformations\n    tool.transformation = T      # no copy, no rebuild\n    (faces, points), labels, face_labels = tf.boolean_difference(dragon, tool)\n",[50,394,395,418,436,443,461,475],{"__ignoreMap":34},[139,396,397,400,402,405,408,411,415],{"class":141,"line":142},[139,398,399],{"class":145},"dragon ",[139,401,150],{"class":149},[139,403,404],{"class":145}," tf.Mesh(",[139,406,407],{"class":149},"*",[139,409,410],{"class":145},"tf.read_stl(",[139,412,414],{"class":413},"sU2Wk","\"dragon.stl\"",[139,416,417],{"class":145},"))\n",[139,419,420,423,425,427,429,431,434],{"class":141,"line":160},[139,421,422],{"class":145},"tool ",[139,424,150],{"class":149},[139,426,404],{"class":145},[139,428,407],{"class":149},[139,430,410],{"class":145},[139,432,433],{"class":413},"\"tool.stl\"",[139,435,417],{"class":145},[139,437,439],{"class":141,"line":438},3,[139,440,442],{"emptyLinePlaceholder":441},true,"\n",[139,444,446,449,452,455,458],{"class":141,"line":445},4,[139,447,448],{"class":149},"for",[139,450,451],{"class":145}," T ",[139,453,454],{"class":149},"in",[139,456,457],{"class":145}," poses:                  ",[139,459,460],{"class":156},"# 4×4 transformations\n",[139,462,464,467,469,472],{"class":141,"line":463},5,[139,465,466],{"class":145},"    tool.transformation ",[139,468,150],{"class":149},[139,470,471],{"class":145}," T      ",[139,473,474],{"class":156},"# no copy, no rebuild\n",[139,476,478,481,483],{"class":141,"line":477},6,[139,479,480],{"class":145},"    (faces, points), labels, face_labels ",[139,482,150],{"class":149},[139,484,485],{"class":145}," tf.boolean_difference(dragon, tool)\n",[11,487,488,489,493],{},"The ",[15,490,492],{"href":491},"\u002Ftutorials\u002Ffast-mesh-booleans-in-cpp","C++ tutorial"," runs ten booleans on two 500K-polygon dragons this way, with the tree and topology built once.",[11,495,496],{},"The protocols, corpus IDs and per-library notes are in the three comparisons:",[63,498,499,506,513],{},[66,500,501,505],{},[15,502,504],{"href":503},"\u002Falgorithms\u002Fbenchmarking-mesh-boolean-libraries-2026","Mesh Boolean Library Comparison and Benchmarks at Industry Scale — 2026",": eight native libraries.",[66,507,508,512],{},[15,509,511],{"href":510},"\u002Falgorithms\u002Fpython-mesh-boolean-libraries-2026","Python Mesh Boolean Library Comparison and Benchmarks — 2026",": three pip-installable libraries.",[66,514,515,519],{},[15,516,518],{"href":517},"\u002Falgorithms\u002Fbrowser-mesh-boolean-libraries-2026","Browser Mesh Boolean Library Comparison and Benchmarks — 2026",": three client-side libraries, with a watertightness count.",[11,521,522,523,527],{},"Every trueform benchmark, by module and language, is on the ",[15,524,526],{"href":525},"\u002Ftrueform\u002Fbenchmarks","trueform benchmarks"," page.",[42,529,531],{"id":530},"the-call","The call",[11,533,534],{},"Load a mesh, offset a copy, take the union. The copy shares the original's data and carries only a transformation. Intersection and difference are one word away.",[536,537,538,735,894],"code-group",{},[131,539,544],{"className":540,"code":541,"filename":542,"language":543,"meta":34,"style":34},"language-cpp shiki shiki-themes github-dark","#include \u003Ctrueform\u002Ftrueform.hpp>\n\nauto dragon_buffer = tf::read_stl(\"stanford_dragon.stl\");\nauto dragon = dragon_buffer.polygons();\n\nauto T = tf::make_transformation_from_translation(\n    tf::make_vector(0.f, 1.f, 0.f));\nauto translated = dragon | tf::tag(T);\n\n\u002F\u002F tf::boolean_op::intersection, tf::boolean_op::left_difference\nauto [result_buffer, labels, face_labels] = tf::make_boolean(\n    dragon, translated, tf::boolean_op::merge);\n","C++","cpp",[50,545,546,554,558,587,605,609,627,663,688,693,699,718],{"__ignoreMap":34},[139,547,548,551],{"class":141,"line":142},[139,549,550],{"class":149},"#include",[139,552,553],{"class":413}," \u003Ctrueform\u002Ftrueform.hpp>\n",[139,555,556],{"class":141,"line":160},[139,557,442],{"emptyLinePlaceholder":441},[139,559,560,563,566,568,572,575,578,581,584],{"class":141,"line":438},[139,561,562],{"class":149},"auto",[139,564,565],{"class":145}," dragon_buffer ",[139,567,150],{"class":149},[139,569,571],{"class":570},"svObZ"," tf",[139,573,574],{"class":145},"::",[139,576,577],{"class":570},"read_stl",[139,579,580],{"class":145},"(",[139,582,583],{"class":413},"\"stanford_dragon.stl\"",[139,585,586],{"class":145},");\n",[139,588,589,591,594,596,599,602],{"class":141,"line":445},[139,590,562],{"class":149},[139,592,593],{"class":145}," dragon ",[139,595,150],{"class":149},[139,597,598],{"class":145}," dragon_buffer.",[139,600,601],{"class":570},"polygons",[139,603,604],{"class":145},"();\n",[139,606,607],{"class":141,"line":463},[139,608,442],{"emptyLinePlaceholder":441},[139,610,611,613,615,617,619,621,624],{"class":141,"line":477},[139,612,562],{"class":149},[139,614,451],{"class":145},[139,616,150],{"class":149},[139,618,571],{"class":570},[139,620,574],{"class":145},[139,622,623],{"class":570},"make_transformation_from_translation",[139,625,626],{"class":145},"(\n",[139,628,630,633,635,638,640,643,646,649,652,654,656,658,660],{"class":141,"line":629},7,[139,631,632],{"class":570},"    tf",[139,634,574],{"class":145},[139,636,637],{"class":570},"make_vector",[139,639,580],{"class":145},[139,641,642],{"class":230},"0.",[139,644,645],{"class":149},"f",[139,647,648],{"class":145},", ",[139,650,651],{"class":230},"1.",[139,653,645],{"class":149},[139,655,648],{"class":145},[139,657,642],{"class":230},[139,659,645],{"class":149},[139,661,662],{"class":145},"));\n",[139,664,666,668,671,673,675,678,680,682,685],{"class":141,"line":665},8,[139,667,562],{"class":149},[139,669,670],{"class":145}," translated ",[139,672,150],{"class":149},[139,674,593],{"class":145},[139,676,677],{"class":149},"|",[139,679,571],{"class":570},[139,681,574],{"class":145},[139,683,684],{"class":570},"tag",[139,686,687],{"class":145},"(T);\n",[139,689,691],{"class":141,"line":690},9,[139,692,442],{"emptyLinePlaceholder":441},[139,694,696],{"class":141,"line":695},10,[139,697,698],{"class":156},"\u002F\u002F tf::boolean_op::intersection, tf::boolean_op::left_difference\n",[139,700,702,704,707,709,711,713,716],{"class":141,"line":701},11,[139,703,562],{"class":149},[139,705,706],{"class":145}," [result_buffer, labels, face_labels] ",[139,708,150],{"class":149},[139,710,571],{"class":570},[139,712,574],{"class":145},[139,714,715],{"class":570},"make_boolean",[139,717,626],{"class":145},[139,719,721,724,727,729,732],{"class":141,"line":720},12,[139,722,723],{"class":145},"    dragon, translated, ",[139,725,726],{"class":570},"tf",[139,728,574],{"class":145},[139,730,731],{"class":570},"boolean_op",[139,733,734],{"class":145},"::merge);\n",[131,736,739],{"className":133,"code":737,"filename":738,"language":135,"meta":34,"style":34},"import numpy as np\nimport trueform as tf\n\ndragon = tf.Mesh(*tf.read_stl(\"stanford_dragon.stl\"))\n\ntranslated = dragon.shared_view()\nT = np.eye(4, dtype=np.float32)\nT[:3, 3] = [0.0, 1.0, 0.0]\ntranslated.transformation = T\n\n# tf.boolean_intersection, tf.boolean_difference\n(result_faces, result_points), labels, face_labels = \\\n    tf.boolean_union(dragon, translated)\n","Python",[50,740,741,755,767,771,787,791,801,824,859,869,873,878,888],{"__ignoreMap":34},[139,742,743,746,749,752],{"class":141,"line":142},[139,744,745],{"class":149},"import",[139,747,748],{"class":145}," numpy ",[139,750,751],{"class":149},"as",[139,753,754],{"class":145}," np\n",[139,756,757,759,762,764],{"class":141,"line":160},[139,758,745],{"class":149},[139,760,761],{"class":145}," trueform ",[139,763,751],{"class":149},[139,765,766],{"class":145}," tf\n",[139,768,769],{"class":141,"line":438},[139,770,442],{"emptyLinePlaceholder":441},[139,772,773,775,777,779,781,783,785],{"class":141,"line":445},[139,774,399],{"class":145},[139,776,150],{"class":149},[139,778,404],{"class":145},[139,780,407],{"class":149},[139,782,410],{"class":145},[139,784,583],{"class":413},[139,786,417],{"class":145},[139,788,789],{"class":141,"line":463},[139,790,442],{"emptyLinePlaceholder":441},[139,792,793,796,798],{"class":141,"line":477},[139,794,795],{"class":145},"translated ",[139,797,150],{"class":149},[139,799,800],{"class":145}," dragon.shared_view()\n",[139,802,803,806,808,811,814,816,819,821],{"class":141,"line":629},[139,804,805],{"class":145},"T ",[139,807,150],{"class":149},[139,809,810],{"class":145}," np.eye(",[139,812,813],{"class":230},"4",[139,815,648],{"class":145},[139,817,818],{"class":221},"dtype",[139,820,150],{"class":149},[139,822,823],{"class":145},"np.float32)\n",[139,825,826,829,832,834,836,839,841,844,847,849,852,854,856],{"class":141,"line":665},[139,827,828],{"class":145},"T[:",[139,830,831],{"class":230},"3",[139,833,648],{"class":145},[139,835,831],{"class":230},[139,837,838],{"class":145},"] ",[139,840,150],{"class":149},[139,842,843],{"class":145}," [",[139,845,846],{"class":230},"0.0",[139,848,648],{"class":145},[139,850,851],{"class":230},"1.0",[139,853,648],{"class":145},[139,855,846],{"class":230},[139,857,858],{"class":145},"]\n",[139,860,861,864,866],{"class":141,"line":690},[139,862,863],{"class":145},"translated.transformation ",[139,865,150],{"class":149},[139,867,868],{"class":145}," T\n",[139,870,871],{"class":141,"line":695},[139,872,442],{"emptyLinePlaceholder":441},[139,874,875],{"class":141,"line":701},[139,876,877],{"class":156},"# tf.boolean_intersection, tf.boolean_difference\n",[139,879,880,883,885],{"class":141,"line":720},[139,881,882],{"class":145},"(result_faces, result_points), labels, face_labels ",[139,884,150],{"class":149},[139,886,887],{"class":145}," \\\n",[139,889,891],{"class":141,"line":890},13,[139,892,893],{"class":145},"    tf.boolean_union(dragon, translated)\n",[131,895,900],{"className":896,"code":897,"filename":898,"language":899,"meta":34,"style":34},"language-ts shiki shiki-themes github-dark","import * as tf from \"@polydera\u002Ftrueform\";\n\nconst response = await fetch(\"stanford_dragon.stl\");\nconst bytes = new Uint8Array(await response.arrayBuffer());\nconst dragon = tf.readStl(bytes);\n\nconst translated = dragon.shallowCopy();\ntranslated.transformation = tf.makeTranslation(0, 1, 0);\n\n\u002F\u002F tf.async.booleanIntersection, tf.async.booleanDifference\nconst { mesh, labels, faceLabels } =\n    await tf.async.booleanUnion(dragon, translated);\n","JavaScript","ts",[50,901,902,924,928,951,980,998,1002,1019,1045,1049,1054,1080],{"__ignoreMap":34},[139,903,904,906,909,912,915,918,921],{"class":141,"line":142},[139,905,745],{"class":149},[139,907,908],{"class":230}," *",[139,910,911],{"class":149}," as",[139,913,914],{"class":145}," tf ",[139,916,917],{"class":149},"from",[139,919,920],{"class":413}," \"@polydera\u002Ftrueform\"",[139,922,923],{"class":145},";\n",[139,925,926],{"class":141,"line":160},[139,927,442],{"emptyLinePlaceholder":441},[139,929,930,933,936,939,942,945,947,949],{"class":141,"line":438},[139,931,932],{"class":149},"const",[139,934,935],{"class":230}," response",[139,937,938],{"class":149}," =",[139,940,941],{"class":149}," await",[139,943,944],{"class":570}," fetch",[139,946,580],{"class":145},[139,948,583],{"class":413},[139,950,586],{"class":145},[139,952,953,955,958,960,963,966,968,971,974,977],{"class":141,"line":445},[139,954,932],{"class":149},[139,956,957],{"class":230}," bytes",[139,959,938],{"class":149},[139,961,962],{"class":149}," new",[139,964,965],{"class":570}," Uint8Array",[139,967,580],{"class":145},[139,969,970],{"class":149},"await",[139,972,973],{"class":145}," response.",[139,975,976],{"class":570},"arrayBuffer",[139,978,979],{"class":145},"());\n",[139,981,982,984,987,989,992,995],{"class":141,"line":463},[139,983,932],{"class":149},[139,985,986],{"class":230}," dragon",[139,988,938],{"class":149},[139,990,991],{"class":145}," tf.",[139,993,994],{"class":570},"readStl",[139,996,997],{"class":145},"(bytes);\n",[139,999,1000],{"class":141,"line":477},[139,1001,442],{"emptyLinePlaceholder":441},[139,1003,1004,1006,1009,1011,1014,1017],{"class":141,"line":629},[139,1005,932],{"class":149},[139,1007,1008],{"class":230}," translated",[139,1010,938],{"class":149},[139,1012,1013],{"class":145}," dragon.",[139,1015,1016],{"class":570},"shallowCopy",[139,1018,604],{"class":145},[139,1020,1021,1023,1025,1027,1030,1032,1035,1037,1039,1041,1043],{"class":141,"line":665},[139,1022,863],{"class":145},[139,1024,150],{"class":149},[139,1026,991],{"class":145},[139,1028,1029],{"class":570},"makeTranslation",[139,1031,580],{"class":145},[139,1033,1034],{"class":230},"0",[139,1036,648],{"class":145},[139,1038,231],{"class":230},[139,1040,648],{"class":145},[139,1042,1034],{"class":230},[139,1044,586],{"class":145},[139,1046,1047],{"class":141,"line":690},[139,1048,442],{"emptyLinePlaceholder":441},[139,1050,1051],{"class":141,"line":695},[139,1052,1053],{"class":156},"\u002F\u002F tf.async.booleanIntersection, tf.async.booleanDifference\n",[139,1055,1056,1058,1061,1064,1066,1069,1071,1074,1077],{"class":141,"line":701},[139,1057,932],{"class":149},[139,1059,1060],{"class":145}," { ",[139,1062,1063],{"class":230},"mesh",[139,1065,648],{"class":145},[139,1067,1068],{"class":230},"labels",[139,1070,648],{"class":145},[139,1072,1073],{"class":230},"faceLabels",[139,1075,1076],{"class":145}," } ",[139,1078,1079],{"class":149},"=\n",[139,1081,1082,1085,1088,1091],{"class":141,"line":720},[139,1083,1084],{"class":149},"    await",[139,1086,1087],{"class":145}," tf.async.",[139,1089,1090],{"class":570},"booleanUnion",[139,1092,1093],{"class":145},"(dragon, translated);\n",[11,1095,1096,1097,1099,1100,648,1102,1105],{},"Every call returns three things. The first is the output mesh. The second, ",[50,1098,1068],{},", says which operand each output face came from: 0 or 1. The third maps each output face to its source face, so colours, materials and other attributes follow the geometry. Ask for curves too, and the call also returns the polylines where the two surfaces cross. Install with ",[50,1101,52],{},[50,1103,1104],{},"npm install @polydera\u002Ftrueform",", or CMake, conan or nuget for C++.",[42,1107,1109],{"id":1108},"where-to-go-next","Where to go next",[63,1111,1112,1131,1139,1154,1177],{},[66,1113,1114,125,1117,648,1119,648,1122,1125,1126,1130],{},[122,1115,1116],{},"Tutorials.",[15,1118,542],{"href":491},[15,1120,738],{"href":1121},"\u002Ftutorials\u002Ffast-mesh-booleans-in-python",[15,1123,898],{"href":1124},"\u002Ftutorials\u002Ffast-mesh-booleans-in-javascript",", and ",[15,1127,1129],{"href":1128},"\u002Ftutorials\u002Ffast-variadic-mesh-booleans-in-cpp","variadic booleans in C++"," for one arrangement over many meshes.",[66,1132,1133,125,1136,1138],{},[122,1134,1135],{},"Many meshes, sheets and domains.",[15,1137,189],{"href":188},": hundreds of operands cut into domains, with regions selected by expression or mask.",[66,1140,1141,1144,1145,1149,1150,240],{},[122,1142,1143],{},"Related operations."," The crossing curves on their own: ",[15,1146,1148],{"href":1147},"\u002Ftutorials\u002Fmesh-intersection-curves-in-cpp","mesh intersection curves",". Only whether two meshes touch: ",[15,1151,1153],{"href":1152},"\u002Falgorithms\u002Fmesh-collision-detection","collision detection",[66,1155,1156,1159,1160,648,1164,1168,1169,1174,1175,240],{},[122,1157,1158],{},"Documentation."," The CSG module in ",[15,1161,542],{"href":1162,"rel":1163},"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fmodules\u002Fcsg",[285],[15,1165,738],{"href":1166,"rel":1167},"https:\u002F\u002Ftrueform.polydera.com\u002Fpy\u002Fmodules\u002Fcsg",[285]," and ",[15,1170,1173],{"href":1171,"rel":1172},"https:\u002F\u002Ftrueform.polydera.com\u002Fts\u002Fmodules\u002Fcsg",[285],"TypeScript",". Run a boolean in your browser in the ",[15,1176,35],{"href":33,"external":34},[66,1178,1179,125,1182,1186,1187,1192],{},[122,1180,1181],{},"Research.",[1183,1184,1185],"em",{},"Real-Time Mesh Booleans that Commute with Mesh Idealization",", Žiga Sajovic and Dejan Knez, 2025 (",[15,1188,1191],{"href":1189,"rel":1190},"https:\u002F\u002Fdoi.org\u002F10.36227\u002Ftechrxiv.174667714.42575478\u002Fv1",[285],"doi",").",[11,1194,1195,1196,240],{},"Booleans are one module of trueform. The same engine does spatial queries, topology, remeshing, isocontours, geodesics and more. See ",[15,1197,18],{"href":17},[1199,1200],"article-cta",{":buttons":1201},"[{\"label\":\"trueform\",\"to\":\"\u002Ftrueform\",\"icon\":\"i-lucide-cpu\",\"variant\":\"soft\",\"color\":\"neutral\"},{\"label\":\"GitHub\",\"to\":\"https:\u002F\u002Fgithub.com\u002Fpolydera\u002Ftrueform\",\"icon\":\"i-simple-icons-github\",\"variant\":\"soft\",\"color\":\"neutral\"},{\"label\":\"Documentation\",\"to\":\"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fmodules\u002Fcsg\",\"variant\":\"soft\",\"color\":\"neutral\"},{\"label\":\"Try it live\",\"to\":\"\u002Ftrueform\u002Flive\u002Fboolean\",\"icon\":\"i-lucide-play\"}]",[1203,1204],"cite-as",{"author":1205,"title":5},"Sajovic, {\\v{Z}}iga",[1207,1208,1209],"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 .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}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 .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}",{"title":34,"searchDepth":160,"depth":160,"links":1211},[1212,1218,1219,1220,1221,1222,1223],{"id":44,"depth":160,"text":45,"children":1213},[1214,1215,1216,1217],{"id":57,"depth":438,"text":58},{"id":80,"depth":438,"text":81},{"id":108,"depth":438,"text":109},{"id":199,"depth":438,"text":200},{"id":243,"depth":160,"text":244},{"id":269,"depth":160,"text":270},{"id":309,"depth":160,"text":310},{"id":374,"depth":160,"text":375},{"id":530,"depth":160,"text":531},{"id":1108,"depth":160,"text":1109},"2026-11-08","Union, intersection and difference of 3D meshes, tested on the inputs that break booleans: identical operands, flush faces, non-manifold edges, meshes that cross themselves. Then the speed, in C++, Python and JavaScript.","md",[1228,298,1229,1230,1231,1232,1233,1234,1235,1236,1237],"mesh booleans","mesh boolean library","robust mesh boolean","exact mesh booleans","coplanar mesh boolean","CSG","mesh union","mesh intersection","mesh difference","mesh subtraction",{},"\u002Fimg\u002Fog\u002Falgorithms\u002Fmesh-booleans.png","\u002Falgorithms\u002Fmesh-booleans",{"title":1242,"description":1243},"Mesh Booleans: Union, Intersection and Difference","Mesh booleans tested on hard input: identical, flush, non-manifold and self-intersecting meshes. Union, intersection and difference in C++, Python and JS.","algorithms\u002Fmesh-booleans",[1246,1247,1248],"booleans","csg","overview","2026-11-12T12:06:28.000Z","w66LXlVAGI_7IkndGBJLheYRiN56PSZYpVyfY8JJgNY",1794486339528]