thinInstanceMesh.ts 18 KB

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