mrdoob/three.js · error · Error

THREE.BatchedMesh: Reserved space not large enough for provi

Error message

THREE.BatchedMesh: Reserved space not large enough for provided geometry.

What it means

Thrown by BatchedMesh.setGeometryAt() when the new geometry needs more vertices or indices than were reserved at addGeometry() time. Each geometry slot has a fixed reservedVertexCount/reservedIndexCount; setGeometryAt refuses to write past that reservation even if the backing buffer has spare room.

Source

Thrown at src/objects/BatchedMesh.js:735

			throw new Error( 'THREE.BatchedMesh: Maximum geometry count reached.' );

		}

		this._validateGeometry( geometry );

		const batchGeometry = this.geometry;
		const hasIndex = batchGeometry.getIndex() !== null;
		const dstIndex = batchGeometry.getIndex();
		const srcIndex = geometry.getIndex();
		const geometryInfo = this._geometryInfo[ geometryId ];
		if (
			hasIndex &&
			srcIndex.count > geometryInfo.reservedIndexCount ||
			geometry.attributes.position.count > geometryInfo.reservedVertexCount
		) {

			throw new Error( 'THREE.BatchedMesh: Reserved space not large enough for provided geometry.' );

		}

		// copy geometry buffer data over
		const vertexStart = geometryInfo.vertexStart;
		const reservedVertexCount = geometryInfo.reservedVertexCount;
		geometryInfo.vertexCount = geometry.getAttribute( 'position' ).count;

		for ( const attributeName in batchGeometry.attributes ) {

			// copy attribute data
			const srcAttribute = geometry.getAttribute( attributeName );
			const dstAttribute = batchGeometry.getAttribute( attributeName );
			copyAttributeData( srcAttribute, dstAttribute, vertexStart );

			// fill the rest in with zeroes
			const itemSize = srcAttribute.itemSize;
			for ( let i = srcAttribute.count, l = reservedVertexCount; i < l; i ++ ) {

View on GitHub (pinned to da05705fa3)

Solutions

  1. When calling addGeometry, reserve enough space for the LARGEST geometry you will ever place in that slot: addGeometry(placeholder, maxVerts, maxIndices).
  2. If the slot is too small, deleteGeometry then addGeometry again with a larger reservation to get a fresh ID.
  3. Pre-measure all candidate geometries and pass the max as the reservation before any setGeometryAt.
  4. Track per-slot reservation and validate geometry.attributes.position.count and geometry.index.count against it before calling setGeometryAt.

Example fix

// before
const id = batch.addGeometry( lowLodGeo ); // reserved = lowLod size
batch.setGeometryAt( id, highLodGeo ); // throws: highLod > reserved

// after
const maxVerts = Math.max( lowLodGeo.attributes.position.count, highLodGeo.attributes.position.count );
const maxIdx = Math.max( lowLodGeo.index.count, highLodGeo.index.count );
const id = batch.addGeometry( lowLodGeo, maxVerts, maxIdx );
batch.setGeometryAt( id, highLodGeo );
Defensive patterns

Strategy: validation

Validate before calling

const info = batchedMesh.geometryInfo[ geometryId ];
const fitsV = geometry.attributes.position.count <= info.reservedVertexCount;
const idx = geometry.getIndex();
const fitsI = !idx || ( batchedMesh.geometry.index && idx.count <= info.reservedIndexCount );
if ( fitsV && fitsI ) batchedMesh.setGeometryAt( geometryId, geometry );

Prevention

When it happens

Trigger: addGeometry was called with a small reservedVertexCount/reservedIndexCount (or the default sized to a placeholder), then setGeometryAt replaces it with a geometry whose position.count or index.count exceeds the reservation. Replacing a low-LOD mesh with a high-LOD variant in the same slot.

Common situations: LOD swapping where the high-detail mesh is larger than the initial reservation; loading async geometry where the final size was unknown at reservation time and defaulted to -1 (the placeholder's size); procedural geometry that grew.

Related errors


AI-assisted analysis of mrdoob/three.js@da05705fa3 (2026-08-12). Data as JSON: /api/errors/e331efe93c75be2a. Report an issue: GitHub.