Skip to content

Commit fdd528d

Browse files
authored
Batchmesh: Fix cases where calling optimize can result in inconsistent state (#29624)
* Fix skipping over active rather than inactive geometry * Fix optimize * Correct the shifting of geometry data * Account for a case where the index bit size can be increased. * typo fix * Remove comments * Add comments, removed unnecessary changes * Dispose of textures on instance resize
1 parent b9144b9 commit fdd528d

File tree

1 file changed

+67
-53
lines changed

1 file changed

+67
-53
lines changed

src/objects/BatchedMesh.js

Lines changed: 67 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,6 @@ const _batchIntersects = [];
9797
// @TODO: geometry.drawRange support?
9898
// @TODO: geometry.morphAttributes support?
9999
// @TODO: Support uniform parameter per geometry
100-
// @TODO: Add an "optimize" function to pack geometry and remove data gaps
101100

102101
// copies data from attribute "src" into "target" starting at "targetOffset"
103102
function copyAttributeData( src, target, targetOffset = 0 ) {
@@ -132,8 +131,23 @@ function copyAttributeData( src, target, targetOffset = 0 ) {
132131
// safely copies array contents to a potentially smaller array
133132
function copyArrayContents( src, target ) {
134133

135-
const len = Math.min( src.length, target.length );
136-
target.set( new src.constructor( src.buffer, 0, len ) );
134+
if ( src.constructor !== target.constructor ) {
135+
136+
// if arrays are of a different type (eg due to index size increasing) then data must be per-element copied
137+
const len = Math.min( src.length, target.length );
138+
for ( let i = 0; i < len; i ++ ) {
139+
140+
target[ i ] = src[ i ];
141+
142+
}
143+
144+
} else {
145+
146+
// if the arrays use the same data layout we can use a fast block copy
147+
const len = Math.min( src.length, target.length );
148+
target.set( new src.constructor( src.buffer, 0, len ) );
149+
150+
}
137151

138152
}
139153

@@ -180,6 +194,10 @@ class BatchedMesh extends Mesh {
180194
this._multiDrawInstances = null;
181195
this._visibilityChanged = true;
182196

197+
// used to track where the next point is that geometry should be inserted
198+
this._nextIndexStart = 0;
199+
this._nextVertexStart = 0;
200+
183201
// Local matrix per geometry by using data texture
184202
this._matricesTexture = null;
185203
this._indirectTexture = null;
@@ -425,16 +443,11 @@ class BatchedMesh extends Mesh {
425443
indexCount: - 1,
426444
};
427445

428-
let lastRange = null;
429446
const reservedRanges = this._reservedRanges;
430447
const drawRanges = this._drawRanges;
431448
const bounds = this._bounds;
432-
if ( this._geometryCount !== 0 ) {
433-
434-
lastRange = reservedRanges[ reservedRanges.length - 1 ];
435-
436-
}
437449

450+
reservedRange.vertexStart = this._nextVertexStart;
438451
if ( vertexCount === - 1 ) {
439452

440453
reservedRange.vertexCount = geometry.getAttribute( 'position' ).count;
@@ -445,20 +458,11 @@ class BatchedMesh extends Mesh {
445458

446459
}
447460

448-
if ( lastRange === null ) {
449-
450-
reservedRange.vertexStart = 0;
451-
452-
} else {
453-
454-
reservedRange.vertexStart = lastRange.vertexStart + lastRange.vertexCount;
455-
456-
}
457-
458461
const index = geometry.getIndex();
459462
const hasIndex = index !== null;
460463
if ( hasIndex ) {
461464

465+
reservedRange.indexStart = this._nextIndexStart;
462466
if ( indexCount === - 1 ) {
463467

464468
reservedRange.indexCount = index.count;
@@ -469,16 +473,6 @@ class BatchedMesh extends Mesh {
469473

470474
}
471475

472-
if ( lastRange === null ) {
473-
474-
reservedRange.indexStart = 0;
475-
476-
} else {
477-
478-
reservedRange.indexStart = lastRange.indexStart + lastRange.indexCount;
479-
480-
}
481-
482476
}
483477

484478
if (
@@ -531,6 +525,10 @@ class BatchedMesh extends Mesh {
531525
// update the geometry
532526
this.setGeometryAt( geometryId, geometry );
533527

528+
// increment the next geometry position
529+
this._nextIndexStart = reservedRange.indexStart + reservedRange.indexCount;
530+
this._nextVertexStart = reservedRange.vertexStart + reservedRange.vertexCount;
531+
534532
return geometryId;
535533

536534
}
@@ -698,41 +696,56 @@ class BatchedMesh extends Mesh {
698696
let nextVertexStart = 0;
699697
let nextIndexStart = 0;
700698

701-
// iterate over all geometry ranges
699+
// Iterate over all geometry ranges in order sorted from earliest in the geometry buffer to latest
700+
// in the geometry buffer. Because draw range objects can be reused there is no guarantee of their order.
702701
const drawRanges = this._drawRanges;
703702
const reservedRanges = this._reservedRanges;
703+
const indices = drawRanges
704+
.map( ( e, i ) => i )
705+
.sort( ( a, b ) => {
706+
707+
return reservedRanges[ a ].vertexStart - reservedRanges[ b ].vertexStart;
708+
709+
} );
710+
704711
const geometry = this.geometry;
705712
for ( let i = 0, l = drawRanges.length; i < l; i ++ ) {
706713

707714
// if a geometry range is inactive then don't copy anything
708-
const drawRange = drawRanges[ i ];
709-
const reservedRange = reservedRanges[ i ];
715+
const index = indices[ i ];
716+
const drawRange = drawRanges[ index ];
717+
const reservedRange = reservedRanges[ index ];
710718
if ( drawRange.active === false ) {
711719

712720
continue;
713721

714722
}
715723

716724
// if a geometry contains an index buffer then shift it, as well
717-
if ( geometry.index !== null && reservedRange.indexStart !== nextIndexStart ) {
725+
if ( geometry.index !== null ) {
718726

719-
const { indexStart, indexCount } = reservedRange;
720-
const index = geometry.index;
721-
const array = index.array;
727+
if ( reservedRange.indexStart !== nextIndexStart ) {
722728

723-
// shift the index pointers based on how the vertex data will shift
724-
// adjusting the index must happen first so the original vertex start value is available
725-
const elementDelta = nextVertexStart - reservedRange.vertexStart;
726-
for ( let j = indexStart; j < indexStart + indexCount; j ++ ) {
729+
const { indexStart, indexCount } = reservedRange;
730+
const index = geometry.index;
731+
const array = index.array;
727732

728-
array[ j ] = array[ j ] + elementDelta;
733+
// shift the index pointers based on how the vertex data will shift
734+
// adjusting the index must happen first so the original vertex start value is available
735+
const elementDelta = nextVertexStart - reservedRange.vertexStart;
736+
for ( let j = indexStart; j < indexStart + indexCount; j ++ ) {
729737

730-
}
738+
array[ j ] = array[ j ] + elementDelta;
739+
740+
}
741+
742+
index.array.copyWithin( nextIndexStart, indexStart, indexStart + indexCount );
743+
index.addUpdateRange( nextIndexStart, indexCount );
731744

732-
index.array.copyWithin( nextIndexStart, indexStart, indexStart + indexCount );
733-
index.addUpdateRange( nextIndexStart, indexCount );
745+
reservedRange.indexStart = nextIndexStart;
746+
747+
}
734748

735-
reservedRange.indexStart = nextIndexStart;
736749
nextIndexStart += reservedRange.indexCount;
737750

738751
}
@@ -752,12 +765,16 @@ class BatchedMesh extends Mesh {
752765
}
753766

754767
reservedRange.vertexStart = nextVertexStart;
755-
nextVertexStart += reservedRange.vertexCount;
756768

757769
}
758770

771+
nextVertexStart += reservedRange.vertexCount;
759772
drawRange.start = geometry.index ? reservedRange.indexStart : reservedRange.vertexStart;
760773

774+
// step the next geometry points to the shifted position
775+
this._nextIndexStart = geometry.index ? reservedRange.indexStart + reservedRange.indexCount : 0;
776+
this._nextVertexStart = reservedRange.vertexStart + reservedRange.vertexCount;
777+
761778
}
762779

763780
return this;
@@ -857,9 +874,6 @@ class BatchedMesh extends Mesh {
857874

858875
setMatrixAt( instanceId, matrix ) {
859876

860-
// @TODO: Map geometryId to index of the arrays because
861-
// optimize() can make geometryId mismatch the index
862-
863877
const drawInfo = this._drawInfo;
864878
const matricesTexture = this._matricesTexture;
865879
const matricesArray = this._matricesTexture.image.data;
@@ -898,9 +912,6 @@ class BatchedMesh extends Mesh {
898912

899913
}
900914

901-
// @TODO: Map id to index of the arrays because
902-
// optimize() can make id mismatch the index
903-
904915
const colorsTexture = this._colorsTexture;
905916
const colorsArray = this._colorsTexture.image.data;
906917
const drawInfo = this._drawInfo;
@@ -1055,14 +1066,17 @@ class BatchedMesh extends Mesh {
10551066
const matricesTexture = this._matricesTexture;
10561067
const colorsTexture = this._colorsTexture;
10571068

1069+
indirectTexture.dispose();
10581070
this._initIndirectTexture();
10591071
copyArrayContents( indirectTexture.image.data, this._indirectTexture.image.data );
10601072

1073+
matricesTexture.dispose();
10611074
this._initMatricesTexture();
10621075
copyArrayContents( matricesTexture.image.data, this._matricesTexture.image.data );
10631076

10641077
if ( colorsTexture ) {
10651078

1079+
colorsTexture.dispose();
10661080
this._initColorsTexture();
10671081
copyArrayContents( colorsTexture.image.data, this._colorsTexture.image.data );
10681082

@@ -1158,7 +1172,7 @@ class BatchedMesh extends Mesh {
11581172
const drawRange = drawRanges[ geometryId ];
11591173
_mesh.geometry.setDrawRange( drawRange.start, drawRange.count );
11601174

1161-
// ge the intersects
1175+
// get the intersects
11621176
this.getMatrixAt( i, _mesh.matrixWorld ).premultiply( matrixWorld );
11631177
this.getBoundingBoxAt( geometryId, _mesh.geometry.boundingBox );
11641178
this.getBoundingSphereAt( geometryId, _mesh.geometry.boundingSphere );

0 commit comments

Comments
 (0)