Skip to content

Commit 186d7b4

Browse files
authored
BatchedMesh: add support for resizing instance count, geometry size (#29577)
* Add setInstanceCount function * Add setGeometrySize function * Prefer using earlier ids when reusing ids * Check for size validity * Fix implementation bugs * Add comment * Add docs
1 parent 0c7cf78 commit 186d7b4

File tree

2 files changed

+146
-3
lines changed

2 files changed

+146
-3
lines changed

docs/api/en/objects/BatchedMesh.html

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,14 +301,36 @@ <h3>
301301
Calling this will change all instances that are rendering that geometry.
302302
</p>
303303

304-
305304
<h3>
306305
[method:this optimize]()
307306
</h3>
308307
<p>
309308
Repacks the sub geometries in [name] to remove any unused space remaining from previously deleted geometry, freeing up space to add new geometry.
310309
</p>
311310

311+
<h3>
312+
[method:this setGeometrySize]( maxVertexCount, maxIndexCount )
313+
</h3>
314+
<p>
315+
Resizes the available space in [name]'s vertex and index buffer attributes to the provided sizes. If the provided arguments shrink the geometry buffers
316+
but there is not enough unused space at the end of the geometry attributes then an error is thrown.
317+
</p>
318+
<p>
319+
[page:Integer maxVertexCount] - the max number of vertices to be used by all unique geometries to resize to.<br />
320+
[page:Integer maxIndexCount] - the max number of indices to be used by all unique geometries to resize to.<br />
321+
</p>
322+
323+
<h3>
324+
[method:this setInstanceCount]( maxInstanceCount )
325+
</h3>
326+
<p>
327+
Resizes the necessary buffers to support the provided number of instances. If the provided arguments shrink the number of instances but there are not enough
328+
unused ids at the end of the list then an error is thrown.
329+
</p>
330+
<p>
331+
[page:Integer maxInstanceCount] - the max number of individual instances that can be added and rendered by the [name].<br />
332+
</p>
333+
312334
<!--
313335
<h3>
314336
[method:Integer getInstanceCountAt]( [param:Integer index] )

src/objects/BatchedMesh.js

Lines changed: 123 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ import { Frustum } from '../math/Frustum.js';
1212
import { Vector3 } from '../math/Vector3.js';
1313
import { Color } from '../math/Color.js';
1414

15+
function ascIdSort( a, b ) {
16+
17+
return a - b;
18+
19+
}
20+
1521
function sortOpaque( a, b ) {
1622

1723
return a.z - b.z;
@@ -123,6 +129,14 @@ function copyAttributeData( src, target, targetOffset = 0 ) {
123129

124130
}
125131

132+
// safely copies array contents to a potentially smaller array
133+
function copyArrayContents( src, target ) {
134+
135+
const len = Math.min( src.length, target.length );
136+
target.set( new src.constructor( src.buffer, 0, len ) );
137+
138+
}
139+
126140
class BatchedMesh extends Mesh {
127141

128142
get maxInstanceCount() {
@@ -368,7 +382,9 @@ class BatchedMesh extends Mesh {
368382
// Prioritize using previously freed instance ids
369383
if ( this._availableInstanceIds.length > 0 ) {
370384

371-
drawId = this._availableInstanceIds.pop();
385+
this._availableInstanceIds.sort( ascIdSort );
386+
387+
drawId = this._availableInstanceIds.shift();
372388
this._drawInfo[ drawId ] = instanceDrawInfo;
373389

374390
} else {
@@ -494,7 +510,9 @@ class BatchedMesh extends Mesh {
494510
let geometryId;
495511
if ( this._availableGeometryIds.length > 0 ) {
496512

497-
geometryId = this._availableGeometryIds.pop();
513+
this._availableGeometryIds.sort( ascIdSort );
514+
515+
geometryId = this._availableGeometryIds.shift();
498516
reservedRanges[ geometryId ] = reservedRange;
499517
drawRanges[ geometryId ] = drawRange;
500518
bounds[ geometryId ] = boundsInfo;
@@ -1002,6 +1020,109 @@ class BatchedMesh extends Mesh {
10021020

10031021
}
10041022

1023+
setInstanceCount( maxInstanceCount ) {
1024+
1025+
// shrink the available instances as much as possible
1026+
const availableInstanceIds = this._availableInstanceIds;
1027+
const drawInfo = this._drawInfo;
1028+
availableInstanceIds.sort( ascIdSort );
1029+
while ( availableInstanceIds[ availableInstanceIds.length - 1 ] === drawInfo.length ) {
1030+
1031+
drawInfo.pop();
1032+
availableInstanceIds.pop();
1033+
1034+
}
1035+
1036+
// throw an error if it can't be shrunk to the desired size
1037+
if ( maxInstanceCount < drawInfo.length ) {
1038+
1039+
throw new Error( `BatchedMesh: Instance ids outside the range ${ maxInstanceCount } are being used. Cannot shrink instance count.` );
1040+
1041+
}
1042+
1043+
// copy the multi draw counts
1044+
const multiDrawCounts = new Int32Array( maxInstanceCount );
1045+
const multiDrawStarts = new Int32Array( maxInstanceCount );
1046+
copyArrayContents( this._multiDrawCounts, multiDrawCounts );
1047+
copyArrayContents( this._multiDrawStarts, multiDrawStarts );
1048+
1049+
this._multiDrawCounts = multiDrawCounts;
1050+
this._multiDrawStarts = multiDrawStarts;
1051+
this._maxInstanceCount = maxInstanceCount;
1052+
1053+
// update texture data for instance sampling
1054+
const indirectTexture = this._indirectTexture;
1055+
const matricesTexture = this._matricesTexture;
1056+
const colorsTexture = this._colorsTexture;
1057+
1058+
this._initIndirectTexture();
1059+
copyArrayContents( indirectTexture.image.data, this._indirectTexture.image.data );
1060+
1061+
this._initMatricesTexture();
1062+
copyArrayContents( matricesTexture.image.data, this._matricesTexture.image.data );
1063+
1064+
if ( colorsTexture ) {
1065+
1066+
this._initColorsTexture();
1067+
copyArrayContents( colorsTexture.image.data, this._colorsTexture.image.data );
1068+
1069+
}
1070+
1071+
}
1072+
1073+
setGeometrySize( maxVertexCount, maxIndexCount ) {
1074+
1075+
// Check if we can shrink to the requested vertex attribute size
1076+
const validRanges = [ ...this._reservedRanges ].filter( ( range, i ) => this._drawRanges[ i ].active );
1077+
const requiredVertexLength = Math.max( ...validRanges.map( range => range.vertexStart + range.vertexCount ) );
1078+
if ( requiredVertexLength > maxVertexCount ) {
1079+
1080+
throw new Error( `BatchedMesh: Geometry vertex values are being used outside the range ${ maxIndexCount }. Cannot shrink further.` );
1081+
1082+
}
1083+
1084+
// Check if we can shrink to the requested index attribute size
1085+
if ( this.geometry.index ) {
1086+
1087+
const requiredIndexLength = Math.max( ...validRanges.map( range => range.indexStart + range.indexCount ) );
1088+
if ( requiredIndexLength > maxIndexCount ) {
1089+
1090+
throw new Error( `BatchedMesh: Geometry index values are being used outside the range ${ maxIndexCount }. Cannot shrink further.` );
1091+
1092+
}
1093+
1094+
}
1095+
1096+
//
1097+
1098+
// dispose of the previous geometry
1099+
const oldGeometry = this.geometry;
1100+
oldGeometry.dispose();
1101+
1102+
// recreate the geometry needed based on the previous variant
1103+
this._maxVertexCount = maxVertexCount;
1104+
this._maxIndexCount = maxIndexCount;
1105+
this._geometryInitialized = false;
1106+
1107+
this.geometry = new BufferGeometry();
1108+
this._initializeGeometry( oldGeometry );
1109+
1110+
// copy data from the previous geometry
1111+
const geometry = this.geometry;
1112+
if ( oldGeometry.index ) {
1113+
1114+
copyArrayContents( oldGeometry.index.array, geometry.index.array );
1115+
1116+
}
1117+
1118+
for ( const key in oldGeometry.attributes ) {
1119+
1120+
copyArrayContents( oldGeometry.attributes[ key ].array, geometry.attributes[ key ].array );
1121+
1122+
}
1123+
1124+
}
1125+
10051126
raycast( raycaster, intersects ) {
10061127

10071128
const drawInfo = this._drawInfo;

0 commit comments

Comments
 (0)