three.js icon indicating copy to clipboard operation
three.js copied to clipboard

SceneUtils: Add sortInstancedMesh(mesh, fn)

Open donmccurdy opened this issue 2 years ago • 3 comments

After implementing a function for sorting instances of a THREE.InstancedMesh, it turned out to be more code than expected and I thought we might want a helper function here. The new method takes an InstancedMesh and a comparator function:

import { sortInstancedMesh } from 'three/examples/jsm/utils/SceneUtils.js';

sortInstancedMesh( mesh, ( indexA, indexB ) => {

  let condition; 

  // ...

  return condition ? 1 : -1;

} );

This operation can be expensive for lots of instances, and shouldn't be done on every frame, but can certainly be useful as a bit of pre-processing to get transparency arranged correctly.


This contribution is funded by The New York Times.

donmccurdy avatar May 23 '22 19:05 donmccurdy

I'd missed #23348 and the removal of unit tests on examples/jsm/*, but for posterity here are tests on SceneUtils.sortInstancedMesh:

import { sortInstancedMesh } from '../../../../examples/jsm/utils/SceneUtils.js';

import { BufferAttribute } from '../../../../src/core/BufferAttribute.js';
import { BoxGeometry } from '../../../../src/geometries/BoxGeometry.js';
import { InstancedBufferAttribute } from '../../../../src/core/InstancedBufferAttribute.js';
import { InstancedMesh } from '../../../../src/objects/InstancedMesh.js';
import { Color } from '../../../../src/math/Color.js';
import { Matrix4 } from '../../../../src/math/Matrix4.js';

const color = new Color();
const matrix = new Matrix4();

export default QUnit.module( 'Utils', () => {

	QUnit.module( 'SceneUtils', () => {

		QUnit.test( 'sortInstancedMesh()', ( assert ) => {

			const valueA = new InstancedBufferAttribute( new Uint8Array( [ 2, 7, 1 ] ), 1 );
			const valueB = new InstancedBufferAttribute( new Float32Array( [ 1, 1, 1, 2, 2, 2, 3, 3, 3 ] ), 3 );

			const geometry = new BoxGeometry()
				.setAttribute( 'instanceValueA', valueA )
				.setAttribute( 'instanceValueB', valueB );

			const mesh = new InstancedMesh( geometry, undefined, 3 );

			mesh.setMatrixAt( 0, matrix.makeTranslation( 10, 0, 0 ) );
			mesh.setMatrixAt( 1, matrix.makeTranslation( 0, 10, 0 ) );
			mesh.setMatrixAt( 2, matrix.makeTranslation( 0, 0, 10 ) );

			mesh.setColorAt( 0, color.setRGB( 1, 0, 0 ) );
			mesh.setColorAt( 1, color.setRGB( 0, 1, 0 ) );
			mesh.setColorAt( 2, color.setRGB( 0, 0, 1 ) );

			sortInstancedMesh( mesh, ( a, b ) => valueA.getX( a ) - valueA.getX( b ) );

			//

			assert.smartEqual( Array.from( mesh.instanceMatrix.array ), [

				1, 0, 0, 0,
				0, 1, 0, 0,
				0, 0, 1, 0,
				0, 0, 10, 1,

				1, 0, 0, 0,
				0, 1, 0, 0,
				0, 0, 1, 0,
				10, 0, 0, 1,

				1, 0, 0, 0,
				0, 1, 0, 0,
				0, 0, 1, 0,
				0, 10, 0, 1,

			], 'sorted instanceMatrix' );

			assert.smartEqual( Array.from( mesh.instanceColor.array ), [

				0, 0, 1,
				1, 0, 0,
				0, 1, 0,

			], 'sorted instanceColor' );

			assert.smartEqual( Array.from( valueA.array ), [ 1, 2, 7 ], 'sorted instanceValueA' );
			assert.smartEqual( Array.from( valueB.array ), [ 3, 3, 3, 1, 1, 1, 2, 2, 2 ], 'sorted instanceValueB' );

		} );

	} );

} );

donmccurdy avatar May 23 '22 19:05 donmccurdy

Thanks for making a useful function.

This operation can be expensive for lots of instances, and shouldn't be done on every frame,

Nit: I think it would be useful for users to write this note in the code or documents.

takahirox avatar May 25 '22 14:05 takahirox

Agreed! I'll add documentation for sortInstancedMesh if the PR is accepted.

donmccurdy avatar May 25 '22 14:05 donmccurdy

  • Documentation: https://github.com/mrdoob/three.js/pull/24833

donmccurdy avatar Oct 21 '22 14:10 donmccurdy