Updating geometry doesn't work correctly as expected

I am working on a voxel geometry in threejs and everything is right for loading the geometry once or removing faces but when adding new faces the geometry gets empty with [.WebGL-00006C2000754000] GL_INVALID_OPERATION: Vertex buffer is not big enough for the draw call warning. IDK why but the addface works after removing one face before it.

I use threejs r162

import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
//import * as BufferGeometryUtils from "three/addons/utils/BufferGeometryUtils.js";

const palette = [
  [0x1d,0x18,0x26],
  [0x8b,0x7f,0xb0],
  [0xc3,0xbe,0xe5],
  [0xff,0xe8,0xe9],
  [0x65,0x26,0x4e],
  [0xa0,0x1a,0x3d],
  [0xde,0x1b,0x45],
  [0xf2,0x63,0x7b],
  [0x8b,0x3f,0x39],
  [0xbb,0x45,0x31],
  [0xef,0x5d,0x0e],
  [0xff,0x95,0x00],
  [0x00,0xa0,0x3d],
  [0x12,0xd5,0x00],
  [0xb4,0xd8,0x00],
  [0xff,0xc3,0x1f],
  [0x00,0x6e,0x69],
  [0x00,0xae,0x85],
  [0x00,0xda,0xa7],
  [0x4f,0xd6,0xff],
  [0x2b,0x27,0x54],
  [0x3c,0x51,0xaf],
  [0x18,0x88,0xde],
  [0x00,0xa9,0xe1],
  [0x59,0x3c,0x97],
  [0x89,0x44,0xcf],
  [0xb4,0x4a,0xff],
  [0xe9,0x59,0xff],
  [0xe7,0x87,0x6d],
  [0xff,0xba,0x8c],
  [0xff,0xef,0x5c],
  [0xff,0x9c,0xde]
];

const palette_01 = new Array(32).fill(null).map((_,i)=>palette[i].map(c=>c/256));

class VoxelGeometry {
  constructor(sizex, sizey, sizez, palette) {
    this.sizex = sizex;
    this.sizey = sizey;
    this.sizez = sizez;
    this.palette = palette;
    this.faces = [];
    this.geometry = new THREE.BufferGeometry();
    this.material = new THREE.MeshLambertMaterial({ vertexColors: true });
    this.mesh = new THREE.Mesh(this.geometry, this.material);
    this.mesh.position.set(-sizex / 2, -sizey / 2, -sizez / 2);
  }

  addFace(x, y, z, side, color) {
    const face = {x,y,z,side,color};
    this.faces.push(face);
  }

  removeFace(x, y, z, side) {
    const faceIndex = this.faces.findIndex(f => f.x === x && f.y === y && f.z === z && f.side === side);
    if (faceIndex !== -1) {
      this.faces.splice(faceIndex, 1);
    }
  }

  getFaceVertices(x, y, z, side) {
    switch (side) {
      case "left": return [[x, y + 1, z + 1], [x, y + 1, z], [x, y, z], [x, y, z + 1]];
      case "right": return [[x + 1, y + 1, z + 1], [x + 1, y, z + 1], [x + 1, y, z], [x + 1, y + 1, z]];
      case "bottom": return [[x, y, z], [x + 1, y, z], [x + 1, y, z + 1], [x, y, z + 1]];
      case "top": return [[x, y + 1, z], [x, y + 1, z + 1], [x + 1, y + 1, z + 1], [x + 1, y + 1, z]];
      case "back": return [[x, y, z], [x, y + 1, z], [x + 1, y + 1, z], [x + 1, y, z]];
      case "front": return [[x, y, z + 1], [x + 1, y, z + 1], [x + 1, y + 1, z + 1], [x, y + 1, z + 1]];
    }
  }

  updateGeometry() {
    const positions = [];
    const colors = [];
    const indices = [];

    this.faces.forEach((face, index) => {
      const vertexIndex = index * 4;
      const vertices = this.getFaceVertices(face.x, face.y, face.z, face.side);
      const [v0,v1,v2,v3] = vertices;
      positions.push(v0[0],v0[1],v0[2],v1[0],v1[1],v1[2],v2[0],v2[1],v2[2],v3[0],v3[1],v3[2]);
      const color = this.palette[face.color&31];
      colors.push(color[0],color[1],color[2],color[0],color[1],color[2],color[0],color[1],color[2],color[0],color[1],color[2]);
      indices.push(
        vertexIndex, vertexIndex + 1, vertexIndex + 2,
        vertexIndex, vertexIndex + 2, vertexIndex + 3
      );
    });

    this.geometry.setAttribute("position", new THREE.Float32BufferAttribute(positions, 3));
    this.geometry.setAttribute("color", new THREE.Float32BufferAttribute(colors, 3));
    this.geometry.setIndex(indices);
    this.geometry.computeVertexNormals();
    this.geometry.attributes.position.needsUpdate = true;
    this.geometry.attributes.color.needsUpdate = true;
    this.geometry.index.needsUpdate = true;
  }

