BatchedMesh.js 26 KB

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