BatchedMesh.js 23 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013
  1. import { BufferAttribute } from '../core/BufferAttribute.js';
  2. import { BufferGeometry } from '../core/BufferGeometry.js';
  3. import { DataTexture } from '../textures/DataTexture.js';
  4. import { FloatType } from '../constants.js';
  5. import { Matrix4 } from '../math/Matrix4.js';
  6. import { Mesh } from './Mesh.js';
  7. import { RGBAFormat } from '../constants.js';
  8. import { Box3 } from '../math/Box3.js';
  9. import { Sphere } from '../math/Sphere.js';
  10. import { Frustum } from '../math/Frustum.js';
  11. import { WebGLCoordinateSystem } from '../constants.js';
  12. import { WebGPUCoordinateSystem } from '../constants.js';
  13. import { Vector3 } from '../math/Vector3.js';
  14. function sortOpaque( a, b ) {
  15. return a.z - b.z;
  16. }
  17. function sortTransparent( a, b ) {
  18. return b.z - a.z;
  19. }
  20. class MultiDrawRenderList {
  21. constructor() {
  22. this.index = 0;
  23. this.pool = [];
  24. this.list = [];
  25. }
  26. push( drawRange, z ) {
  27. const pool = this.pool;
  28. const list = this.list;
  29. if ( this.index >= pool.length ) {
  30. pool.push( {
  31. start: - 1,
  32. count: - 1,
  33. z: - 1,
  34. } );
  35. }
  36. const item = pool[ this.index ];
  37. list.push( item );
  38. this.index ++;
  39. item.start = drawRange.start;
  40. item.count = drawRange.count;
  41. item.z = z;
  42. }
  43. reset() {
  44. this.list.length = 0;
  45. this.index = 0;
  46. }
  47. }
  48. const ID_ATTR_NAME = 'batchId';
  49. const _matrix = new Matrix4();
  50. const _identityMatrix = new Matrix4();
  51. const _projScreenMatrix = new Matrix4();
  52. const _frustum = new Frustum();
  53. const _box = new Box3();
  54. const _sphere = new Sphere();
  55. const _vector = new Vector3();
  56. const _renderList = new MultiDrawRenderList();
  57. const _mesh = new Mesh();
  58. const _batchIntersects = [];
  59. // @TODO: SkinnedMesh support?
  60. // @TODO: geometry.groups support?
  61. // @TODO: geometry.drawRange support?
  62. // @TODO: geometry.morphAttributes support?
  63. // @TODO: Support uniform parameter per geometry
  64. // @TODO: Add an "optimize" function to pack geometry and remove data gaps
  65. // copies data from attribute "src" into "target" starting at "targetOffset"
  66. function copyAttributeData( src, target, targetOffset = 0 ) {
  67. const itemSize = target.itemSize;
  68. if ( src.isInterleavedBufferAttribute || src.array.constructor !== target.array.constructor ) {
  69. // use the component getters and setters if the array data cannot
  70. // be copied directly
  71. const vertexCount = src.count;
  72. for ( let i = 0; i < vertexCount; i ++ ) {
  73. for ( let c = 0; c < itemSize; c ++ ) {
  74. target.setComponent( i + targetOffset, c, src.getComponent( i, c ) );
  75. }
  76. }
  77. } else {
  78. // faster copy approach using typed array set function
  79. target.array.set( src.array, targetOffset * itemSize );
  80. }
  81. target.needsUpdate = true;
  82. }
  83. class BatchedMesh extends Mesh {
  84. get maxGeometryCount() {
  85. return this._maxGeometryCount;
  86. }
  87. constructor( maxGeometryCount, maxVertexCount, maxIndexCount = maxVertexCount * 2, material ) {
  88. super( new BufferGeometry(), material );
  89. this.isBatchedMesh = true;
  90. this.perObjectFrustumCulled = true;
  91. this.sortObjects = true;
  92. this.boundingBox = null;
  93. this.boundingSphere = null;
  94. this.customSort = null;
  95. this._drawRanges = [];
  96. this._reservedRanges = [];
  97. this._visibility = [];
  98. this._active = [];
  99. this._bounds = [];
  100. this._maxGeometryCount = maxGeometryCount;
  101. this._maxVertexCount = maxVertexCount;
  102. this._maxIndexCount = maxIndexCount;
  103. this._geometryInitialized = false;
  104. this._geometryCount = 0;
  105. this._multiDrawCounts = new Int32Array( maxGeometryCount );
  106. this._multiDrawStarts = new Int32Array( maxGeometryCount );
  107. this._multiDrawCount = 0;
  108. this._visibilityChanged = true;
  109. // Local matrix per geometry by using data texture
  110. this._matricesTexture = null;
  111. this._initMatricesTexture();
  112. }
  113. _initMatricesTexture() {
  114. // layout (1 matrix = 4 pixels)
  115. // RGBA RGBA RGBA RGBA (=> column1, column2, column3, column4)
  116. // with 8x8 pixel texture max 16 matrices * 4 pixels = (8 * 8)
  117. // 16x16 pixel texture max 64 matrices * 4 pixels = (16 * 16)
  118. // 32x32 pixel texture max 256 matrices * 4 pixels = (32 * 32)
  119. // 64x64 pixel texture max 1024 matrices * 4 pixels = (64 * 64)
  120. let size = Math.sqrt( this._maxGeometryCount * 4 ); // 4 pixels needed for 1 matrix
  121. size = Math.ceil( size / 4 ) * 4;
  122. size = Math.max( size, 4 );
  123. const matricesArray = new Float32Array( size * size * 4 ); // 4 floats per RGBA pixel
  124. const matricesTexture = new DataTexture( matricesArray, size, size, RGBAFormat, FloatType );
  125. this._matricesTexture = matricesTexture;
  126. }
  127. _initializeGeometry( reference ) {
  128. const geometry = this.geometry;
  129. const maxVertexCount = this._maxVertexCount;
  130. const maxGeometryCount = this._maxGeometryCount;
  131. const maxIndexCount = this._maxIndexCount;
  132. if ( this._geometryInitialized === false ) {
  133. for ( const attributeName in reference.attributes ) {
  134. const srcAttribute = reference.getAttribute( attributeName );
  135. const { array, itemSize, normalized } = srcAttribute;
  136. const dstArray = new array.constructor( maxVertexCount * itemSize );
  137. const dstAttribute = new srcAttribute.constructor( dstArray, itemSize, normalized );
  138. dstAttribute.setUsage( srcAttribute.usage );
  139. geometry.setAttribute( attributeName, dstAttribute );
  140. }
  141. if ( reference.getIndex() !== null ) {
  142. const indexArray = maxVertexCount > 65536
  143. ? new Uint32Array( maxIndexCount )
  144. : new Uint16Array( maxIndexCount );
  145. geometry.setIndex( new BufferAttribute( indexArray, 1 ) );
  146. }
  147. const idArray = maxGeometryCount > 65536
  148. ? new Uint32Array( maxVertexCount )
  149. : new Uint16Array( maxVertexCount );
  150. geometry.setAttribute( ID_ATTR_NAME, new BufferAttribute( idArray, 1 ) );
  151. this._geometryInitialized = true;
  152. }
  153. }
  154. // Make sure the geometry is compatible with the existing combined geometry atributes
  155. _validateGeometry( geometry ) {
  156. // check that the geometry doesn't have a version of our reserved id attribute
  157. if ( geometry.getAttribute( ID_ATTR_NAME ) ) {
  158. throw new Error( `BatchedMesh: Geometry cannot use attribute "${ ID_ATTR_NAME }"` );
  159. }
  160. // check to ensure the geometries are using consistent attributes and indices
  161. const batchGeometry = this.geometry;
  162. if ( Boolean( geometry.getIndex() ) !== Boolean( batchGeometry.getIndex() ) ) {
  163. throw new Error( 'BatchedMesh: All geometries must consistently have "index".' );
  164. }
  165. for ( const attributeName in batchGeometry.attributes ) {
  166. if ( attributeName === ID_ATTR_NAME ) {
  167. continue;
  168. }
  169. if ( ! geometry.hasAttribute( attributeName ) ) {
  170. throw new Error( `BatchedMesh: Added geometry missing "${ attributeName }". All geometries must have consistent attributes.` );
  171. }
  172. const srcAttribute = geometry.getAttribute( attributeName );
  173. const dstAttribute = batchGeometry.getAttribute( attributeName );
  174. if ( srcAttribute.itemSize !== dstAttribute.itemSize || srcAttribute.normalized !== dstAttribute.normalized ) {
  175. throw new Error( 'BatchedMesh: All attributes must have a consistent itemSize and normalized value.' );
  176. }
  177. }
  178. }
  179. setCustomSort( func ) {
  180. this.customSort = func;
  181. return this;
  182. }
  183. computeBoundingBox() {
  184. if ( this.boundingBox === null ) {
  185. this.boundingBox = new Box3();
  186. }
  187. const geometryCount = this._geometryCount;
  188. const boundingBox = this.boundingBox;
  189. const active = this._active;
  190. boundingBox.makeEmpty();
  191. for ( let i = 0; i < geometryCount; i ++ ) {
  192. if ( active[ i ] === false ) continue;
  193. this.getMatrixAt( i, _matrix );
  194. this.getBoundingBoxAt( i, _box ).applyMatrix4( _matrix );
  195. boundingBox.union( _box );
  196. }
  197. }
  198. computeBoundingSphere() {
  199. if ( this.boundingSphere === null ) {
  200. this.boundingSphere = new Sphere();
  201. }
  202. const geometryCount = this._geometryCount;
  203. const boundingSphere = this.boundingSphere;
  204. const active = this._active;
  205. boundingSphere.makeEmpty();
  206. for ( let i = 0; i < geometryCount; i ++ ) {
  207. if ( active[ i ] === false ) continue;
  208. this.getMatrixAt( i, _matrix );
  209. this.getBoundingSphereAt( i, _sphere ).applyMatrix4( _matrix );
  210. boundingSphere.union( _sphere );
  211. }
  212. }
  213. addGeometry( geometry, vertexCount = - 1, indexCount = - 1 ) {
  214. this._initializeGeometry( geometry );
  215. this._validateGeometry( geometry );
  216. // ensure we're not over geometry
  217. if ( this._geometryCount >= this._maxGeometryCount ) {
  218. throw new Error( 'BatchedMesh: Maximum geometry count reached.' );
  219. }
  220. // get the necessary range fo the geometry
  221. const reservedRange = {
  222. vertexStart: - 1,
  223. vertexCount: - 1,
  224. indexStart: - 1,
  225. indexCount: - 1,
  226. };
  227. let lastRange = null;
  228. const reservedRanges = this._reservedRanges;
  229. const drawRanges = this._drawRanges;
  230. const bounds = this._bounds;
  231. if ( this._geometryCount !== 0 ) {
  232. lastRange = reservedRanges[ reservedRanges.length - 1 ];
  233. }
  234. if ( vertexCount === - 1 ) {
  235. reservedRange.vertexCount = geometry.getAttribute( 'position' ).count;
  236. } else {
  237. reservedRange.vertexCount = vertexCount;
  238. }
  239. if ( lastRange === null ) {
  240. reservedRange.vertexStart = 0;
  241. } else {
  242. reservedRange.vertexStart = lastRange.vertexStart + lastRange.vertexCount;
  243. }
  244. const index = geometry.getIndex();
  245. const hasIndex = index !== null;
  246. if ( hasIndex ) {
  247. if ( indexCount === - 1 ) {
  248. reservedRange.indexCount = index.count;
  249. } else {
  250. reservedRange.indexCount = indexCount;
  251. }
  252. if ( lastRange === null ) {
  253. reservedRange.indexStart = 0;
  254. } else {
  255. reservedRange.indexStart = lastRange.indexStart + lastRange.indexCount;
  256. }
  257. }
  258. if (
  259. reservedRange.indexStart !== - 1 &&
  260. reservedRange.indexStart + reservedRange.indexCount > this._maxIndexCount ||
  261. reservedRange.vertexStart + reservedRange.vertexCount > this._maxVertexCount
  262. ) {
  263. throw new Error( 'BatchedMesh: Reserved space request exceeds the maximum buffer size.' );
  264. }
  265. const visibility = this._visibility;
  266. const active = this._active;
  267. const matricesTexture = this._matricesTexture;
  268. const matricesArray = this._matricesTexture.image.data;
  269. // push new visibility states
  270. visibility.push( true );
  271. active.push( true );
  272. // update id
  273. const geometryId = this._geometryCount;
  274. this._geometryCount ++;
  275. // initialize matrix information
  276. _identityMatrix.toArray( matricesArray, geometryId * 16 );
  277. matricesTexture.needsUpdate = true;
  278. // add the reserved range and draw range objects
  279. reservedRanges.push( reservedRange );
  280. drawRanges.push( {
  281. start: hasIndex ? reservedRange.indexStart : reservedRange.vertexStart,
  282. count: - 1
  283. } );
  284. bounds.push( {
  285. boxInitialized: false,
  286. box: new Box3(),
  287. sphereInitialized: false,
  288. sphere: new Sphere()
  289. } );
  290. // set the id for the geometry
  291. const idAttribute = this.geometry.getAttribute( ID_ATTR_NAME );
  292. for ( let i = 0; i < reservedRange.vertexCount; i ++ ) {
  293. idAttribute.setX( reservedRange.vertexStart + i, geometryId );
  294. }
  295. idAttribute.needsUpdate = true;
  296. // update the geometry
  297. this.setGeometryAt( geometryId, geometry );
  298. return geometryId;
  299. }
  300. setGeometryAt( id, geometry ) {
  301. if ( id >= this._geometryCount ) {
  302. throw new Error( 'BatchedMesh: Maximum geometry count reached.' );
  303. }
  304. this._validateGeometry( geometry );
  305. const batchGeometry = this.geometry;
  306. const hasIndex = batchGeometry.getIndex() !== null;
  307. const dstIndex = batchGeometry.getIndex();
  308. const srcIndex = geometry.getIndex();
  309. const reservedRange = this._reservedRanges[ id ];
  310. if (
  311. hasIndex &&
  312. srcIndex.count > reservedRange.indexCount ||
  313. geometry.attributes.position.count > reservedRange.vertexCount
  314. ) {
  315. throw new Error( 'BatchedMesh: Reserved space not large enough for provided geometry.' );
  316. }
  317. // copy geometry over
  318. const vertexStart = reservedRange.vertexStart;
  319. const vertexCount = reservedRange.vertexCount;
  320. for ( const attributeName in batchGeometry.attributes ) {
  321. if ( attributeName === ID_ATTR_NAME ) {
  322. continue;
  323. }
  324. // copy attribute data
  325. const srcAttribute = geometry.getAttribute( attributeName );
  326. const dstAttribute = batchGeometry.getAttribute( attributeName );
  327. copyAttributeData( srcAttribute, dstAttribute, vertexStart );
  328. // fill the rest in with zeroes
  329. const itemSize = srcAttribute.itemSize;
  330. for ( let i = srcAttribute.count, l = vertexCount; i < l; i ++ ) {
  331. const index = vertexStart + i;
  332. for ( let c = 0; c < itemSize; c ++ ) {
  333. dstAttribute.setComponent( index, c, 0 );
  334. }
  335. }
  336. dstAttribute.needsUpdate = true;
  337. }
  338. // copy index
  339. if ( hasIndex ) {
  340. const indexStart = reservedRange.indexStart;
  341. // copy index data over
  342. for ( let i = 0; i < srcIndex.count; i ++ ) {
  343. dstIndex.setX( indexStart + i, vertexStart + srcIndex.getX( i ) );
  344. }
  345. // fill the rest in with zeroes
  346. for ( let i = srcIndex.count, l = reservedRange.indexCount; i < l; i ++ ) {
  347. dstIndex.setX( indexStart + i, vertexStart );
  348. }
  349. dstIndex.needsUpdate = true;
  350. }
  351. // store the bounding boxes
  352. const bound = this._bounds[ id ];
  353. if ( geometry.boundingBox !== null ) {
  354. bound.box.copy( geometry.boundingBox );
  355. bound.boxInitialized = true;
  356. } else {
  357. bound.boxInitialized = false;
  358. }
  359. if ( geometry.boundingSphere !== null ) {
  360. bound.sphere.copy( geometry.boundingSphere );
  361. bound.sphereInitialized = true;
  362. } else {
  363. bound.sphereInitialized = false;
  364. }
  365. // set drawRange count
  366. const drawRange = this._drawRanges[ id ];
  367. const posAttr = geometry.getAttribute( 'position' );
  368. drawRange.count = hasIndex ? srcIndex.count : posAttr.count;
  369. this._visibilityChanged = true;
  370. return id;
  371. }
  372. deleteGeometry( geometryId ) {
  373. // Note: User needs to call optimize() afterward to pack the data.
  374. const active = this._active;
  375. if ( geometryId >= active.length || active[ geometryId ] === false ) {
  376. return this;
  377. }
  378. active[ geometryId ] = false;
  379. this._visibilityChanged = true;
  380. return this;
  381. }
  382. // get bounding box and compute it if it doesn't exist
  383. getBoundingBoxAt( id, target ) {
  384. const active = this._active;
  385. if ( active[ id ] === false ) {
  386. return this;
  387. }
  388. // compute bounding box
  389. const bound = this._bounds[ id ];
  390. const box = bound.box;
  391. const geometry = this.geometry;
  392. if ( bound.boxInitialized === false ) {
  393. box.makeEmpty();
  394. const index = geometry.index;
  395. const position = geometry.attributes.position;
  396. const drawRange = this._drawRanges[ id ];
  397. for ( let i = drawRange.start, l = drawRange.start + drawRange.count; i < l; i ++ ) {
  398. let iv = i;
  399. if ( index ) {
  400. iv = index.getX( iv );
  401. }
  402. box.expandByPoint( _vector.fromBufferAttribute( position, iv ) );
  403. }
  404. bound.boxInitialized = true;
  405. }
  406. target.copy( box );
  407. return target;
  408. }
  409. // get bounding sphere and compute it if it doesn't exist
  410. getBoundingSphereAt( id, target ) {
  411. const active = this._active;
  412. if ( active[ id ] === false ) {
  413. return this;
  414. }
  415. // compute bounding sphere
  416. const bound = this._bounds[ id ];
  417. const sphere = bound.sphere;
  418. const geometry = this.geometry;
  419. if ( bound.sphereInitialized === false ) {
  420. sphere.makeEmpty();
  421. this.getBoundingBoxAt( id, _box );
  422. _box.getCenter( sphere.center );
  423. const index = geometry.index;
  424. const position = geometry.attributes.position;
  425. const drawRange = this._drawRanges[ id ];
  426. let maxRadiusSq = 0;
  427. for ( let i = drawRange.start, l = drawRange.start + drawRange.count; i < l; i ++ ) {
  428. let iv = i;
  429. if ( index ) {
  430. iv = index.getX( iv );
  431. }
  432. _vector.fromBufferAttribute( position, iv );
  433. maxRadiusSq = Math.max( maxRadiusSq, sphere.center.distanceToSquared( _vector ) );
  434. }
  435. sphere.radius = Math.sqrt( maxRadiusSq );
  436. bound.sphereInitialized = true;
  437. }
  438. target.copy( sphere );
  439. return target;
  440. }
  441. setMatrixAt( geometryId, matrix ) {
  442. // @TODO: Map geometryId to index of the arrays because
  443. // optimize() can make geometryId mismatch the index
  444. const active = this._active;
  445. const matricesTexture = this._matricesTexture;
  446. const matricesArray = this._matricesTexture.image.data;
  447. const geometryCount = this._geometryCount;
  448. if ( geometryId >= geometryCount || active[ geometryId ] === false ) {
  449. return this;
  450. }
  451. matrix.toArray( matricesArray, geometryId * 16 );
  452. matricesTexture.needsUpdate = true;
  453. return this;
  454. }
  455. getMatrixAt( geometryId, matrix ) {
  456. const active = this._active;
  457. const matricesArray = this._matricesTexture.image.data;
  458. const geometryCount = this._geometryCount;
  459. if ( geometryId >= geometryCount || active[ geometryId ] === false ) {
  460. return null;
  461. }
  462. return matrix.fromArray( matricesArray, geometryId * 16 );
  463. }
  464. setVisibleAt( geometryId, value ) {
  465. const visibility = this._visibility;
  466. const active = this._active;
  467. const geometryCount = this._geometryCount;
  468. // if the geometry is out of range, not active, or visibility state
  469. // does not change then return early
  470. if (
  471. geometryId >= geometryCount ||
  472. active[ geometryId ] === false ||
  473. visibility[ geometryId ] === value
  474. ) {
  475. return this;
  476. }
  477. visibility[ geometryId ] = value;
  478. this._visibilityChanged = true;
  479. return this;
  480. }
  481. getVisibleAt( geometryId ) {
  482. const visibility = this._visibility;
  483. const active = this._active;
  484. const geometryCount = this._geometryCount;
  485. // return early if the geometry is out of range or not active
  486. if ( geometryId >= geometryCount || active[ geometryId ] === false ) {
  487. return false;
  488. }
  489. return visibility[ geometryId ];
  490. }
  491. raycast( raycaster, intersects ) {
  492. const visibility = this._visibility;
  493. const active = this._active;
  494. const drawRanges = this._drawRanges;
  495. const geometryCount = this._geometryCount;
  496. const matrixWorld = this.matrixWorld;
  497. const batchGeometry = this.geometry;
  498. // iterate over each geometry
  499. _mesh.material = this.material;
  500. _mesh.geometry.index = batchGeometry.index;
  501. _mesh.geometry.attributes = batchGeometry.attributes;
  502. if ( _mesh.geometry.boundingBox === null ) {
  503. _mesh.geometry.boundingBox = new Box3();
  504. }
  505. if ( _mesh.geometry.boundingSphere === null ) {
  506. _mesh.geometry.boundingSphere = new Sphere();
  507. }
  508. for ( let i = 0; i < geometryCount; i ++ ) {
  509. if ( ! visibility[ i ] || ! active[ i ] ) {
  510. continue;
  511. }
  512. const drawRange = drawRanges[ i ];
  513. _mesh.geometry.setDrawRange( drawRange.start, drawRange.count );
  514. // ge the intersects
  515. this.getMatrixAt( i, _mesh.matrixWorld ).premultiply( matrixWorld );
  516. this.getBoundingBoxAt( i, _mesh.geometry.boundingBox );
  517. this.getBoundingSphereAt( i, _mesh.geometry.boundingSphere );
  518. _mesh.raycast( raycaster, _batchIntersects );
  519. // add batch id to the intersects
  520. for ( let j = 0, l = _batchIntersects.length; j < l; j ++ ) {
  521. const intersect = _batchIntersects[ j ];
  522. intersect.object = this;
  523. intersect.batchId = i;
  524. intersects.push( intersect );
  525. }
  526. _batchIntersects.length = 0;
  527. }
  528. _mesh.material = null;
  529. _mesh.geometry.index = null;
  530. _mesh.geometry.attributes = {};
  531. _mesh.geometry.setDrawRange( 0, Infinity );
  532. }
  533. copy( source ) {
  534. super.copy( source );
  535. this.geometry = source.geometry.clone();
  536. this.perObjectFrustumCulled = source.perObjectFrustumCulled;
  537. this.sortObjects = source.sortObjects;
  538. this.boundingBox = source.boundingBox !== null ? source.boundingBox.clone() : null;
  539. this.boundingSphere = source.boundingSphere !== null ? source.boundingSphere.clone() : null;
  540. this._drawRanges = source._drawRanges.map( range => ( { ...range } ) );
  541. this._reservedRanges = source._reservedRanges.map( range => ( { ...range } ) );
  542. this._visibility = source._visibility.slice();
  543. this._active = source._active.slice();
  544. this._bounds = source._bounds.map( bound => ( {
  545. boxInitialized: bound.boxInitialized,
  546. box: bound.box.clone(),
  547. sphereInitialized: bound.sphereInitialized,
  548. sphere: bound.sphere.clone()
  549. } ) );
  550. this._maxGeometryCount = source._maxGeometryCount;
  551. this._maxVertexCount = source._maxVertexCount;
  552. this._maxIndexCount = source._maxIndexCount;
  553. this._geometryInitialized = source._geometryInitialized;
  554. this._geometryCount = source._geometryCount;
  555. this._multiDrawCounts = source._multiDrawCounts.slice();
  556. this._multiDrawStarts = source._multiDrawStarts.slice();
  557. this._matricesTexture = source._matricesTexture.clone();
  558. this._matricesTexture.image.data = this._matricesTexture.image.slice();
  559. return this;
  560. }
  561. dispose() {
  562. // Assuming the geometry is not shared with other meshes
  563. this.geometry.dispose();
  564. this._matricesTexture.dispose();
  565. this._matricesTexture = null;
  566. return this;
  567. }
  568. onBeforeRender( _renderer, _scene, camera, geometry, material/*, _group*/ ) {
  569. // if visibility has not changed and frustum culling and object sorting is not required
  570. // then skip iterating over all items
  571. if ( ! this._visibilityChanged && ! this.perObjectFrustumCulled && ! this.sortObjects ) {
  572. return;
  573. }
  574. // the indexed version of the multi draw function requires specifying the start
  575. // offset in bytes.
  576. const index = geometry.getIndex();
  577. const bytesPerElement = index === null ? 1 : index.array.BYTES_PER_ELEMENT;
  578. const visibility = this._visibility;
  579. const multiDrawStarts = this._multiDrawStarts;
  580. const multiDrawCounts = this._multiDrawCounts;
  581. const drawRanges = this._drawRanges;
  582. const perObjectFrustumCulled = this.perObjectFrustumCulled;
  583. // prepare the frustum
  584. if ( perObjectFrustumCulled ) {
  585. _projScreenMatrix
  586. .multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse )
  587. .multiply( this.matrixWorld );
  588. _frustum.setFromProjectionMatrix(
  589. _projScreenMatrix,
  590. _renderer.isWebGPURenderer ? WebGPUCoordinateSystem : WebGLCoordinateSystem
  591. );
  592. }
  593. let count = 0;
  594. if ( this.sortObjects ) {
  595. // get the camera position
  596. _vector.setFromMatrixPosition( camera.matrixWorld );
  597. for ( let i = 0, l = visibility.length; i < l; i ++ ) {
  598. if ( visibility[ i ] ) {
  599. // get the bounds in world space
  600. this.getMatrixAt( i, _matrix );
  601. this.getBoundingSphereAt( i, _sphere ).applyMatrix4( _matrix );
  602. // determine whether the batched geometry is within the frustum
  603. let culled = false;
  604. if ( perObjectFrustumCulled ) {
  605. culled = ! _frustum.intersectsSphere( _sphere );
  606. }
  607. if ( ! culled ) {
  608. // get the distance from camera used for sorting
  609. const z = _vector.distanceTo( _sphere.center );
  610. _renderList.push( drawRanges[ i ], z );
  611. }
  612. }
  613. }
  614. // Sort the draw ranges and prep for rendering
  615. const list = _renderList.list;
  616. const customSort = this.customSort;
  617. if ( customSort === null ) {
  618. list.sort( material.transparent ? sortTransparent : sortOpaque );
  619. } else {
  620. customSort.call( this, list, camera );
  621. }
  622. for ( let i = 0, l = list.length; i < l; i ++ ) {
  623. const item = list[ i ];
  624. multiDrawStarts[ count ] = item.start * bytesPerElement;
  625. multiDrawCounts[ count ] = item.count;
  626. count ++;
  627. }
  628. _renderList.reset();
  629. } else {
  630. for ( let i = 0, l = visibility.length; i < l; i ++ ) {
  631. if ( visibility[ i ] ) {
  632. // determine whether the batched geometry is within the frustum
  633. let culled = false;
  634. if ( perObjectFrustumCulled ) {
  635. // get the bounds in world space
  636. this.getMatrixAt( i, _matrix );
  637. this.getBoundingSphereAt( i, _sphere ).applyMatrix4( _matrix );
  638. culled = ! _frustum.intersectsSphere( _sphere );
  639. }
  640. if ( ! culled ) {
  641. const range = drawRanges[ i ];
  642. multiDrawStarts[ count ] = range.start * bytesPerElement;
  643. multiDrawCounts[ count ] = range.count;
  644. count ++;
  645. }
  646. }
  647. }
  648. }
  649. this._multiDrawCount = count;
  650. this._visibilityChanged = false;
  651. }
  652. }
  653. export { BatchedMesh };
粤ICP备19079148号