THREE.Geometry will be removed from core with r125

Looks like it will only be one break for people not using ‘BufferGeometry’ and none for those that do…

@Mugen87 would this be more effective/quicker than just renaming ‘Geometry’ to ‘BufferGeometry’ in a project? For instance just using notepad++ to rename all instances at once?

Right — Geometry has been deprecated for a long time, and no project written in the past couple of years should have been using it. If you’re already using BufferGeometry, and I expect most projects are, then upgrading from r124 -> r125 will not change anything.

In other words, renaming “BufferGeometry” to “Geometry” now would make this a major change for all users, with no prior warning. As it is, it’s a major change for only a subset of users, with quite a bit of prior warning.

If your project was using Geometry anyway, this would be a great time to upgrade. But if you can’t do that yet you can still import it from the examples/jsm/deprecated folder.

7 Likes

@donmccurdy
one question, the only place i’ve had to use cube geometry is to detect collisions with other objects in “collidableMeshList[]” like this… where MovingCube is a CubeGeometry…

var originPoint = MovingCube.position.clone();

for (var vertexIndex = 0; vertexIndex < MovingCube.geometry.vertices.length; vertexIndex++)
{		
var localVertex = MovingCube.geometry.vertices[vertexIndex].clone();
var globalVertex = localVertex.applyMatrix4( MovingCube.matrix );
var directionVector = globalVertex.sub( MovingCube.position );

var ray = new THREE.Raycaster( originPoint, directionVector.clone().normalize() );
var collisionResults = ray.intersectObjects( collidableMeshList );

in r124 if i try to make MovingCube a BoxBufferGeometry it actually breaks the code as it can’t determine geometry.vertices.length, all meshes pushed to collidableMeshList[] use BoxBufferGeometry so bit confused…

will this not be a problem in r125 if updating MovingCube to a BoxBufferGeometry? or would i have to refer to this…?

edit: is there a different syntax to detect vertices of a buffer geometry? are vertices not accessable on buffer geometry or do they need to be declared in a Float32Array first?

I think someone on discord mentioned that removing Geometry is one thing, but renaming BufferGeometry to Geometry will absolutely confuse anyone trying to learn three.js with tutorials / guides / videos / courses.

If you get an error in the console that Geometry doesn’t exist - that’s pretty straight forward. It doesn’t exist -> go to docs -> learn about BufferGeometry (or just Google: “why doesn’t Three.Geometry exist?”)

But if you follow some tutorial that uses old Geometry, and you use new Geometry - then only half of the methods work, the other half doesn’t seem to exist at all, and nothing works as described in the tutorial - it’s mind bending for a beginner, and searching Google / SO for “why doesn’t [this] in Three.Geometry work” will then likely lead to outdated answers about the original Geometry.

It’d feel kinda like placing this in a beginners code.

4 Likes

@forerunrun Your code will definitely not work with r125 even if you include THREE.Geometry from the examples since raycasting does not support THREE.Geometry anymore. So you will have to learn how to work with BufferGeometry.

As a small teaser, you iterate through all vertices like so:

const positionAttribute = MovingCube.geometry.getAttribute( 'position' );

const localVertex = new THREE.Vector3();
const globalVertex = new THREE.Vector3();

for ( let vertexIndex = 0; vertexIndex < positionAttribute.count; vertexIndex ++ ) {

	localVertex.fromBufferAttribute( positionAttribute, vertexIndex );
	globalVertex.copy( localVertex ).applyMatrix4( MovingCube.matrixWorld );

}

Notice that you have no clone() operation in your for loop anymore which is good for performance. Besides, always use matrixWorld if you want to transform something to world space.

4 Likes

@Mugen87
amazing! thanks for the pointers and clarification, the fact that of course THREE.Geometry would be stripped from support of raycasting went completely over my head… i do need to learn how to properly work with BufferGeometry and this is the perfect time to do so!

1 Like

I don’t think we should rename THREE.BufferGeometry to THREE.Geometry, it would be too confusing.

I suggested introducing THREE.MeshGeometry, THREE.LineGeometry and THREE.PointsGeometry (which would extend THREE.BufferGeometry) as a solution for prettifying the API.

8 Likes

@Mugen87 I did try try to implement this exact solution to my collision detections but it failed to detect any collisions…

Is there something I am missing here?

The code looks like what I have provided. It’s best if you create a new topic with a live example. We can then debug the code.

@Mugen87
Yes apologies it is a seperate tangential topic. Will create one

1 Like

Does BufferGeometry have an equivalent to Geometry.mergeMesh()?

The closest method is BufferGeometryUtils.mergeBufferGeometries([a, b, c, d, ...]), but when using it you would need to first apply the world transform of each mesh to its geometry.

4 Likes

Hmm… yikes. Ok thank you.

Did you have any particular concern with that method? I think there could be better APIs for this (e.g. that avoid an extra pass on the vertices) but I suspect it is still more efficient than using the old method from Geometry.

IMO, the method mergeMesh() was problematic since its name implies that you can use the method only with an instance of THREE.Mesh which is not true. Geometry can also represent points and lines. Internally, it does this:

this.merge( mesh.geometry, mesh.matrix );

which seems not correct either since it should use the world matrix.

Here’s the idea I’d been thinking about: BufferGeometry.merge or BufferGeometryUtils.mergeBufferGeometries: Add support for transformation matrix · Issue #18918 · mrdoob/three.js · GitHub it’s probably independent of these other methods that do a simple merge, but would be a nice way to create (and update) a draw batch representing multiple objects.

1 Like

Yea I’ve used that method for a while no issues. I will work to redo it using the tools available for buffer geometries. I have one other use case dependent on Geometry involving a custom function that West Langley helped me with a while back but I’ll save that for later.

A little bummed that the legacy Geometry is for module use only as not everyone wants to use that method. On the other hand I’ll be glad to get rid of Geometry all together, I’ve been weeding it’s use cases out in my app for years.

1 Like

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>

I’ve never written an answer to the original topic but the performance difference happens because of different number of draw calls in your demo.

Geometry: approx 8600
BufferGeometry: 20000

I don’t know how you generate your geometry data but I recommend to group all buffer geometry groups which share the same material index.

1 Like