|
|
@@ -12,6 +12,12 @@ import { Frustum } from '../math/Frustum.js';
|
|
|
import { Vector3 } from '../math/Vector3.js';
|
|
|
import { Color } from '../math/Color.js';
|
|
|
|
|
|
+function ascIdSort( a, b ) {
|
|
|
+
|
|
|
+ return a - b;
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
function sortOpaque( a, b ) {
|
|
|
|
|
|
return a.z - b.z;
|
|
|
@@ -123,6 +129,14 @@ function copyAttributeData( src, target, targetOffset = 0 ) {
|
|
|
|
|
|
}
|
|
|
|
|
|
+// safely copies array contents to a potentially smaller array
|
|
|
+function copyArrayContents( src, target ) {
|
|
|
+
|
|
|
+ const len = Math.min( src.length, target.length );
|
|
|
+ target.set( new src.constructor( src.buffer, 0, len ) );
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
class BatchedMesh extends Mesh {
|
|
|
|
|
|
get maxInstanceCount() {
|
|
|
@@ -368,7 +382,9 @@ class BatchedMesh extends Mesh {
|
|
|
// Prioritize using previously freed instance ids
|
|
|
if ( this._availableInstanceIds.length > 0 ) {
|
|
|
|
|
|
- drawId = this._availableInstanceIds.pop();
|
|
|
+ this._availableInstanceIds.sort( ascIdSort );
|
|
|
+
|
|
|
+ drawId = this._availableInstanceIds.shift();
|
|
|
this._drawInfo[ drawId ] = instanceDrawInfo;
|
|
|
|
|
|
} else {
|
|
|
@@ -494,7 +510,9 @@ class BatchedMesh extends Mesh {
|
|
|
let geometryId;
|
|
|
if ( this._availableGeometryIds.length > 0 ) {
|
|
|
|
|
|
- geometryId = this._availableGeometryIds.pop();
|
|
|
+ this._availableGeometryIds.sort( ascIdSort );
|
|
|
+
|
|
|
+ geometryId = this._availableGeometryIds.shift();
|
|
|
reservedRanges[ geometryId ] = reservedRange;
|
|
|
drawRanges[ geometryId ] = drawRange;
|
|
|
bounds[ geometryId ] = boundsInfo;
|
|
|
@@ -1002,6 +1020,109 @@ class BatchedMesh extends Mesh {
|
|
|
|
|
|
}
|
|
|
|
|
|
+ setInstanceCount( maxInstanceCount ) {
|
|
|
+
|
|
|
+ // shrink the available instances as much as possible
|
|
|
+ const availableInstanceIds = this._availableInstanceIds;
|
|
|
+ const drawInfo = this._drawInfo;
|
|
|
+ availableInstanceIds.sort( ascIdSort );
|
|
|
+ while ( availableInstanceIds[ availableInstanceIds.length - 1 ] === drawInfo.length ) {
|
|
|
+
|
|
|
+ drawInfo.pop();
|
|
|
+ availableInstanceIds.pop();
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ // throw an error if it can't be shrunk to the desired size
|
|
|
+ if ( maxInstanceCount < drawInfo.length ) {
|
|
|
+
|
|
|
+ throw new Error( `BatchedMesh: Instance ids outside the range ${ maxInstanceCount } are being used. Cannot shrink instance count.` );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ // copy the multi draw counts
|
|
|
+ const multiDrawCounts = new Int32Array( maxInstanceCount );
|
|
|
+ const multiDrawStarts = new Int32Array( maxInstanceCount );
|
|
|
+ copyArrayContents( this._multiDrawCounts, multiDrawCounts );
|
|
|
+ copyArrayContents( this._multiDrawStarts, multiDrawStarts );
|
|
|
+
|
|
|
+ this._multiDrawCounts = multiDrawCounts;
|
|
|
+ this._multiDrawStarts = multiDrawStarts;
|
|
|
+ this._maxInstanceCount = maxInstanceCount;
|
|
|
+
|
|
|
+ // update texture data for instance sampling
|
|
|
+ const indirectTexture = this._indirectTexture;
|
|
|
+ const matricesTexture = this._matricesTexture;
|
|
|
+ const colorsTexture = this._colorsTexture;
|
|
|
+
|
|
|
+ this._initIndirectTexture();
|
|
|
+ copyArrayContents( indirectTexture.image.data, this._indirectTexture.image.data );
|
|
|
+
|
|
|
+ this._initMatricesTexture();
|
|
|
+ copyArrayContents( matricesTexture.image.data, this._matricesTexture.image.data );
|
|
|
+
|
|
|
+ if ( colorsTexture ) {
|
|
|
+
|
|
|
+ this._initColorsTexture();
|
|
|
+ copyArrayContents( colorsTexture.image.data, this._colorsTexture.image.data );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ setGeometrySize( maxVertexCount, maxIndexCount ) {
|
|
|
+
|
|
|
+ // Check if we can shrink to the requested vertex attribute size
|
|
|
+ const validRanges = [ ...this._reservedRanges ].filter( ( range, i ) => this._drawRanges[ i ].active );
|
|
|
+ const requiredVertexLength = Math.max( ...validRanges.map( range => range.vertexStart + range.vertexCount ) );
|
|
|
+ if ( requiredVertexLength > maxVertexCount ) {
|
|
|
+
|
|
|
+ throw new Error( `BatchedMesh: Geometry vertex values are being used outside the range ${ maxIndexCount }. Cannot shrink further.` );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check if we can shrink to the requested index attribute size
|
|
|
+ if ( this.geometry.index ) {
|
|
|
+
|
|
|
+ const requiredIndexLength = Math.max( ...validRanges.map( range => range.indexStart + range.indexCount ) );
|
|
|
+ if ( requiredIndexLength > maxIndexCount ) {
|
|
|
+
|
|
|
+ throw new Error( `BatchedMesh: Geometry index values are being used outside the range ${ maxIndexCount }. Cannot shrink further.` );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ //
|
|
|
+
|
|
|
+ // dispose of the previous geometry
|
|
|
+ const oldGeometry = this.geometry;
|
|
|
+ oldGeometry.dispose();
|
|
|
+
|
|
|
+ // recreate the geometry needed based on the previous variant
|
|
|
+ this._maxVertexCount = maxVertexCount;
|
|
|
+ this._maxIndexCount = maxIndexCount;
|
|
|
+ this._geometryInitialized = false;
|
|
|
+
|
|
|
+ this.geometry = new BufferGeometry();
|
|
|
+ this._initializeGeometry( oldGeometry );
|
|
|
+
|
|
|
+ // copy data from the previous geometry
|
|
|
+ const geometry = this.geometry;
|
|
|
+ if ( oldGeometry.index ) {
|
|
|
+
|
|
|
+ copyArrayContents( oldGeometry.index.array, geometry.index.array );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ for ( const key in oldGeometry.attributes ) {
|
|
|
+
|
|
|
+ copyArrayContents( oldGeometry.attributes[ key ].array, geometry.attributes[ key ].array );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
raycast( raycaster, intersects ) {
|
|
|
|
|
|
const drawInfo = this._drawInfo;
|