thinInstanceMesh.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. import { Nullable, DeepImmutableObject } from "../types";
  2. import { Mesh, _InstancesBatch } from "../Meshes/mesh";
  3. import { VertexBuffer, Buffer } from './buffer';
  4. import { Matrix, Vector3, TmpVectors } from '../Maths/math.vector';
  5. declare module "./mesh" {
  6. export interface Mesh {
  7. /**
  8. * Creates a new thin instance
  9. * @param matrix the matrix or array of matrices (position, rotation, scale) of the thin instance(s) to create
  10. * @param refresh true to refresh the underlying gpu buffer (default: true). If you do multiple calls to this method in a row, set refresh to true only for the last call to save performance
  11. * @returns the thin instance index number. If you pass an array of matrices, other instance indexes are index+1, index+2, etc
  12. */
  13. thinInstanceAdd(matrix: DeepImmutableObject<Matrix> | Array<DeepImmutableObject<Matrix>>, refresh: boolean): number;
  14. /**
  15. * Adds the transformation (matrix) of the current mesh as a thin instance
  16. * @param refresh true to refresh the underlying gpu buffer (default: true). If you do multiple calls to this method in a row, set refresh to true only for the last call to save performance
  17. * @returns the thin instance index number
  18. */
  19. thinInstanceAddSelf(refresh: boolean): number;
  20. /**
  21. * Registers a custom attribute to be used with thin instances
  22. * @param kind name of the attribute
  23. * @param stride size in floats of the attribute
  24. */
  25. thinInstanceRegisterAttribute(kind: string, stride: number): void;
  26. /**
  27. * Sets the matrix of a thin instance
  28. * @param index index of the thin instance
  29. * @param matrix matrix to set
  30. * @param refresh true to refresh the underlying gpu buffer (default: true). If you do multiple calls to this method in a row, set refresh to true only for the last call to save performance
  31. */
  32. thinInstanceSetMatrixAt(index: number, matrix: DeepImmutableObject<Matrix>, refresh: boolean): void;
  33. /**
  34. * Sets the value of a custom attribute for a thin instance
  35. * @param kind name of the attribute
  36. * @param index index of the thin instance
  37. * @param value value to set
  38. * @param refresh true to refresh the underlying gpu buffer (default: true). If you do multiple calls to this method in a row, set refresh to true only for the last call to save performance
  39. */
  40. thinInstanceSetAttributeAt(kind: string, index: number, value: Array<number>, refresh: boolean): void;
  41. /**
  42. * Gets / sets the number of thin instances to display. Note that you can't set a number higher than what the underlying buffer can handle.
  43. */
  44. thinInstanceCount: number;
  45. /**
  46. * Sets a buffer to be used with thin instances. This method is a faster way to setup multiple instances than calling thinInstanceAdd repeatedly
  47. * @param kind name of the attribute. Use "matrix" to setup the buffer of matrices
  48. * @param buffer buffer to set
  49. * @param stride size in floats of each value of the buffer
  50. * @param staticBuffer indicates that the buffer is static, so that you won't change it after it is set (better performances - false by default)
  51. */
  52. thinInstanceSetBuffer(kind: string, buffer: Nullable<Float32Array>, stride: number, staticBuffer: boolean): void;
  53. /**
  54. * Synchronize the gpu buffers with a thin instance buffer. Call this method if you update later on the buffers passed to thinInstanceSetBuffer
  55. * @param kind name of the attribute to update. Use "matrix" to update the buffer of matrices
  56. */
  57. thinInstanceBufferUpdated(kind: string): void;
  58. /**
  59. * Refreshes the bounding info, taking into account all the thin instances defined
  60. * @param forceRefreshParentInfo true to force recomputing the mesh bounding info and use it to compute the aggregated bounding info
  61. */
  62. thinInstanceRefreshBoundingInfo(forceRefreshParentInfo: boolean): void;
  63. /** @hidden */
  64. _thinInstanceInitializeUserStorage(): void;
  65. /** @hidden */
  66. _thinInstanceUpdateBufferSize(kind: string, numInstances: number): void;
  67. /** @hidden */
  68. _userThinInstanceBuffersStorage: {
  69. data: {[key: string]: Float32Array},
  70. sizes: {[key: string]: number},
  71. vertexBuffers: {[key: string]: Nullable<VertexBuffer>},
  72. strides: {[key: string]: number}
  73. };
  74. }
  75. }
  76. Mesh.prototype.thinInstanceAdd = function(matrix: DeepImmutableObject<Matrix> | Array<DeepImmutableObject<Matrix>>, refresh: boolean = true): number {
  77. this._thinInstanceUpdateBufferSize("matrix", Array.isArray(matrix) ? matrix.length : 1);
  78. const index = this._thinInstanceDataStorage.instancesCount;
  79. if (Array.isArray(matrix)) {
  80. for (let i = 0; i < matrix.length; ++i) {
  81. this.thinInstanceSetMatrixAt(this._thinInstanceDataStorage.instancesCount++, matrix[i], (i === matrix.length - 1) && refresh);
  82. }
  83. } else {
  84. this.thinInstanceSetMatrixAt(this._thinInstanceDataStorage.instancesCount++, matrix, refresh);
  85. }
  86. return index;
  87. };
  88. Mesh.prototype.thinInstanceAddSelf = function(refresh: boolean = true): number {
  89. return this.thinInstanceAdd(Matrix.IdentityReadOnly, refresh);
  90. };
  91. Mesh.prototype.thinInstanceRegisterAttribute = function(kind: string, stride: number): void {
  92. this.removeVerticesData(kind);
  93. this._thinInstanceInitializeUserStorage();
  94. this._userThinInstanceBuffersStorage.strides[kind] = stride;
  95. this._userThinInstanceBuffersStorage.sizes[kind] = stride * Math.max(32, this._thinInstanceDataStorage.instancesCount); // Initial size
  96. this._userThinInstanceBuffersStorage.data[kind] = new Float32Array(this._userThinInstanceBuffersStorage.sizes[kind]);
  97. this._userThinInstanceBuffersStorage.vertexBuffers[kind] = new VertexBuffer(this.getEngine(), this._userThinInstanceBuffersStorage.data[kind], kind, true, false, stride, true);
  98. this.setVerticesBuffer(this._userThinInstanceBuffersStorage.vertexBuffers[kind]!);
  99. };
  100. Mesh.prototype.thinInstanceSetMatrixAt = function(index: number, matrix: DeepImmutableObject<Matrix>, refresh: boolean = true): boolean {
  101. if (!this._thinInstanceDataStorage.matrixData || index >= this._thinInstanceDataStorage.instancesCount) {
  102. return false;
  103. }
  104. const matrixData = this._thinInstanceDataStorage.matrixData;
  105. matrix.copyToArray(matrixData, index * 16);
  106. if (refresh) {
  107. this.thinInstanceBufferUpdated("matrix");
  108. if (!this.doNotSyncBoundingInfo) {
  109. this.thinInstanceRefreshBoundingInfo(false);
  110. }
  111. }
  112. return true;
  113. };
  114. Mesh.prototype.thinInstanceSetAttributeAt = function(kind: string, index: number, value: Array<number>, refresh: boolean = true): boolean {
  115. if (!this._userThinInstanceBuffersStorage || !this._userThinInstanceBuffersStorage.data[kind] || index >= this._thinInstanceDataStorage.instancesCount) {
  116. return false;
  117. }
  118. this._thinInstanceUpdateBufferSize(kind, 0); // make sur the buffer for the kind attribute is big enough
  119. this._userThinInstanceBuffersStorage.data[kind].set(value, index * this._userThinInstanceBuffersStorage.strides[kind]);
  120. if (refresh) {
  121. this.thinInstanceBufferUpdated(kind);
  122. }
  123. return true;
  124. };
  125. Object.defineProperty(Mesh.prototype, "thinInstanceCount", {
  126. get: function(this: Mesh) {
  127. return this._thinInstanceDataStorage.instancesCount;
  128. },
  129. set: function(this: Mesh, value: number) {
  130. const numMaxInstances = (this._thinInstanceDataStorage.matrixData?.length ?? 0) / 16;
  131. if (value <= numMaxInstances) {
  132. this._thinInstanceDataStorage.instancesCount = value;
  133. }
  134. },
  135. enumerable: true,
  136. configurable: true
  137. });
  138. Mesh.prototype.thinInstanceSetBuffer = function(kind: string, buffer: Nullable<Float32Array>, stride: number = 0, staticBuffer: boolean = false): void {
  139. stride = stride || 16;
  140. if (kind === "matrix") {
  141. this._thinInstanceDataStorage.matrixBuffer?.dispose();
  142. this._thinInstanceDataStorage.matrixBuffer = null;
  143. this._thinInstanceDataStorage.matrixBufferSize = buffer ? buffer.length : 32 * stride;
  144. this._thinInstanceDataStorage.matrixData = buffer;
  145. if (buffer !== null) {
  146. this._thinInstanceDataStorage.instancesCount = buffer.length / stride;
  147. const matrixBuffer = new Buffer(this.getEngine(), buffer, !staticBuffer, stride, false, true);
  148. this._thinInstanceDataStorage.matrixBuffer = matrixBuffer;
  149. this.setVerticesBuffer(matrixBuffer.createVertexBuffer("world0", 0, 4));
  150. this.setVerticesBuffer(matrixBuffer.createVertexBuffer("world1", 4, 4));
  151. this.setVerticesBuffer(matrixBuffer.createVertexBuffer("world2", 8, 4));
  152. this.setVerticesBuffer(matrixBuffer.createVertexBuffer("world3", 12, 4));
  153. if (!this.doNotSyncBoundingInfo) {
  154. this.thinInstanceRefreshBoundingInfo(false);
  155. }
  156. } else {
  157. this._thinInstanceDataStorage.instancesCount = 0;
  158. if (!this.doNotSyncBoundingInfo) {
  159. // mesh has no more thin instances, so need to recompute the bounding box because it's the regular mesh that will now be displayed
  160. this.refreshBoundingInfo(true);
  161. }
  162. }
  163. } else {
  164. if (buffer === null) {
  165. if (this._userThinInstanceBuffersStorage?.data[kind]) {
  166. this.removeVerticesData(kind);
  167. delete this._userThinInstanceBuffersStorage.data[kind];
  168. delete this._userThinInstanceBuffersStorage.strides[kind];
  169. delete this._userThinInstanceBuffersStorage.sizes[kind];
  170. delete this._userThinInstanceBuffersStorage.vertexBuffers[kind];
  171. }
  172. } else {
  173. this._thinInstanceInitializeUserStorage();
  174. this._userThinInstanceBuffersStorage.data[kind] = buffer;
  175. this._userThinInstanceBuffersStorage.strides[kind] = stride;
  176. this._userThinInstanceBuffersStorage.sizes[kind] = buffer.length;
  177. this._userThinInstanceBuffersStorage.vertexBuffers[kind] = new VertexBuffer(this.getEngine(), buffer, kind, !staticBuffer, false, stride, true);
  178. this.setVerticesBuffer(this._userThinInstanceBuffersStorage.vertexBuffers[kind]!);
  179. }
  180. }
  181. };
  182. Mesh.prototype.thinInstanceBufferUpdated = function(kind: string): void {
  183. if (kind === "matrix") {
  184. if (this._thinInstanceDataStorage.matrixBuffer) {
  185. this._thinInstanceDataStorage.matrixBuffer!.updateDirectly(this._thinInstanceDataStorage.matrixData!, 0, this._thinInstanceDataStorage.instancesCount);
  186. }
  187. } else if (this._userThinInstanceBuffersStorage?.vertexBuffers[kind]) {
  188. this._userThinInstanceBuffersStorage.vertexBuffers[kind]!.updateDirectly(this._userThinInstanceBuffersStorage.data[kind], 0);
  189. }
  190. };
  191. Mesh.prototype.thinInstanceRefreshBoundingInfo = function(forceRefreshParentInfo: boolean = false) {
  192. if (!this._thinInstanceDataStorage.matrixData || !this._thinInstanceDataStorage.matrixBuffer) {
  193. return;
  194. }
  195. const vectors = this._thinInstanceDataStorage.boundingVectors;
  196. if (forceRefreshParentInfo) {
  197. vectors.length = 0;
  198. this.refreshBoundingInfo(true);
  199. }
  200. const boundingInfo = this.getBoundingInfo();
  201. const matrixData = this._thinInstanceDataStorage.matrixData;
  202. if (vectors.length === 0) {
  203. const worldMatrix = this.getWorldMatrix();
  204. for (let v = 0; v < boundingInfo.boundingBox.vectors.length; ++v) {
  205. vectors.push(boundingInfo.boundingBox.vectors[v].clone());
  206. Vector3.TransformCoordinatesToRef(vectors[v], worldMatrix, vectors[v]);
  207. }
  208. }
  209. TmpVectors.Vector3[0].setAll(Number.MAX_VALUE); // min
  210. TmpVectors.Vector3[1].setAll(Number.MIN_VALUE); // max
  211. for (let i = 0; i < this._thinInstanceDataStorage.instancesCount; ++i) {
  212. Matrix.FromArrayToRef(matrixData, i * 16, TmpVectors.Matrix[0]);
  213. for (let v = 0; v < vectors.length; ++v) {
  214. Vector3.TransformCoordinatesToRef(vectors[v], TmpVectors.Matrix[0], TmpVectors.Vector3[2]);
  215. TmpVectors.Vector3[0].minimizeInPlace(TmpVectors.Vector3[2]);
  216. TmpVectors.Vector3[1].maximizeInPlace(TmpVectors.Vector3[2]);
  217. }
  218. }
  219. boundingInfo.reConstruct(TmpVectors.Vector3[0], TmpVectors.Vector3[1]);
  220. };
  221. Mesh.prototype._thinInstanceUpdateBufferSize = function(kind: string, numInstances: number = 1) {
  222. const kindIsMatrix = kind === "matrix";
  223. if (!kindIsMatrix && (!this._userThinInstanceBuffersStorage || !this._userThinInstanceBuffersStorage.strides[kind])) {
  224. return;
  225. }
  226. const stride = kindIsMatrix ? 16 : this._userThinInstanceBuffersStorage.strides[kind];
  227. const currentSize = kindIsMatrix ? this._thinInstanceDataStorage.matrixBufferSize : this._userThinInstanceBuffersStorage.sizes[kind];
  228. let data = kindIsMatrix ? this._thinInstanceDataStorage.matrixData : this._userThinInstanceBuffersStorage.data[kind];
  229. const bufferSize = (this._thinInstanceDataStorage.instancesCount + numInstances) * stride;
  230. let newSize = currentSize;
  231. while (newSize < bufferSize) {
  232. newSize *= 2;
  233. }
  234. if (!data || currentSize != newSize) {
  235. if (!data) {
  236. data = new Float32Array(newSize);
  237. } else {
  238. const newData = new Float32Array(newSize);
  239. newData.set(data, 0);
  240. data = newData;
  241. }
  242. if (kindIsMatrix) {
  243. this._thinInstanceDataStorage.matrixBuffer?.dispose();
  244. const matrixBuffer = new Buffer(this.getEngine(), data, true, stride, false, true);
  245. this._thinInstanceDataStorage.matrixBuffer = matrixBuffer;
  246. this._thinInstanceDataStorage.matrixData = data;
  247. this._thinInstanceDataStorage.matrixBufferSize = newSize;
  248. this.setVerticesBuffer(matrixBuffer.createVertexBuffer("world0", 0, 4));
  249. this.setVerticesBuffer(matrixBuffer.createVertexBuffer("world1", 4, 4));
  250. this.setVerticesBuffer(matrixBuffer.createVertexBuffer("world2", 8, 4));
  251. this.setVerticesBuffer(matrixBuffer.createVertexBuffer("world3", 12, 4));
  252. } else {
  253. this._userThinInstanceBuffersStorage.vertexBuffers[kind]?.dispose();
  254. this._userThinInstanceBuffersStorage.data[kind] = data;
  255. this._userThinInstanceBuffersStorage.sizes[kind] = newSize;
  256. this._userThinInstanceBuffersStorage.vertexBuffers[kind] = new VertexBuffer(this.getEngine(), data, kind, true, false, stride, true);
  257. this.setVerticesBuffer(this._userThinInstanceBuffersStorage.vertexBuffers[kind]!);
  258. }
  259. }
  260. };
  261. Mesh.prototype._thinInstanceInitializeUserStorage = function() {
  262. if (!this._userThinInstanceBuffersStorage) {
  263. this._userThinInstanceBuffersStorage = {
  264. data: {},
  265. sizes: {},
  266. vertexBuffers: {},
  267. strides: {},
  268. };
  269. }
  270. };
  271. Mesh.prototype._disposeThinInstanceSpecificData = function() {
  272. if (this._thinInstanceDataStorage?.matrixBuffer) {
  273. this._thinInstanceDataStorage.matrixBuffer.dispose();
  274. this._thinInstanceDataStorage.matrixBuffer = null;
  275. }
  276. };