Open meshes
Meshes can be classified into two categories:
- Open - a surface or 3D shape where some edges are not connected to other faces, leaving gaps or holes in the structure.
- Closed - a 3D shape where all edges are fully connected to adjacent faces, forming a continuous surface with no gaps or holes.
For example, a sphere is a closed mesh, and a disk is an open mesh.
Sometimes, knowing which edges sit on the borders of an open mesh is required to implement geometry processing algorithms. The following is an example of how to identify open borders in a Jitter geometry using custom scripts.
Open or closed?
Let's fill a Jitter matrix with the vertex coordinates of a triangle, let's turn it into a Jitter geometry with jit.geom.togeom, and finally, let's convert it to a dictionary with jit.geom.todict.

A single triangle is, by definition, an open mesh. If you take a look at the content of the dictionary, there's an entry called "closed", which reports if the Jitter geometry is open or closed.

As expected, this Jitter geometry is open as closed, which is a boolean value [0; 1], says the shape isn't closed. We could imagine such a simple geometry structure as follows:

3 vertices (, , ) connected by 3 edges (, , ), with 3 halfedges pointing from one vertex to the next (, , ), and 1 face (). But if you take a closer look at the dictionary, something is not lining up as expected:

The halfedges_size reports not 3, but 6 halfedges. That is because of how Jitter geometry deals with open meshes:
In Jitter geometry structures, EVERY halfedge points to an opposite halfedge, even if there's no adjacent face. Let's try to follow the halfedges' pointers in the dictionary:

The halfedge of index points to an opposite halfedge of index . If you look at the face to which halfedge belongs, it reports a face of index . This indicates that halfedge , and thus halfedge , are at an open end of the mesh.
Note: The dictionary lists the geometry elements as counting from 1, but the geometry structure's pointers count from 0.
The geometry structure of our triangle looks, in fact, like this:

All halfedges at the open end of the mesh belong to a face of index . While this may sound complicated at first, it's actually a very effective way of knowing where the mesh's open border is.
Drawing the open edges of the mesh
Open the patch geom.draw.borders.maxpat

This patch takes a Jitter geometry and draws a line corresponding to the outer edge of an open mesh.
Double-click on v8 geom.draw.contours.js
to look at the script.
function dictionary(dict){
// Create a reference to the Max dictionary using the dictionary name
let d = new Dict(dict);
// Turn this into a JavaScript object
let fullGeometryDesc = JSON.parse(d.stringify());
// Get the first geometry
geom = fullGeometryDesc.geomlist[0];
// Quit if the geometry isn't open
if(geom.closed == 1){
post("this is not an open geometry!!", "\n");
return;
}
// Initialize an empty array to store the position of the border's vertices
let outline = [];
// Iterate through the halfedges
for(let h = geom.halfedges_size-1; h >= 0; h--){
// If this halfedge points to an unexisting face
if(geom.halfedges[h].face == -1){
// Get its endpoints
let v0 = geom.halfedges[h].from;
let v1 = geom.halfedges[h].to;
// And push them into the array
outline.push(geom.vertices[v0].point);
outline.push(geom.vertices[v1].point);
}
}
// Create an empty Jitter matrix
let mLines = new JitterMatrix(3, "float32", outline.length);
// Copy the vertex positions into the matrix
for(let i = outline.length-1; i >= 0; i--) mLines.setcell(i, "val", outline[i]);
// Output the Jitter matrix
outlet(0, "jit_matrix", mLines.name);
}
The script receives a dictionary containing the Jitter geometry and turns it into a JavaScript object. It then iterates through the halfedges, and if they point to a face of index , it pushes the endpoints of such a halfedge into an array. Finally, it copies the array with the position of the vertices into a Jitter matrix and outputs it.