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
- 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.
- Trim the array to Math.floor(values.length / stride) * stride elements so it aligns per vertex.
- Ensure all attributes describe the same vertex count as positions; slice all of them with the same vertex range.
- Re-export/re-generate the source data to rule out padding or trailing bytes from an external file or buffer.
- 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
- Derive every attribute array from the same vertex range/count as positions.
- Check array.length % stride === 0 for each attribute before constructing or merging VertexData.
- When slicing attribute buffers, slice by vertex index (index * stride), not arbitrary element counts.
- When building data by hand, log positions.length/3 versus each attribute's length/stride.
- Validate imported buffers for padding or trailing bytes before use.
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
- Draco: Cannot decode invalid geometry type ${type}
- CSG must be used with geometries having the same number of p
- The vertexData must at least have positions and indices
- Unexpected number of color components: ${vertexBuffer.getSiz
- Sample2DRgbaToRef: widthPx and heightPx must be positive.
AI-assisted analysis of BabylonJS/Babylon.js@0592b347b8 (2026-08-30).
Data as JSON: /api/errors/fa30ca8cfbd29eeb.
Report an issue: GitHub.