Skip to content

Mesh Data Structures

Blender uses two main data structures for meshes:

  • Mesh: The main data structure associated with the ID_ME data-block type. Focuses on performance with many elements. The rest of this document mainly describes this data structure.
  • BMesh: Edit mode data structure that prioritizes implementation of small topology-changing operations (mainly documented here).

Mesh

Meshes use data structures oriented around a struct of arrays paradigm. One idea behind that design is to decrease the amount of memory accessed in hot loops that generally only deal with a few data elements at a time. It also allows using generic algorithms that aren't necessarily only designed for mesh. For more background on the SoA paradigm in meshes, see the development design task.

Meshes support all generic attribute types.

Lazily Caches

In order to defer calculation of some runtime data until it's actually needed without duplicating work, there are a few lazily calculated caches stored on meshes. Some examples are:

  • Bounds: (BKE_mesh_minmax) The min and max position of the mesh
  • Vertex Normals: (Mesh.vert_normals()) The normal of every vertex. The same as the surrounding face normals mixed together, weighted by the corner angle.
  • Face Normals: (Mesh.poly_normals()) The normal direction of each face according to its winding direction.
  • Loose Edges: (Mesh.loose_edges()) Whether each edge is loose, and the total number of loose edges.

When a mesh is changed, these caches need to be removed or tagged dirty. Generally this is possible with functions like BKE_mesh_tag_positions_changed. Because the caches are stored with SharedCache (see BLI_shared_cache.hh), they are shared between meshes. If they aren't invalidated, calculating the cache on one mesh can make the result available on another too.

Topology Storage

At the core of Mesh there are a few arrays of basic integer data types that store the relationships between mesh elements in a single "top-down" direction. Faces reference face corners, and face corners reference vertices and edges. It's important to distinguish this topology mapping, the core "first principle" data that everything else is based off of from maps like "which face is this corner a part of?" that can always be recreated from the basic data.

  • Vertices: At the lowest level of the mesh "topology hierarchy", vertices are used to store various attributes like position. Conceptually though, a Vertex is all the attributes, it's just an index into all of the vertex domain attribute arrays.
  • Edges: Edges store the index of the two vertices they connect. Edges are more complex, since they have a tautological relationship with the references that each face corner has to the next edge in the face. Though storing edges explicitly is the only way to encode loose edges not connected to any faces, edges and corner edge indices must match the edges described implicitly by corner vertex references. Edges are often generated from scratch from these corner vertex indices (see bke::mesh_calc_edges).
  • Faces: Also known as "polygons", they are the highest level element in the core mesh topology. Given a specific face, it's easy to know which corners, vertices, and edges it's made out of. Faces are only stored as a range of corner indices. Because faces and face corners share the same order, each face can be stored as a single integer using OffsetIndices.

    OffsetIndices is a general abstraction for splitting a larger array into many contiguous groups. Every group is represented by a single integer-- the first index of the elements in the group. The end of the group is simply the next integer in the offsets array. For example, the face offsets [0,3,7,10,14] encode a triangle, a quad, a triangle, and a quad, in that order.

  • Face Corners: Corners are how faces reference edges and vertices. They aren't shared between faces, unlike edges and vertices. In the mesh arrays, each face corner knows the indices of the vertex it's attached to, and the index of the next edge clockwise around the face. In order to retrieve the next or previous corner, the face must be known, since the corners for each face loop around at the beginning/end.

For consistency, Mesh data arrays are usually retrieved and passed as arguments in the order below.

Span<float3> positions = mesh.vert_positions();
Span<int2> edges = mesh.edges();
OffsetIndices<int> faces = mesh.faces();
Span<int> corner_verts = mesh.corner_verts();
Span<int> corner_edges = mesh.corner_edges();

Patterns and Naming

Vertices in a Face

const Span<int> face_verts = corner_verts.slice(face);
for (const int vert : corner_verts.slice(face)) {
  ...
}

Edges in a Face

const Span<int> face_edges = corner_edges.slice(face);
for (const int edge : corner_edges.slice(face)) {
  ...
}

Corners in a Face

for (const int corner : face) {
  ...
}
for (const int corner : face) {
  const int corner_prev = mesh::face_corner_prev(face, corner);
  const int corner_next = mesh::face_corner_next(face, corner);
  ...
}

Normals

TODO

Namespaces

The blender::bke::mesh namespace is meant for common lower-level operations on mesh data. Public functions in that namespace shouldn't take Mesh as an argument, but rather raw data, in array form or just single elements. This functional style is meant to make it clear what data algorithms use and output, and help to guide APIs into a more understandable design. The purpose is similar to that of blender::bke::curves.