  createVoxelMesh(voxels) {
    for (let x = 0; x < this.sizex; x++) {
      for (let y = 0; y < this.sizey; y++) {
        for (let z = 0; z < this.sizez; z++) {
          const voxel = voxels[x + y * this.sizex + z * this.sizex * this.sizey];
          if (voxel != -1) {
            if (voxels[(x - 1) + y * this.sizex + z * this.sizex * this.sizey] == -1 || x == 0) {
              this.addFace(x, y, z,'left',voxel);
            }
            if (voxels[(x + 1) + y * this.sizex + z * this.sizex * this.sizey] == -1 || x == this.sizex - 1) {
              this.addFace(x, y, z,'right',voxel);
            }
            if (voxels[x + (y - 1) * this.sizex + z * this.sizex * this.sizey] == -1 || y == 0) {
              this.addFace(x, y, z,'bottom',voxel);
            }
            if (voxels[x + (y + 1) * this.sizex + z * this.sizex * this.sizey] == -1 || y == this.sizey - 1) {
              this.addFace(x, y, z,'top',voxel);
            }
            if (voxels[x + y * this.sizex + (z - 1) * this.sizex * this.sizey] == -1 || z == 0) {
              this.addFace(x, y, z, 'back',voxel);
            }
            if (voxels[x + y * this.sizex + (z + 1) * this.sizex * this.sizey] == -1 || z == this.sizez - 1) {
              this.addFace(x, y, z, 'front',voxel);
            }
          }
        }
      }
    }
    this.updateGeometry();
    return this.mesh;
  }
}

const floor = Math.floor;
const round = Math.round;
const abs = Math.abs;
const min = Math.min;
const max = Math.max;

const voxel_map = [];
const size = 8;
const c = size/2-0.5;
for (let x = 0; x < size; x++) {
  for (let y = 0; y < size; y++) {
    for (let z = 0; z < size; z++) {
      let d = max(max(abs(x-c),abs(y-c)),abs(z-c));
      let dm = min(min(abs(x-c),abs(y-c)),abs(z-c));
      if ((x^y^z)==0) {
        voxel_map[x+y*size+z*size**2] = d&31;
      } else {
        voxel_map[x+y*size+z*size**2] = -1;
      }
    }
  }
}

const context = canvas.getContext("webgl",{antialias:false});
const renderer = new THREE.WebGLRenderer({canvas,context});
renderer.setSize(window.innerWidth,window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio,2));
renderer.outputColorSpace  = THREE.LinearSRGBColorSpace;

window.addEventListener("resize",() => {
  camera.aspect = window.innerWidth/window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth,window.innerHeight);
});

const scene = new THREE.Scene();

const camera = new THREE.PerspectiveCamera(45,window.innerWidth/window.innerHeight,0.01,2048);
camera.position.z = size*2;

const controls = new OrbitControls(camera, renderer.domElement);

//--light--//
const light = new THREE.DirectionalLight(0xffffff,Math.PI);
light.position.set(0.75*size/2,size/2,0.5*size/2);
scene.add(light);
const light2 = new THREE.DirectionalLight(0xffffff,Math.PI);
light2.position.set(-0.75*size/2,-size/2,-0.5*size/2);
scene.add(light2);
const hemiLight = new THREE.HemisphereLight(0xffffff,0xffffff,Math.PI*0.35);
hemiLight.position.set(0,1,0);
scene.add(hemiLight);

//--voxels--//
const g = new VoxelGeometry(size,size,size,palette_01);
window.onclick = () => {
  g.addFace(0,0,0,"front",6);
  g.updateGeometry();
}
const mesh = g.createVoxelMesh(voxel_map);
scene.add(mesh);

//--cube--//
const geometry = new THREE.BoxGeometry(size,size,size);
const material = new THREE.MeshLambertMaterial({color:0xfffffff,side:THREE.BackSide});
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

//--update--//
function animate() {
  controls.update();
  renderer.render(scene, camera);
  requestAnimationFrame(animate);
}
animate();

I found that setting the maximum size for the geometry will fix everything

1 Like