InstancedMesh add/remove

Hi,

I created a custom InstancedMesh to add/remove instances at runtime. This is how my implementation looks like:

import * as THREE from "three";

export class DynamicInstancedMesh extends THREE.InstancedMesh {

  constructor(geometry, material, count, initialCapacity = count, capacityIncrement = 10) {
    super(geometry, material, count);
    this.bufferCapacity = initialCapacity < count ? count : initialCapacity;
    this.bufferCapacityIncrement = capacityIncrement;
    if (count < initialCapacity) {
      this.updateCapacity();
    }
  }

  updateCapacity() {
    console.log("update capacity");
    const matrixBuffer = new Float32Array(this.instanceMatrix.itemSize * this.bufferCapacity);
    matrixBuffer.set(this.instanceMatrix.array);
    this.instanceMatrix.count = this.bufferCapacity;
    this.instanceMatrix.array = matrixBuffer;

    if (this.instanceColor !== null) {
      const colorBuffer = new Float32Array(this.instanceColor.itemSize * this.bufferCapacity);
      colorBuffer.set(this.instanceColor.array);
      this.instanceColor.count = this.bufferCapacity;
      this.instanceColor.array = colorBuffer;
    }
  }

  addInstance(matrix4) {
    this.count++;
    if (this.count > this.bufferCapacity) {
      this.bufferCapacity += this.bufferCapacityIncrement;
      this.updateCapacity();
    }
    this.instanceMatrix.array.set(matrix4.elements, this.instanceMatrix.itemSize * (this.count - 1));
  }

  removeInstance(i) {
    if (i >= this.count) {
      return false;
    }
    // matrix
    const endMatrix = this.instanceMatrix.itemSize * this.count;
    const lastMatrix = this.instanceMatrix.array.subarray(endMatrix - this.instanceMatrix.itemSize, endMatrix);
    this.instanceMatrix.array.set(lastMatrix, i * this.instanceMatrix.itemSize);
    // color
    if (this.instanceColor !== null) {
      const endColor = this.instanceColor.itemSize * this.count;
      const lastColor = this.instanceColor.array.subarray(endColor - this.instanceColor.itemSize, endColor);
      this.instanceColor.array.set(lastColor, i * this.instanceColor.itemSize);
    }
    this.count--;
    return true;
  }

  setColorAt(index, color) {
    if (this.instanceColor === null) {
      this.instanceColor = new THREE.BufferAttribute(new Float32Array(this.bufferCapacity * 3), 3);
    }
    color.toArray(this.instanceColor.array, index * 3);
  }

}

It works as expected at first. But as soon as I increase the capacity after the renderer is initialized I get an “WebGL: INVALID_VALUE: bufferSubData: buffer overflow” error.
The reason for that is that the WebGLAttributes uses a cached (fixed size) GL Buffer which will not increase automatically.
My idea was now to do that in my DynamicInstancedMesh class. But I couldn’t find a way to access the WebGLAttributes through the renderer.
So my two questions would be. Is that the right way to go at all? And if yes, how can I make it work?

This might not be what you’re looking for but have you tried allocating a sufficiently large amount of instances and temporarily hide them by e.g setting the position off screen?

I know I could create a mesh with a huge capacity and each update I use the updateRange method (Im using this right now too):

this.mesh.instanceMatrix.updateRange = {
  offset: 0,
  count: this.mesh.count * 16
}

But I still would be thankful if there’s a better solution.

Well, there isn’t. WebGL (and WebGPU) buffers are fixed sized and they can’t be changed in size after their creation. Every time a structural change would be applied, it would be necessary to delete the existing buffer and create a new one.

To avoid this overhead, it is necessary to do it like mentioned by @vuoriov4. At least if you are heading for proper performance.

Thanks for your fast responses! I guess I will do as you suggested. The only thing I could think of now is disposing the whole mesh and create a new one but that seems to be even more overhead.

Is there a reason why the internal API is private?