solidParticleSystem.js 85 KB

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