An editable non-manifold polygon mesh data structure

28 July 2025

Tags: Geometry Data Structures

One core data structure in my 3D modeling application is a non-manifold polygon mesh data structure. This data structure is a graph of Vertices, Edges and Faces. It takes inspiration from Blender’s BMesh data structure, the Radial Edge Structure (see Nvidia SMLib - Topology documentation) and the paper Partial Entity Structure: A Compact Boundary Representation for Non-Manifold Geometric Modeling by Lee et al., but arrives at a simpler design that still has the desired feature set.

The data structure supports:

The main goal of the data structure is to enable adjacency / topological queries:

Mesh

Vertex uses #

We wish to have fast insertion and deletion of elements, and no memory fragmentation. Therefore, a naive implementation such as:

1
2
3
struct Vertex {
  std::vector<Edge*> connected_edges;
};

would not be a good idea.

Therefore, for a given Vertex, we add a circularly linked list of VertexUses:

VertexUses

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// circularly linked list
struct VertexUse {
  Vertex* vertex;
  VertexUse* prev;
  VertexUse* next;
  Edge* connected_edge;
};
struct Vertex {
  VertexUse* uses;
  Point* point;
};

This way, we can iterate over all Edges of a given Vertex, and not have millions of separate instances of std::vector<Edge*>.

Edge uses #

This same approach is then also used for Edges, which get an accompanying circularly linked list of EdgeUses to answer the question “for a given edge, which faces are connected to it?”:

EdgeUses

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
struct EdgeUse {
  Edge* edge;
  EdgeUse* prev;
  EdgeUse* next;
  Face* connected_face;
};
struct Edge {
  EdgeUse* uses;
  VertexUse* v0;
  VertexUse* v1;
};

Face loop #

Then, the final task is to add the FaceLoop, which answers the question “for a given face, which vertices / edges make up the given face?”:

Note that we also store the vertex for a given FaceLoop segment, because v0 and v1 of an Edge are unordered.

FaceLoop

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// circularly linked list
struct FaceLoop {
  Face* face;
  FaceLoop* prev;
  FaceLoop* next;
  EdgeUse* edge;
  VertexUse* vertex;
};
struct Face {
  FaceLoop* loop;
};

Merging face loop and edge uses #

Because a given EdgeUse is only attached to a single face, we can merge the FaceLoop linked list into the EdgeUse, thus reducing the need for an additional element type in the graph:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
struct EdgeUse {
  Edge* edge;
  Face* attached_face;

  // radial (circularly linked list)
  EdgeUse* radial_prev;
  EdgeUse* radial_next;
  
  // face loop (circularly linked list)
  EdgeUse* loop_prev;
  EdgeUse* loop_next;
  Vertex* loop_vertex;
};

This gives us the final structure:

1
2
3
4
5
6
7
struct EditableMesh{
  Pool<Vertex> vertices;
  Pool<Edge> edges;
  Pool<Face> faces;
  Pool<VertexUse> vertex_uses;
  Pool<EdgeUse> edge_uses;
};

Conclusion #

This post presents a simple, performant non-manifold polygon mesh data structure, that enables topological / adjacency queries.