# How the Meshoptimizer Index Codec Rotates Triangles for Better Compression

> Discover how the meshoptimizer index codec rotates triangles to optimize FIFO cache usage and achieve superior compression for your 3D models.

- Repository: [Arseny Kapoulkine/meshoptimizer](https://github.com/zeux/meshoptimizer)
- Tags: internals
- Published: 2026-07-12

---

**The meshoptimizer index codec rotates triangles so that the vertex most likely to exist in the FIFO cache appears first, reducing the number of bits required to encode each triangle.**

The meshoptimizer library provides high-performance algorithms for mesh optimization, including a sophisticated index buffer codec that achieves significant compression ratios by exploiting spatial and temporal coherence. At the heart of this codec lies a triangle rotation strategy that reorders vertex indices to maximize cache hits before encoding. This article examines the rotation mechanism implemented in [`src/indexcodec.cpp`](https://github.com/zeux/meshoptimizer/blob/main/src/indexcodec.cpp) and explains how it minimizes encoded output size.

## Triangle Rotation Logic in the Encoder

The codec processes triangles sequentially and attempts to predict which vertices have already been seen recently. When a triangle cannot be matched to an existing edge in the FIFO cache, the encoder applies a rotation heuristic to determine the optimal vertex ordering.

### The rotateTriangle Helper Function

In [`src/indexcodec.cpp`](https://github.com/zeux/meshoptimizer/blob/main/src/indexcodec.cpp) (lines 27-32), the static function `rotateTriangle` determines how many positions to rotate the triangle based on which vertex matches the expected next value:

```cpp
static int rotateTriangle(unsigned int a, unsigned int b, unsigned int c, unsigned int next)
{
    (void)a;
    return (b == next) ? 1 : (c == next ? 2 : 0);
}

```

This function compares the second vertex (`b`) and third vertex (`c`) against the `next` parameter, which represents the vertex the codec expects to process next. If `b` equals `next`, the function returns 1 (rotate once). If `c` equals `next`, it returns 2 (rotate twice). Otherwise, it returns 0 (no rotation). The first vertex `a` is explicitly unused in this calculation because the rotation logic prioritizes making either `b` or `c` the first vertex in the encoded output.

### The Rotation Table Mapping

The codec uses a static lookup table defined at line 97 to map rotation indices to vertex orderings:

```cpp
static const int rotations[] = {0, 1, 2, 0, 1};

```

This array allows the encoder to fetch the appropriate permutation indices without complex branching. When `rotateTriangle` returns a value, the encoder uses it as an offset into this table to determine which physical vertex indices correspond to the logical `a`, `b`, and `c` positions in the compression algorithm.

### Integration in the Encoding Loop

When the encoder encounters a triangle that does not match the edge FIFO (indicated by `fer < 0`), it falls back to the rotation path (lines 51-56 in [`src/indexcodec.cpp`](https://github.com/zeux/meshoptimizer/blob/main/src/indexcodec.cpp)):

```cpp
int rotation = rotateTriangle(indices[i + 0], indices[i + 1], indices[i + 2], next);
const int* order = rotations + rotation;

unsigned int a = indices[i + order[0]],
             b = indices[i + order[1]],
             c = indices[i + order[2]];

```

The encoder retrieves the rotation index, applies the mapping from the `rotations` array, and reorders the vertices accordingly. This ensures that the vertex most likely to be in the 16-entry vertex FIFO appears in the first position (`a`), while the subsequent vertices form an edge that may already exist in the edge FIFO.

## Why Rotation Improves Compression Efficiency

The triangle rotation strategy directly impacts three compression mechanisms in the codec. By maximizing the probability of cache hits, the encoder can omit or reduce the size of several data fields in the output stream.

### Vertex-FIFO Hit Optimization

The codec maintains a 16-entry vertex FIFO cache. When the first vertex of a triangle (`a`) matches the expected `next` value, the encoder can emit the triangle with `fea = 0`, indicating that no FIFO index needs to be encoded for the first vertex. This saves a 4-bit field in the triangle's code byte. Rotation ensures that the vertex continuing the current strip appears first, making this optimization applicable to the majority of triangles in well-optimized meshes.

### Edge-FIFO Reference Efficiency

After rotation, the edge formed by the first two vertices (`a-b`) is more likely to already exist in the edge FIFO. When this edge is found in the cache, the encoder can reference it using a small 4-bit index (`fe`) rather than encoding two full vertex indices. This reduces the per-triangle overhead from 6 bytes (three 16-bit indices) to potentially 1 byte (the code byte plus a 4-bit edge reference).

### Delta-Encoding Benefits

Vertices not present in the FIFO are delta-encoded relative to the previous vertex. By rotating the triangle so that the vertex continuing the strip appears first, the delta between consecutive vertices is often zero or a small integer. Since the codec uses variable-length encoding for these deltas, smaller values require fewer bits, further reducing the compressed size.

## Practical Implementation Example

The following example demonstrates how to use the meshoptimizer index codec, which automatically applies triangle rotation during encoding:

```cpp
#include "meshoptimizer.h"
#include <vector>
#include <cstdio>

int main()
{
    // Example index buffer (12 indices = 4 triangles)
    std::vector<unsigned int> indices = {
        0, 1, 2,
        2, 1, 3,   // rotated to make vertex 2 the next expected one
        3, 4, 5,
        5, 4, 6
    };

    // Allocate a buffer that is guaranteed to be large enough
    size_t bufSize = meshopt_encodeIndexBufferBound(indices.size(), 7);
    std::vector<unsigned char> buffer(bufSize);

    // Encode – the codec will rotate the second triangle automatically
    size_t encoded = meshopt_encodeIndexBuffer(buffer.data(), bufSize,
                                               indices.data(), indices.size());

    printf("Encoded %zu bytes out of %zu\n", encoded, bufSize);

    // Decode back to verify correctness
    std::vector<unsigned int> decoded(indices.size());
    int err = meshopt_decodeIndexBuffer(decoded.data(), decoded.size(),
                                        sizeof(unsigned int),
                                        buffer.data(), encoded);
    return err;
}

```

In this example, the second triangle (`2, 1, 3`) is automatically rotated by the codec so that vertex 2 (the expected `next` value) becomes the first vertex. This produces a more compact encoding than the original ordering would have allowed, demonstrating how the rotation logic operates transparently during the encoding process.

## Summary

- **The `rotateTriangle` function** in [`src/indexcodec.cpp`](https://github.com/zeux/meshoptimizer/blob/main/src/indexcodec.cpp) determines rotation by checking if the second or third vertex matches the expected next value, returning 0, 1, or 2 respectively.
- **The `rotations` array** maps these indices to vertex orderings, allowing the encoder to permute triangle vertices efficiently.
- **Rotation maximizes FIFO hits** by ensuring the vertex most likely to be in the 16-entry vertex cache appears first, often eliminating the need to encode a FIFO index.
- **Edge-FIFO utilization** improves because the rotated ordering makes the first edge more likely to exist in the edge cache, allowing 4-bit references instead of full vertex indices.
- **Delta compression** benefits from sequential vertices having smaller differences, which the variable-length encoding represents with fewer bits.

## Frequently Asked Questions

### What is the purpose of triangle rotation in mesh compression?

Triangle rotation reorders the vertices of each triangle so that the vertex most likely to already exist in the codec's FIFO cache appears in the first position. This maximizes cache hits and allows the encoder to omit or reduce the size of vertex references, significantly improving compression ratios for indexed geometry.

### How does the rotateTriangle function determine the rotation amount?

The function checks if the second vertex (`b`) equals the expected `next` value (returning 1), or if the third vertex (`c`) equals `next` (returning 2). If neither matches, it returns 0 for no rotation. This logic, implemented in lines 27-32 of [`src/indexcodec.cpp`](https://github.com/zeux/meshoptimizer/blob/main/src/indexcodec.cpp), prioritizes placing the continuing vertex from the mesh strip into the first position.

### What is the maximum rotation amount applied to a triangle?

The maximum rotation is 2 positions, which moves the third vertex to the first position. This is sufficient because the codec only needs to check which of the three vertices matches the expected next value; any of the three vertices can be rotated into the first position with at most two cyclic permutations.

### Where is the rotation logic implemented in the meshoptimizer source code?

The core rotation logic resides in [`src/indexcodec.cpp`](https://github.com/zeux/meshoptimizer/blob/main/src/indexcodec.cpp), specifically in the `rotateTriangle` helper function (lines 27-32) and the encoding loop (lines 51-56) that uses the static `rotations` array (line 97). The public API that exposes this functionality is defined in [`include/meshoptimizer.h`](https://github.com/zeux/meshoptimizer/blob/main/include/meshoptimizer.h) and implemented through `meshopt_encodeIndexBuffer`.