Skip to content

Commit

Permalink
BatchedMesh: Add setGeometryAt, addGeometry functions (#27078)
Browse files Browse the repository at this point in the history
* BatchedMesh: Remove unused fields

* BatchedMesh: Add thrown errors in unimplemented functions

* Add PURE and glsl identifiers

* Add more error handling

* More errors

* Fix batched index overrun

* BatchedMesh: Check for normalized, as well

* Fix screenshot

* Add "setGeometry" function

* Fixes

* Remove "applyGeometry"

* Use "addGeometry" in example

* Swap arguments

* Update set geometry functions

* Cleanup

* Add error messages

* linting

* Reset the BATCHING value

* Remove unnecessary value set

* Swap the batch define true / false order
  • Loading branch information
gkjohnson authored Oct 31, 2023
1 parent d177020 commit baf3546
Show file tree
Hide file tree
Showing 2 changed files with 194 additions and 69 deletions.
261 changes: 193 additions & 68 deletions examples/jsm/objects/BatchedMesh.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ class BatchedMesh extends Mesh {
this._vertexCounts = [];
this._indexStarts = [];
this._indexCounts = [];
this._reservedRanges = [];

this._visible = [];
this._active = [];
Expand Down Expand Up @@ -247,6 +248,50 @@ class BatchedMesh extends Mesh {

}

// Make sure the geometry is compatible with the existing combined geometry atributes
_validateGeometry( geometry ) {

// check that the geometry doesn't have a version of our reserved id attribute
if ( geometry.getAttribute( ID_ATTR_NAME ) ) {

throw new Error( `BatchedMesh: Geometry cannot use attribute "${ ID_ATTR_NAME }"` );

}

// check to ensure the geometries are using consistent attributes and indices
const batchGeometry = this.geometry;
if ( Boolean( geometry.getIndex() ) !== Boolean( batchGeometry.getIndex() ) ) {

throw new Error( 'BatchedMesh: All geometries must consistently have "index".' );

}

for ( const attributeName in batchGeometry.attributes ) {

if ( attributeName === ID_ATTR_NAME ) {

continue;

}

if ( ! geometry.hasAttribute( attributeName ) ) {

throw new Error( `BatchedMesh: Added geometry missing "${ attributeName }". All geometries must have consistent attributes.` );

}

const srcAttribute = geometry.getAttribute( attributeName );
const dstAttribute = batchGeometry.getAttribute( attributeName );
if ( srcAttribute.itemSize !== dstAttribute.itemSize || srcAttribute.normalized !== dstAttribute.normalized ) {

throw new Error( 'BatchedMesh: All attributes must have a consistent itemSize and normalized value.' );

}

}

}

getGeometryCount() {

return this._geometryCount;
Expand All @@ -265,94 +310,176 @@ class BatchedMesh extends Mesh {

}

applyGeometry( geometry ) {
addGeometry( geometry, vertexCount = - 1, indexCount = - 1 ) {

this._initializeGeometry( geometry );

this._validateGeometry( geometry );

// ensure we're not over geometry
if ( this._geometryCount >= this._maxGeometryCount ) {

throw new Error( 'BatchedMesh: Maximum geometry count reached.' );

}

// check that the geometry doesn't have a version of our reserved id attribute
if ( geometry.getAttribute( ID_ATTR_NAME ) ) {
// get the necessary range fo the geometry
const range = {
vertexStart: - 1,
vertexCount: - 1,
indexStart: - 1,
indexCount: - 1,
};

throw new Error( `BatchedMesh: Geometry cannot use attribute "${ ID_ATTR_NAME }"` );
let lastRange = null;
const reservedRanges = this._reservedRanges;
if ( this._geometryCount !== 0 ) {

lastRange = reservedRanges[ reservedRanges.length - 1 ];

}

// check to ensure the geometries are using consistent attributes and indices
const batchGeometry = this.geometry;
if ( Boolean( geometry.getIndex() ) !== Boolean( batchGeometry.getIndex() ) ) {
if ( vertexCount === - 1 ) {

throw new Error( 'BatchedMesh: All geometries must consistently have "index".' );
range.vertexCount = geometry.getAttribute( 'position' ).count;

} else {

range.vertexCount = vertexCount;

}

for ( const attributeName in batchGeometry.attributes ) {
if ( lastRange === null ) {

if ( attributeName === ID_ATTR_NAME ) {
range.vertexStart = 0;

continue;
} else {

}
range.vertexStart = lastRange.vertexStart + lastRange.vertexCount;

if ( ! geometry.hasAttribute( attributeName ) ) {
}

throw new Error( `BatchedMesh: Added geometry missing "${ attributeName }". All geometries must have consistent attributes.` );
if ( geometry.getIndex() !== null ) {

if ( indexCount === - 1 ) {

range.indexCount = geometry.getIndex().count;

} else {

range.indexCount = indexCount;

}

const srcAttribute = geometry.getAttribute( attributeName );
const dstAttribute = batchGeometry.getAttribute( attributeName );
if ( srcAttribute.itemSize !== dstAttribute.itemSize || srcAttribute.normalized !== dstAttribute.normalized ) {
if ( lastRange === null ) {

throw new Error( 'BatchedMesh: All attributes must have a consistent itemSize and normalized value.' );
range.indexStart = 0;

} else {

range.indexStart = lastRange.indexStart + lastRange.indexCount;

}

}

// Assuming geometry has position attribute
const srcPositionAttribute = geometry.getAttribute( 'position' );
const vertexCount = this._vertexCount;
const indexCount = this._indexCount;
const maxVertexCount = this._maxVertexCount;
const maxIndexCount = this._maxIndexCount;

// check if we're going over our maximum buffer capacity
if (
geometry.getIndex() !== null &&
indexCount + geometry.getIndex().count > maxIndexCount ||
vertexCount + srcPositionAttribute.count > maxVertexCount
range.indexStart !== - 1 &&
range.indexStart + range.indexCount > this._maxIndexCount ||
range.vertexStart + range.vertexCount > this._maxVertexCount
) {

throw new Error( 'BatchedMesh: Added geometry is larger than available buffer capacity.' );
throw new Error( 'BatchedMesh: Reserved space request exceeds the maximum buffer size.' );

}

const indexCounts = this._indexCounts;
const indexStarts = this._indexStarts;
const vertexCounts = this._vertexCounts;
const vertexStarts = this._vertexStarts;

const visible = this._visible;
const active = this._active;
const matricesTexture = this._matricesTexture;
const matrices = this._matrices;
const matricesArray = this._matricesTexture.image.data;

const indexCounts = this._indexCounts;
const indexStarts = this._indexStarts;
const vertexCounts = this._vertexCounts;
const vertexStarts = this._vertexStarts;
// push new visibility states
visible.push( true );
active.push( true );

// update id
const geometryId = this._geometryCount;
this._geometryCount ++;

// initialize matrix information
matrices.push( new Matrix4() );
_identityMatrix.toArray( matricesArray, geometryId * 16 );
matricesTexture.needsUpdate = true;

// add the reserved range
reservedRanges.push( range );

// push new geometry data range
vertexStarts.push( range.vertexStart );
vertexCounts.push( range.vertexCount );

if ( geometry.getIndex() !== null ) {

// push new index range
indexStarts.push( range.indexCount );
indexCounts.push( range.indexCount );

}

// set the id for the geometry
const idAttribute = this.geometry.getAttribute( ID_ATTR_NAME );
for ( let i = 0; i < range.vertexCount; i ++ ) {

idAttribute.setX( range.vertexStart + i, geometryId );

}

idAttribute.needsUpdate = true;

// update the geometry
this.setGeometryAt( geometryId, geometry );

return geometryId;

}

setGeometryAt( id, geometry ) {

if ( id >= this._geometryCount ) {

throw new Error( 'BatchedMesh: Maximum geometry count reached.' );

}

this._validateGeometry( geometry );

const range = this._reservedRanges[ id ];
if (
geometry.getIndex() !== null &&
geometry.getIndex().count > range.indexCount ||
geometry.attributes.position.count > range.vertexCount
) {

throw new Error( 'BatchedMesh: Reserved space not large enough for provided geometry.' );

}

// copy geometry over
const batchGeometry = this.geometry;
const srcPositionAttribute = geometry.getAttribute( 'position' );
const hasIndex = batchGeometry.getIndex() !== null;
const dstIndex = batchGeometry.getIndex();
const srcIndex = geometry.getIndex();

// push new geometry data range
vertexStarts.push( vertexCount );
vertexCounts.push( srcPositionAttribute.count );

// copy attribute data over
const vertexStart = range.vertexStart;
const vertexCount = range.vertexCount;
for ( const attributeName in batchGeometry.attributes ) {

if ( attributeName === ID_ATTR_NAME ) {
Expand All @@ -363,54 +490,52 @@ class BatchedMesh extends Mesh {

const srcAttribute = geometry.getAttribute( attributeName );
const dstAttribute = batchGeometry.getAttribute( attributeName );
copyAttributeData( srcAttribute, dstAttribute, vertexCount );
copyAttributeData( srcAttribute, dstAttribute, vertexStart );

}
// fill the rest in with zeroes
const itemSize = srcAttribute.itemSize;
for ( let i = srcAttribute.count, l = vertexCount; i < l; i ++ ) {

if ( hasIndex ) {

// push new index range
indexStarts.push( indexCount );
indexCounts.push( srcIndex.count );
const index = vertexStart + i;
for ( let c = 0; c < itemSize; c ++ ) {

// copy index data over
for ( let i = 0; i < srcIndex.count; i ++ ) {
dstAttribute.setComponent( index, c, 0 );

dstIndex.setX( indexCount + i, vertexCount + srcIndex.getX( i ) );
}

}

this._indexCount += srcIndex.count;
dstIndex.needsUpdate = true;
dstAttribute.needsUpdate = true;

}

// fill in the geometry ids
const geometryId = this._geometryCount;
this._geometryCount ++;
this._vertexCounts[ id ] = srcPositionAttribute.count;

const idAttribute = batchGeometry.getAttribute( ID_ATTR_NAME );
for ( let i = 0; i < srcPositionAttribute.count; i ++ ) {
if ( hasIndex ) {

idAttribute.setX( this._vertexCount + i, geometryId );
// fill the rest in with zeroes
const indexStart = range.indexStart;

}
// copy index data over
for ( let i = 0; i < srcIndex.count; i ++ ) {

idAttribute.needsUpdate = true;
dstIndex.setX( indexStart + i, vertexStart + srcIndex.getX( i ) );

// extend new range
this._vertexCount += srcPositionAttribute.count;
}

// push new visibility states
visible.push( true );
active.push( true );
// fill the rest in with zeroes
for ( let i = srcIndex.count, l = range.indexCount; i < l; i ++ ) {

// initialize matrix information
matrices.push( new Matrix4() );
_identityMatrix.toArray( matricesArray, geometryId * 16 );
matricesTexture.needsUpdate = true;
dstIndex.setX( indexStart + i, vertexStart );

return geometryId;
}

dstIndex.needsUpdate = true;
this._indexCounts[ id ] = srcIndex.count;

}

return id;

}

Expand Down
2 changes: 1 addition & 1 deletion examples/webgl_mesh_batch.html
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@

for ( let i = 0; i < api.count; i ++ ) {

const id = mesh.applyGeometry( geometries[ i % geometries.length ] );
const id = mesh.addGeometry( geometries[ i % geometries.length ] );
mesh.setMatrixAt( id, randomizeMatrix( matrix ) );

const rotationMatrix = new THREE.Matrix4();
Expand Down

0 comments on commit baf3546

Please sign in to comment.