Three.js: how to combine several indices & vector arrays to one

Viewed 170

I am trying to visualize a grand strategy (EU4, CK3, HOI) like map in Three.js. I started creating meshes for every cell. the results are fine (screenshot 1 & 2).

Separate mesh approach - simple land / water differentiation :

enter image description here

Separate mesh approach - random cell color :

enter image description here

however, with a lot of cells, performance becomes an issue (I am getting 15fps with 10k cells). In order to improve performance I would like to combine all these separate indices & vertex arrays into 2 big arrays, which will then be used to create a single mesh.

I am looping through all my cells to push their indices, vertices & colors into the big arrays like so:

 addCellGeometryToMapGeometry(cell) {

    let startIndex = this.mapVertices.length;
    let cellIndices = cell.indices.length;
    let cellVertices = cell.vertices.length;

    let color = new THREE.Color( Math.random(), Math.random(), Math.random() );

    for (let i = 0; i < cellIndices; i++) {
        this.mapIndices.push(startIndex + cell.indices[i]);
    }
    for (let i = 0; i < cellVertices; i++) {
        this.mapVertices.push(cell.vertices[i]);
        this.mapColors.push (color);
    }

}

I then generate the combined mesh:

 generateMapMesh() {
    let geometry = new THREE.BufferGeometry();
    const material = new THREE.MeshPhongMaterial( {
        side: THREE.DoubleSide,
        flatShading: true,
        vertexColors: true,
        shininess: 0
    } );

    geometry.setIndex( this.mapIndices );
    geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( this.mapVertices, 3 ) );
    geometry.setAttribute( 'color', new THREE.Float32BufferAttribute( new Float32Array(this.mapColors.length), 3 ) );

    for ( let i = 0; i < this.mapColors.length; i ++ ) {
        geometry.attributes.color.setXYZ(i,  this.mapColors[i].r,  this.mapColors[i].g,  this.mapColors[i].b);
    }

    return new THREE.Mesh( geometry, material );
}

Unfortunately the results are underwhelming: While the data in the combined arrays look okay, only every third cell is rendered. In some cases the indices seem to get mixed up too.

Combined approach - random cell colors :

enter image description here

In other similar topics it is recommended to merge existing meshes. However, I figured that my approach should allow me to better understand what is actually happening & potentially save on performance as well.

Has my code obvious flaws that I cannot see?
Or am I generally on a wrong path, if so, how should it be done instead?

1 Answers

I actually found the issue in my code. wrong:

let startIndex = this.mapVertices.length;

The issue here is that the values in the indices array always reference a vertex (which consists of 3 consecutive array entries in the vertices array). correct:

let startIndex = this.mapVertices.length / 3;

Additionally I should only push one color per vertex instead of one per vertex array entry (= 1 per coordinate) but make sure that the arraylength of the geometry.color attribute stays at it is.

With these 2 changes, the result for the combined mesh looks exactly the same as when creating a separate mesh for every cell. The performance improvement is impressive.

separate meshes:

  • 60 - 65 ms needed to render a frame
  • 144 mb allocated memory

combined mesh:

  • 0 - 1 ms needed to render a frame
  • 58 mb allocated memory

Here are the fixed snippets:

 addCellGeometryToMapGeometry(cell) {

    let startIndex = this.mapVertices.length / 3;
    let cellIndices = cell.indices.length;
    let cellVertices = cell.vertices.length;

    console.log('Vertex-- maplength: ' + startIndex + ' celllength: ' + cellVertices);
    console.log('Indices -- maplength: ' + this.mapIndices.length + ' celllength: ' + cellIndices);
    console.log({cell});

    let color = new THREE.Color( Math.random(), Math.random(), Math.random() );

    for (let i = 0; i < cellIndices; i++) {
        this.mapIndices.push(startIndex + cell.indices[i]);
    }
    for (let i = 0; i < cellVertices; i++) {
        this.mapVertices.push(cell.vertices[i]);
        if (i % 3 === 0) { this.mapColors.push (color); }
    }

}


 generateMapMesh() {
    let geometry = new THREE.BufferGeometry();
    const material = new THREE.MeshPhongMaterial( {
        side: THREE.DoubleSide,
        flatShading: true,
        vertexColors: true,
        shininess: 0
    } );

    geometry.setIndex( this.mapIndices );
    geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( this.mapVertices, 3 ) );
    geometry.setAttribute( 'color', new THREE.Float32BufferAttribute( new Float32Array(this.mapVertices.length), 3 ) );

    for ( let i = 0; i < this.mapColors.length; i ++ ) {
        geometry.attributes.color.setXYZ(i,  this.mapColors[i].r,  this.mapColors[i].g,  this.mapColors[i].b);
    }

    return new THREE.Mesh( geometry, material );
}
Related