solidParticleSystem.ts 85 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804
  1. import { Nullable, IndicesArray, FloatArray } from "../types";
  2. import { Vector3, Matrix, TmpVectors, Quaternion } from "../Maths/math.vector";
  3. import { Color4 } from '../Maths/math.color';
  4. import { VertexBuffer } from "../Meshes/buffer";
  5. import { VertexData } from "../Meshes/mesh.vertexData";
  6. import { Mesh } from "../Meshes/mesh";
  7. import { DiscBuilder } from "../Meshes/Builders/discBuilder";
  8. import { EngineStore } from "../Engines/engineStore";
  9. import { Scene, IDisposable } from "../scene";
  10. import { DepthSortedParticle, SolidParticle, ModelShape } from "./solidParticle";
  11. import { TargetCamera } from "../Cameras/targetCamera";
  12. import { BoundingInfo } from "../Culling/boundingInfo";
  13. import { Axis } from '../Maths/math.axis';
  14. import { SubMesh } from '../Meshes/subMesh';
  15. import { Material } from '../Materials/material';
  16. import { StandardMaterial } from '../Materials/standardMaterial';
  17. import { MultiMaterial } from '../Materials/multiMaterial';
  18. import { PickingInfo } from '../Collisions/pickingInfo';
  19. /**
  20. * The SPS is a single updatable mesh. The solid particles are simply separate parts or faces fo this big mesh.
  21. *As it is just a mesh, the SPS has all the same properties than any other BJS mesh : not more, not less. It can be scaled, rotated, translated, enlighted, textured, moved, etc.
  22. * The SPS is also a particle system. It provides some methods to manage the particles.
  23. * However it is behavior agnostic. This means it has no emitter, no particle physics, no particle recycler. You have to implement your own behavior.
  24. *
  25. * Full documentation here : http://doc.babylonjs.com/how_to/Solid_Particle_System
  26. */
  27. export class SolidParticleSystem implements IDisposable {
  28. /**
  29. * The SPS array of Solid Particle objects. Just access each particle as with any classic array.
  30. * Example : var p = SPS.particles[i];
  31. */
  32. public particles: SolidParticle[] = new Array<SolidParticle>();
  33. /**
  34. * The SPS total number of particles. Read only. Use SPS.counter instead if you need to set your own value.
  35. */
  36. public nbParticles: number = 0;
  37. /**
  38. * If the particles must ever face the camera (default false). Useful for planar particles.
  39. */
  40. public billboard: boolean = false;
  41. /**
  42. * Recompute normals when adding a shape
  43. */
  44. public recomputeNormals: boolean = false;
  45. /**
  46. * This a counter ofr your own usage. It's not set by any SPS functions.
  47. */
  48. public counter: number = 0;
  49. /**
  50. * The SPS name. This name is also given to the underlying mesh.
  51. */
  52. public name: string;
  53. /**
  54. * The SPS mesh. It's a standard BJS Mesh, so all the methods from the Mesh class are avalaible.
  55. */
  56. public mesh: Mesh;
  57. /**
  58. * This empty object is intended to store some SPS specific or temporary values in order to lower the Garbage Collector activity.
  59. * Please read : http://doc.babylonjs.com/how_to/Solid_Particle_System#garbage-collector-concerns
  60. */
  61. public vars: any = {};
  62. /**
  63. * This array is populated when the SPS is set as 'pickable'.
  64. * Each key of this array is a `faceId` value that you can get from a pickResult object.
  65. * Each element of this array is an object `{idx: int, faceId: int}`.
  66. * `idx` is the picked particle index in the `SPS.particles` array
  67. * `faceId` is the picked face index counted within this particle.
  68. * This array is the first element of the pickedBySubMesh array : sps.pickBySubMesh[0].
  69. * It's not pertinent to use it when using a SPS with the support for MultiMaterial enabled.
  70. * Use the method SPS.pickedParticle(pickingInfo) instead.
  71. * Please read : http://doc.babylonjs.com/how_to/Solid_Particle_System#pickable-particles
  72. */
  73. public pickedParticles: { idx: number; faceId: number }[];
  74. /**
  75. * This array is populated when the SPS is set as 'pickable'
  76. * Each key of this array is a submesh index.
  77. * Each element of this array is a second array defined like this :
  78. * Each key of this second array is a `faceId` value that you can get from a pickResult object.
  79. * Each element of this second array is an object `{idx: int, faceId: int}`.
  80. * `idx` is the picked particle index in the `SPS.particles` array
  81. * `faceId` is the picked face index counted within this particle.
  82. * It's better to use the method SPS.pickedParticle(pickingInfo) rather than using directly this array.
  83. * Please read : http://doc.babylonjs.com/how_to/Solid_Particle_System#pickable-particles
  84. */
  85. public pickedBySubMesh: { idx: number; faceId: number}[][];
  86. /**
  87. * This array is populated when `enableDepthSort` is set to true.
  88. * Each element of this array is an instance of the class DepthSortedParticle.
  89. */
  90. public depthSortedParticles: DepthSortedParticle[];
  91. /**
  92. * If the particle intersection must be computed only with the bounding sphere (no bounding box computation, so faster). (Internal use only)
  93. * @hidden
  94. */
  95. public _bSphereOnly: boolean = false;
  96. /**
  97. * A number to multiply the boundind sphere radius by in order to reduce it for instance. (Internal use only)
  98. * @hidden
  99. */
  100. public _bSphereRadiusFactor: number = 1.0;
  101. private _scene: Scene;
  102. private _positions: number[] = new Array<number>();
  103. private _indices: number[] = new Array<number>();
  104. private _normals: number[] = new Array<number>();
  105. private _colors: number[] = new Array<number>();
  106. private _uvs: number[] = new Array<number>();
  107. private _indices32: IndicesArray; // used as depth sorted array if depth sort enabled, else used as typed indices
  108. private _positions32: Float32Array; // updated positions for the VBO
  109. private _normals32: Float32Array; // updated normals for the VBO
  110. private _fixedNormal32: Float32Array; // initial normal references
  111. private _colors32: Float32Array;
  112. private _uvs32: Float32Array;
  113. private _index: number = 0; // indices index
  114. private _updatable: boolean = true;
  115. private _pickable: boolean = false;
  116. private _isVisibilityBoxLocked = false;
  117. private _alwaysVisible: boolean = false;
  118. private _depthSort: boolean = false;
  119. private _expandable: boolean = false;
  120. private _shapeCounter: number = 0;
  121. private _copy: SolidParticle = new SolidParticle(0, 0, 0, 0, null, 0, 0, this);
  122. private _color: Color4 = new Color4(0, 0, 0, 0);
  123. private _computeParticleColor: boolean = true;
  124. private _computeParticleTexture: boolean = true;
  125. private _computeParticleRotation: boolean = true;
  126. private _computeParticleVertex: boolean = false;
  127. private _computeBoundingBox: boolean = false;
  128. private _depthSortParticles: boolean = true;
  129. private _camera: TargetCamera;
  130. private _mustUnrotateFixedNormals = false;
  131. private _particlesIntersect: boolean = false;
  132. private _needs32Bits: boolean = false;
  133. private _isNotBuilt: boolean = true;
  134. private _lastParticleId: number = 0;
  135. private _idxOfId: number[] = []; // array : key = particle.id / value = particle.idx
  136. private _multimaterialEnabled: boolean = false;
  137. private _useModelMaterial: boolean = false;
  138. private _indicesByMaterial: number[];
  139. private _materialIndexes: number[];
  140. private _depthSortFunction = (p1: DepthSortedParticle, p2: DepthSortedParticle) => p2.sqDistance - p1.sqDistance;
  141. private _materialSortFunction = (p1: DepthSortedParticle, p2: DepthSortedParticle) => p1.materialIndex - p2.materialIndex;
  142. private _materials: Material[];
  143. private _multimaterial: MultiMaterial;
  144. private _materialIndexesById: any;
  145. private _defaultMaterial: Material;
  146. private _autoUpdateSubMeshes: boolean = false;
  147. /**
  148. * Creates a SPS (Solid Particle System) object.
  149. * @param name (String) is the SPS name, this will be the underlying mesh name.
  150. * @param scene (Scene) is the scene in which the SPS is added.
  151. * @param options defines the options of the sps e.g.
  152. * * updatable (optional boolean, default true) : if the SPS must be updatable or immutable.
  153. * * isPickable (optional boolean, default false) : if the solid particles must be pickable.
  154. * * enableDepthSort (optional boolean, default false) : if the solid particles must be sorted in the geometry according to their distance to the camera.
  155. * * useModelMaterial (optional boolean, defaut false) : if the model materials must be used to create the SPS multimaterial. This enables the multimaterial supports of the SPS.
  156. * * enableMultiMaterial (optional boolean, default false) : if the solid particles can be given different materials.
  157. * * expandable (optional boolean, default false) : if particles can still be added after the initial SPS mesh creation.
  158. * * particleIntersection (optional boolean, default false) : if the solid particle intersections must be computed.
  159. * * boundingSphereOnly (optional boolean, default false) : if the particle intersection must be computed only with the bounding sphere (no bounding box computation, so faster).
  160. * * bSphereRadiusFactor (optional float, default 1.0) : a number to multiply the boundind sphere radius by in order to reduce it for instance.
  161. * @example bSphereRadiusFactor = 1.0 / Math.sqrt(3.0) => the bounding sphere exactly matches a spherical mesh.
  162. */
  163. constructor(name: string, scene: Scene, options?: { updatable?: boolean; isPickable?: boolean; enableDepthSort?: boolean; particleIntersection?: boolean; boundingSphereOnly?: boolean; bSphereRadiusFactor?: number; expandable?: boolean; useModelMaterial?: boolean; enableMultiMaterial?: boolean; }) {
  164. this.name = name;
  165. this._scene = scene || EngineStore.LastCreatedScene;
  166. this._camera = <TargetCamera>scene.activeCamera;
  167. this._pickable = options ? <boolean>options.isPickable : false;
  168. this._depthSort = options ? <boolean>options.enableDepthSort : false;
  169. this._multimaterialEnabled = options ? <boolean>options.enableMultiMaterial : false;
  170. this._useModelMaterial = options ? <boolean>options.useModelMaterial : false;
  171. this._multimaterialEnabled = (this._useModelMaterial) ? true : this._multimaterialEnabled;
  172. this._expandable = options ? <boolean>options.expandable : false;
  173. this._particlesIntersect = options ? <boolean>options.particleIntersection : false;
  174. this._bSphereOnly = options ? <boolean>options.boundingSphereOnly : false;
  175. this._bSphereRadiusFactor = (options && options.bSphereRadiusFactor) ? options.bSphereRadiusFactor : 1.0;
  176. if (options && options.updatable !== undefined) {
  177. this._updatable = options.updatable;
  178. } else {
  179. this._updatable = true;
  180. }
  181. if (this._pickable) {
  182. this.pickedBySubMesh = [[]];
  183. this.pickedParticles = this.pickedBySubMesh[0];
  184. }
  185. if (this._depthSort || this._multimaterialEnabled) {
  186. this.depthSortedParticles = [];
  187. }
  188. if (this._multimaterialEnabled) {
  189. this._multimaterial = new MultiMaterial(this.name + "MultiMaterial", this._scene);
  190. this._materials = [];
  191. this._materialIndexesById = {};
  192. }
  193. }
  194. /**
  195. * Builds the SPS underlying mesh. Returns a standard Mesh.
  196. * If no model shape was added to the SPS, the returned mesh is just a single triangular plane.
  197. * @returns the created mesh
  198. */
  199. public buildMesh(): Mesh {
  200. if (!this._isNotBuilt && this.mesh) {
  201. return this.mesh;
  202. }
  203. if (this.nbParticles === 0 && !this.mesh) {
  204. var triangle = DiscBuilder.CreateDisc("", { radius: 1, tessellation: 3 }, this._scene);
  205. this.addShape(triangle, 1);
  206. triangle.dispose();
  207. }
  208. this._indices32 = (this._needs32Bits) ? new Uint32Array(this._indices) : new Uint16Array(this._indices);
  209. this._positions32 = new Float32Array(this._positions);
  210. this._uvs32 = new Float32Array(this._uvs);
  211. this._colors32 = new Float32Array(this._colors);
  212. if (!this.mesh) { // in case it's already expanded
  213. var mesh = new Mesh(this.name, this._scene);
  214. this.mesh = mesh;
  215. }
  216. if (!this._updatable && this._multimaterialEnabled) {
  217. this._sortParticlesByMaterial(); // this may reorder the indices32
  218. }
  219. if (this.recomputeNormals) {
  220. VertexData.ComputeNormals(this._positions32, this._indices32, this._normals);
  221. }
  222. this._normals32 = new Float32Array(this._normals);
  223. this._fixedNormal32 = new Float32Array(this._normals);
  224. if (this._mustUnrotateFixedNormals) { // the particles could be created already rotated in the mesh with a positionFunction
  225. this._unrotateFixedNormals();
  226. }
  227. var vertexData = new VertexData();
  228. vertexData.indices = (this._depthSort) ? this._indices : this._indices32;
  229. vertexData.set(this._positions32, VertexBuffer.PositionKind);
  230. vertexData.set(this._normals32, VertexBuffer.NormalKind);
  231. if (this._uvs32.length > 0) {
  232. vertexData.set(this._uvs32, VertexBuffer.UVKind);
  233. }
  234. if (this._colors32.length > 0) {
  235. vertexData.set(this._colors32, VertexBuffer.ColorKind);
  236. }
  237. vertexData.applyToMesh(this.mesh, this._updatable);
  238. this.mesh.isPickable = this._pickable;
  239. if (this._multimaterialEnabled) {
  240. this.setMultiMaterial(this._materials);
  241. }
  242. if (!this._expandable) {
  243. // free memory
  244. if (!this._depthSort && !this._multimaterialEnabled) {
  245. (<any>this._indices) = null;
  246. }
  247. (<any>this._positions) = null;
  248. (<any>this._normals) = null;
  249. (<any>this._uvs) = null;
  250. (<any>this._colors) = null;
  251. if (!this._updatable) {
  252. this.particles.length = 0;
  253. }
  254. }
  255. this._isNotBuilt = false;
  256. this.recomputeNormals = false;
  257. return this.mesh;
  258. }
  259. /**
  260. * Digests the mesh and generates as many solid particles in the system as wanted. Returns the SPS.
  261. * These particles will have the same geometry than the mesh parts and will be positioned at the same localisation than the mesh original places.
  262. * Thus the particles generated from `digest()` have their property `position` set yet.
  263. * @param mesh ( Mesh ) is the mesh to be digested
  264. * @param options {facetNb} (optional integer, default 1) is the number of mesh facets per particle, this parameter is overriden by the parameter `number` if any
  265. * {delta} (optional integer, default 0) is the random extra number of facets per particle , each particle will have between `facetNb` and `facetNb + delta` facets
  266. * {number} (optional positive integer) is the wanted number of particles : each particle is built with `mesh_total_facets / number` facets
  267. * {storage} (optional existing array) is an array where the particles will be stored for a further use instead of being inserted in the SPS.
  268. * @returns the current SPS
  269. */
  270. public digest(mesh: Mesh, options?: { facetNb?: number; number?: number; delta?: number; storage?: [] }): SolidParticleSystem {
  271. var size: number = (options && options.facetNb) || 1;
  272. var number: number = (options && options.number) || 0;
  273. var delta: number = (options && options.delta) || 0;
  274. var meshPos = <FloatArray>mesh.getVerticesData(VertexBuffer.PositionKind);
  275. var meshInd = <IndicesArray>mesh.getIndices();
  276. var meshUV = <FloatArray>mesh.getVerticesData(VertexBuffer.UVKind);
  277. var meshCol = <FloatArray>mesh.getVerticesData(VertexBuffer.ColorKind);
  278. var meshNor = <FloatArray>mesh.getVerticesData(VertexBuffer.NormalKind);
  279. var storage = (options && options.storage) ? options.storage : null;
  280. var f: number = 0; // facet counter
  281. var totalFacets: number = meshInd.length / 3; // a facet is a triangle, so 3 indices
  282. // compute size from number
  283. if (number) {
  284. number = (number > totalFacets) ? totalFacets : number;
  285. size = Math.round(totalFacets / number);
  286. delta = 0;
  287. } else {
  288. size = (size > totalFacets) ? totalFacets : size;
  289. }
  290. var facetPos: number[] = []; // submesh positions
  291. var facetNor: number[] = [];
  292. var facetInd: number[] = []; // submesh indices
  293. var facetUV: number[] = []; // submesh UV
  294. var facetCol: number[] = []; // submesh colors
  295. var barycenter: Vector3 = Vector3.Zero();
  296. var sizeO: number = size;
  297. while (f < totalFacets) {
  298. size = sizeO + Math.floor((1 + delta) * Math.random());
  299. if (f > totalFacets - size) {
  300. size = totalFacets - f;
  301. }
  302. // reset temp arrays
  303. facetPos.length = 0;
  304. facetNor.length = 0;
  305. facetInd.length = 0;
  306. facetUV.length = 0;
  307. facetCol.length = 0;
  308. // iterate over "size" facets
  309. var fi: number = 0;
  310. for (var j = f * 3; j < (f + size) * 3; j++) {
  311. facetInd.push(fi);
  312. var i: number = meshInd[j];
  313. var i3: number = i * 3;
  314. facetPos.push(meshPos[i3], meshPos[i3 + 1], meshPos[i3 + 2]);
  315. facetNor.push(meshNor[i3], meshNor[i3 + 1], meshNor[i3 + 2]);
  316. if (meshUV) {
  317. var i2: number = i * 2;
  318. facetUV.push(meshUV[i2], meshUV[i2 + 1]);
  319. }
  320. if (meshCol) {
  321. var i4: number = i * 4;
  322. facetCol.push(meshCol[i4], meshCol[i4 + 1], meshCol[i4 + 2], meshCol[i4 + 3]);
  323. }
  324. fi++;
  325. }
  326. // create a model shape for each single particle
  327. var idx: number = this.nbParticles;
  328. var shape: Vector3[] = this._posToShape(facetPos);
  329. var shapeUV: number[] = this._uvsToShapeUV(facetUV);
  330. var shapeInd = Array.from(facetInd);
  331. var shapeCol = Array.from(facetCol);
  332. var shapeNor = Array.from(facetNor);
  333. // compute the barycenter of the shape
  334. barycenter.copyFromFloats(0, 0, 0);
  335. var v: number;
  336. for (v = 0; v < shape.length; v++) {
  337. barycenter.addInPlace(shape[v]);
  338. }
  339. barycenter.scaleInPlace(1 / shape.length);
  340. // shift the shape from its barycenter to the origin
  341. // and compute the BBox required for intersection.
  342. var minimum: Vector3 = new Vector3(Infinity, Infinity, Infinity);
  343. var maximum: Vector3 = new Vector3(-Infinity, -Infinity, -Infinity);
  344. for (v = 0; v < shape.length; v++) {
  345. shape[v].subtractInPlace(barycenter);
  346. minimum.minimizeInPlaceFromFloats(shape[v].x, shape[v].y, shape[v].z);
  347. maximum.maximizeInPlaceFromFloats(shape[v].x, shape[v].y, shape[v].z);
  348. }
  349. var bInfo;
  350. if (this._particlesIntersect) {
  351. bInfo = new BoundingInfo(minimum, maximum);
  352. }
  353. var material = null;
  354. if (this._useModelMaterial) {
  355. material = (mesh.material) ? mesh.material : this._setDefaultMaterial();
  356. }
  357. var modelShape = new ModelShape(this._shapeCounter, shape, shapeInd, shapeNor, shapeCol, shapeUV, null, null, material);
  358. // add the particle in the SPS
  359. var currentPos = this._positions.length;
  360. var currentInd = this._indices.length;
  361. this._meshBuilder(this._index, currentInd, shape, this._positions, shapeInd, this._indices, facetUV, this._uvs, shapeCol, this._colors, shapeNor, this._normals, idx, 0, null, modelShape);
  362. this._addParticle(idx, this._lastParticleId, currentPos, currentInd, modelShape, this._shapeCounter, 0, bInfo, storage);
  363. // initialize the particle position
  364. this.particles[this.nbParticles].position.addInPlace(barycenter);
  365. if (!storage) {
  366. this._index += shape.length;
  367. idx++;
  368. this.nbParticles++;
  369. this._lastParticleId++;
  370. }
  371. this._shapeCounter++;
  372. f += size;
  373. }
  374. this._isNotBuilt = true; // buildMesh() is now expected for setParticles() to work
  375. return this;
  376. }
  377. /**
  378. * Unrotate the fixed normals in case the mesh was built with pre-rotated particles, ex : use of positionFunction in addShape()
  379. * @hidden
  380. */
  381. private _unrotateFixedNormals() {
  382. var index = 0;
  383. var idx = 0;
  384. const tmpNormal = TmpVectors.Vector3[0];
  385. const quaternion = TmpVectors.Quaternion[0];
  386. const invertedRotMatrix = TmpVectors.Matrix[0];
  387. for (var p = 0; p < this.particles.length; p++) {
  388. const particle = this.particles[p];
  389. const shape = particle._model._shape;
  390. // computing the inverse of the rotation matrix from the quaternion
  391. // is equivalent to computing the matrix of the inverse quaternion, i.e of the conjugate quaternion
  392. if (particle.rotationQuaternion) {
  393. particle.rotationQuaternion.conjugateToRef(quaternion);
  394. }
  395. else {
  396. const rotation = particle.rotation;
  397. Quaternion.RotationYawPitchRollToRef(rotation.y, rotation.x, rotation.z, quaternion);
  398. quaternion.conjugateInPlace();
  399. }
  400. quaternion.toRotationMatrix(invertedRotMatrix);
  401. for (var pt = 0; pt < shape.length; pt++) {
  402. idx = index + pt * 3;
  403. Vector3.TransformNormalFromFloatsToRef(this._normals32[idx], this._normals32[idx + 1], this._normals32[idx + 2], invertedRotMatrix, tmpNormal);
  404. tmpNormal.toArray(this._fixedNormal32, idx);
  405. }
  406. index = idx + 3;
  407. }
  408. }
  409. /**
  410. * Resets the temporary working copy particle
  411. * @hidden
  412. */
  413. private _resetCopy() {
  414. const copy = this._copy;
  415. copy.position.setAll(0);
  416. copy.rotation.setAll(0);
  417. copy.rotationQuaternion = null;
  418. copy.scaling.setAll(1);
  419. copy.uvs.copyFromFloats(0.0, 0.0, 1.0, 1.0);
  420. copy.color = null;
  421. copy.translateFromPivot = false;
  422. copy.materialIndex = null;
  423. }
  424. /**
  425. * Inserts the shape model geometry in the global SPS mesh by updating the positions, indices, normals, colors, uvs arrays
  426. * @param p the current index in the positions array to be updated
  427. * @param ind the current index in the indices array
  428. * @param shape a Vector3 array, the shape geometry
  429. * @param positions the positions array to be updated
  430. * @param meshInd the shape indices array
  431. * @param indices the indices array to be updated
  432. * @param meshUV the shape uv array
  433. * @param uvs the uv array to be updated
  434. * @param meshCol the shape color array
  435. * @param colors the color array to be updated
  436. * @param meshNor the shape normals array
  437. * @param normals the normals array to be updated
  438. * @param idx the particle index
  439. * @param idxInShape the particle index in its shape
  440. * @param options the addShape() method passed options
  441. * @model the particle model
  442. * @hidden
  443. */
  444. private _meshBuilder(p: number, ind: number, shape: Vector3[], positions: number[], meshInd: IndicesArray, indices: number[], meshUV: number[] | Float32Array, uvs: number[], meshCol: number[] | Float32Array, colors: number[], meshNor: number[] | Float32Array, normals: number[], idx: number, idxInShape: number, options: any, model: ModelShape): SolidParticle {
  445. var i;
  446. var u = 0;
  447. var c = 0;
  448. var n = 0;
  449. this._resetCopy();
  450. const copy = this._copy;
  451. const storeApart = (options && options.storage) ? true : false;
  452. copy.idx = idx;
  453. copy.idxInShape = idxInShape;
  454. if (this._useModelMaterial) {
  455. var materialId = model._material!.uniqueId;
  456. const materialIndexesById = this._materialIndexesById;
  457. if (!materialIndexesById.hasOwnProperty(materialId)) {
  458. materialIndexesById[materialId] = this._materials.length;
  459. this._materials.push(model._material!);
  460. }
  461. var matIdx = materialIndexesById[materialId];
  462. copy.materialIndex = matIdx;
  463. }
  464. if (options && options.positionFunction) { // call to custom positionFunction
  465. options.positionFunction(copy, idx, idxInShape);
  466. this._mustUnrotateFixedNormals = true;
  467. }
  468. // in case the particle geometry must NOT be inserted in the SPS mesh geometry
  469. if (storeApart) {
  470. return copy;
  471. }
  472. const rotMatrix = TmpVectors.Matrix[0];
  473. const tmpVertex = TmpVectors.Vector3[0];
  474. const tmpRotated = TmpVectors.Vector3[1];
  475. const pivotBackTranslation = TmpVectors.Vector3[2];
  476. const scaledPivot = TmpVectors.Vector3[3];
  477. Matrix.IdentityToRef(rotMatrix);
  478. copy.getRotationMatrix(rotMatrix);
  479. copy.pivot.multiplyToRef(copy.scaling, scaledPivot);
  480. if (copy.translateFromPivot) {
  481. pivotBackTranslation.setAll(0.0);
  482. }
  483. else {
  484. pivotBackTranslation.copyFrom(scaledPivot);
  485. }
  486. var someVertexFunction = (options && options.vertexFunction);
  487. for (i = 0; i < shape.length; i++) {
  488. tmpVertex.copyFrom(shape[i]);
  489. if (someVertexFunction) {
  490. options.vertexFunction(copy, tmpVertex, i);
  491. }
  492. tmpVertex.multiplyInPlace(copy.scaling).subtractInPlace(scaledPivot);
  493. Vector3.TransformCoordinatesToRef(tmpVertex, rotMatrix, tmpRotated);
  494. tmpRotated.addInPlace(pivotBackTranslation).addInPlace(copy.position);
  495. positions.push(tmpRotated.x, tmpRotated.y, tmpRotated.z);
  496. if (meshUV) {
  497. const copyUvs = copy.uvs;
  498. uvs.push((copyUvs.z - copyUvs.x) * meshUV[u] + copyUvs.x, (copyUvs.w - copyUvs.y) * meshUV[u + 1] + copyUvs.y);
  499. u += 2;
  500. }
  501. if (copy.color) {
  502. this._color = copy.color;
  503. } else {
  504. const color = this._color;
  505. if (meshCol && meshCol[c] !== undefined) {
  506. color.r = meshCol[c];
  507. color.g = meshCol[c + 1];
  508. color.b = meshCol[c + 2];
  509. color.a = meshCol[c + 3];
  510. } else {
  511. color.r = 1.0;
  512. color.g = 1.0;
  513. color.b = 1.0;
  514. color.a = 1.0;
  515. }
  516. }
  517. colors.push(this._color.r, this._color.g, this._color.b, this._color.a);
  518. c += 4;
  519. if (!this.recomputeNormals && meshNor) {
  520. Vector3.TransformNormalFromFloatsToRef(meshNor[n], meshNor[n + 1], meshNor[n + 2], rotMatrix, tmpVertex);
  521. normals.push(tmpVertex.x, tmpVertex.y, tmpVertex.z);
  522. n += 3;
  523. }
  524. }
  525. for (i = 0; i < meshInd.length; i++) {
  526. var current_ind = p + meshInd[i];
  527. indices.push(current_ind);
  528. if (current_ind > 65535) {
  529. this._needs32Bits = true;
  530. }
  531. }
  532. if (this._pickable) {
  533. var nbfaces = meshInd.length / 3;
  534. for (i = 0; i < nbfaces; i++) {
  535. this.pickedParticles.push({ idx: idx, faceId: i });
  536. }
  537. }
  538. if (this._depthSort || this._multimaterialEnabled) {
  539. var matIndex = (copy.materialIndex !== null) ? copy.materialIndex : 0;
  540. this.depthSortedParticles.push(new DepthSortedParticle(idx, ind, meshInd.length, matIndex));
  541. }
  542. return copy;
  543. }
  544. /**
  545. * Returns a shape Vector3 array from positions float array
  546. * @param positions float array
  547. * @returns a vector3 array
  548. * @hidden
  549. */
  550. private _posToShape(positions: number[] | Float32Array): Vector3[] {
  551. var shape = [];
  552. for (var i = 0; i < positions.length; i += 3) {
  553. shape.push(Vector3.FromArray(positions, i));
  554. }
  555. return shape;
  556. }
  557. /**
  558. * Returns a shapeUV array from a float uvs (array deep copy)
  559. * @param uvs as a float array
  560. * @returns a shapeUV array
  561. * @hidden
  562. */
  563. private _uvsToShapeUV(uvs: number[] | Float32Array): number[] {
  564. var shapeUV = [];
  565. if (uvs) {
  566. for (var i = 0; i < uvs.length; i++) {
  567. shapeUV.push(uvs[i]);
  568. }
  569. }
  570. return shapeUV;
  571. }
  572. /**
  573. * Adds a new particle object in the particles array
  574. * @param idx particle index in particles array
  575. * @param id particle id
  576. * @param idxpos positionIndex : the starting index of the particle vertices in the SPS "positions" array
  577. * @param idxind indiceIndex : he starting index of the particle indices in the SPS "indices" array
  578. * @param model particle ModelShape object
  579. * @param shapeId model shape identifier
  580. * @param idxInShape index of the particle in the current model
  581. * @param bInfo model bounding info object
  582. * @param storage target storage array, if any
  583. * @hidden
  584. */
  585. private _addParticle(idx: number, id: number, idxpos: number, idxind: number, model: ModelShape, shapeId: number, idxInShape: number, bInfo: Nullable<BoundingInfo> = null, storage: Nullable<[]> = null): SolidParticle {
  586. var sp = new SolidParticle(idx, id, idxpos, idxind, model, shapeId, idxInShape, this, bInfo);
  587. var target = (storage) ? storage : this.particles;
  588. target.push(sp);
  589. return sp;
  590. }
  591. /**
  592. * Adds some particles to the SPS from the model shape. Returns the shape id.
  593. * Please read the doc : http://doc.babylonjs.com/how_to/Solid_Particle_System#create-an-immutable-sps
  594. * @param mesh is any Mesh object that will be used as a model for the solid particles.
  595. * @param nb (positive integer) the number of particles to be created from this model
  596. * @param options {positionFunction} is an optional javascript function to called for each particle on SPS creation.
  597. * {vertexFunction} is an optional javascript function to called for each vertex of each particle on SPS creation
  598. * {storage} (optional existing array) is an array where the particles will be stored for a further use instead of being inserted in the SPS.
  599. * @returns the number of shapes in the system
  600. */
  601. public addShape(mesh: Mesh, nb: number, options?: { positionFunction?: any; vertexFunction?: any; storage?: [] }): number {
  602. var meshPos = <FloatArray>mesh.getVerticesData(VertexBuffer.PositionKind);
  603. var meshInd = <IndicesArray>mesh.getIndices();
  604. var meshUV = <FloatArray>mesh.getVerticesData(VertexBuffer.UVKind);
  605. var meshCol = <FloatArray>mesh.getVerticesData(VertexBuffer.ColorKind);
  606. var meshNor = <FloatArray>mesh.getVerticesData(VertexBuffer.NormalKind);
  607. this.recomputeNormals = (meshNor) ? false : true;
  608. var indices = Array.from(meshInd);
  609. var shapeNormals = Array.from(meshNor);
  610. var shapeColors = (meshCol) ? Array.from(meshCol) : [];
  611. var storage = (options && options.storage) ? options.storage : null;
  612. var bbInfo: Nullable<BoundingInfo> = null;
  613. if (this._particlesIntersect) {
  614. bbInfo = mesh.getBoundingInfo();
  615. }
  616. var shape = this._posToShape(meshPos);
  617. var shapeUV = this._uvsToShapeUV(meshUV);
  618. var posfunc = options ? options.positionFunction : null;
  619. var vtxfunc = options ? options.vertexFunction : null;
  620. var material = null;
  621. if (this._useModelMaterial) {
  622. material = (mesh.material) ? mesh.material : this._setDefaultMaterial();
  623. }
  624. var modelShape = new ModelShape(this._shapeCounter, shape, indices, shapeNormals, shapeColors, shapeUV, posfunc, vtxfunc, material);
  625. // particles
  626. for (var i = 0; i < nb; i++) {
  627. this._insertNewParticle(this.nbParticles, i, modelShape, shape, meshInd, meshUV, meshCol, meshNor, bbInfo, storage, options);
  628. }
  629. this._shapeCounter++;
  630. this._isNotBuilt = true; // buildMesh() call is now expected for setParticles() to work
  631. return this._shapeCounter - 1;
  632. }
  633. /**
  634. * Rebuilds a particle back to its just built status : if needed, recomputes the custom positions and vertices
  635. * @hidden
  636. */
  637. private _rebuildParticle(particle: SolidParticle, reset: boolean = false): void {
  638. this._resetCopy();
  639. const copy = this._copy;
  640. if (particle._model._positionFunction) { // recall to stored custom positionFunction
  641. particle._model._positionFunction(copy, particle.idx, particle.idxInShape);
  642. }
  643. const rotMatrix = TmpVectors.Matrix[0];
  644. const tmpVertex = TmpVectors.Vector3[0];
  645. const tmpRotated = TmpVectors.Vector3[1];
  646. const pivotBackTranslation = TmpVectors.Vector3[2];
  647. const scaledPivot = TmpVectors.Vector3[3];
  648. copy.getRotationMatrix(rotMatrix);
  649. particle.pivot.multiplyToRef(particle.scaling, scaledPivot);
  650. if (copy.translateFromPivot) {
  651. pivotBackTranslation.copyFromFloats(0.0, 0.0, 0.0);
  652. }
  653. else {
  654. pivotBackTranslation.copyFrom(scaledPivot);
  655. }
  656. const shape = particle._model._shape;
  657. for (var pt = 0; pt < shape.length; pt++) {
  658. tmpVertex.copyFrom(shape[pt]);
  659. if (particle._model._vertexFunction) {
  660. particle._model._vertexFunction(copy, tmpVertex, pt); // recall to stored vertexFunction
  661. }
  662. tmpVertex.multiplyInPlace(copy.scaling).subtractInPlace(scaledPivot);
  663. Vector3.TransformCoordinatesToRef(tmpVertex, rotMatrix, tmpRotated);
  664. tmpRotated.addInPlace(pivotBackTranslation).addInPlace(copy.position).toArray(this._positions32, particle._pos + pt * 3);
  665. }
  666. if (reset) {
  667. particle.position.setAll(0.0);
  668. particle.rotation.setAll(0.0);
  669. particle.rotationQuaternion = null;
  670. particle.scaling.setAll(1.0);
  671. particle.uvs.setAll(0.0);
  672. particle.pivot.setAll(0.0);
  673. particle.translateFromPivot = false;
  674. particle.parentId = null;
  675. }
  676. }
  677. /**
  678. * Rebuilds the whole mesh and updates the VBO : custom positions and vertices are recomputed if needed.
  679. * @param reset boolean, default false : if the particles must be reset at position and rotation zero, scaling 1, color white, initial UVs and not parented.
  680. * @returns the SPS.
  681. */
  682. public rebuildMesh(reset: boolean = false): SolidParticleSystem {
  683. for (var p = 0; p < this.particles.length; p++) {
  684. this._rebuildParticle(this.particles[p], reset);
  685. }
  686. this.mesh.updateVerticesData(VertexBuffer.PositionKind, this._positions32, false, false);
  687. return this;
  688. }
  689. /** Removes the particles from the start-th to the end-th included from an expandable SPS (required).
  690. * Returns an array with the removed particles.
  691. * If the number of particles to remove is lower than zero or greater than the global remaining particle number, then an empty array is returned.
  692. * The SPS can't be empty so at least one particle needs to remain in place.
  693. * Under the hood, the VertexData array, so the VBO buffer, is recreated each call.
  694. * @param start index of the first particle to remove
  695. * @param end index of the last particle to remove (included)
  696. * @returns an array populated with the removed particles
  697. */
  698. public removeParticles(start: number, end: number): SolidParticle[] {
  699. var nb = end - start + 1;
  700. if (!this._expandable || nb <= 0 || nb >= this.nbParticles || !this._updatable) {
  701. return [];
  702. }
  703. const particles = this.particles;
  704. const currentNb = this.nbParticles;
  705. if (end < currentNb - 1) { // update the particle indexes in the positions array in case they're remaining particles after the last removed
  706. var firstRemaining = end + 1;
  707. var shiftPos = particles[firstRemaining]._pos - particles[start]._pos;
  708. var shifInd = particles[firstRemaining]._ind - particles[start]._ind;
  709. for (var i = firstRemaining; i < currentNb; i++) {
  710. var part = particles[i];
  711. part._pos -= shiftPos;
  712. part._ind -= shifInd;
  713. }
  714. }
  715. var removed = particles.splice(start, nb);
  716. this._positions.length = 0;
  717. this._indices.length = 0;
  718. this._colors.length = 0;
  719. this._uvs.length = 0;
  720. this._normals.length = 0;
  721. this._index = 0;
  722. this._idxOfId.length = 0;
  723. if (this._depthSort || this._multimaterialEnabled) {
  724. this.depthSortedParticles = [];
  725. }
  726. var ind = 0;
  727. const particlesLength = particles.length;
  728. for (var p = 0; p < particlesLength; p++) {
  729. var particle = particles[p];
  730. var model = particle._model;
  731. var shape = model._shape;
  732. var modelIndices = model._indices;
  733. var modelNormals = model._normals;
  734. var modelColors = model._shapeColors;
  735. var modelUVs = model._shapeUV;
  736. particle.idx = p;
  737. this._idxOfId[particle.id] = p;
  738. this._meshBuilder(this._index, ind, shape, this._positions, modelIndices, this._indices, modelUVs, this._uvs, modelColors, this._colors, modelNormals, this._normals, particle.idx, particle.idxInShape, null, model);
  739. this._index += shape.length;
  740. ind += modelIndices.length;
  741. }
  742. this.nbParticles -= nb;
  743. this._isNotBuilt = true; // buildMesh() call is now expected for setParticles() to work
  744. return removed;
  745. }
  746. /**
  747. * Inserts some pre-created particles in the solid particle system so that they can be managed by setParticles().
  748. * @param solidParticleArray an array populated with Solid Particles objects
  749. * @returns the SPS
  750. */
  751. public insertParticlesFromArray(solidParticleArray: SolidParticle[]): SolidParticleSystem {
  752. if (!this._expandable) {
  753. return this;
  754. }
  755. var idxInShape = 0;
  756. var currentShapeId = solidParticleArray[0].shapeId;
  757. const nb = solidParticleArray.length;
  758. for (var i = 0; i < nb; i++) {
  759. var sp = solidParticleArray[i];
  760. var model = sp._model;
  761. var shape = model._shape;
  762. var meshInd = model._indices;
  763. var meshUV = model._shapeUV;
  764. var meshCol = model._shapeColors;
  765. var meshNor = model._normals;
  766. var noNor = (meshNor) ? false : true;
  767. this.recomputeNormals = (noNor || this.recomputeNormals);
  768. var bbInfo = sp._boundingInfo;
  769. var newPart = this._insertNewParticle(this.nbParticles, idxInShape, model, shape, meshInd, meshUV, meshCol, meshNor, bbInfo, null, null);
  770. sp.copyToRef(newPart!);
  771. idxInShape++;
  772. if (currentShapeId != sp.shapeId) {
  773. currentShapeId = sp.shapeId;
  774. idxInShape = 0;
  775. }
  776. }
  777. this._isNotBuilt = true; // buildMesh() call is now expected for setParticles() to work
  778. return this;
  779. }
  780. /**
  781. * Creates a new particle and modifies the SPS mesh geometry :
  782. * - calls _meshBuilder() to increase the SPS mesh geometry step by step
  783. * - calls _addParticle() to populate the particle array
  784. * factorized code from addShape() and insertParticlesFromArray()
  785. * @param idx particle index in the particles array
  786. * @param i particle index in its shape
  787. * @param modelShape particle ModelShape object
  788. * @param shape shape vertex array
  789. * @param meshInd shape indices array
  790. * @param meshUV shape uv array
  791. * @param meshCol shape color array
  792. * @param meshNor shape normals array
  793. * @param bbInfo shape bounding info
  794. * @param storage target particle storage
  795. * @options addShape() passed options
  796. * @hidden
  797. */
  798. private _insertNewParticle(idx: number, i: number, modelShape: ModelShape, shape: Vector3[], meshInd: IndicesArray, meshUV: number[] | Float32Array, meshCol: number[] | Float32Array, meshNor: number[] | Float32Array, bbInfo: Nullable<BoundingInfo>, storage: Nullable<[]> , options: any): Nullable<SolidParticle> {
  799. var currentPos = this._positions.length;
  800. var currentInd = this._indices.length;
  801. var currentCopy = this._meshBuilder(this._index, currentInd, shape, this._positions, meshInd, this._indices, meshUV, this._uvs, meshCol, this._colors, meshNor, this._normals, idx, i, options, modelShape);
  802. var sp: Nullable<SolidParticle> = null;
  803. if (this._updatable) {
  804. sp = this._addParticle(this.nbParticles, this._lastParticleId, currentPos, currentInd, modelShape, this._shapeCounter, i, bbInfo, storage);
  805. sp.position.copyFrom(currentCopy.position);
  806. sp.rotation.copyFrom(currentCopy.rotation);
  807. if (currentCopy.rotationQuaternion) {
  808. if (sp.rotationQuaternion) {
  809. sp.rotationQuaternion.copyFrom(currentCopy.rotationQuaternion);
  810. }
  811. else {
  812. sp.rotationQuaternion = currentCopy.rotationQuaternion.clone();
  813. }
  814. }
  815. if (currentCopy.color) {
  816. if (sp.color) {
  817. sp.color.copyFrom(currentCopy.color);
  818. }
  819. else {
  820. sp.color = currentCopy.color.clone();
  821. }
  822. }
  823. sp.scaling.copyFrom(currentCopy.scaling);
  824. sp.uvs.copyFrom(currentCopy.uvs);
  825. if (currentCopy.materialIndex !== null) {
  826. sp.materialIndex = currentCopy.materialIndex;
  827. }
  828. if (this.expandable) {
  829. this._idxOfId[sp.id] = sp.idx;
  830. }
  831. }
  832. if (!storage) {
  833. this._index += shape.length;
  834. this.nbParticles++;
  835. this._lastParticleId++;
  836. }
  837. return sp;
  838. }
  839. /**
  840. * Sets all the particles : this method actually really updates the mesh according to the particle positions, rotations, colors, textures, etc.
  841. * This method calls `updateParticle()` for each particle of the SPS.
  842. * For an animated SPS, it is usually called within the render loop.
  843. * This methods does nothing if called on a non updatable or not yet built SPS. Example : buildMesh() not called after having added or removed particles from an expandable SPS.
  844. * @param start The particle index in the particle array where to start to compute the particle property values _(default 0)_
  845. * @param end The particle index in the particle array where to stop to compute the particle property values _(default nbParticle - 1)_
  846. * @param update If the mesh must be finally updated on this call after all the particle computations _(default true)_
  847. * @returns the SPS.
  848. */
  849. public setParticles(start: number = 0, end: number = this.nbParticles - 1, update: boolean = true): SolidParticleSystem {
  850. if (!this._updatable || this._isNotBuilt) {
  851. return this;
  852. }
  853. // custom beforeUpdate
  854. this.beforeUpdateParticles(start, end, update);
  855. const rotMatrix = TmpVectors.Matrix[0];
  856. const invertedMatrix = TmpVectors.Matrix[1];
  857. const mesh = this.mesh;
  858. const colors32 = this._colors32;
  859. const positions32 = this._positions32;
  860. const normals32 = this._normals32;
  861. const uvs32 = this._uvs32;
  862. const indices32 = this._indices32;
  863. const indices = this._indices;
  864. const fixedNormal32 = this._fixedNormal32;
  865. const tempVectors = TmpVectors.Vector3;
  866. const camAxisX = tempVectors[5].copyFromFloats(1.0, 0.0, 0.0);
  867. const camAxisY = tempVectors[6].copyFromFloats(0.0, 1.0, 0.0);
  868. const camAxisZ = tempVectors[7].copyFromFloats(0.0, 0.0, 1.0);
  869. const minimum = tempVectors[8].setAll(Number.MAX_VALUE);
  870. const maximum = tempVectors[9].setAll(-Number.MAX_VALUE);
  871. const camInvertedPosition = tempVectors[10].setAll(0);
  872. // cases when the World Matrix is to be computed first
  873. if (this.billboard || this._depthSort) {
  874. this.mesh.computeWorldMatrix(true);
  875. this.mesh._worldMatrix.invertToRef(invertedMatrix);
  876. }
  877. // if the particles will always face the camera
  878. if (this.billboard) {
  879. // compute the camera position and un-rotate it by the current mesh rotation
  880. const tmpVertex = tempVectors[0];
  881. this._camera.getDirectionToRef(Axis.Z, tmpVertex);
  882. Vector3.TransformNormalToRef(tmpVertex, invertedMatrix, camAxisZ);
  883. camAxisZ.normalize();
  884. // same for camera up vector extracted from the cam view matrix
  885. var view = this._camera.getViewMatrix(true);
  886. Vector3.TransformNormalFromFloatsToRef(view.m[1], view.m[5], view.m[9], invertedMatrix, camAxisY);
  887. Vector3.CrossToRef(camAxisY, camAxisZ, camAxisX);
  888. camAxisY.normalize();
  889. camAxisX.normalize();
  890. }
  891. // if depthSort, compute the camera global position in the mesh local system
  892. if (this._depthSort) {
  893. Vector3.TransformCoordinatesToRef(this._camera.globalPosition, invertedMatrix, camInvertedPosition); // then un-rotate the camera
  894. }
  895. Matrix.IdentityToRef(rotMatrix);
  896. var idx = 0; // current position index in the global array positions32
  897. var index = 0; // position start index in the global array positions32 of the current particle
  898. var colidx = 0; // current color index in the global array colors32
  899. var colorIndex = 0; // color start index in the global array colors32 of the current particle
  900. var uvidx = 0; // current uv index in the global array uvs32
  901. var uvIndex = 0; // uv start index in the global array uvs32 of the current particle
  902. var pt = 0; // current index in the particle model shape
  903. if (this.mesh.isFacetDataEnabled) {
  904. this._computeBoundingBox = true;
  905. }
  906. end = (end >= this.nbParticles) ? this.nbParticles - 1 : end;
  907. if (this._computeBoundingBox) {
  908. if (start != 0 || end != this.nbParticles - 1) { // only some particles are updated, then use the current existing BBox basis. Note : it can only increase.
  909. const boundingInfo = this.mesh._boundingInfo;
  910. if (boundingInfo) {
  911. minimum.copyFrom(boundingInfo.minimum);
  912. maximum.copyFrom(boundingInfo.maximum);
  913. }
  914. }
  915. }
  916. // particle loop
  917. index = this.particles[start]._pos;
  918. const vpos = (index / 3) | 0;
  919. colorIndex = vpos * 4;
  920. uvIndex = vpos * 2;
  921. for (var p = start; p <= end; p++) {
  922. const particle = this.particles[p];
  923. // call to custom user function to update the particle properties
  924. this.updateParticle(particle);
  925. const shape = particle._model._shape;
  926. const shapeUV = particle._model._shapeUV;
  927. const particleRotationMatrix = particle._rotationMatrix;
  928. const particlePosition = particle.position;
  929. const particleRotation = particle.rotation;
  930. const particleScaling = particle.scaling;
  931. const particleGlobalPosition = particle._globalPosition;
  932. // camera-particle distance for depth sorting
  933. if (this._depthSort && this._depthSortParticles) {
  934. var dsp = this.depthSortedParticles[p];
  935. dsp.idx = particle.idx;
  936. dsp.ind = particle._ind;
  937. dsp.indicesLength = particle._model._indicesLength;
  938. dsp.sqDistance = Vector3.DistanceSquared(particle.position, camInvertedPosition);
  939. }
  940. // skip the computations for inactive or already invisible particles
  941. if (!particle.alive || (particle._stillInvisible && !particle.isVisible)) {
  942. // increment indexes for the next particle
  943. pt = shape.length;
  944. index += pt * 3;
  945. colorIndex += pt * 4;
  946. uvIndex += pt * 2;
  947. continue;
  948. }
  949. if (particle.isVisible) {
  950. particle._stillInvisible = false; // un-mark permanent invisibility
  951. const scaledPivot = tempVectors[12];
  952. particle.pivot.multiplyToRef(particleScaling, scaledPivot);
  953. // particle rotation matrix
  954. if (this.billboard) {
  955. particleRotation.x = 0.0;
  956. particleRotation.y = 0.0;
  957. }
  958. if (this._computeParticleRotation || this.billboard) {
  959. particle.getRotationMatrix(rotMatrix);
  960. }
  961. const particleHasParent = (particle.parentId !== null);
  962. if (particleHasParent) {
  963. const parent = this.getParticleById(particle.parentId!);
  964. if (parent) {
  965. const parentRotationMatrix = parent._rotationMatrix;
  966. const parentGlobalPosition = parent._globalPosition;
  967. const rotatedY = particlePosition.x * parentRotationMatrix[1] + particlePosition.y * parentRotationMatrix[4] + particlePosition.z * parentRotationMatrix[7];
  968. const rotatedX = particlePosition.x * parentRotationMatrix[0] + particlePosition.y * parentRotationMatrix[3] + particlePosition.z * parentRotationMatrix[6];
  969. const rotatedZ = particlePosition.x * parentRotationMatrix[2] + particlePosition.y * parentRotationMatrix[5] + particlePosition.z * parentRotationMatrix[8];
  970. particleGlobalPosition.x = parentGlobalPosition.x + rotatedX;
  971. particleGlobalPosition.y = parentGlobalPosition.y + rotatedY;
  972. particleGlobalPosition.z = parentGlobalPosition.z + rotatedZ;
  973. if (this._computeParticleRotation || this.billboard) {
  974. const rotMatrixValues = rotMatrix.m;
  975. particleRotationMatrix[0] = rotMatrixValues[0] * parentRotationMatrix[0] + rotMatrixValues[1] * parentRotationMatrix[3] + rotMatrixValues[2] * parentRotationMatrix[6];
  976. particleRotationMatrix[1] = rotMatrixValues[0] * parentRotationMatrix[1] + rotMatrixValues[1] * parentRotationMatrix[4] + rotMatrixValues[2] * parentRotationMatrix[7];
  977. particleRotationMatrix[2] = rotMatrixValues[0] * parentRotationMatrix[2] + rotMatrixValues[1] * parentRotationMatrix[5] + rotMatrixValues[2] * parentRotationMatrix[8];
  978. particleRotationMatrix[3] = rotMatrixValues[4] * parentRotationMatrix[0] + rotMatrixValues[5] * parentRotationMatrix[3] + rotMatrixValues[6] * parentRotationMatrix[6];
  979. particleRotationMatrix[4] = rotMatrixValues[4] * parentRotationMatrix[1] + rotMatrixValues[5] * parentRotationMatrix[4] + rotMatrixValues[6] * parentRotationMatrix[7];
  980. particleRotationMatrix[5] = rotMatrixValues[4] * parentRotationMatrix[2] + rotMatrixValues[5] * parentRotationMatrix[5] + rotMatrixValues[6] * parentRotationMatrix[8];
  981. particleRotationMatrix[6] = rotMatrixValues[8] * parentRotationMatrix[0] + rotMatrixValues[9] * parentRotationMatrix[3] + rotMatrixValues[10] * parentRotationMatrix[6];
  982. particleRotationMatrix[7] = rotMatrixValues[8] * parentRotationMatrix[1] + rotMatrixValues[9] * parentRotationMatrix[4] + rotMatrixValues[10] * parentRotationMatrix[7];
  983. particleRotationMatrix[8] = rotMatrixValues[8] * parentRotationMatrix[2] + rotMatrixValues[9] * parentRotationMatrix[5] + rotMatrixValues[10] * parentRotationMatrix[8];
  984. }
  985. }
  986. else { // in case the parent were removed at some moment
  987. particle.parentId = null;
  988. }
  989. }
  990. else {
  991. particleGlobalPosition.x = particlePosition.x;
  992. particleGlobalPosition.y = particlePosition.y;
  993. particleGlobalPosition.z = particlePosition.z;
  994. if (this._computeParticleRotation || this.billboard) {
  995. const rotMatrixValues = rotMatrix.m;
  996. particleRotationMatrix[0] = rotMatrixValues[0];
  997. particleRotationMatrix[1] = rotMatrixValues[1];
  998. particleRotationMatrix[2] = rotMatrixValues[2];
  999. particleRotationMatrix[3] = rotMatrixValues[4];
  1000. particleRotationMatrix[4] = rotMatrixValues[5];
  1001. particleRotationMatrix[5] = rotMatrixValues[6];
  1002. particleRotationMatrix[6] = rotMatrixValues[8];
  1003. particleRotationMatrix[7] = rotMatrixValues[9];
  1004. particleRotationMatrix[8] = rotMatrixValues[10];
  1005. }
  1006. }
  1007. const pivotBackTranslation = tempVectors[11];
  1008. if (particle.translateFromPivot) {
  1009. pivotBackTranslation.setAll(0.0);
  1010. }
  1011. else {
  1012. pivotBackTranslation.copyFrom(scaledPivot);
  1013. }
  1014. // particle vertex loop
  1015. for (pt = 0; pt < shape.length; pt++) {
  1016. idx = index + pt * 3;
  1017. colidx = colorIndex + pt * 4;
  1018. uvidx = uvIndex + pt * 2;
  1019. const tmpVertex = tempVectors[0];
  1020. tmpVertex.copyFrom(shape[pt]);
  1021. if (this._computeParticleVertex) {
  1022. this.updateParticleVertex(particle, tmpVertex, pt);
  1023. }
  1024. // positions
  1025. const vertexX = tmpVertex.x * particleScaling.x - scaledPivot.x;
  1026. const vertexY = tmpVertex.y * particleScaling.y - scaledPivot.y;
  1027. const vertexZ = tmpVertex.z * particleScaling.z - scaledPivot.z;
  1028. let rotatedX = vertexX * particleRotationMatrix[0] + vertexY * particleRotationMatrix[3] + vertexZ * particleRotationMatrix[6];
  1029. let rotatedY = vertexX * particleRotationMatrix[1] + vertexY * particleRotationMatrix[4] + vertexZ * particleRotationMatrix[7];
  1030. let rotatedZ = vertexX * particleRotationMatrix[2] + vertexY * particleRotationMatrix[5] + vertexZ * particleRotationMatrix[8];
  1031. rotatedX += pivotBackTranslation.x;
  1032. rotatedY += pivotBackTranslation.y;
  1033. rotatedZ += pivotBackTranslation.z;
  1034. const px = positions32[idx] = particleGlobalPosition.x + camAxisX.x * rotatedX + camAxisY.x * rotatedY + camAxisZ.x * rotatedZ;
  1035. const py = positions32[idx + 1] = particleGlobalPosition.y + camAxisX.y * rotatedX + camAxisY.y * rotatedY + camAxisZ.y * rotatedZ;
  1036. const pz = positions32[idx + 2] = particleGlobalPosition.z + camAxisX.z * rotatedX + camAxisY.z * rotatedY + camAxisZ.z * rotatedZ;
  1037. if (this._computeBoundingBox) {
  1038. minimum.minimizeInPlaceFromFloats(px, py, pz);
  1039. maximum.maximizeInPlaceFromFloats(px, py, pz);
  1040. }
  1041. // normals : if the particles can't be morphed then just rotate the normals, what is much more faster than ComputeNormals()
  1042. if (!this._computeParticleVertex) {
  1043. const normalx = fixedNormal32[idx];
  1044. const normaly = fixedNormal32[idx + 1];
  1045. const normalz = fixedNormal32[idx + 2];
  1046. const rotatedx = normalx * particleRotationMatrix[0] + normaly * particleRotationMatrix[3] + normalz * particleRotationMatrix[6];
  1047. const rotatedy = normalx * particleRotationMatrix[1] + normaly * particleRotationMatrix[4] + normalz * particleRotationMatrix[7];
  1048. const rotatedz = normalx * particleRotationMatrix[2] + normaly * particleRotationMatrix[5] + normalz * particleRotationMatrix[8];
  1049. normals32[idx] = camAxisX.x * rotatedx + camAxisY.x * rotatedy + camAxisZ.x * rotatedz;
  1050. normals32[idx + 1] = camAxisX.y * rotatedx + camAxisY.y * rotatedy + camAxisZ.y * rotatedz;
  1051. normals32[idx + 2] = camAxisX.z * rotatedx + camAxisY.z * rotatedy + camAxisZ.z * rotatedz;
  1052. }
  1053. if (this._computeParticleColor && particle.color) {
  1054. const color = particle.color;
  1055. const colors32 = this._colors32;
  1056. colors32[colidx] = color.r;
  1057. colors32[colidx + 1] = color.g;
  1058. colors32[colidx + 2] = color.b;
  1059. colors32[colidx + 3] = color.a;
  1060. }
  1061. if (this._computeParticleTexture) {
  1062. const uvs = particle.uvs;
  1063. uvs32[uvidx] = shapeUV[pt * 2] * (uvs.z - uvs.x) + uvs.x;
  1064. uvs32[uvidx + 1] = shapeUV[pt * 2 + 1] * (uvs.w - uvs.y) + uvs.y;
  1065. }
  1066. }
  1067. }
  1068. // particle just set invisible : scaled to zero and positioned at the origin
  1069. else {
  1070. particle._stillInvisible = true; // mark the particle as invisible
  1071. for (pt = 0; pt < shape.length; pt++) {
  1072. idx = index + pt * 3;
  1073. colidx = colorIndex + pt * 4;
  1074. uvidx = uvIndex + pt * 2;
  1075. positions32[idx] = positions32[idx + 1] = positions32[idx + 2] = 0;
  1076. normals32[idx] = normals32[idx + 1] = normals32[idx + 2] = 0;
  1077. if (this._computeParticleColor && particle.color) {
  1078. const color = particle.color;
  1079. colors32[colidx] = color.r;
  1080. colors32[colidx + 1] = color.g;
  1081. colors32[colidx + 2] = color.b;
  1082. colors32[colidx + 3] = color.a;
  1083. }
  1084. if (this._computeParticleTexture) {
  1085. const uvs = particle.uvs;
  1086. uvs32[uvidx] = shapeUV[pt * 2] * (uvs.z - uvs.x) + uvs.x;
  1087. uvs32[uvidx + 1] = shapeUV[pt * 2 + 1] * (uvs.w - uvs.y) + uvs.y;
  1088. }
  1089. }
  1090. }
  1091. // if the particle intersections must be computed : update the bbInfo
  1092. if (this._particlesIntersect) {
  1093. const bInfo = particle._boundingInfo;
  1094. const bBox = bInfo.boundingBox;
  1095. const bSphere = bInfo.boundingSphere;
  1096. const modelBoundingInfo = particle._modelBoundingInfo;
  1097. if (!this._bSphereOnly) {
  1098. // place, scale and rotate the particle bbox within the SPS local system, then update it
  1099. const modelBoundingInfoVectors = modelBoundingInfo.boundingBox.vectors;
  1100. const tempMin = tempVectors[1];
  1101. const tempMax = tempVectors[2];
  1102. tempMin.setAll(Number.MAX_VALUE);
  1103. tempMax.setAll(-Number.MAX_VALUE);
  1104. for (var b = 0; b < 8; b++) {
  1105. const scaledX = modelBoundingInfoVectors[b].x * particleScaling.x;
  1106. const scaledY = modelBoundingInfoVectors[b].y * particleScaling.y;
  1107. const scaledZ = modelBoundingInfoVectors[b].z * particleScaling.z;
  1108. const rotatedX = scaledX * particleRotationMatrix[0] + scaledY * particleRotationMatrix[3] + scaledZ * particleRotationMatrix[6];
  1109. const rotatedY = scaledX * particleRotationMatrix[1] + scaledY * particleRotationMatrix[4] + scaledZ * particleRotationMatrix[7];
  1110. const rotatedZ = scaledX * particleRotationMatrix[2] + scaledY * particleRotationMatrix[5] + scaledZ * particleRotationMatrix[8];
  1111. const x = particlePosition.x + camAxisX.x * rotatedX + camAxisY.x * rotatedY + camAxisZ.x * rotatedZ;
  1112. const y = particlePosition.y + camAxisX.y * rotatedX + camAxisY.y * rotatedY + camAxisZ.y * rotatedZ;
  1113. const z = particlePosition.z + camAxisX.z * rotatedX + camAxisY.z * rotatedY + camAxisZ.z * rotatedZ;
  1114. tempMin.minimizeInPlaceFromFloats(x, y, z);
  1115. tempMax.maximizeInPlaceFromFloats(x, y, z);
  1116. }
  1117. bBox.reConstruct(tempMin, tempMax, mesh._worldMatrix);
  1118. }
  1119. // place and scale the particle bouding sphere in the SPS local system, then update it
  1120. const minBbox = modelBoundingInfo.minimum.multiplyToRef(particleScaling, tempVectors[1]);
  1121. const maxBbox = modelBoundingInfo.maximum.multiplyToRef(particleScaling, tempVectors[2]);
  1122. const bSphereCenter = maxBbox.addToRef(minBbox, tempVectors[3]).scaleInPlace(0.5).addInPlace(particleGlobalPosition);
  1123. const halfDiag = maxBbox.subtractToRef(minBbox, tempVectors[4]).scaleInPlace(0.5 * this._bSphereRadiusFactor);
  1124. const bSphereMinBbox = bSphereCenter.subtractToRef(halfDiag, tempVectors[1]);
  1125. const bSphereMaxBbox = bSphereCenter.addToRef(halfDiag, tempVectors[2]);
  1126. bSphere.reConstruct(bSphereMinBbox, bSphereMaxBbox, mesh._worldMatrix);
  1127. }
  1128. // increment indexes for the next particle
  1129. index = idx + 3;
  1130. colorIndex = colidx + 4;
  1131. uvIndex = uvidx + 2;
  1132. }
  1133. // if the VBO must be updated
  1134. if (update) {
  1135. if (this._computeParticleColor) {
  1136. mesh.updateVerticesData(VertexBuffer.ColorKind, colors32, false, false);
  1137. }
  1138. if (this._computeParticleTexture) {
  1139. mesh.updateVerticesData(VertexBuffer.UVKind, uvs32, false, false);
  1140. }
  1141. mesh.updateVerticesData(VertexBuffer.PositionKind, positions32, false, false);
  1142. if (!mesh.areNormalsFrozen || mesh.isFacetDataEnabled) {
  1143. if (this._computeParticleVertex || mesh.isFacetDataEnabled) {
  1144. // recompute the normals only if the particles can be morphed, update then also the normal reference array _fixedNormal32[]
  1145. var params = mesh.isFacetDataEnabled ? mesh.getFacetDataParameters() : null;
  1146. VertexData.ComputeNormals(positions32, indices32, normals32, params);
  1147. for (var i = 0; i < normals32.length; i++) {
  1148. fixedNormal32[i] = normals32[i];
  1149. }
  1150. }
  1151. if (!mesh.areNormalsFrozen) {
  1152. mesh.updateVerticesData(VertexBuffer.NormalKind, normals32, false, false);
  1153. }
  1154. }
  1155. if (this._depthSort && this._depthSortParticles) {
  1156. const depthSortedParticles = this.depthSortedParticles;
  1157. depthSortedParticles.sort(this._depthSortFunction);
  1158. const dspl = depthSortedParticles.length;
  1159. let sid = 0;
  1160. for (let sorted = 0; sorted < dspl; sorted++) {
  1161. const lind = depthSortedParticles[sorted].indicesLength;
  1162. const sind = depthSortedParticles[sorted].ind;
  1163. for (var i = 0; i < lind; i++) {
  1164. indices32[sid] = indices[sind + i];
  1165. sid++;
  1166. }
  1167. }
  1168. mesh.updateIndices(indices32);
  1169. }
  1170. }
  1171. if (this._computeBoundingBox) {
  1172. if (mesh._boundingInfo) {
  1173. mesh._boundingInfo.reConstruct(minimum, maximum, mesh._worldMatrix);
  1174. }
  1175. else {
  1176. mesh._boundingInfo = new BoundingInfo(minimum, maximum, mesh._worldMatrix);
  1177. }
  1178. }
  1179. if (this._autoUpdateSubMeshes) {
  1180. this.computeSubMeshes();
  1181. }
  1182. this.afterUpdateParticles(start, end, update);
  1183. return this;
  1184. }
  1185. /**
  1186. * Disposes the SPS.
  1187. */
  1188. public dispose(): void {
  1189. this.mesh.dispose();
  1190. this.vars = null;
  1191. // drop references to internal big arrays for the GC
  1192. (<any>this._positions) = null;
  1193. (<any>this._indices) = null;
  1194. (<any>this._normals) = null;
  1195. (<any>this._uvs) = null;
  1196. (<any>this._colors) = null;
  1197. (<any>this._indices32) = null;
  1198. (<any>this._positions32) = null;
  1199. (<any>this._normals32) = null;
  1200. (<any>this._fixedNormal32) = null;
  1201. (<any>this._uvs32) = null;
  1202. (<any>this._colors32) = null;
  1203. (<any>this.pickedParticles) = null;
  1204. }
  1205. /** Returns an object {idx: numbern faceId: number} for the picked particle from the passed pickingInfo object.
  1206. * idx is the particle index in the SPS
  1207. * faceId is the picked face index counted within this particle.
  1208. * Returns null if the pickInfo can't identify a picked particle.
  1209. * @param pickingInfo (PickingInfo object)
  1210. * @returns {idx: number, faceId: number} or null
  1211. */
  1212. public pickedParticle(pickingInfo: PickingInfo): Nullable<{idx: number, faceId: number}> {
  1213. if (pickingInfo.hit) {
  1214. const subMesh = pickingInfo.subMeshId;
  1215. const faceId = pickingInfo.faceId;
  1216. const picked = this.pickedBySubMesh;
  1217. if (picked[subMesh] && picked[subMesh][faceId]) {
  1218. return picked[subMesh][faceId];
  1219. }
  1220. }
  1221. return null;
  1222. }
  1223. /**
  1224. * Returns a SolidParticle object from its identifier : particle.id
  1225. * @param id (integer) the particle Id
  1226. * @returns the searched particle or null if not found in the SPS.
  1227. */
  1228. public getParticleById(id: number): Nullable<SolidParticle> {
  1229. const p = this.particles[id];
  1230. if (p && p.id == id) {
  1231. return p;
  1232. }
  1233. const particles = this.particles;
  1234. const idx = this._idxOfId[id];
  1235. if (idx !== undefined) {
  1236. return particles[idx];
  1237. }
  1238. var i = 0;
  1239. const nb = this.nbParticles;
  1240. while (i < nb) {
  1241. var particle = particles[i];
  1242. if (particle.id == id) {
  1243. return particle;
  1244. }
  1245. i++;
  1246. }
  1247. return null;
  1248. }
  1249. /**
  1250. * Returns a new array populated with the particles having the passed shapeId.
  1251. * @param shapeId (integer) the shape identifier
  1252. * @returns a new solid particle array
  1253. */
  1254. public getParticlesByShapeId(shapeId: number): SolidParticle[] {
  1255. var ref: SolidParticle[] = [];
  1256. this.getParticlesByShapeIdToRef(shapeId, ref);
  1257. return ref;
  1258. }
  1259. /**
  1260. * Populates the passed array "ref" with the particles having the passed shapeId.
  1261. * @param shapeId the shape identifier
  1262. * @returns the SPS
  1263. * @param ref
  1264. */
  1265. public getParticlesByShapeIdToRef(shapeId: number, ref: SolidParticle[]): SolidParticleSystem {
  1266. ref.length = 0;
  1267. for (var i = 0; i < this.nbParticles; i++) {
  1268. var p = this.particles[i];
  1269. if (p.shapeId == shapeId) {
  1270. ref.push(p);
  1271. }
  1272. }
  1273. return this;
  1274. }
  1275. /**
  1276. * Computes the required SubMeshes according the materials assigned to the particles.
  1277. * @returns the solid particle system.
  1278. * Does nothing if called before the SPS mesh is built.
  1279. */
  1280. public computeSubMeshes(): SolidParticleSystem {
  1281. if (!this.mesh || !this._multimaterialEnabled) {
  1282. return this;
  1283. }
  1284. const depthSortedParticles = this.depthSortedParticles;
  1285. if (this.particles.length > 0) {
  1286. for (let p = 0; p < this.particles.length; p++) {
  1287. let part = this.particles[p];
  1288. if (!part.materialIndex) {
  1289. part.materialIndex = 0;
  1290. }
  1291. let sortedPart = depthSortedParticles[p];
  1292. sortedPart.materialIndex = part.materialIndex;
  1293. sortedPart.ind = part._ind;
  1294. sortedPart.indicesLength = part._model._indicesLength;
  1295. sortedPart.idx = part.idx;
  1296. }
  1297. }
  1298. this._sortParticlesByMaterial();
  1299. const indicesByMaterial = this._indicesByMaterial;
  1300. const materialIndexes = this._materialIndexes;
  1301. const mesh = this.mesh;
  1302. mesh.subMeshes = [];
  1303. const vcount = mesh.getTotalVertices();
  1304. for (let m = 0; m < materialIndexes.length; m++) {
  1305. let start = indicesByMaterial[m];
  1306. let count = indicesByMaterial[m + 1] - start;
  1307. let matIndex = materialIndexes[m];
  1308. new SubMesh(matIndex, 0, vcount, start, count, mesh);
  1309. }
  1310. return this;
  1311. }
  1312. /**
  1313. * Sorts the solid particles by material when MultiMaterial is enabled.
  1314. * Updates the indices32 array.
  1315. * Updates the indicesByMaterial array.
  1316. * Updates the mesh indices array.
  1317. * @returns the SPS
  1318. * @hidden
  1319. */
  1320. private _sortParticlesByMaterial(): SolidParticleSystem {
  1321. const indicesByMaterial = [0];
  1322. this._indicesByMaterial = indicesByMaterial;
  1323. const materialIndexes: number[] = [];
  1324. this._materialIndexes = materialIndexes;
  1325. const depthSortedParticles = this.depthSortedParticles;
  1326. depthSortedParticles.sort(this._materialSortFunction);
  1327. const length = depthSortedParticles.length;
  1328. const indices32 = this._indices32;
  1329. const indices = this._indices;
  1330. let subMeshIndex = 0;
  1331. let subMeshFaceId = 0;
  1332. let sid = 0;
  1333. let lastMatIndex = depthSortedParticles[0].materialIndex;
  1334. materialIndexes.push(lastMatIndex);
  1335. if (this._pickable) {
  1336. this.pickedBySubMesh = [[]];
  1337. this.pickedParticles = this.pickedBySubMesh[0];
  1338. }
  1339. for (let sorted = 0; sorted < length; sorted++) {
  1340. let sortedPart = depthSortedParticles[sorted];
  1341. let lind = sortedPart.indicesLength;
  1342. let sind = sortedPart.ind;
  1343. if (sortedPart.materialIndex !== lastMatIndex) {
  1344. lastMatIndex = sortedPart.materialIndex;
  1345. indicesByMaterial.push(sid);
  1346. materialIndexes.push(lastMatIndex);
  1347. if (this._pickable) {
  1348. subMeshIndex++;
  1349. this.pickedBySubMesh[subMeshIndex] = [];
  1350. subMeshFaceId = 0;
  1351. }
  1352. }
  1353. let faceId = 0;
  1354. for (let i = 0; i < lind; i++) {
  1355. indices32[sid] = indices[sind + i];
  1356. if (this._pickable) {
  1357. let f = i % 3;
  1358. if (f == 0) {
  1359. const pickedData = {idx: sortedPart.idx, faceId: faceId};
  1360. this.pickedBySubMesh[subMeshIndex][subMeshFaceId] = pickedData;
  1361. subMeshFaceId++;
  1362. faceId++;
  1363. }
  1364. }
  1365. sid++;
  1366. }
  1367. }
  1368. indicesByMaterial.push(indices32.length); // add the last number to ease the indices start/count values for subMeshes creation
  1369. if (this._updatable) {
  1370. this.mesh.updateIndices(indices32);
  1371. }
  1372. return this;
  1373. }
  1374. /**
  1375. * Sets the material indexes by id materialIndexesById[id] = materialIndex
  1376. * @hidden
  1377. */
  1378. private _setMaterialIndexesById() {
  1379. this._materialIndexesById = {};
  1380. for (var i = 0; i < this._materials.length; i++) {
  1381. var id = this._materials[i].uniqueId;
  1382. this._materialIndexesById[id] = i;
  1383. }
  1384. }
  1385. /**
  1386. * Returns an array with unique values of Materials from the passed array
  1387. * @param array the material array to be checked and filtered
  1388. * @hidden
  1389. */
  1390. private _filterUniqueMaterialId(array: Material[]): Material[] {
  1391. var filtered = array.filter(function(value, index, self) {
  1392. return self.indexOf(value) === index;
  1393. });
  1394. return filtered;
  1395. }
  1396. /**
  1397. * Sets a new Standard Material as _defaultMaterial if not already set.
  1398. * @hidden
  1399. */
  1400. private _setDefaultMaterial(): Material {
  1401. if (!this._defaultMaterial) {
  1402. this._defaultMaterial = new StandardMaterial(this.name + "DefaultMaterial", this._scene);
  1403. }
  1404. return this._defaultMaterial;
  1405. }
  1406. /**
  1407. * Visibilty helper : Recomputes the visible size according to the mesh bounding box
  1408. * doc : http://doc.babylonjs.com/how_to/Solid_Particle_System#sps-visibility
  1409. * @returns the SPS.
  1410. */
  1411. public refreshVisibleSize(): SolidParticleSystem {
  1412. if (!this._isVisibilityBoxLocked) {
  1413. this.mesh.refreshBoundingInfo();
  1414. }
  1415. return this;
  1416. }
  1417. /**
  1418. * Visibility helper : Sets the size of a visibility box, this sets the underlying mesh bounding box.
  1419. * @param size the size (float) of the visibility box
  1420. * note : this doesn't lock the SPS mesh bounding box.
  1421. * doc : http://doc.babylonjs.com/how_to/Solid_Particle_System#sps-visibility
  1422. */
  1423. public setVisibilityBox(size: number): void {
  1424. var vis = size / 2;
  1425. this.mesh._boundingInfo = new BoundingInfo(new Vector3(-vis, -vis, -vis), new Vector3(vis, vis, vis));
  1426. }
  1427. /**
  1428. * Gets whether the SPS as always visible or not
  1429. * doc : http://doc.babylonjs.com/how_to/Solid_Particle_System#sps-visibility
  1430. */
  1431. public get isAlwaysVisible(): boolean {
  1432. return this._alwaysVisible;
  1433. }
  1434. /**
  1435. * Sets the SPS as always visible or not
  1436. * doc : http://doc.babylonjs.com/how_to/Solid_Particle_System#sps-visibility
  1437. */
  1438. public set isAlwaysVisible(val: boolean) {
  1439. this._alwaysVisible = val;
  1440. this.mesh.alwaysSelectAsActiveMesh = val;
  1441. }
  1442. /**
  1443. * Sets the SPS visibility box as locked or not. This enables/disables the underlying mesh bounding box updates.
  1444. * doc : http://doc.babylonjs.com/how_to/Solid_Particle_System#sps-visibility
  1445. */
  1446. public set isVisibilityBoxLocked(val: boolean) {
  1447. this._isVisibilityBoxLocked = val;
  1448. let boundingInfo = this.mesh.getBoundingInfo();
  1449. boundingInfo.isLocked = val;
  1450. }
  1451. /**
  1452. * Gets if the SPS visibility box as locked or not. This enables/disables the underlying mesh bounding box updates.
  1453. * doc : http://doc.babylonjs.com/how_to/Solid_Particle_System#sps-visibility
  1454. */
  1455. public get isVisibilityBoxLocked(): boolean {
  1456. return this._isVisibilityBoxLocked;
  1457. }
  1458. /**
  1459. * Tells to `setParticles()` to compute the particle rotations or not.
  1460. * Default value : true. The SPS is faster when it's set to false.
  1461. * Note : the particle rotations aren't stored values, so setting `computeParticleRotation` to false will prevents the particle to rotate.
  1462. */
  1463. public set computeParticleRotation(val: boolean) {
  1464. this._computeParticleRotation = val;
  1465. }
  1466. /**
  1467. * Tells to `setParticles()` to compute the particle colors or not.
  1468. * Default value : true. The SPS is faster when it's set to false.
  1469. * Note : the particle colors are stored values, so setting `computeParticleColor` to false will keep yet the last colors set.
  1470. */
  1471. public set computeParticleColor(val: boolean) {
  1472. this._computeParticleColor = val;
  1473. }
  1474. public set computeParticleTexture(val: boolean) {
  1475. this._computeParticleTexture = val;
  1476. }
  1477. /**
  1478. * Tells to `setParticles()` to call the vertex function for each vertex of each particle, or not.
  1479. * Default value : false. The SPS is faster when it's set to false.
  1480. * Note : the particle custom vertex positions aren't stored values.
  1481. */
  1482. public set computeParticleVertex(val: boolean) {
  1483. this._computeParticleVertex = val;
  1484. }
  1485. /**
  1486. * Tells to `setParticles()` to compute or not the mesh bounding box when computing the particle positions.
  1487. */
  1488. public set computeBoundingBox(val: boolean) {
  1489. this._computeBoundingBox = val;
  1490. }
  1491. /**
  1492. * Tells to `setParticles()` to sort or not the distance between each particle and the camera.
  1493. * Skipped when `enableDepthSort` is set to `false` (default) at construction time.
  1494. * Default : `true`
  1495. */
  1496. public set depthSortParticles(val: boolean) {
  1497. this._depthSortParticles = val;
  1498. }
  1499. /**
  1500. * Gets if `setParticles()` computes the particle rotations or not.
  1501. * Default value : true. The SPS is faster when it's set to false.
  1502. * Note : the particle rotations aren't stored values, so setting `computeParticleRotation` to false will prevents the particle to rotate.
  1503. */
  1504. public get computeParticleRotation(): boolean {
  1505. return this._computeParticleRotation;
  1506. }
  1507. /**
  1508. * Gets if `setParticles()` computes the particle colors or not.
  1509. * Default value : true. The SPS is faster when it's set to false.
  1510. * Note : the particle colors are stored values, so setting `computeParticleColor` to false will keep yet the last colors set.
  1511. */
  1512. public get computeParticleColor(): boolean {
  1513. return this._computeParticleColor;
  1514. }
  1515. /**
  1516. * Gets if `setParticles()` computes the particle textures or not.
  1517. * Default value : true. The SPS is faster when it's set to false.
  1518. * Note : the particle textures are stored values, so setting `computeParticleTexture` to false will keep yet the last colors set.
  1519. */
  1520. public get computeParticleTexture(): boolean {
  1521. return this._computeParticleTexture;
  1522. }
  1523. /**
  1524. * Gets if `setParticles()` calls the vertex function for each vertex of each particle, or not.
  1525. * Default value : false. The SPS is faster when it's set to false.
  1526. * Note : the particle custom vertex positions aren't stored values.
  1527. */
  1528. public get computeParticleVertex(): boolean {
  1529. return this._computeParticleVertex;
  1530. }
  1531. /**
  1532. * Gets if `setParticles()` computes or not the mesh bounding box when computing the particle positions.
  1533. */
  1534. public get computeBoundingBox(): boolean {
  1535. return this._computeBoundingBox;
  1536. }
  1537. /**
  1538. * Gets if `setParticles()` sorts or not the distance between each particle and the camera.
  1539. * Skipped when `enableDepthSort` is set to `false` (default) at construction time.
  1540. * Default : `true`
  1541. */
  1542. public get depthSortParticles(): boolean {
  1543. return this._depthSortParticles;
  1544. }
  1545. /**
  1546. * Gets if the SPS is created as expandable at construction time.
  1547. * Default : `false`
  1548. */
  1549. public get expandable(): boolean {
  1550. return this._expandable;
  1551. }
  1552. /**
  1553. * Gets if the SPS supports the Multi Materials
  1554. */
  1555. public get multimaterialEnabled(): boolean {
  1556. return this._multimaterialEnabled;
  1557. }
  1558. /**
  1559. * Gets if the SPS uses the model materials for its own multimaterial.
  1560. */
  1561. public get useModelMaterial(): boolean {
  1562. return this._useModelMaterial;
  1563. }
  1564. /**
  1565. * The SPS used material array.
  1566. */
  1567. public get materials(): Material[] {
  1568. return this._materials;
  1569. }
  1570. /**
  1571. * Sets the SPS MultiMaterial from the passed materials.
  1572. * Note : the passed array is internally copied and not used then by reference.
  1573. * @param materials an array of material objects. This array indexes are the materialIndex values of the particles.
  1574. */
  1575. public setMultiMaterial(materials: Material[]) {
  1576. this._materials = this._filterUniqueMaterialId(materials);
  1577. this._setMaterialIndexesById();
  1578. if (this._multimaterial) {
  1579. this._multimaterial.dispose();
  1580. }
  1581. this._multimaterial = new MultiMaterial(this.name + "MultiMaterial", this._scene);
  1582. for (var m = 0; m < this._materials.length; m++) {
  1583. this._multimaterial.subMaterials.push(this._materials[m]);
  1584. }
  1585. this.computeSubMeshes();
  1586. this.mesh.material = this._multimaterial;
  1587. }
  1588. /**
  1589. * The SPS computed multimaterial object
  1590. */
  1591. public get multimaterial(): MultiMaterial {
  1592. return this._multimaterial;
  1593. }
  1594. public set multimaterial(mm) {
  1595. this._multimaterial = mm;
  1596. }
  1597. /**
  1598. * If the subMeshes must be updated on the next call to setParticles()
  1599. */
  1600. public get autoUpdateSubMeshes(): boolean {
  1601. return this._autoUpdateSubMeshes;
  1602. }
  1603. public set autoUpdateSubMeshes(val: boolean) {
  1604. this._autoUpdateSubMeshes = val;
  1605. }
  1606. // =======================================================================
  1607. // Particle behavior logic
  1608. // these following methods may be overwritten by the user to fit his needs
  1609. /**
  1610. * This function does nothing. It may be overwritten to set all the particle first values.
  1611. * The SPS doesn't call this function, you may have to call it by your own.
  1612. * doc : http://doc.babylonjs.com/how_to/Solid_Particle_System#particle-management
  1613. */
  1614. public initParticles(): void {
  1615. }
  1616. /**
  1617. * This function does nothing. It may be overwritten to recycle a particle.
  1618. * The SPS doesn't call this function, you may have to call it by your own.
  1619. * doc : http://doc.babylonjs.com/how_to/Solid_Particle_System#particle-management
  1620. * @param particle The particle to recycle
  1621. * @returns the recycled particle
  1622. */
  1623. public recycleParticle(particle: SolidParticle): SolidParticle {
  1624. return particle;
  1625. }
  1626. /**
  1627. * Updates a particle : this function should be overwritten by the user.
  1628. * It is called on each particle by `setParticles()`. This is the place to code each particle behavior.
  1629. * doc : http://doc.babylonjs.com/how_to/Solid_Particle_System#particle-management
  1630. * @example : just set a particle position or velocity and recycle conditions
  1631. * @param particle The particle to update
  1632. * @returns the updated particle
  1633. */
  1634. public updateParticle(particle: SolidParticle): SolidParticle {
  1635. return particle;
  1636. }
  1637. /**
  1638. * Updates a vertex of a particle : it can be overwritten by the user.
  1639. * This will be called on each vertex particle by `setParticles()` if `computeParticleVertex` is set to true only.
  1640. * @param particle the current particle
  1641. * @param vertex the current index of the current particle
  1642. * @param pt the index of the current vertex in the particle shape
  1643. * doc : http://doc.babylonjs.com/how_to/Solid_Particle_System#update-each-particle-shape
  1644. * @example : just set a vertex particle position
  1645. * @returns the updated vertex
  1646. */
  1647. public updateParticleVertex(particle: SolidParticle, vertex: Vector3, pt: number): Vector3 {
  1648. return vertex;
  1649. }
  1650. /**
  1651. * This will be called before any other treatment by `setParticles()` and will be passed three parameters.
  1652. * This does nothing and may be overwritten by the user.
  1653. * @param start the particle index in the particle array where to stop to iterate, same than the value passed to setParticle()
  1654. * @param stop the particle index in the particle array where to stop to iterate, same than the value passed to setParticle()
  1655. * @param update the boolean update value actually passed to setParticles()
  1656. */
  1657. public beforeUpdateParticles(start?: number, stop?: number, update?: boolean): void {
  1658. }
  1659. /**
  1660. * This will be called by `setParticles()` after all the other treatments and just before the actual mesh update.
  1661. * This will be passed three parameters.
  1662. * This does nothing and may be overwritten by the user.
  1663. * @param start the particle index in the particle array where to stop to iterate, same than the value passed to setParticle()
  1664. * @param stop the particle index in the particle array where to stop to iterate, same than the value passed to setParticle()
  1665. * @param update the boolean update value actually passed to setParticles()
  1666. */
  1667. public afterUpdateParticles(start?: number, stop?: number, update?: boolean): void {
  1668. }
  1669. }