MCP 3D Printer Server

by DMontgomery40
Verified
import { BufferAttribute } from '../core/BufferAttribute.js'; import { BufferGeometry } from '../core/BufferGeometry.js'; import { DataTexture } from '../textures/DataTexture.js'; import { FloatType, RedIntegerFormat, UnsignedIntType, RGBAFormat } from '../constants.js'; import { Matrix4 } from '../math/Matrix4.js'; import { Mesh } from './Mesh.js'; import { ColorManagement } from '../math/ColorManagement.js'; import { Box3 } from '../math/Box3.js'; import { Sphere } from '../math/Sphere.js'; 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; } function sortTransparent( a, b ) { return b.z - a.z; } class MultiDrawRenderList { constructor() { this.index = 0; this.pool = []; this.list = []; } push( start, count, z, index ) { const pool = this.pool; const list = this.list; if ( this.index >= pool.length ) { pool.push( { start: - 1, count: - 1, z: - 1, index: - 1, } ); } const item = pool[ this.index ]; list.push( item ); this.index ++; item.start = start; item.count = count; item.z = z; item.index = index; } reset() { this.list.length = 0; this.index = 0; } } const _matrix = /*@__PURE__*/ new Matrix4(); const _whiteColor = /*@__PURE__*/ new Color( 1, 1, 1 ); const _frustum = /*@__PURE__*/ new Frustum(); const _box = /*@__PURE__*/ new Box3(); const _sphere = /*@__PURE__*/ new Sphere(); const _vector = /*@__PURE__*/ new Vector3(); const _forward = /*@__PURE__*/ new Vector3(); const _temp = /*@__PURE__*/ new Vector3(); const _renderList = /*@__PURE__*/ new MultiDrawRenderList(); const _mesh = /*@__PURE__*/ new Mesh(); const _batchIntersects = []; // copies data from attribute "src" into "target" starting at "targetOffset" function copyAttributeData( src, target, targetOffset = 0 ) { const itemSize = target.itemSize; if ( src.isInterleavedBufferAttribute || src.array.constructor !== target.array.constructor ) { // use the component getters and setters if the array data cannot // be copied directly const vertexCount = src.count; for ( let i = 0; i < vertexCount; i ++ ) { for ( let c = 0; c < itemSize; c ++ ) { target.setComponent( i + targetOffset, c, src.getComponent( i, c ) ); } } } else { // faster copy approach using typed array set function target.array.set( src.array, targetOffset * itemSize ); } target.needsUpdate = true; } // safely copies array contents to a potentially smaller array function copyArrayContents( src, target ) { 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 ) ); } } class BatchedMesh extends Mesh { get maxInstanceCount() { return this._maxInstanceCount; } get instanceCount() { return this._instanceInfo.length - this._availableInstanceIds.length; } get unusedVertexCount() { return this._maxVertexCount - this._nextVertexStart; } get unusedIndexCount() { return this._maxIndexCount - this._nextIndexStart; } constructor( maxInstanceCount, maxVertexCount, maxIndexCount = maxVertexCount * 2, material ) { super( new BufferGeometry(), material ); this.isBatchedMesh = true; this.perObjectFrustumCulled = true; this.sortObjects = true; this.boundingBox = null; this.boundingSphere = null; this.customSort = null; // stores visible, active, and geometry id per instance and reserved buffer ranges for geometries this._instanceInfo = []; this._geometryInfo = []; // instance, geometry ids that have been set as inactive, and are available to be overwritten this._availableInstanceIds = []; this._availableGeometryIds = []; // used to track where the next point is that geometry should be inserted this._nextIndexStart = 0; this._nextVertexStart = 0; this._geometryCount = 0; // flags this._visibilityChanged = true; this._geometryInitialized = false; // cached user options this._maxInstanceCount = maxInstanceCount; this._maxVertexCount = maxVertexCount; this._maxIndexCount = maxIndexCount; // buffers for multi draw this._multiDrawCounts = new Int32Array( maxInstanceCount ); this._multiDrawStarts = new Int32Array( maxInstanceCount ); this._multiDrawCount = 0; this._multiDrawInstances = null; // Local matrix per geometry by using data texture this._matricesTexture = null; this._indirectTexture = null; this._colorsTexture = null; this._initMatricesTexture(); this._initIndirectTexture(); } _initMatricesTexture() { // layout (1 matrix = 4 pixels) // RGBA RGBA RGBA RGBA (=> column1, column2, column3, column4) // with 8x8 pixel texture max 16 matrices * 4 pixels = (8 * 8) // 16x16 pixel texture max 64 matrices * 4 pixels = (16 * 16) // 32x32 pixel texture max 256 matrices * 4 pixels = (32 * 32) // 64x64 pixel texture max 1024 matrices * 4 pixels = (64 * 64) let size = Math.sqrt( this._maxInstanceCount * 4 ); // 4 pixels needed for 1 matrix size = Math.ceil( size / 4 ) * 4; size = Math.max( size, 4 ); const matricesArray = new Float32Array( size * size * 4 ); // 4 floats per RGBA pixel const matricesTexture = new DataTexture( matricesArray, size, size, RGBAFormat, FloatType ); this._matricesTexture = matricesTexture; } _initIndirectTexture() { let size = Math.sqrt( this._maxInstanceCount ); size = Math.ceil( size ); const indirectArray = new Uint32Array( size * size ); const indirectTexture = new DataTexture( indirectArray, size, size, RedIntegerFormat, UnsignedIntType ); this._indirectTexture = indirectTexture; } _initColorsTexture() { let size = Math.sqrt( this._maxInstanceCount ); size = Math.ceil( size ); // 4 floats per RGBA pixel initialized to white const colorsArray = new Float32Array( size * size * 4 ).fill( 1 ); const colorsTexture = new DataTexture( colorsArray, size, size, RGBAFormat, FloatType ); colorsTexture.colorSpace = ColorManagement.workingColorSpace; this._colorsTexture = colorsTexture; } _initializeGeometry( reference ) { const geometry = this.geometry; const maxVertexCount = this._maxVertexCount; const maxIndexCount = this._maxIndexCount; if ( this._geometryInitialized === false ) { for ( const attributeName in reference.attributes ) { const srcAttribute = reference.getAttribute( attributeName ); const { array, itemSize, normalized } = srcAttribute; const dstArray = new array.constructor( maxVertexCount * itemSize ); const dstAttribute = new BufferAttribute( dstArray, itemSize, normalized ); geometry.setAttribute( attributeName, dstAttribute ); } if ( reference.getIndex() !== null ) { // Reserve last u16 index for primitive restart. const indexArray = maxVertexCount > 65535 ? new Uint32Array( maxIndexCount ) : new Uint16Array( maxIndexCount ); geometry.setIndex( new BufferAttribute( indexArray, 1 ) ); } this._geometryInitialized = true; } } // Make sure the geometry is compatible with the existing combined geometry attributes _validateGeometry( geometry ) { // 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( 'THREE.BatchedMesh: All geometries must consistently have "index".' ); } for ( const attributeName in batchGeometry.attributes ) { if ( ! geometry.hasAttribute( attributeName ) ) { throw new Error( `THREE.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( 'THREE.BatchedMesh: All attributes must have a consistent itemSize and normalized value.' ); } } } validateInstanceId( instanceId ) { const instanceInfo = this._instanceInfo; if ( instanceId < 0 || instanceId >= instanceInfo.length || instanceInfo[ instanceId ].active === false ) { throw new Error( `THREE.BatchedMesh: Invalid instanceId ${instanceId}. Instance is either out of range or has been deleted.` ); } } validateGeometryId( geometryId ) { const geometryInfoList = this._geometryInfo; if ( geometryId < 0 || geometryId >= geometryInfoList.length || geometryInfoList[ geometryId ].active === false ) { throw new Error( `THREE.BatchedMesh: Invalid geometryId ${geometryId}. Geometry is either out of range or has been deleted.` ); } } setCustomSort( func ) { this.customSort = func; return this; } computeBoundingBox() { if ( this.boundingBox === null ) { this.boundingBox = new Box3(); } const boundingBox = this.boundingBox; const instanceInfo = this._instanceInfo; boundingBox.makeEmpty(); for ( let i = 0, l = instanceInfo.length; i < l; i ++ ) { if ( instanceInfo[ i ].active === false ) continue; const geometryId = instanceInfo[ i ].geometryIndex; this.getMatrixAt( i, _matrix ); this.getBoundingBoxAt( geometryId, _box ).applyMatrix4( _matrix ); boundingBox.union( _box ); } } computeBoundingSphere() { if ( this.boundingSphere === null ) { this.boundingSphere = new Sphere(); } const boundingSphere = this.boundingSphere; const instanceInfo = this._instanceInfo; boundingSphere.makeEmpty(); for ( let i = 0, l = instanceInfo.length; i < l; i ++ ) { if ( instanceInfo[ i ].active === false ) continue; const geometryId = instanceInfo[ i ].geometryIndex; this.getMatrixAt( i, _matrix ); this.getBoundingSphereAt( geometryId, _sphere ).applyMatrix4( _matrix ); boundingSphere.union( _sphere ); } } addInstance( geometryId ) { const atCapacity = this._instanceInfo.length >= this.maxInstanceCount; // ensure we're not over geometry if ( atCapacity && this._availableInstanceIds.length === 0 ) { throw new Error( 'THREE.BatchedMesh: Maximum item count reached.' ); } const instanceInfo = { visible: true, active: true, geometryIndex: geometryId, }; let drawId = null; // Prioritize using previously freed instance ids if ( this._availableInstanceIds.length > 0 ) { this._availableInstanceIds.sort( ascIdSort ); drawId = this._availableInstanceIds.shift(); this._instanceInfo[ drawId ] = instanceInfo; } else { drawId = this._instanceInfo.length; this._instanceInfo.push( instanceInfo ); } const matricesTexture = this._matricesTexture; _matrix.identity().toArray( matricesTexture.image.data, drawId * 16 ); matricesTexture.needsUpdate = true; const colorsTexture = this._colorsTexture; if ( colorsTexture ) { _whiteColor.toArray( colorsTexture.image.data, drawId * 4 ); colorsTexture.needsUpdate = true; } this._visibilityChanged = true; return drawId; } addGeometry( geometry, reservedVertexCount = - 1, reservedIndexCount = - 1 ) { this._initializeGeometry( geometry ); this._validateGeometry( geometry ); const geometryInfo = { // geometry information vertexStart: - 1, vertexCount: - 1, reservedVertexCount: - 1, indexStart: - 1, indexCount: - 1, reservedIndexCount: - 1, // draw range information start: - 1, count: - 1, // state boundingBox: null, boundingSphere: null, active: true, }; const geometryInfoList = this._geometryInfo; geometryInfo.vertexStart = this._nextVertexStart; geometryInfo.reservedVertexCount = reservedVertexCount === - 1 ? geometry.getAttribute( 'position' ).count : reservedVertexCount; const index = geometry.getIndex(); const hasIndex = index !== null; if ( hasIndex ) { geometryInfo.indexStart = this._nextIndexStart; geometryInfo.reservedIndexCount = reservedIndexCount === - 1 ? index.count : reservedIndexCount; } if ( geometryInfo.indexStart !== - 1 && geometryInfo.indexStart + geometryInfo.reservedIndexCount > this._maxIndexCount || geometryInfo.vertexStart + geometryInfo.reservedVertexCount > this._maxVertexCount ) { throw new Error( 'THREE.BatchedMesh: Reserved space request exceeds the maximum buffer size.' ); } // update id let geometryId; if ( this._availableGeometryIds.length > 0 ) { this._availableGeometryIds.sort( ascIdSort ); geometryId = this._availableGeometryIds.shift(); geometryInfoList[ geometryId ] = geometryInfo; } else { geometryId = this._geometryCount; this._geometryCount ++; geometryInfoList.push( geometryInfo ); } // update the geometry this.setGeometryAt( geometryId, geometry ); // increment the next geometry position this._nextIndexStart = geometryInfo.indexStart + geometryInfo.reservedIndexCount; this._nextVertexStart = geometryInfo.vertexStart + geometryInfo.reservedVertexCount; return geometryId; } setGeometryAt( geometryId, geometry ) { if ( geometryId >= this._geometryCount ) { throw new Error( 'THREE.BatchedMesh: Maximum geometry count reached.' ); } this._validateGeometry( geometry ); const batchGeometry = this.geometry; const hasIndex = batchGeometry.getIndex() !== null; const dstIndex = batchGeometry.getIndex(); const srcIndex = geometry.getIndex(); const geometryInfo = this._geometryInfo[ geometryId ]; if ( hasIndex && srcIndex.count > geometryInfo.reservedIndexCount || geometry.attributes.position.count > geometryInfo.reservedVertexCount ) { throw new Error( 'THREE.BatchedMesh: Reserved space not large enough for provided geometry.' ); } // copy geometry buffer data over const vertexStart = geometryInfo.vertexStart; const reservedVertexCount = geometryInfo.reservedVertexCount; geometryInfo.vertexCount = geometry.getAttribute( 'position' ).count; for ( const attributeName in batchGeometry.attributes ) { // copy attribute data const srcAttribute = geometry.getAttribute( attributeName ); const dstAttribute = batchGeometry.getAttribute( attributeName ); copyAttributeData( srcAttribute, dstAttribute, vertexStart ); // fill the rest in with zeroes const itemSize = srcAttribute.itemSize; for ( let i = srcAttribute.count, l = reservedVertexCount; i < l; i ++ ) { const index = vertexStart + i; for ( let c = 0; c < itemSize; c ++ ) { dstAttribute.setComponent( index, c, 0 ); } } dstAttribute.needsUpdate = true; dstAttribute.addUpdateRange( vertexStart * itemSize, reservedVertexCount * itemSize ); } // copy index if ( hasIndex ) { const indexStart = geometryInfo.indexStart; const reservedIndexCount = geometryInfo.reservedIndexCount; geometryInfo.indexCount = geometry.getIndex().count; // copy index data over for ( let i = 0; i < srcIndex.count; i ++ ) { dstIndex.setX( indexStart + i, vertexStart + srcIndex.getX( i ) ); } // fill the rest in with zeroes for ( let i = srcIndex.count, l = reservedIndexCount; i < l; i ++ ) { dstIndex.setX( indexStart + i, vertexStart ); } dstIndex.needsUpdate = true; dstIndex.addUpdateRange( indexStart, geometryInfo.reservedIndexCount ); } // update the draw range geometryInfo.start = hasIndex ? geometryInfo.indexStart : geometryInfo.vertexStart; geometryInfo.count = hasIndex ? geometryInfo.indexCount : geometryInfo.vertexCount; // store the bounding boxes geometryInfo.boundingBox = null; if ( geometry.boundingBox !== null ) { geometryInfo.boundingBox = geometry.boundingBox.clone(); } geometryInfo.boundingSphere = null; if ( geometry.boundingSphere !== null ) { geometryInfo.boundingSphere = geometry.boundingSphere.clone(); } this._visibilityChanged = true; return geometryId; } deleteGeometry( geometryId ) { const geometryInfoList = this._geometryInfo; if ( geometryId >= geometryInfoList.length || geometryInfoList[ geometryId ].active === false ) { return this; } // delete any instances associated with this geometry const instanceInfo = this._instanceInfo; for ( let i = 0, l = instanceInfo.length; i < l; i ++ ) { if ( instanceInfo[ i ].active && instanceInfo[ i ].geometryIndex === geometryId ) { this.deleteInstance( i ); } } geometryInfoList[ geometryId ].active = false; this._availableGeometryIds.push( geometryId ); this._visibilityChanged = true; return this; } deleteInstance( instanceId ) { this.validateInstanceId( instanceId ); this._instanceInfo[ instanceId ].active = false; this._availableInstanceIds.push( instanceId ); this._visibilityChanged = true; return this; } optimize() { // track the next indices to copy data to let nextVertexStart = 0; let nextIndexStart = 0; // 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 geometryInfoList = this._geometryInfo; const indices = geometryInfoList .map( ( e, i ) => i ) .sort( ( a, b ) => { return geometryInfoList[ a ].vertexStart - geometryInfoList[ b ].vertexStart; } ); const geometry = this.geometry; for ( let i = 0, l = geometryInfoList.length; i < l; i ++ ) { // if a geometry range is inactive then don't copy anything const index = indices[ i ]; const geometryInfo = geometryInfoList[ index ]; if ( geometryInfo.active === false ) { continue; } // if a geometry contains an index buffer then shift it, as well if ( geometry.index !== null ) { if ( geometryInfo.indexStart !== nextIndexStart ) { const { indexStart, vertexStart, reservedIndexCount } = geometryInfo; const index = geometry.index; const array = index.array; // 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 - vertexStart; for ( let j = indexStart; j < indexStart + reservedIndexCount; j ++ ) { array[ j ] = array[ j ] + elementDelta; } index.array.copyWithin( nextIndexStart, indexStart, indexStart + reservedIndexCount ); index.addUpdateRange( nextIndexStart, reservedIndexCount ); geometryInfo.indexStart = nextIndexStart; } nextIndexStart += geometryInfo.reservedIndexCount; } // if a geometry needs to be moved then copy attribute data to overwrite unused space if ( geometryInfo.vertexStart !== nextVertexStart ) { const { vertexStart, reservedVertexCount } = geometryInfo; const attributes = geometry.attributes; for ( const key in attributes ) { const attribute = attributes[ key ]; const { array, itemSize } = attribute; array.copyWithin( nextVertexStart * itemSize, vertexStart * itemSize, ( vertexStart + reservedVertexCount ) * itemSize ); attribute.addUpdateRange( nextVertexStart * itemSize, reservedVertexCount * itemSize ); } geometryInfo.vertexStart = nextVertexStart; } nextVertexStart += geometryInfo.reservedVertexCount; geometryInfo.start = geometry.index ? geometryInfo.indexStart : geometryInfo.vertexStart; // step the next geometry points to the shifted position this._nextIndexStart = geometry.index ? geometryInfo.indexStart + geometryInfo.reservedIndexCount : 0; this._nextVertexStart = geometryInfo.vertexStart + geometryInfo.reservedVertexCount; } return this; } // get bounding box and compute it if it doesn't exist getBoundingBoxAt( geometryId, target ) { if ( geometryId >= this._geometryCount ) { return null; } // compute bounding box const geometry = this.geometry; const geometryInfo = this._geometryInfo[ geometryId ]; if ( geometryInfo.boundingBox === null ) { const box = new Box3(); const index = geometry.index; const position = geometry.attributes.position; for ( let i = geometryInfo.start, l = geometryInfo.start + geometryInfo.count; i < l; i ++ ) { let iv = i; if ( index ) { iv = index.getX( iv ); } box.expandByPoint( _vector.fromBufferAttribute( position, iv ) ); } geometryInfo.boundingBox = box; } target.copy( geometryInfo.boundingBox ); return target; } // get bounding sphere and compute it if it doesn't exist getBoundingSphereAt( geometryId, target ) { if ( geometryId >= this._geometryCount ) { return null; } // compute bounding sphere const geometry = this.geometry; const geometryInfo = this._geometryInfo[ geometryId ]; if ( geometryInfo.boundingSphere === null ) { const sphere = new Sphere(); this.getBoundingBoxAt( geometryId, _box ); _box.getCenter( sphere.center ); const index = geometry.index; const position = geometry.attributes.position; let maxRadiusSq = 0; for ( let i = geometryInfo.start, l = geometryInfo.start + geometryInfo.count; i < l; i ++ ) { let iv = i; if ( index ) { iv = index.getX( iv ); } _vector.fromBufferAttribute( position, iv ); maxRadiusSq = Math.max( maxRadiusSq, sphere.center.distanceToSquared( _vector ) ); } sphere.radius = Math.sqrt( maxRadiusSq ); geometryInfo.boundingSphere = sphere; } target.copy( geometryInfo.boundingSphere ); return target; } setMatrixAt( instanceId, matrix ) { this.validateInstanceId( instanceId ); const matricesTexture = this._matricesTexture; const matricesArray = this._matricesTexture.image.data; matrix.toArray( matricesArray, instanceId * 16 ); matricesTexture.needsUpdate = true; return this; } getMatrixAt( instanceId, matrix ) { this.validateInstanceId( instanceId ); return matrix.fromArray( this._matricesTexture.image.data, instanceId * 16 ); } setColorAt( instanceId, color ) { this.validateInstanceId( instanceId ); if ( this._colorsTexture === null ) { this._initColorsTexture(); } color.toArray( this._colorsTexture.image.data, instanceId * 4 ); this._colorsTexture.needsUpdate = true; return this; } getColorAt( instanceId, color ) { this.validateInstanceId( instanceId ); return color.fromArray( this._colorsTexture.image.data, instanceId * 4 ); } setVisibleAt( instanceId, value ) { this.validateInstanceId( instanceId ); if ( this._instanceInfo[ instanceId ].visible === value ) { return this; } this._instanceInfo[ instanceId ].visible = value; this._visibilityChanged = true; return this; } getVisibleAt( instanceId ) { this.validateInstanceId( instanceId ); return this._instanceInfo[ instanceId ].visible; } setGeometryIdAt( instanceId, geometryId ) { this.validateInstanceId( instanceId ); this.validateGeometryId( geometryId ); this._instanceInfo[ instanceId ].geometryIndex = geometryId; return this; } getGeometryIdAt( instanceId ) { this.validateInstanceId( instanceId ); return this._instanceInfo[ instanceId ].geometryIndex; } getGeometryRangeAt( geometryId, target = {} ) { this.validateGeometryId( geometryId ); const geometryInfo = this._geometryInfo[ geometryId ]; target.vertexStart = geometryInfo.vertexStart; target.vertexCount = geometryInfo.vertexCount; target.reservedVertexCount = geometryInfo.reservedVertexCount; target.indexStart = geometryInfo.indexStart; target.indexCount = geometryInfo.indexCount; target.reservedIndexCount = geometryInfo.reservedIndexCount; target.start = geometryInfo.start; target.count = geometryInfo.count; return target; } setInstanceCount( maxInstanceCount ) { // shrink the available instances as much as possible const availableInstanceIds = this._availableInstanceIds; const instanceInfo = this._instanceInfo; availableInstanceIds.sort( ascIdSort ); while ( availableInstanceIds[ availableInstanceIds.length - 1 ] === instanceInfo.length ) { instanceInfo.pop(); availableInstanceIds.pop(); } // throw an error if it can't be shrunk to the desired size if ( maxInstanceCount < instanceInfo.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; 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 ); } } setGeometrySize( maxVertexCount, maxIndexCount ) { // Check if we can shrink to the requested vertex attribute size const validRanges = [ ...this._geometryInfo ].filter( info => info.active ); const requiredVertexLength = Math.max( ...validRanges.map( range => range.vertexStart + range.reservedVertexCount ) ); 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.reservedIndexCount ) ); 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; if ( this._geometryInitialized ) { 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 instanceInfo = this._instanceInfo; const geometryInfoList = this._geometryInfo; const matrixWorld = this.matrixWorld; const batchGeometry = this.geometry; // iterate over each geometry _mesh.material = this.material; _mesh.geometry.index = batchGeometry.index; _mesh.geometry.attributes = batchGeometry.attributes; if ( _mesh.geometry.boundingBox === null ) { _mesh.geometry.boundingBox = new Box3(); } if ( _mesh.geometry.boundingSphere === null ) { _mesh.geometry.boundingSphere = new Sphere(); } for ( let i = 0, l = instanceInfo.length; i < l; i ++ ) { if ( ! instanceInfo[ i ].visible || ! instanceInfo[ i ].active ) { continue; } const geometryId = instanceInfo[ i ].geometryIndex; const geometryInfo = geometryInfoList[ geometryId ]; _mesh.geometry.setDrawRange( geometryInfo.start, geometryInfo.count ); // get the intersects this.getMatrixAt( i, _mesh.matrixWorld ).premultiply( matrixWorld ); this.getBoundingBoxAt( geometryId, _mesh.geometry.boundingBox ); this.getBoundingSphereAt( geometryId, _mesh.geometry.boundingSphere ); _mesh.raycast( raycaster, _batchIntersects ); // add batch id to the intersects for ( let j = 0, l = _batchIntersects.length; j < l; j ++ ) { const intersect = _batchIntersects[ j ]; intersect.object = this; intersect.batchId = i; intersects.push( intersect ); } _batchIntersects.length = 0; } _mesh.material = null; _mesh.geometry.index = null; _mesh.geometry.attributes = {}; _mesh.geometry.setDrawRange( 0, Infinity ); } copy( source ) { super.copy( source ); this.geometry = source.geometry.clone(); this.perObjectFrustumCulled = source.perObjectFrustumCulled; this.sortObjects = source.sortObjects; this.boundingBox = source.boundingBox !== null ? source.boundingBox.clone() : null; this.boundingSphere = source.boundingSphere !== null ? source.boundingSphere.clone() : null; this._geometryInfo = source._geometryInfo.map( info => ( { ...info, boundingBox: info.boundingBox !== null ? info.boundingBox.clone() : null, boundingSphere: info.boundingSphere !== null ? info.boundingSphere.clone() : null, } ) ); this._instanceInfo = source._instanceInfo.map( info => ( { ...info } ) ); this._maxInstanceCount = source._maxInstanceCount; this._maxVertexCount = source._maxVertexCount; this._maxIndexCount = source._maxIndexCount; this._geometryInitialized = source._geometryInitialized; this._geometryCount = source._geometryCount; this._multiDrawCounts = source._multiDrawCounts.slice(); this._multiDrawStarts = source._multiDrawStarts.slice(); this._matricesTexture = source._matricesTexture.clone(); this._matricesTexture.image.data = this._matricesTexture.image.data.slice(); if ( this._colorsTexture !== null ) { this._colorsTexture = source._colorsTexture.clone(); this._colorsTexture.image.data = this._colorsTexture.image.data.slice(); } return this; } dispose() { // Assuming the geometry is not shared with other meshes this.geometry.dispose(); this._matricesTexture.dispose(); this._matricesTexture = null; this._indirectTexture.dispose(); this._indirectTexture = null; if ( this._colorsTexture !== null ) { this._colorsTexture.dispose(); this._colorsTexture = null; } return this; } onBeforeRender( renderer, scene, camera, geometry, material/*, _group*/ ) { // if visibility has not changed and frustum culling and object sorting is not required // then skip iterating over all items if ( ! this._visibilityChanged && ! this.perObjectFrustumCulled && ! this.sortObjects ) { return; } // the indexed version of the multi draw function requires specifying the start // offset in bytes. const index = geometry.getIndex(); const bytesPerElement = index === null ? 1 : index.array.BYTES_PER_ELEMENT; const instanceInfo = this._instanceInfo; const multiDrawStarts = this._multiDrawStarts; const multiDrawCounts = this._multiDrawCounts; const geometryInfoList = this._geometryInfo; const perObjectFrustumCulled = this.perObjectFrustumCulled; const indirectTexture = this._indirectTexture; const indirectArray = indirectTexture.image.data; // prepare the frustum in the local frame if ( perObjectFrustumCulled ) { _matrix .multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ) .multiply( this.matrixWorld ); _frustum.setFromProjectionMatrix( _matrix, renderer.coordinateSystem ); } let multiDrawCount = 0; if ( this.sortObjects ) { // get the camera position in the local frame _matrix.copy( this.matrixWorld ).invert(); _vector.setFromMatrixPosition( camera.matrixWorld ).applyMatrix4( _matrix ); _forward.set( 0, 0, - 1 ).transformDirection( camera.matrixWorld ).transformDirection( _matrix ); for ( let i = 0, l = instanceInfo.length; i < l; i ++ ) { if ( instanceInfo[ i ].visible && instanceInfo[ i ].active ) { const geometryId = instanceInfo[ i ].geometryIndex; // get the bounds in world space this.getMatrixAt( i, _matrix ); this.getBoundingSphereAt( geometryId, _sphere ).applyMatrix4( _matrix ); // determine whether the batched geometry is within the frustum let culled = false; if ( perObjectFrustumCulled ) { culled = ! _frustum.intersectsSphere( _sphere ); } if ( ! culled ) { // get the distance from camera used for sorting const geometryInfo = geometryInfoList[ geometryId ]; const z = _temp.subVectors( _sphere.center, _vector ).dot( _forward ); _renderList.push( geometryInfo.start, geometryInfo.count, z, i ); } } } // Sort the draw ranges and prep for rendering const list = _renderList.list; const customSort = this.customSort; if ( customSort === null ) { list.sort( material.transparent ? sortTransparent : sortOpaque ); } else { customSort.call( this, list, camera ); } for ( let i = 0, l = list.length; i < l; i ++ ) { const item = list[ i ]; multiDrawStarts[ multiDrawCount ] = item.start * bytesPerElement; multiDrawCounts[ multiDrawCount ] = item.count; indirectArray[ multiDrawCount ] = item.index; multiDrawCount ++; } _renderList.reset(); } else { for ( let i = 0, l = instanceInfo.length; i < l; i ++ ) { if ( instanceInfo[ i ].visible && instanceInfo[ i ].active ) { const geometryId = instanceInfo[ i ].geometryIndex; // determine whether the batched geometry is within the frustum let culled = false; if ( perObjectFrustumCulled ) { // get the bounds in world space this.getMatrixAt( i, _matrix ); this.getBoundingSphereAt( geometryId, _sphere ).applyMatrix4( _matrix ); culled = ! _frustum.intersectsSphere( _sphere ); } if ( ! culled ) { const geometryInfo = geometryInfoList[ geometryId ]; multiDrawStarts[ multiDrawCount ] = geometryInfo.start * bytesPerElement; multiDrawCounts[ multiDrawCount ] = geometryInfo.count; indirectArray[ multiDrawCount ] = i; multiDrawCount ++; } } } } indirectTexture.needsUpdate = true; this._multiDrawCount = multiDrawCount; this._visibilityChanged = false; } onBeforeShadow( renderer, object, camera, shadowCamera, geometry, depthMaterial/* , group */ ) { this.onBeforeRender( renderer, null, shadowCamera, geometry, depthMaterial ); } } export { BatchedMesh };