
Research
/Security News
Chrome and Firefox Extensions Posing as Free VPNs Add Clipboard Stealers via Malicious Updates
Malicious Chrome and Firefox extensions posed as free VPNs while stealing clipboard data through later extension updates.
trimesh-boolean
Advanced tools
Triangle mesh boolean operations — supports open surfaces, terrain intersection, and mesh repair
Triangle mesh boolean operations for JavaScript — supports open surfaces.
Unlike every other mesh boolean package in the npm ecosystem, trimesh-boolean works on open (non-watertight) meshes like terrain surfaces, DTMs, and partial shells. It also works on closed solids.
| Package | Open surfaces? | Approach |
|---|---|---|
| three-bvh-csg | No | BVH-accelerated BSP |
| three-csg-ts | No | BSP tree (TS) |
| manifold-3d | No | WASM C++ |
| trimesh-boolean | Yes | Moller intersection + fan triangulation + shared Steiner points + hybrid boundary/barrier-normal classification |
Every existing package requires closed, manifold input. If you're working with terrain surfaces, geological models, or any open mesh — they won't work. trimesh-boolean will.
npm install trimesh-boolean
Or use the CDN for browser scripts:
<script src="https://unpkg.com/trimesh-boolean/build/trimesh-boolean.min.js"></script>
<!-- Exposes window.TrimeshBoolean -->
Links: npm · GitHub · Live Demo
The live demo is built and deployed automatically on every push to main or master by .github/workflows/pages.yml (npm run build:docs in CI). You do not need to commit the docs/ folder for the site to update.
One-time repo setting: GitHub → Settings → Pages → Build and deployment → Source: GitHub Actions (not “Deploy from a branch”). After that, each push runs the workflow and refreshes the demo.
To preview the same build locally:
npm run build:docs
Edit demos only under examples/ (Vite root). Local dev: npm run dev.
import { boolean, splitMeshPair, mergeSplitGroups,
splitToComponents, mergeSmallComponents, mergeComponents,
selectSplits, repairMesh, intersectMeshPair } from 'trimesh-boolean';
// Triangle soup: simple {v0, v1, v2} objects
var meshA = [
{ v0: {x:0,y:0,z:0}, v1: {x:2,y:0,z:0}, v2: {x:1,y:2,z:0} },
// ... more triangles
];
var meshB = [/* ... */];
// One-shot boolean operation
var result = boolean(meshA, meshB, 'subtract');
// result = { soup: Triangle[], points: Vertex[], triangles: WeldedTri[] }
// Split-and-pick workflow (inspect groups before merging)
var split = splitMeshPair(meshA, meshB);
// split.groups = { aInside, aOutside, bInside, bOutside }
// split.segments = TaggedSegment[]
var merged = mergeSplitGroups(split.groups, 'union');
// Custom surface workflow — user picks individual components
var comps = splitToComponents(split.groups);
// comps = [{ mesh:"A", side:"inside", index:0, soup:[...], triCount:599 }, ...]
comps = mergeSmallComponents(comps, 50); // collapse tiny fragments
var picked = mergeComponents([
{ soup: comps[0].soup }, // keep A-inside #0
{ soup: comps[2].soup, flip: true } // keep B-outside #0, flip normals
]);
// ── BMS Pipeline (v0.5.0) — hybrid classification + per-component walks ──
import { bmsBooleanOp } from 'trimesh-boolean';
var bms = bmsBooleanOp(meshA, meshB, null, { preRepair: true });
// bms.groups = { aInside, aOutside, bInside, bOutside }
// bms.segments = pool-vertex intersection segments
// bms.polylines = chained intersection polylines
// bms.meshEdgePolys = { A: {...}, B: {...} } — per-mesh boundary polygons
// bms.componentWalks = per-component boundary walk segments
// bms.pool = shared vertex pool
// BMS + splitToComponents for per-region analysis
var comps = splitToComponents(bms.groups);
// 9 components for double-crossing terrain + convoluted block
// Just intersection segments (no boolean)
var segments = intersectMeshPair(meshA, meshB);
// segments = [{ p0: {x,y,z}, p1: {x,y,z} }, ...]
// Mesh repair
var repaired = await repairMesh(meshA, {
closeMode: 'stitch',
snapTolerance: 0.01
});
import { booleanFromMeshes, meshToSoup, soupToMesh } from 'trimesh-boolean/three';
// Boolean on Three.js meshes directly
var resultMesh = booleanFromMeshes(threeGroupA, threeGroupB, 'subtract');
scene.add(resultMesh);
// Or convert manually
var soup = meshToSoup(threeMesh);
var mesh = soupToMesh(soup, { color: 0xff0000 });
boolean(soupA, soupB, operation)Perform a boolean operation on two triangle soups.
Triangle[] — arrays of { v0, v1, v2 } where each vertex is { x, y, z }"subtract" | "union" | "intersect"{ soup, points, triangles } or nullsplitMeshPair(soupA, soupB)Split two meshes into 4 inside/outside groups without combining them. This is the "split-and-pick" workflow: compute groups, then the caller decides which to keep.
Triangle[]{ groups: { aInside, aOutside, bInside, bOutside }, segments } or nullmergeSplitGroups(groups, operation)Merge split groups into a single result based on the operation type.
{ aInside, aOutside, bInside, bOutside } from splitMeshPair"subtract" | "union" | "intersect"{ soup, points, triangles } or nullsplitToComponents(groups)Decompose each of the 4 split groups into connected components (disconnected mesh regions). Useful for multi-crossing surfaces where a single group contains multiple spatially separated zones.
{ aInside, aOutside, bInside, bOutside } from splitMeshPair[{ mesh: "A"|"B", side: "inside"|"outside", index: number, soup: Triangle[], triCount: number }, ...] — sorted largest-first within each groupmergeSmallComponents(comps, threshold?)Merge small fragment components into their nearest same-group (mesh + side) larger component by centroid proximity. Cleans up tiny classification artifacts.
splitToComponents50)mergeComponents(picks)Merge an arbitrary selection of component soups into a single welded result. Works with the output of splitToComponents — pass in the components the user has selected.
[{ soup: Triangle[], flip?: boolean }, ...]{ soup, points, triangles } or nullselectSplits(groups, selections)Select specific split groups by name, with optional normal flipping.
{ aInside, aOutside, bInside, bOutside } from splitMeshPair{ aInside?: boolean|"flip", aOutside?: boolean|"flip", bInside?: boolean|"flip", bOutside?: boolean|"flip" }{ soup, points, triangles } or nullThe BMS (Brent's Mega Soup) pipeline is a boolean pipeline designed for open surfaces. It solves the core problems of the original pipeline: shared Steiner points, identity-based segment chaining, fan triangulation with guaranteed constraint edges, and hybrid classification (v0.5.0).
bmsBooleanOp(soupA, soupB, operation?, options?)Run the full BMS pipeline. Both meshes are split into a unified mega soup where intersection points are shared by object reference (not string matching).
"subtract" | "union" | "intersect" — omit to get groups onlyboolean — resolve T-junctions + weld before splittingnumber — vertex pool merge tolerance{ groups, segments, polylines, meshEdgePolys, componentWalks, megaSoup, pool }bmsIntersect(trisA, trisB, options?)Compute intersections with a shared vertex pool. Every segment endpoint goes through the pool — both meshes get the exact same object reference at each intersection location.
bmsSplit(trisA, trisB, intersectResult)Re-triangulate crossed triangles using fan triangulation with pool vertex references. Produces a tagged mega soup: [{v0, v1, v2, mesh: "A"|"B", origIdx}].
bmsChain(segments)Chain intersection segments using pool vertex identity (integer ID lookup, not distance threshold). Two segments sharing a pool vertex connect by definition.
bmsClosePolylines(polylines, trisA, trisB, megaSoup?, segments?)Build mesh edge polygons connecting intersection polylines via graph-walks and boundary edge-walks. Graph-walks avoid crossing intersection lines (barrier-aware BFS).
bmsClassify(megaSoup, closedPolylines, segments, trisA, trisB)Hybrid classification (v0.5.0). Barrier flood-fill produces connected components per mesh. Classification method depends on mesh type:
Also extracts per-component boundary walk segments (componentWalks) for visualization.
createVertexPool(tolerance)Create a shared vertex pool with spatial hash deduplication. Points within tolerance get merged to the same object reference.
intersectMeshPair(trisA, trisB)Find all intersection segments between two meshes.
Segment[] — [{ p0, p1 }, ...]intersectMeshPairTagged(trisA, trisB)Like intersectMeshPair but each segment carries source triangle indices.
[{ p0, p1, idxA, idxB }, ...]repairMesh(soup, config?, onProgress?)High-level async mesh repair pipeline.
"none" | "weld" | "stitch" (default: "none")0)1.0)true)0.01)true)false)1e-4)Promise<{ soup, points, triangles }>Individual repair steps, usable standalone:
| Function | Description |
|---|---|
deduplicateSeamVertices(tris, tol?) | Merge coincident seam vertices |
resolveTJunctions(soup, tol?, maxPasses?) | Split edges at T-junction vertices |
weldVertices(tris, tolerance) | Merge vertices within tolerance → indexed mesh |
weldedToSoup(weldedTris) | Convert indexed mesh back to soup |
removeDegenerateTriangles(tris, minArea?, sliverRatio?) | Remove zero-area and sliver triangles |
extractBoundaryLoops(tris) | Find open boundary loops |
triangulateLoop(loop) | Triangulate a 3D polygon loop |
capBoundaryLoops(tris) | Cap all boundary loops |
stitchByProximity(tris, tolerance?) | Connect nearby boundary edges |
cleanCrossingTriangles(tris) | Remove over-shared edge duplicates |
removeOverlappingTriangles(tris, tol?) | Remove anti-parallel internal walls |
forceCloseIndexedMesh(points, triangles) | Force-close an indexed mesh |
fillOpenEdgeLoops(soup) | Fill closed loops of open edges with fan triangles |
weldBoundaryVertices(tris, tolerance) | Weld boundary-only vertices |
| Function | Description |
|---|---|
findConnectedComponents(soup) | Split a soup into connected components via shared edges |
splitToComponents(groups) | Decompose 4 split groups into per-component list |
mergeSmallComponents(comps, threshold?) | Merge small fragments into nearest same-group neighbor |
mergeComponents(picks) | Merge user-selected component soups into welded result |
selectSplits(groups, selections) | Select specific groups with optional flip |
| Function | Description |
|---|---|
triNormal(tri) | Unit face normal of a triangle |
ensureZUpNormals(tris) | Flip downward-facing triangles to Z-up |
flipAllNormals(tris) | Reverse all triangle winding |
classifyNormalDirection(tris, isClosed, volume) | Classify dominant normal direction |
computeSignedVolume(tris) | Signed volume via divergence theorem |
computeProjectedArea(tris, plane) | Projected footprint area |
compute3DSurfaceArea(tris) | True 3D surface area |
| Function | Description |
|---|---|
triTriIntersection(triA, triB) | Moller tri-tri intersection → segment |
triTriIntersectionDetailed(triA, triB) | With signed distances |
chainSegments(segments, threshold) | Chain segments into polylines |
simplifyPolyline(points, spacing) | Distance-based simplification |
buildSpatialGrid(tris, cellSize) | Build XY spatial hash (for Z-ray) |
buildSpatialGridOnAxes(tris, cellSize, getA, getB) | Build spatial hash on arbitrary axes |
queryGrid(grid, bb, cellSize) | Query XY spatial hash |
queryGridOnAxes(grid, a, b, cellSize) | Query arbitrary-axis spatial hash |
| Function | Description |
|---|---|
classifyPointMultiAxis(point, otherTris, grids) | Classify inside/outside via 3-axis majority vote |
classifyByFloodFill(tris, crossedMap, otherTris, otherGrids) | BFS flood-fill classification with multi-axis seeds |
fanTriangulate(tri, segments) | Fan triangulation of crossed triangle (primary method) |
retriangulateWithSteinerPoints(tri, segments) | CDT split of crossed triangle with Steiner points (fallback) |
buildCurtainAndCap(tris, floorOffset) | Extrude boundary to floor + cap |
generateClosingTriangles(tris, maxDist) | Iteratively close boundary gaps |
Internal (non-exported) functions used by the boolean pipeline:
| Function | Description |
|---|---|
splitStraddlingAndClassify(tris, classifications, crossedMap, otherTris, otherGrids, otherIdxKey) | Split crossed tris via fan triangulation, classify via border-segment priority + half-space fallback, enforce constraints across segment edges |
halfSpaceTest(point) | Classify a point against the nearest intersection segment using the other mesh's triangle normal |
segHalfSpace(point, seg) | Classify a point against a specific segment's other-mesh triangle plane |
| Function | Description |
|---|---|
dist3(a, b) | 3D Euclidean distance |
distSq3(a, b) | Squared 3D distance |
triangleArea3D(tri) | Triangle area via cross product |
computeBounds(points) | Axis-aligned bounding box |
cross(a, b) | 3D cross product |
lerpVert(a, b, t) | Linear vertex interpolation |
countOpenEdges(tris) | Count boundary and non-manifold edges |
The library includes real-world mining surface data from the Kirra application. Kirra surfaces use the format { vertices: [{x,y,z}, ...] } per triangle. To convert to trimesh-boolean soup:
// Convert Kirra triangles to trimesh-boolean soup
function kirraToSoup(surface) {
var soup = [];
for (var i = 0; i < surface.triangles.length; i++) {
var verts = surface.triangles[i].vertices;
if (verts && verts.length >= 3) {
soup.push({ v0: verts[0], v1: verts[1], v2: verts[2] });
}
}
return soup;
}
Pre-extracted Kirra surfaces (terrain, cylinder, cup, convoluted block) are included in examples/public/kirra-surfaces.json with UTM coordinates centroid-subtracted for demo use. The convoluted block is a 32-triangle open surface that crosses the terrain twice — the hardest test case for multi-crossing classification.
The Three.js demo (examples/index.html) also has Import KAP / Export KAP: import reads surfaces.json from a Kirra .kap ZIP into the Mesh A/B dropdowns; export writes a minimal Kirra-compatible archive (manifest + two surfaces for the current Mesh A and B) for round-tripping or testing in Kirra.
The library uses plain JavaScript objects — no classes, no Three.js types:
// Vertex
{ x: number, y: number, z: number }
// Triangle (soup format)
{ v0: Vertex, v1: Vertex, v2: Vertex }
// Welded triangle (indexed format)
{ vertices: [Vertex, Vertex, Vertex] }
// Segment
{ p0: Vertex, p1: Vertex }
Find all triangle-triangle intersection segments between mesh A and mesh B using the Moller algorithm.
idxA, idxB)crossedMap — which triangles are "crossed" by the other meshBuild 3 spatial hash grids per mesh (XY, YZ, XZ) for accelerated ray casting along Z, X, and Y axes.
2x the average edge length of each meshBFS from non-crossed seed triangles to classify connected regions as inside/outside via multi-axis majority vote.
Split every crossed triangle at its intersection segment endpoints using fan triangulation (CDT fallback for edge cases).
Classify every triangle (non-crossed and crossed sub-triangles) using a multi-stage approach:
Step D0 — Border-segment priority: If a crossed sub-triangle shares an edge with an intersection segment, classify its centroid directly against that specific segment's other-mesh triangle plane. This prevents "nearest segment" from picking a wrong crossing in multi-crossing scenarios.
Steps D1-D4 — Fallback chain: half-space test against nearest segment, vertex adjacency inheritance, sub-triangle centroid half-space, ray-cast centroid (last resort).
Step E3 — Constraint enforcement: After initial classification and adjacency propagation, iterate through all segment edges. Sub-triangles sharing a segment edge MUST have opposite classifications (one inside, one outside). Any violations are corrected using the specific segment's plane.
Merge coincident vertices along the intersection boundary to produce a clean seam.
1e-4Ensure consistent winding order across the result mesh.
Combine inside/outside groups based on the requested operation.
| Operation | Formula | Groups kept |
|---|---|---|
subtract | A \ B | A-outside-B + B-inside-A (flipped) |
union | A ∪ B | A-outside-B + B-outside-A |
intersect | A ∩ B | A-inside-B + B-inside-A |
Weld the combined triangle soup into an indexed mesh and return { soup, points, triangles }.
Optional peer dependency:
>=0.150.0 — Only needed for trimesh-boolean/three adapterMIT
FAQs
Triangle mesh boolean operations — supports open surfaces, terrain intersection, and mesh repair
The npm package trimesh-boolean receives a total of 66 weekly downloads. As such, trimesh-boolean popularity was classified as not popular.
We found that trimesh-boolean demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
/Security News
Malicious Chrome and Firefox extensions posed as free VPNs while stealing clipboard data through later extension updates.

Research
/Security News
Miasma Mini Shai-Hulud hits @immobiliarelabs Backstage plugins, targeting GitLab and LDAP auth packages on npm.

Security News
Rolldown paused Rust React Compiler integration after a 5MB binary size increase raised concerns about shipping React-specific code to all Vite users.