Skip to content

vertex_positions returns garbage (bit-reinterpreted doubles) for FBX files with double Vertices arrays #10

Description

@wdw89

Bug: vertex_positions returns garbage for FBX files with double Vertices arrays

pyufbx version: 0.0.7 (Windows, Python 3.13, installed from PyPI wheel)

Summary

When loading an FBX 7.4 binary file whose Vertices array uses type code d (double), mesh.vertex_positions returns garbage values (denormals / huge powers of two) instead of the actual coordinates. The topology (faces/indices) is correct — only vertex data is corrupted.

Root cause

In src/ufbx_wrapper.c L213-221:

const float* ufbx_wrapper_mesh_get_vertex_positions(const ufbx_mesh *mesh, size_t *out_count) {
    ...
    *out_count = mesh->vertex_position.values.count;
    return (const float*)mesh->vertex_position.values.data;   // ← blind cast
}

ufbx's ufbx_real defaults to double (float requires opt-in via UFBX_REAL_IS_FLOAT). If the extension is not compiled with UFBX_REAL_IS_FLOAT, values.data points to a double buffer, and casting it to const float* makes the Cython layer reinterpret the raw bytes as float32 — a bit-level reinterpretation, not a value conversion.

The same blind cast exists in the other vertex attribute getters (vertex_normals L223, vertex_uvs L233, vertex_tangents L243, vertex_bitangents L253).

Evidence (bit-exact)

Test file: Meshy-exported FBX, Vertices array = 15954 doubles, zlib-compressed.

  1. Manual parse of the array header [ArrayLength=15954][Encoding=1][CompressedLength=50087], zlib decompress, read as <f8 → 5318 vertices, all coordinates in ±0.5 (correct).
  2. ufbx output vs raw double bytes reinterpreted as float32: bit-for-bit identical (e.g. both produce 0x20000000 = 1.0842022e-19 for the first component; correct value is -0.367188).
  3. ufbx output vs correct values cast to float32: all 15954 components differ.

Bit-identical to a byte reinterpretation can only happen with a memcpy-style cast — a real double→float value conversion would produce values close to the correct ones.

Note: world_transform getters correctly use double*, which is why node transforms are fine — only the vertex attribute getters are affected.

Suggested fix

Either:

  • compile the extension with UFBX_REAL_IS_FLOAT (then the cast is correct and ufbx does the value conversion internally), or
  • keep ufbx_real as double and convert properly in the wrapper (copy into a float buffer, or expose the real element size).

The first option is the one-line fix if float32 precision is acceptable for the Python API.

Repro

import ufbx
import numpy as np, struct, zlib

scene = ufbx.load_file("model.fbx")  # FBX with double Vertices array
vp = np.array(scene.meshes[0].vertex_positions, dtype=np.float64)
print(np.abs(vp).max())   # expect ~0.5, get ~1e19 garbage for ~12% of components

Happy to provide the test FBX if useful.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions