THREE.Geometry will be removed from core with r125

The announcement that Geometry is now being taken out of the core reminded me that there is still an outstanding issue.

Why is the Geometry faster than BufferGeometry? - #10 by hofk

I have brought the old test example to r124 and simplified it even more and structured it better. In addition comments in the source code, which clarify the few absolutely necessary differences.

See SpeedTest_r124

In the meantime I had another performance problem.

Jerky camera movement with many objects

The solution was:
“The new approach is to do completely without groups and instead create a separate geometry and mesh for each material used. This only makes sense if the parts are immobile after creation.”

Thereupon the selection in the test example.
Material Index,
multiMaterial array or single material

It can be seen that here, too, the many groups obviously influence the performance.

 // ***** write groups for multi material *****

for ( let f = 0, p = 0; f < faceCount; f ++, p += 3 ) {
	
	g.addGroup( p, 3, 0 );
	
}

Since I am not able to trace the transformation from Geometry to the actual rendering via the GPU, I can only guess.

Is the concrete way from Geometry via the intermediate BufferGeometry for (material)groups more efficient than the way from the self-defined Buffergeometry?

// set material index:   Geometry - faces /  BufferGeometry - groups 
if ( multimat )	{

	if ( g.isGeometry ) {
		
		g.faces[ fIdx ].materialIndex = materialSegment;
		g.faces[ fIdx + 1 ].materialIndex = materialSegment;
		
	}
	
	if ( g.isBufferGeometry ) {
		
		g.groups[ fIdx ].materialIndex = materialSegment;
		g.groups[ fIdx + 1 ].materialIndex = materialSegment;
		
	}
}

How do I get the same performance with BufferGeometry ?

For me this is just a test out of curiosity, but may be relevant for migration of some application.

I myself have been working only with BufferGeometry for a long time now.


source code
(// for BufferGeometry: docs: .groupsNeedUpdate ?)

<!DOCTYPE html>
<!--  https://discourse.threejs.org/t/three-geometry-will-be-removed-from-core-with-r125/22401/25 -->
<head>
	<title>  SpeedTest_r124  </title>
	<meta charset="utf-8" />
<style>
	body{
	overflow: hidden;
	margin: 0;
	}  
 </style>
</head>
<body> 

	_______ ..... segments 
	<input type="text" size="5" id="hs" value="100" >  x  
 	<input type="text" size="5" id="rs" value="100" > 
	 .......
	<input type="checkbox" name="color" id="matindex" checked="checked" > Mat.index
	</br>_______ ..... 	
	<input type="radio" name="geom" id="Geometry" checked="checked" > Geometry
	<input type="radio" name="geom" id="BufferGeometry" > ind. BufferGeo. 
	|
	<input type="radio" name="mat" id="multimat" checked="checked" > multi Mat. [ ]
	<input type="radio" name="mat" id="singlemat"  > single Mat. |
	</br>.................... 
	<button type="button" id="show"> >>>>> show new mesh <<<<< </button>
	
	
</body>

	<script src="three.min.124.js"></script>
	<script src="OrbitControls.124.js"></script>
	<script src="stats.min.124.js"></script>
	
<script>

'use strict'

// @author hofk

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 200000 );
camera.position.set( 400, 600, 1000 );
const renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setClearColor( 0xeeeeee, 1 );
const container = document.createElement( 'div' );
document.body.appendChild( container );
container.appendChild( renderer.domElement ); 

const controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.enableZoom = true;
const clock  = new THREE.Clock( true );
let t; // time
let	g; // Geometry or BufferGeometry
let mesh;
let hs; // height segment count ( y direction )
let rs; // radial segment count ( here in test:  x direction )
let hss; // hs + 1
let rss; // rs + 1
let vertexCount;
let vIdx;	// vertex Index
let idxCount;
let faceCount;
let fIdx;	// face index
let j0, j1; // j index 
let a, b1, c1, c2; // vertex indices,  b2 equals c1
let ni, nj; // relative counter variable
let posIdx; // position Index
let x, y, z; // position coordinates
let materialSegment;
let matindex = null;
let multimat = null;;

