thinInstanceMesh.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  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. * Refreshes the bounding info, taking into account all the thin instances defined
  69. * @param forceRefreshParentInfo true to force recomputing the mesh bounding info and use it to compute the aggregated bounding info
  70. */
  71. thinInstanceRefreshBoundingInfo(forceRefreshParentInfo: boolean): void;
  72. /** @hidden */
  73. _thinInstanceInitializeUserStorage(): void;
  74. /** @hidden */
  75. _thinInstanceUpdateBufferSize(kind: string, numInstances: number): void;
  76. /** @hidden */
  77. _userThinInstanceBuffersStorage: {
  78. data: {[key: string]: Float32Array},
  79. sizes: {[key: string]: number},
  80. vertexBuffers: {[key: string]: Nullable<VertexBuffer>},
  81. strides: {[key: string]: number}
  82. };
  83. }
  84. }
  85. Mesh.prototype.thinInstanceAdd = function(matrix: DeepImmutableObject<Matrix> | Array<DeepImmutableObject<Matrix>>, refresh: boolean = true): number {
  86. this._thinInstanceUpdateBufferSize("matrix", Array.isArray(matrix) ? matrix.length : 1);
  87. const index = this._thinInstanceDataStorage.instancesCount;
  88. if (Array.isArray(matrix)) {
  89. for (let i = 0; i < matrix.length; ++i) {
  90. this.thinInstanceSetMatrixAt(this._thinInstanceDataStorage.instancesCount++, matrix[i], (i === matrix.length - 1) && refresh);
  91. }
  92. } else {
  93. this.thinInstanceSetMatrixAt(this._thinInstanceDataStorage.instancesCount++, matrix, refresh);
  94. }
  95. return index;
  96. };
  97. Mesh.prototype.thinInstanceAddSelf = function(refresh: boolean = true): number {
  98. return this.thinInstanceAdd(Matrix.IdentityReadOnly, refresh);
  99. };
  100. Mesh.prototype.thinInstanceRegisterAttribute = function(kind: string, stride: number): void {
  101. this.removeVerticesData(kind);
  102. this._thinInstanceInitializeUserStorage();
  103. this._userThinInstanceBuffersStorage.strides[kind] = stride;
  104. this._userThinInstanceBuffersStorage.sizes[kind] = stride * Math.max(32, this._thinInstanceDataStorage.instancesCount); // Initial size
  105. this._userThinInstanceBuffersStorage.data[kind] = new Float32Array(this._userThinInstanceBuffersStorage.sizes[kind]);
  106. this._userThinInstanceBuffersStorage.vertexBuffers[kind] = new VertexBuffer(this.getEngine(), this._userThinInstanceBuffersStorage.data[kind], kind, true, false, stride, true);
  107. this.setVerticesBuffer(this._userThinInstanceBuffersStorage.vertexBuffers[kind]!);
  108. };
  109. Mesh.prototype.thinInstanceSetMatrixAt = function(index: number, matrix: DeepImmutableObject<Matrix>, refresh: boolean = true): boolean {
  110. if (!this._thinInstanceDataStorage.matrixData || index >= this._thinInstanceDataStorage.instancesCount) {
  111. return false;
  112. }
  113. const matrixData = this._thinInstanceDataStorage.matrixData;
  114. matrix.copyToArray(matrixData, index * 16);
  115. if (this._thinInstanceDataStorage.worldMatrices) {
  116. this._thinInstanceDataStorage.worldMatrices[index] = matrix as Matrix;
  117. }
  118. if (refresh) {
  119. this.thinInstanceBufferUpdated("matrix");
  120. if (!this.doNotSyncBoundingInfo) {
  121. this.thinInstanceRefreshBoundingInfo(false);
  122. }
  123. }
  124. return true;
  125. };
  126. Mesh.prototype.thinInstanceSetAttributeAt = function(kind: string, index: number, value: Array<number>, refresh: boolean = true): boolean {
  127. if (!this._userThinInstanceBuffersStorage || !this._userThinInstanceBuffersStorage.data[kind] || index >= this._thinInstanceDataStorage.instancesCount) {
  128. return false;
  129. }
  130. this._thinInstanceUpdateBufferSize(kind, 0); // make sur the buffer for the kind attribute is big enough
  131. this._userThinInstanceBuffersStorage.data[kind].set(value, index * this._userThinInstanceBuffersStorage.strides[kind]);
  132. if (refresh) {
  133. this.thinInstanceBufferUpdated(kind);
  134. }
  135. return true;
  136. };
  137. Object.defineProperty(Mesh.prototype, "thinInstanceCount", {
  138. get: function(this: Mesh) {
  139. return this._thinInstanceDataStorage.instancesCount;
  140. },
  141. set: function(this: Mesh, value: number) {
  142. const numMaxInstances = (this._thinInstanceDataStorage.matrixData?.length ?? 0) / 16;
  143. if (value <= numMaxInstances) {
  144. this._thinInstanceDataStorage.instancesCount = value;
  145. }
  146. },
  147. enumerable: true,
  148. configurable: true
  149. });
  150. Mesh.prototype.thinInstanceSetBuffer = function(kind: string, buffer: Nullable<Float32Array>, stride: number = 0, staticBuffer: boolean = false): void {
  151. stride = stride || 16;
  152. if (kind === "matrix") {
  153. this._thinInstanceDataStorage.matrixBuffer?.dispose();
  154. this._thinInstanceDataStorage.matrixBuffer = null;
  155. this._thinInstanceDataStorage.matrixBufferSize = buffer ? buffer.length : 32 * stride;
  156. this._thinInstanceDataStorage.matrixData = buffer;
  157. this._thinInstanceDataStorage.worldMatrices = null;
  158. if (buffer !== null) {
  159. this._thinInstanceDataStorage.instancesCount = buffer.length / stride;
  160. const matrixBuffer = new Buffer(this.getEngine(), buffer, !staticBuffer, stride, false, true);
  161. this._thinInstanceDataStorage.matrixBuffer = matrixBuffer;
  162. this.setVerticesBuffer(matrixBuffer.createVertexBuffer("world0", 0, 4));
  163. this.setVerticesBuffer(matrixBuffer.createVertexBuffer("world1", 4, 4));
  164. this.setVerticesBuffer(matrixBuffer.createVertexBuffer("world2", 8, 4));
  165. this.setVerticesBuffer(matrixBuffer.createVertexBuffer("world3", 12, 4));
  166. if (!this.doNotSyncBoundingInfo) {
  167. this.thinInstanceRefreshBoundingInfo(false);
  168. }
  169. } else {
  170. this._thinInstanceDataStorage.instancesCount = 0;
  171. if (!this.doNotSyncBoundingInfo) {
  172. // mesh has no more thin instances, so need to recompute the bounding box because it's the regular mesh that will now be displayed
  173. this.refreshBoundingInfo(true);
  174. }
  175. }
  176. } else {
  177. if (buffer === null) {
  178. if (this._userThinInstanceBuffersStorage?.data[kind]) {
  179. this.removeVerticesData(kind);
  180. delete this._userThinInstanceBuffersStorage.data[kind];
  181. delete this._userThinInstanceBuffersStorage.strides[kind];
  182. delete this._userThinInstanceBuffersStorage.sizes[kind];
  183. delete this._userThinInstanceBuffersStorage.vertexBuffers[kind];
  184. }
  185. } else {
  186. this._thinInstanceInitializeUserStorage();
  187. this._userThinInstanceBuffersStorage.data[kind] = buffer;
  188. this._userThinInstanceBuffersStorage.strides[kind] = stride;
  189. this._userThinInstanceBuffersStorage.sizes[kind] = buffer.length;
  190. this._userThinInstanceBuffersStorage.vertexBuffers[kind] = new VertexBuffer(this.getEngine(), buffer, kind, !staticBuffer, false, stride, true);
  191. this.setVerticesBuffer(this._userThinInstanceBuffersStorage.vertexBuffers[kind]!);
  192. }
  193. }
  194. };
  195. Mesh.prototype.thinInstanceBufferUpdated = function(kind: string): void {
  196. if (kind === "matrix") {
  197. if (this._thinInstanceDataStorage.matrixBuffer) {
  198. this._thinInstanceDataStorage.matrixBuffer!.updateDirectly(this._thinInstanceDataStorage.matrixData!, 0, this._thinInstanceDataStorage.instancesCount);
  199. }
  200. } else if (this._userThinInstanceBuffersStorage?.vertexBuffers[kind]) {
  201. this._userThinInstanceBuffersStorage.vertexBuffers[kind]!.updateDirectly(this._userThinInstanceBuffersStorage.data[kind], 0);
  202. }
  203. };
  204. Mesh.prototype.thinInstanceGetWorldMatrices = function(): Matrix[] {
  205. if (!this._thinInstanceDataStorage.matrixData || !this._thinInstanceDataStorage.matrixBuffer) {
  206. return [];
  207. }
  208. const matrixData = this._thinInstanceDataStorage.matrixData;
  209. if (!this._thinInstanceDataStorage.worldMatrices) {
  210. this._thinInstanceDataStorage.worldMatrices = new Array<Matrix>();
  211. for (let i = 0; i < this._thinInstanceDataStorage.instancesCount; ++i) {
  212. this._thinInstanceDataStorage.worldMatrices[i] = Matrix.FromArray(matrixData, i * 16);
  213. }
  214. }
  215. return this._thinInstanceDataStorage.worldMatrices;
  216. };
  217. Mesh.prototype.thinInstanceRefreshBoundingInfo = function(forceRefreshParentInfo: boolean = false) {
  218. if (!this._thinInstanceDataStorage.matrixData || !this._thinInstanceDataStorage.matrixBuffer) {
  219. return;
  220. }
  221. const vectors = this._thinInstanceDataStorage.boundingVectors;
  222. if (forceRefreshParentInfo) {
  223. vectors.length = 0;
  224. this.refreshBoundingInfo(true);
  225. }
  226. const boundingInfo = this.getBoundingInfo();
  227. const matrixData = this._thinInstanceDataStorage.matrixData;
  228. if (vectors.length === 0) {
  229. const worldMatrix = this.getWorldMatrix();
  230. for (let v = 0; v < boundingInfo.boundingBox.vectors.length; ++v) {
  231. vectors.push(boundingInfo.boundingBox.vectors[v].clone());
  232. Vector3.TransformCoordinatesToRef(vectors[v], worldMatrix, vectors[v]);
  233. }
  234. }
  235. TmpVectors.Vector3[0].setAll(Number.MAX_VALUE); // min
  236. TmpVectors.Vector3[1].setAll(Number.MIN_VALUE); // max
  237. for (let i = 0; i < this._thinInstanceDataStorage.instancesCount; ++i) {
  238. Matrix.FromArrayToRef(matrixData, i * 16, TmpVectors.Matrix[0]);
  239. for (let v = 0; v < vectors.length; ++v) {
  240. Vector3.TransformCoordinatesToRef(vectors[v], TmpVectors.Matrix[0], TmpVectors.Vector3[2]);
  241. TmpVectors.Vector3[0].minimizeInPlace(TmpVectors.Vector3[2]);
  242. TmpVectors.Vector3[1].maximizeInPlace(TmpVectors.Vector3[2]);
  243. }
  244. }
  245. boundingInfo.reConstruct(TmpVectors.Vector3[0], TmpVectors.Vector3[1], this.getWorldMatrix());
  246. };
  247. Mesh.prototype._thinInstanceUpdateBufferSize = function(kind: string, numInstances: number = 1) {
  248. const kindIsMatrix = kind === "matrix";
  249. if (!kindIsMatrix && (!this._userThinInstanceBuffersStorage || !this._userThinInstanceBuffersStorage.strides[kind])) {
  250. return;
  251. }
  252. const stride = kindIsMatrix ? 16 : this._userThinInstanceBuffersStorage.strides[kind];
  253. const currentSize = kindIsMatrix ? this._thinInstanceDataStorage.matrixBufferSize : this._userThinInstanceBuffersStorage.sizes[kind];
  254. let data = kindIsMatrix ? this._thinInstanceDataStorage.matrixData : this._userThinInstanceBuffersStorage.data[kind];
  255. const bufferSize = (this._thinInstanceDataStorage.instancesCount + numInstances) * stride;
  256. let newSize = currentSize;
  257. while (newSize < bufferSize) {
  258. newSize *= 2;
  259. }
  260. if (!data || currentSize != newSize) {
  261. if (!data) {
  262. data = new Float32Array(newSize);
  263. } else {
  264. const newData = new Float32Array(newSize);
  265. newData.set(data, 0);
  266. data = newData;
  267. }
  268. if (kindIsMatrix) {
  269. this._thinInstanceDataStorage.matrixBuffer?.dispose();
  270. const matrixBuffer = new Buffer(this.getEngine(), data, true, stride, false, true);
  271. this._thinInstanceDataStorage.matrixBuffer = matrixBuffer;
  272. this._thinInstanceDataStorage.matrixData = data;
  273. this._thinInstanceDataStorage.matrixBufferSize = newSize;
  274. this.setVerticesBuffer(matrixBuffer.createVertexBuffer("world0", 0, 4));
  275. this.setVerticesBuffer(matrixBuffer.createVertexBuffer("world1", 4, 4));
  276. this.setVerticesBuffer(matrixBuffer.createVertexBuffer("world2", 8, 4));
  277. this.setVerticesBuffer(matrixBuffer.createVertexBuffer("world3", 12, 4));
  278. } else {
  279. this._userThinInstanceBuffersStorage.vertexBuffers[kind]?.dispose();
  280. this._userThinInstanceBuffersStorage.data[kind] = data;
  281. this._userThinInstanceBuffersStorage.sizes[kind] = newSize;
  282. this._userThinInstanceBuffersStorage.vertexBuffers[kind] = new VertexBuffer(this.getEngine(), data, kind, true, false, stride, true);
  283. this.setVerticesBuffer(this._userThinInstanceBuffersStorage.vertexBuffers[kind]!);
  284. }
  285. }
  286. };
  287. Mesh.prototype._thinInstanceInitializeUserStorage = function() {
  288. if (!this._userThinInstanceBuffersStorage) {
  289. this._userThinInstanceBuffersStorage = {
  290. data: {},
  291. sizes: {},
  292. vertexBuffers: {},
  293. strides: {},
  294. };
  295. }
  296. };
  297. Mesh.prototype._disposeThinInstanceSpecificData = function() {
  298. if (this._thinInstanceDataStorage?.matrixBuffer) {
  299. this._thinInstanceDataStorage.matrixBuffer.dispose();
  300. this._thinInstanceDataStorage.matrixBuffer = null;
  301. }
  302. };