diff --git a/src/objects/BatchedMesh.js b/src/objects/BatchedMesh.js index 4e13b16a57163b..514e94b2d99dcd 100644 --- a/src/objects/BatchedMesh.js +++ b/src/objects/BatchedMesh.js @@ -97,7 +97,6 @@ const _batchIntersects = []; // @TODO: geometry.drawRange support? // @TODO: geometry.morphAttributes support? // @TODO: Support uniform parameter per geometry -// @TODO: Add an "optimize" function to pack geometry and remove data gaps // copies data from attribute "src" into "target" starting at "targetOffset" function copyAttributeData( src, target, targetOffset = 0 ) { @@ -132,8 +131,23 @@ 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 ) ); + if ( src.constructor !== target.constructor ) { + + // if arrays are of a different type (eg due to index size increasing) then data must be per-element copied + const len = Math.min( src.length, target.length ); + for ( let i = 0; i < len; i ++ ) { + + target[ i ] = src[ i ]; + + } + + } else { + + // if the arrays use the same data layout we can use a fast block copy + const len = Math.min( src.length, target.length ); + target.set( new src.constructor( src.buffer, 0, len ) ); + + } } @@ -180,6 +194,10 @@ class BatchedMesh extends Mesh { this._multiDrawInstances = null; this._visibilityChanged = true; + // used to track where the next point is that geometry should be inserted + this._nextIndexStart = 0; + this._nextVertexStart = 0; + // Local matrix per geometry by using data texture this._matricesTexture = null; this._indirectTexture = null; @@ -425,16 +443,11 @@ class BatchedMesh extends Mesh { indexCount: - 1, }; - let lastRange = null; const reservedRanges = this._reservedRanges; const drawRanges = this._drawRanges; const bounds = this._bounds; - if ( this._geometryCount !== 0 ) { - - lastRange = reservedRanges[ reservedRanges.length - 1 ]; - - } + reservedRange.vertexStart = this._nextVertexStart; if ( vertexCount === - 1 ) { reservedRange.vertexCount = geometry.getAttribute( 'position' ).count; @@ -445,20 +458,11 @@ class BatchedMesh extends Mesh { } - if ( lastRange === null ) { - - reservedRange.vertexStart = 0; - - } else { - - reservedRange.vertexStart = lastRange.vertexStart + lastRange.vertexCount; - - } - const index = geometry.getIndex(); const hasIndex = index !== null; if ( hasIndex ) { + reservedRange.indexStart = this._nextIndexStart; if ( indexCount === - 1 ) { reservedRange.indexCount = index.count; @@ -469,16 +473,6 @@ class BatchedMesh extends Mesh { } - if ( lastRange === null ) { - - reservedRange.indexStart = 0; - - } else { - - reservedRange.indexStart = lastRange.indexStart + lastRange.indexCount; - - } - } if ( @@ -531,6 +525,10 @@ class BatchedMesh extends Mesh { // update the geometry this.setGeometryAt( geometryId, geometry ); + // increment the next geometry position + this._nextIndexStart = reservedRange.indexStart + reservedRange.indexCount; + this._nextVertexStart = reservedRange.vertexStart + reservedRange.vertexCount; + return geometryId; } @@ -698,15 +696,25 @@ class BatchedMesh extends Mesh { let nextVertexStart = 0; let nextIndexStart = 0; - // iterate over all geometry ranges + // Iterate over all geometry ranges in order sorted from earliest in the geometry buffer to latest + // in the geometry buffer. Because draw range objects can be reused there is no guarantee of their order. const drawRanges = this._drawRanges; const reservedRanges = this._reservedRanges; + const indices = drawRanges + .map( ( e, i ) => i ) + .sort( ( a, b ) => { + + return reservedRanges[ a ].vertexStart - reservedRanges[ b ].vertexStart; + + } ); + const geometry = this.geometry; for ( let i = 0, l = drawRanges.length; i < l; i ++ ) { // if a geometry range is inactive then don't copy anything - const drawRange = drawRanges[ i ]; - const reservedRange = reservedRanges[ i ]; + const index = indices[ i ]; + const drawRange = drawRanges[ index ]; + const reservedRange = reservedRanges[ index ]; if ( drawRange.active === false ) { continue; @@ -714,25 +722,30 @@ class BatchedMesh extends Mesh { } // if a geometry contains an index buffer then shift it, as well - if ( geometry.index !== null && reservedRange.indexStart !== nextIndexStart ) { + if ( geometry.index !== null ) { - const { indexStart, indexCount } = reservedRange; - const index = geometry.index; - const array = index.array; + if ( reservedRange.indexStart !== nextIndexStart ) { - // shift the index pointers based on how the vertex data will shift - // adjusting the index must happen first so the original vertex start value is available - const elementDelta = nextVertexStart - reservedRange.vertexStart; - for ( let j = indexStart; j < indexStart + indexCount; j ++ ) { + const { indexStart, indexCount } = reservedRange; + const index = geometry.index; + const array = index.array; - array[ j ] = array[ j ] + elementDelta; + // shift the index pointers based on how the vertex data will shift + // adjusting the index must happen first so the original vertex start value is available + const elementDelta = nextVertexStart - reservedRange.vertexStart; + for ( let j = indexStart; j < indexStart + indexCount; j ++ ) { - } + array[ j ] = array[ j ] + elementDelta; + + } + + index.array.copyWithin( nextIndexStart, indexStart, indexStart + indexCount ); + index.addUpdateRange( nextIndexStart, indexCount ); - index.array.copyWithin( nextIndexStart, indexStart, indexStart + indexCount ); - index.addUpdateRange( nextIndexStart, indexCount ); + reservedRange.indexStart = nextIndexStart; + + } - reservedRange.indexStart = nextIndexStart; nextIndexStart += reservedRange.indexCount; } @@ -752,12 +765,16 @@ class BatchedMesh extends Mesh { } reservedRange.vertexStart = nextVertexStart; - nextVertexStart += reservedRange.vertexCount; } + nextVertexStart += reservedRange.vertexCount; drawRange.start = geometry.index ? reservedRange.indexStart : reservedRange.vertexStart; + // step the next geometry points to the shifted position + this._nextIndexStart = geometry.index ? reservedRange.indexStart + reservedRange.indexCount : 0; + this._nextVertexStart = reservedRange.vertexStart + reservedRange.vertexCount; + } return this; @@ -857,9 +874,6 @@ class BatchedMesh extends Mesh { setMatrixAt( instanceId, matrix ) { - // @TODO: Map geometryId to index of the arrays because - // optimize() can make geometryId mismatch the index - const drawInfo = this._drawInfo; const matricesTexture = this._matricesTexture; const matricesArray = this._matricesTexture.image.data; @@ -898,9 +912,6 @@ class BatchedMesh extends Mesh { } - // @TODO: Map id to index of the arrays because - // optimize() can make id mismatch the index - const colorsTexture = this._colorsTexture; const colorsArray = this._colorsTexture.image.data; const drawInfo = this._drawInfo; @@ -1055,14 +1066,17 @@ class BatchedMesh extends Mesh { const matricesTexture = this._matricesTexture; const colorsTexture = this._colorsTexture; + indirectTexture.dispose(); this._initIndirectTexture(); copyArrayContents( indirectTexture.image.data, this._indirectTexture.image.data ); + matricesTexture.dispose(); this._initMatricesTexture(); copyArrayContents( matricesTexture.image.data, this._matricesTexture.image.data ); if ( colorsTexture ) { + colorsTexture.dispose(); this._initColorsTexture(); copyArrayContents( colorsTexture.image.data, this._colorsTexture.image.data ); @@ -1158,7 +1172,7 @@ class BatchedMesh extends Mesh { const drawRange = drawRanges[ geometryId ]; _mesh.geometry.setDrawRange( drawRange.start, drawRange.count ); - // ge the intersects + // get the intersects this.getMatrixAt( i, _mesh.matrixWorld ).premultiply( matrixWorld ); this.getBoundingBoxAt( geometryId, _mesh.geometry.boundingBox ); this.getBoundingSphereAt( geometryId, _mesh.geometry.boundingSphere );