BabylonJS/Babylon.js · error

The

Error message

The 

What it means

VertexData._validate checks that each vertex attribute array (normals, uvs, tangents, colors, etc.) has a length that is an exact multiple of the attribute's per-vertex stride computed by VertexBufferDeduceStride (e.g. 3 for normals, 2 for UVs). A non-multiple means per-vertex reads would be misaligned, so Babylon.js refuses to proceed. This fires inside getElementCount while merging VertexData via VertexData.merge or Mesh.mergeMeshes.

Source

Thrown at packages/dev/core/src/Meshes/mesh.vertexData.ts:1285

                for (let i = 0; i < vertexData.length; i++) {
                    ret[offset + i] = vertexData[i];
                }
                transform && transformRange(ret, transform, offset, vertexData.length);
                offset += vertexData.length;
            }
            return ret;
        }
    }

    private _validate(): void {
        if (!this.positions) {
            throw new RuntimeError("Positions are required", ErrorCodes.MeshInvalidPositionsError);
        }

        const getElementCount = (kind: string, values: FloatArray) => {
            const stride = VertexBufferDeduceStride(kind);
            if (values.length % stride !== 0) {
                throw new Error("The " + kind + "s array count must be a multiple of " + stride);
            }

            return values.length / stride;
        };

        const positionsElementCount = getElementCount(VertexBuffer.PositionKind, this.positions);

        const validateElementCount = (kind: string, values: FloatArray) => {
            const elementCount = getElementCount(kind, values);
            if (elementCount !== positionsElementCount) {
                throw new Error("The " + kind + "s element count (" + elementCount + ") does not match the positions count (" + positionsElementCount + ")");
            }
        };

        if (this.normals) {
            validateElementCount(VertexBuffer.NormalKind, this.normals);
        }
        if (this.tangents) {

View on GitHub (pinned to 0592b347b8)

Solutions

  1. Fix the offending attribute so its length is a multiple of the expected stride (3 for positions/normals/tangents, 2 for uvs, 4 for colors) — the kind is named in the full error message.
  2. Trim the array to Math.floor(values.length / stride) * stride elements so it aligns per vertex.
  3. Ensure all attributes describe the same vertex count as positions; slice all of them with the same vertex range.
  4. Re-export/re-generate the source data to rule out padding or trailing bytes from an external file or buffer.
  5. Validate each input VertexData individually (check positions.length/3 vs each attribute length/stride) before merging.

Example fix

// before
const vertexData = new VertexData();
vertexData.positions = positions;            // 900 floats (300 vertices)
vertexData.normals = normals.slice(0, 1000); // 1000 floats -> throws
// after
vertexData.normals = normals.slice(0, 900);  // 900 = 300 * 3
Defensive patterns

Strategy: validation

Validate before calling

const STRIDES = { positions: 3, normals: 3, uvs: 2, uvs2: 2, tangents: 3, colors: 4 };
function validateVertexData(vd) {
  for (const [kind, stride] of Object.entries(STRIDES)) {
    const arr = vd[kind];
    if (arr && arr.length % stride !== 0) {
      throw new Error(kind + '.length (' + arr.length + ') not a multiple of ' + stride);
    }
  }
  return true;
}
validateVertexData(vertexData);
const merged = VertexData.Merge([vd1, vd2]);

Type guard

function isStrideAligned(arr, stride) {
  return arr == null || ((Array.isArray(arr) || arr instanceof Float32Array) && arr.length % stride === 0);
}

Try / catch

try {
  const merged = VertexData.Merge([vd1, vd2]);
} catch (e) {
  if (String(e.message).includes('must be a multiple of')) {
    // identify the misaligned attribute from the message, fix its length, retry
  } else { throw e; }
}

Prevention

When it happens

Trigger: Calling VertexData.merge() (directly or via Mesh.mergeMeshes) where one source VertexData has an attribute array whose length is not divisible by that kind's stride — e.g. a normals array of length 1000 (not divisible by 3) or a UV array of odd length (not divisible by 2).

Common situations: Hand-built vertex data with a copy/paste size typo; slicing an attribute array (normals.slice(0, 1000)) without slicing positions to match; loading attribute buffers from external files with padding or trailing garbage; concatenating attributes from meshes with different vertex counts.

Related errors


AI-assisted analysis of BabylonJS/Babylon.js@0592b347b8 (2026-08-30). Data as JSON: /api/errors/fa30ca8cfbd29eeb. Report an issue: GitHub.