let showGeo = false;

// materials
 
const side = THREE.DoubleSide;

// material or multi materials

const material = new THREE.MeshBasicMaterial( { color: 0x880088, side: side, wireframe: false } );

/*
const materials = [

	new THREE.MeshBasicMaterial( { color: 0x228822, side: side, wireframe: false } ),
	new THREE.MeshBasicMaterial( { color: 0x000000, side: side, wireframe: true } )	
	
];
*/
 
const materials = [
																	// material index 
    new THREE.MeshBasicMaterial( { color: 0xc4a013, transparent: true, opacity: 0.3, side: side } ), //  0 transparent
	new THREE.MeshBasicMaterial( { color: 0xff0000, side: side } ),	//  1 red
	new THREE.MeshBasicMaterial( { color: 0x00ff00, side: side } ),	//  2 green
	new THREE.MeshBasicMaterial( { color: 0x0000ff, side: side } ),	//  3 blue
	new THREE.MeshBasicMaterial( { color: 0xffff00, side: side } ),	//  4 yellow
	new THREE.MeshBasicMaterial( { color: 0xff00ff, side: side } ),	//  5 mgenta
	new THREE.MeshBasicMaterial( { color: 0x00ffff, side: side } ),	//  6 cyan	
	new THREE.MeshBasicMaterial( { color: 0x7755ff, side: side } ),	//  7 color
	new THREE.MeshBasicMaterial( { color: 0x000000, side: side, wireframe: true } )	//  8 grey
	
];
 
const stats = new Stats();
container.appendChild( stats.dom );

document.getElementById( "show" ).onclick = showNewMesh;

animate();

// ......................

function showNewMesh() {

	if ( mesh ) {
	
		scene.remove( mesh );
		g.dispose();
		showGeo = false;
		
		matindex = null;
		multimat = null;
				
	}
	
	hs = Math.floor( document.getElementById( "hs" ).value );
	rs = Math.floor( document.getElementById( "rs" ).value );
	
	hss = hs + 1;
	rss = rs + 1;
	
	vertexCount = hss * rss;
	faceCount =  hs * rs * 2;
	
	matindex = document.getElementById( "matindex" ).checked;
	multimat = document.getElementById( "multimat" ).checked;
	
	//  ....................... Geometry or BufferGeometry ...................................
	
	if ( document.getElementById( "Geometry" ).checked ) g = new THREE.Geometry();
	if ( document.getElementById( "BufferGeometry" ).checked ) g = new THREE.BufferGeometry();
		
	// mesh
	
	if ( multimat ) {
		
		mesh = new THREE.Mesh( g, materials ); // multi materials
		
	} else {
	
		mesh = new THREE.Mesh( g, material ); // single material
			
	}		

	scene.add( mesh );
	
	// configure Geometry or BufferGeometry
	
	if ( g.isGeometry ) {
		
		for ( let i = 0; i < vertexCount; i ++ ) { 
			
			g.vertices.push( new THREE.Vector3( 0, 0, 0 ) ); 
		
		}
		
	}	
		
	if ( g.isBufferGeometry ) {
	
		idxCount = 0;
		
		g.faceIndices = new Uint32Array( faceCount * 3 );
		g.vertices = new Float32Array( vertexCount * 3 );  
				
		g.setIndex( new THREE.BufferAttribute( g.faceIndices, 1 ) );
		//g.addAttribute( 'position', new THREE.BufferAttribute( g.vertices, 3 ).setDynamic( true ) ); // older version
		g.setAttribute( 'position', new THREE.BufferAttribute( g.vertices, 3 ).setUsage(THREE.DynamicDrawUsage ) );
		
		 // ***** write groups for multi material *****
		
		for ( let f = 0, p = 0; f < faceCount; f ++, p += 3 ) {
			
			g.addGroup( p, 3, 0 );
			
		}
 		
	}
	
	// faces Geometry or BufferGeometry	
	
	for ( let j = 0; j < rs; j ++ ) {
	
		j0 = hss * j; 
		j1 = hss * ( j + 1 );
		
		for ( let i = 0; i < hs; i ++ ) {
			
			// 2 faces / segment,  3 vertex indices
			a =  j0 + i;
			b1 = j1 + i;			// right-bottom
			c1 = j1 + 1 + i;
			// b2 = j1 + 1 + i; =c1// left-top 
			c2 = j0 + 1 + i;
			
			if ( g.isGeometry ) {
				
				g.faces.push( new THREE.Face3( a, b1, c1 ) ); // right-bottom
				g.faces.push( new THREE.Face3( a, c1, c2 ) ); // left-top
				
			}
			
			if ( g.isBufferGeometry ) {
			
				g.faceIndices[ idxCount     ] = a; // right-bottom
				g.faceIndices[ idxCount + 1 ] = b1;
				g.faceIndices[ idxCount + 2 ] = c1; 
				
				g.faceIndices[ idxCount + 3 ] = a; // left-top
				g.faceIndices[ idxCount + 4 ] = c1,
				g.faceIndices[ idxCount + 5 ] = c2; 
				
				idxCount += 6;
				
			}
						
		}
		
	}
	
	showGeo = true;	 // start animation
	
}

