# How to Use meshopt_generateVertexRemap with Custom Floating-Point Tolerance

> Learn how to use meshopt_generateVertexRemap with custom floating-point tolerance by utilizing meshopt_generateVertexRemapCustom and defining a comparison callback function for precise vertex remap control.

- Repository: [Arseny Kapoulkine/meshoptimizer](https://github.com/zeux/meshoptimizer)
- Tags: how-to-guide
- Published: 2026-07-11

---

**Use `meshopt_generateVertexRemapCustom` instead of the standard variant, providing a callback function that compares vertex Euclidean distance against your tolerance epsilon, and pass the tolerance value through the `context` parameter.**

The meshoptimizer library provides vertex deduplication through `meshopt_generateVertexRemap`, but this standard function requires binary-identical positions. When you need to merge vertices that are separated by small floating-point distances—such as welding duplicate vertices from disparate mesh sources or accounting for quantization errors—you must use `meshopt_generateVertexRemapCustom` with a custom equality callback.

## Understanding meshopt_generateVertexRemapCustom

The custom variant, declared in [`src/meshoptimizer.h#L88`](https://github.com/zeux/meshoptimizer/blob/master/src/meshoptimizer.h#L88), extends the standard remap logic by allowing you to supply a C function pointer that determines vertex equivalence.

```cpp
size_t meshopt_generateVertexRemapCustom(
    unsigned int* destination,
    const unsigned int* indices,
    size_t index_count,
    const float* vertex_positions,
    size_t vertex_count,
    size_t vertex_positions_stride,
    int (*callback)(void*, unsigned int, unsigned int),
    void* context);

```

**Key parameters:**
- **`destination`** – Pre-allocated array of size `vertex_count` to receive the remap table.
- **`vertex_positions`** – Pointer to packed float3 position data (XYZ tightly packed or interleaved).
- **`vertex_positions_stride`** – Byte offset between consecutive vertices (must be ≥ 12 and a multiple of 4).
- **`callback`** – Your custom function returning `1` if vertices should merge, `0` if distinct.
- **`context`** – Opaque pointer passed unchanged to every callback invocation (ideal for tolerance values).

According to the implementation in [`src/indexgenerator.cpp#L422`](https://github.com/zeux/meshoptimizer/blob/master/src/indexgenerator.cpp#L422), the algorithm first groups vertices by binary-identical positions to build candidate pairs, then invokes your callback to determine final equivalence. This mechanism lets you override the strict equality check with floating-point tolerance logic.

## Implementing a Floating-Point Tolerance Callback

The callback receives two vertex indices and your context pointer. To implement tolerance-based welding:

1. Cast the context pointer to access your epsilon value and vertex buffer pointer.
2. Calculate the memory offset for each vertex using `vertex_positions_stride`.
3. Compute the squared Euclidean distance between the two positions.
4. Return `1` if `distance² ≤ epsilon²`, otherwise return `0`.

**Avoid globals** by packing your tolerance, stride, and position pointer into a struct passed as `context`:

```cpp
struct WeldContext {
    float epsilon;           // Tolerance value (e.g., 0.001f)
    size_t strideBytes;      // Vertex stride in bytes
    const float* positions;  // Base pointer to position array
};

```

### Position Fetch Helper

Since `vertex_positions_stride` may include additional attributes (normals, UVs), calculate positions manually:

```cpp
static inline const float* getPosition(const float* base, size_t stride, unsigned int idx) {
    const unsigned char* ptr = reinterpret_cast<const unsigned char*>(base) + idx * stride;
    return reinterpret_cast<const float*>(ptr);
}

```

## Complete Working Example

Below is a self-contained C++ example that welds vertices closer than `0.001f` units. This pattern appears in the test suite at [`demo/tests.cpp#L1668`](https://github.com/zeux/meshoptimizer/blob/master/demo/tests.cpp#L1668).

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

struct CallbackContext {
    float epsilon;
    size_t strideBytes;
    const float* positions;
};

static inline const float* getPosition(const float* base, size_t stride, unsigned int idx) {
    const unsigned char* ptr = reinterpret_cast<const unsigned char*>(base) + idx * stride;
    return reinterpret_cast<const float*>(ptr);
}

// Callback: returns 1 if distance <= epsilon
static int toleranceCallback(void* rawCtx, unsigned int a, unsigned int b) {
    const CallbackContext* ctx = static_cast<const CallbackContext*>(rawCtx);
    const float* pa = getPosition(ctx->positions, ctx->strideBytes, a);
    const float* pb = getPosition(ctx->positions, ctx->strideBytes, b);
    
    float dx = pa[0] - pb[0];
    float dy = pa[1] - pb[1];
    float dz = pa[2] - pb[2];
    float dist2 = dx*dx + dy*dy + dz*dz;
    
    return dist2 <= ctx->epsilon * ctx->epsilon ? 1 : 0;
}

int main() {
    // Input: 6 vertices where v4 duplicates v1 and v5 duplicates v3
    const float positions[] = {
        0.0f, 0.0f, 0.0f,   // v0
        1.0f, 0.0f, 0.0f,   // v1
        0.0f, 1.0f, 0.0f,   // v2
        0.0f, 0.0f, 1.0f,   // v3
        1.0f, 0.0f, 0.0f,   // v4 (duplicate of v1)
        0.0f, 0.0f, 1.0f    // v5 (duplicate of v3)
    };
    const size_t vertexCount = 6;
    const size_t stride = sizeof(float) * 3;
    
    const unsigned int indices[] = {0,1,2, 0,3,4, 2,5,0};
    const size_t indexCount = 9;

    // Generate remap table with 0.001f tolerance
    std::vector<unsigned int> remap(vertexCount);
    CallbackContext ctx = { 0.001f, stride, positions };
    
    size_t uniqueCount = meshopt_generateVertexRemapCustom(
        remap.data(),
        indices,
        indexCount,
        positions,
        vertexCount,
        stride,
        toleranceCallback,
        &ctx);

    printf("Unique vertices: %zu\n", uniqueCount);
    
    // Compact vertex buffer
    std::vector<float> compacted(uniqueCount * 3);
    meshopt_remapVertexBuffer(compacted.data(), positions, vertexCount, stride, remap.data());
    
    // Compact index buffer
    std::vector<unsigned int> newIndices(indexCount);
    meshopt_remapIndexBuffer(newIndices.data(), indices, indexCount, remap.data());
    
    return 0;
}

```

## Compacting Buffers After Remapping

The remap table produced by `meshopt_generateVertexRemapCustom` maps old vertex indices to new compacted indices. To apply it:

- **`meshopt_remapVertexBuffer`** – Copies surviving unique vertices into a new compacted array.
- **`meshopt_remapIndexBuffer`** – Rewrites triangle indices to point to the new vertex locations.

Pass the same `remap` array to both functions to ensure consistency between your geometry and topology.

## Summary

- **`meshopt_generateVertexRemap`** requires exact binary matches, while **`meshopt_generateVertexRemapCustom`** accepts a callback for fuzzy equality.
- The callback receives vertex indices and a context pointer, enabling you to pass floating-point tolerances without global state.
- First group by binary-identical positions (handled internally), then use your callback to Weld vertices within the tolerance distance.
- Always follow remap generation with `meshopt_remapVertexBuffer` and `meshopt_remapIndexBuffer` to produce render-ready compacted buffers.
- Reference implementation details in [`src/indexgenerator.cpp`](https://github.com/zeux/meshoptimizer/blob/main/src/indexgenerator.cpp) and usage examples in [`demo/tests.cpp`](https://github.com/zeux/meshoptimizer/blob/main/demo/tests.cpp).

## Frequently Asked Questions

### What is the difference between `meshopt_generateVertexRemap` and `meshopt_generateVertexRemapCustom`?

`meshopt_generateVertexRemap` deduplicates only vertices with byte-identical position values, suitable for indexed meshes with exact duplicates. `meshopt_generateVertexRemapCustom` adds a callback parameter that lets you define custom equivalence—such as floating-point tolerance or attribute-aware matching—giving you precise control over the welding process.

### Can I use `meshopt_generateVertexRemap` with a tolerance parameter directly?

No. The standard function has no tolerance parameter. You must use `meshopt_generateVertexRemapCustom` and supply your own callback function that implements the distance check, as shown in the example above.

### How do I pass the tolerance value to the callback without global variables?

Pack the tolerance into a struct along with your vertex buffer pointer and stride, then pass a pointer to that struct as the `context` argument. The library forwards this pointer to every callback invocation, allowing thread-safe, reentrant code with multiple different tolerances.

### What value should I use for `vertex_positions_stride`?

Set it to the byte offset between consecutive vertex positions. For tightly packed float3 data, use `sizeof(float) * 3` (12 bytes). For interleaved vertex formats containing additional attributes (normals, UVs), use the full struct size (e.g., `sizeof(Vertex)`), ensuring the value is at least 12 and divisible by 4.