A character's face in a modern film is a polygon mesh of tens of thousands of vertices, and every
frame the rig has to ask questions about neighbours: which faces touch this vertex?
(for skinning weights), which edges border this face? (for subdivision), which vertices
surround this one? (for smoothing). Those are the queries that
This page is about how a mesh is stored. We start with the obvious way — a list of vertices
and a list of faces — see exactly why it makes adjacency queries slow, then build the half-edge
(doubly-connected edge list) structure that makes local traversal
The simplest storage is two arrays. A vertex list holds the positions,
It is compact and perfect for drawing — the GPU wants precisely this. But it stores geometry,
not connectivity. Ask "which faces touch vertex 0?" and there is no pointer to
follow: you must scan every face and test whether it mentions index 0. That is an
The fix is to store the connectivity explicitly, and the elegant way to do it is the half-edge structure (also called the doubly-connected edge list, or DCEL). The key move: take every edge shared by two faces and split it into two directed half-edges, one for each side, pointing in opposite directions. Each half-edge belongs to exactly one face and runs along one boundary of it.
Notice there is no explicit "list of neighbours" anywhere — the neighbours are reachable by
following pointers, and because every pointer is prev pointer (handy but derivable by following
next around the face).
Below are two triangles sharing one edge. That shared edge is stored as two half-edges
drawn as arrows pointing opposite ways: the upper arrow belongs to face A and runs one
direction; its twin belongs to face B and runs the other. Follow either arrow's
next and you walk around its own face; jump across via twin and you
step onto the neighbour. The arrows are offset slightly off the true edge so you can see both.
This is the whole trick made visible: an undirected edge becomes an oriented boundary segment
of each face, and the two segments know about each other through twin. Orientation is
consistent — every face is wound the same way (say counter-clockwise seen from outside), so a twin
pair always disagrees on direction. That consistency is what makes the traversals below work.
1. Walk every edge of a face. Start at any half-edge of the face and keep following
next. Because next stays on the same face and cycles, you return to the
start after visiting each border exactly once:
2. Visit every face around a vertex (its one-ring). This is the query the naive
structure could not answer cheaply. Around a vertex you alternate twin then
next (or next.twin, depending on your origin convention) to pivot from one
incident face to the next, sweeping around the vertex like the spokes of a fan:
Each iteration is two pointer hops, and the loop runs once per incident face — so the cost is
proportional to the local valence of the vertex (typically 4–6), not to the size of the whole
mesh. That
You could — it is called an adjacency list, and it answers "who are my neighbours?" in
The whole structure rests on an assumption: the mesh is an orientable 2-manifold. That means every edge is shared by at most two faces, and all faces are wound consistently so twin pairs point opposite ways. Break that and the pointers break:
twin — which of the other faces is "the" opposite side?twin.next walk sweeps the wrong way or loops
forever.
Boundaries are fine — an edge on the mesh border simply has a twin whose
face is a special "hole"/null face (or the twin is marked as boundary), so the walk still
works. But genuine non-manifold geometry and bad winding must be cleaned first: merge
or split the offending edges, and unify the orientation, before you build the half-edge structure.
Half-edge is the most common choice, but it has relatives that make the same "store the connectivity" bargain differently:
For animation — deformation, subdivision, editing — half-edge's directed, orientation-carrying edges make the one-ring and face walks branch-free, which is why it dominates in practice.