function move( t ) {
	
	for ( let j = 0; j < rss; j ++ ) {
		
		nj = j / rs;
		
		y = 400 * nj;							// calculate y
		
		for ( let i = 0; i < hss; i ++ ) {
			
			ni   = i / hs;
			
			x = 400 * ni;						// calculate x
			
			z =  400 * Math.sin( t + ni + nj );	// calculate z
			
			vIdx = hss * j + i;
			
			// set vertices Geometry or BufferGeometry
				
			if ( g.isGeometry ) g.vertices[ vIdx ].set( x, y, z );
			
			if ( g.isBufferGeometry ) {
			
				posIdx = vIdx * 3;
				
				g.vertices[ posIdx ]  = x;		
				g.vertices[ posIdx + 1 ]  = y;
				g.vertices[ posIdx + 2 ]  = z;
				
			}	
			
		}
		
	}
	   
	if ( matindex ) {
		
		for ( let j = 0; j < rs ; j ++ ) {
			
			for ( let i = 0; i < hs; i ++ ) {
				
				materialSegment = Math.floor( materials.length * ( 1 + Math.cos( 0.2 * t + i * i + 2 * j ) ) / 2 ); // calculate material
				
				fIdx = 2 * hs * j + 2 * i;
				
				// set material index:   Geometry - faces /  BufferGeometry - groups 
				if ( multimat )	{
				
					if ( g.isGeometry ) {
						
						g.faces[ fIdx ].materialIndex = materialSegment;
						g.faces[ fIdx + 1 ].materialIndex = materialSegment;
						
					}
					
					if ( g.isBufferGeometry ) {
						
						g.groups[ fIdx ].materialIndex = materialSegment;
						g.groups[ fIdx + 1 ].materialIndex = materialSegment;
						
					}
				}
			}			
		}		
	}		

	// dynamic update:  Geometry - vertices  /  BufferGeometry - attributes.position
	
	if ( g.isGeometry ) g.verticesNeedUpdate  = true;	
	if ( g.isBufferGeometry ) g.attributes.position.needsUpdate = true;
	
	// for Geometry:		docs: .groupsNeedUpdate : Boolean | Set to true if a face3 materialIndex has been updated.
	// for BufferGeometry:	docs: ?
	g.groupsNeedUpdate = true;
	
}

function animate() {
	
	requestAnimationFrame( animate );
	t = clock.getElapsedTime();
	
	if ( showGeo ) move( t );
	 
	renderer.render( scene, camera );
	controls.update();
	
	stats.update();
	
}	
</script>
</html>