forked from BabylonJS/Babylon.js
-
Notifications
You must be signed in to change notification settings - Fork 0
/
solidParticleSystem.ts
2058 lines (1917 loc) · 94.5 KB
/
solidParticleSystem.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import type { Nullable, IndicesArray, FloatArray } from "../types";
import { Vector3, Matrix, TmpVectors, Quaternion } from "../Maths/math.vector";
import { Color4 } from "../Maths/math.color";
import { VertexBuffer } from "../Buffers/buffer";
import { VertexData } from "../Meshes/mesh.vertexData";
import { Mesh } from "../Meshes/mesh";
import { CreateDisc } from "../Meshes/Builders/discBuilder";
import { EngineStore } from "../Engines/engineStore";
import type { Scene, IDisposable } from "../scene";
import { DepthSortedParticle, SolidParticle, ModelShape, SolidParticleVertex } from "./solidParticle";
import type { TargetCamera } from "../Cameras/targetCamera";
import { BoundingInfo } from "../Culling/boundingInfo";
import { Axis } from "../Maths/math.axis";
import { SubMesh } from "../Meshes/subMesh";
import type { Material } from "../Materials/material";
import { StandardMaterial } from "../Materials/standardMaterial";
import { MultiMaterial } from "../Materials/multiMaterial";
import type { PickingInfo } from "../Collisions/pickingInfo";
import type { PBRMaterial } from "../Materials/PBR/pbrMaterial";
/**
* The SPS is a single updatable mesh. The solid particles are simply separate parts or faces of this big mesh.
*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.
* The SPS is also a particle system. It provides some methods to manage the particles.
* However it is behavior agnostic. This means it has no emitter, no particle physics, no particle recycler. You have to implement your own behavior.
*
* Full documentation here : https://doc.babylonjs.com/features/featuresDeepDive/particles/solid_particle_system/sps_intro
*/
export class SolidParticleSystem implements IDisposable {
/**
* The SPS array of Solid Particle objects. Just access each particle as with any classic array.
* Example : var p = SPS.particles[i];
*/
public particles: SolidParticle[] = new Array<SolidParticle>();
/**
* The SPS total number of particles. Read only. Use SPS.counter instead if you need to set your own value.
*/
public nbParticles: number = 0;
/**
* If the particles must ever face the camera (default false). Useful for planar particles.
*/
public billboard: boolean = false;
/**
* Recompute normals when adding a shape
*/
public recomputeNormals: boolean = false;
/**
* This a counter ofr your own usage. It's not set by any SPS functions.
*/
public counter: number = 0;
/**
* The SPS name. This name is also given to the underlying mesh.
*/
public name: string;
/**
* The SPS mesh. It's a standard BJS Mesh, so all the methods from the Mesh class are available.
*/
public mesh: Mesh;
/**
* This empty object is intended to store some SPS specific or temporary values in order to lower the Garbage Collector activity.
* Please read : https://doc.babylonjs.com/features/featuresDeepDive/particles/solid_particle_system/optimize_sps#limit-garbage-collection
*/
public vars: any = {};
/**
* This array is populated when the SPS is set as 'pickable'.
* Each key of this array is a `faceId` value that you can get from a pickResult object.
* Each element of this array is an object `{idx: int, faceId: int}`.
* `idx` is the picked particle index in the `SPS.particles` array
* `faceId` is the picked face index counted within this particle.
* This array is the first element of the pickedBySubMesh array : sps.pickBySubMesh[0].
* It's not pertinent to use it when using a SPS with the support for MultiMaterial enabled.
* Use the method SPS.pickedParticle(pickingInfo) instead.
* Please read : https://doc.babylonjs.com/features/featuresDeepDive/particles/solid_particle_system/picking_sps
*/
public pickedParticles: { idx: number; faceId: number }[];
/**
* This array is populated when the SPS is set as 'pickable'
* Each key of this array is a submesh index.
* Each element of this array is a second array defined like this :
* Each key of this second array is a `faceId` value that you can get from a pickResult object.
* Each element of this second array is an object `{idx: int, faceId: int}`.
* `idx` is the picked particle index in the `SPS.particles` array
* `faceId` is the picked face index counted within this particle.
* It's better to use the method SPS.pickedParticle(pickingInfo) rather than using directly this array.
* Please read : https://doc.babylonjs.com/features/featuresDeepDive/particles/solid_particle_system/picking_sps
*/
public pickedBySubMesh: { idx: number; faceId: number }[][];
/**
* This array is populated when `enableDepthSort` is set to true.
* Each element of this array is an instance of the class DepthSortedParticle.
*/
public depthSortedParticles: DepthSortedParticle[];
/**
* If the particle intersection must be computed only with the bounding sphere (no bounding box computation, so faster). (Internal use only)
* @internal
*/
public _bSphereOnly: boolean = false;
/**
* A number to multiply the bounding sphere radius by in order to reduce it for instance. (Internal use only)
* @internal
*/
public _bSphereRadiusFactor: number = 1.0;
protected _scene: Scene;
protected _positions: number[] = new Array<number>();
protected _indices: number[] = new Array<number>();
protected _normals: number[] = new Array<number>();
protected _colors: number[] = new Array<number>();
protected _uvs: number[] = new Array<number>();
protected _indices32: IndicesArray; // used as depth sorted array if depth sort enabled, else used as typed indices
protected _positions32: Float32Array; // updated positions for the VBO
protected _normals32: Float32Array; // updated normals for the VBO
protected _fixedNormal32: Float32Array; // initial normal references
protected _colors32: Float32Array;
protected _uvs32: Float32Array;
protected _index: number = 0; // indices index
protected _updatable: boolean = true;
protected _pickable: boolean = false;
protected _isVisibilityBoxLocked = false;
protected _alwaysVisible: boolean = false;
protected _depthSort: boolean = false;
protected _expandable: boolean = false;
protected _shapeCounter: number = 0;
protected _copy: SolidParticle = new SolidParticle(0, 0, 0, 0, null, 0, 0, this);
protected _color: Color4 = new Color4(0, 0, 0, 0);
protected _computeParticleColor: boolean = true;
protected _computeParticleTexture: boolean = true;
protected _computeParticleRotation: boolean = true;
protected _computeParticleVertex: boolean = false;
protected _computeBoundingBox: boolean = false;
protected _autoFixFaceOrientation: boolean = false;
protected _depthSortParticles: boolean = true;
protected _camera: TargetCamera;
protected _mustUnrotateFixedNormals = false;
protected _particlesIntersect: boolean = false;
protected _needs32Bits: boolean = false;
protected _isNotBuilt: boolean = true;
protected _lastParticleId: number = 0;
protected _idxOfId: number[] = []; // array : key = particle.id / value = particle.idx
protected _multimaterialEnabled: boolean = false;
protected _useModelMaterial: boolean = false;
protected _indicesByMaterial: number[];
protected _materialIndexes: number[];
protected _depthSortFunction = (p1: DepthSortedParticle, p2: DepthSortedParticle) => p2.sqDistance - p1.sqDistance;
protected _materialSortFunction = (p1: DepthSortedParticle, p2: DepthSortedParticle) => p1.materialIndex - p2.materialIndex;
protected _materials: Material[];
protected _multimaterial: MultiMaterial;
protected _materialIndexesById: any;
protected _defaultMaterial: Material;
protected _autoUpdateSubMeshes: boolean = false;
protected _tmpVertex: SolidParticleVertex;
protected _recomputeInvisibles: boolean = false;
/**
* Creates a SPS (Solid Particle System) object.
* @param name (String) is the SPS name, this will be the underlying mesh name.
* @param scene (Scene) is the scene in which the SPS is added.
* @param options defines the options of the sps e.g.
* * updatable (optional boolean, default true) : if the SPS must be updatable or immutable.
* * isPickable (optional boolean, default false) : if the solid particles must be pickable.
* * enableDepthSort (optional boolean, default false) : if the solid particles must be sorted in the geometry according to their distance to the camera.
* * 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.
* * enableMultiMaterial (optional boolean, default false) : if the solid particles can be given different materials.
* * expandable (optional boolean, default false) : if particles can still be added after the initial SPS mesh creation.
* * particleIntersection (optional boolean, default false) : if the solid particle intersections must be computed.
* * boundingSphereOnly (optional boolean, default false) : if the particle intersection must be computed only with the bounding sphere (no bounding box computation, so faster).
* * bSphereRadiusFactor (optional float, default 1.0) : a number to multiply the bounding sphere radius by in order to reduce it for instance.
* * 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.
* * autoFixFaceOrientation (optional boolean, default false): if the particle face orientations will be flipped for transformations that change orientation (scale (-1, 1, 1), for example)
* @param options.updatable
* @param options.isPickable
* @param options.enableDepthSort
* @param options.particleIntersection
* @param options.boundingSphereOnly
* @param options.bSphereRadiusFactor
* @param options.expandable
* @param options.useModelMaterial
* @param options.enableMultiMaterial
* @param options.computeBoundingBox
* @param options.autoFixFaceOrientation
* @example bSphereRadiusFactor = 1.0 / Math.sqrt(3.0) => the bounding sphere exactly matches a spherical mesh.
*/
constructor(
name: string,
scene: Scene,
options?: {
updatable?: boolean;
isPickable?: boolean;
enableDepthSort?: boolean;
particleIntersection?: boolean;
boundingSphereOnly?: boolean;
bSphereRadiusFactor?: number;
expandable?: boolean;
useModelMaterial?: boolean;
enableMultiMaterial?: boolean;
computeBoundingBox?: boolean;
autoFixFaceOrientation?: boolean;
}
) {
this.name = name;
this._scene = scene || EngineStore.LastCreatedScene;
this._camera = <TargetCamera>scene.activeCamera;
this._pickable = options ? <boolean>options.isPickable : false;
this._depthSort = options ? <boolean>options.enableDepthSort : false;
this._multimaterialEnabled = options ? <boolean>options.enableMultiMaterial : false;
this._useModelMaterial = options ? <boolean>options.useModelMaterial : false;
this._multimaterialEnabled = this._useModelMaterial ? true : this._multimaterialEnabled;
this._expandable = options ? <boolean>options.expandable : false;
this._particlesIntersect = options ? <boolean>options.particleIntersection : false;
this._bSphereOnly = options ? <boolean>options.boundingSphereOnly : false;
this._bSphereRadiusFactor = options && options.bSphereRadiusFactor ? options.bSphereRadiusFactor : 1.0;
this._computeBoundingBox = options?.computeBoundingBox ? options.computeBoundingBox : false;
this._autoFixFaceOrientation = options?.autoFixFaceOrientation ? options.autoFixFaceOrientation : false;
if (options && options.updatable !== undefined) {
this._updatable = options.updatable;
} else {
this._updatable = true;
}
if (this._pickable) {
this.pickedBySubMesh = [[]];
this.pickedParticles = this.pickedBySubMesh[0];
}
if (this._depthSort || this._multimaterialEnabled) {
this.depthSortedParticles = [];
}
if (this._multimaterialEnabled) {
this._multimaterial = new MultiMaterial(this.name + "MultiMaterial", this._scene);
this._materials = [];
this._materialIndexesById = {};
}
this._tmpVertex = new SolidParticleVertex();
}
/**
* Builds the SPS underlying mesh. Returns a standard Mesh.
* If no model shape was added to the SPS, the returned mesh is just a single triangular plane.
* @returns the created mesh
*/
public buildMesh(): Mesh {
if (!this._isNotBuilt && this.mesh) {
return this.mesh;
}
if (this.nbParticles === 0 && !this.mesh) {
const triangle = CreateDisc("", { radius: 1, tessellation: 3 }, this._scene);
this.addShape(triangle, 1);
triangle.dispose();
}
this._indices32 = this._needs32Bits ? new Uint32Array(this._indices) : new Uint16Array(this._indices);
this._positions32 = new Float32Array(this._positions);
this._uvs32 = new Float32Array(this._uvs);
this._colors32 = new Float32Array(this._colors);
if (!this.mesh) {
// in case it's already expanded
const mesh = new Mesh(this.name, this._scene);
this.mesh = mesh;
}
if (!this._updatable && this._multimaterialEnabled) {
this._sortParticlesByMaterial(); // this may reorder the indices32
}
if (this.recomputeNormals) {
VertexData.ComputeNormals(this._positions32, this._indices32, this._normals);
}
this._normals32 = new Float32Array(this._normals);
this._fixedNormal32 = new Float32Array(this._normals);
if (this._mustUnrotateFixedNormals) {
// the particles could be created already rotated in the mesh with a positionFunction
this._unrotateFixedNormals();
}
const vertexData = new VertexData();
vertexData.indices = this._depthSort ? this._indices : this._indices32;
vertexData.set(this._positions32, VertexBuffer.PositionKind);
vertexData.set(this._normals32, VertexBuffer.NormalKind);
if (this._uvs32.length > 0) {
vertexData.set(this._uvs32, VertexBuffer.UVKind);
}
if (this._colors32.length > 0) {
vertexData.set(this._colors32, VertexBuffer.ColorKind);
}
vertexData.applyToMesh(this.mesh, this._updatable);
this.mesh.isPickable = this._pickable;
if (this._pickable) {
let faceId = 0;
for (let p = 0; p < this.nbParticles; p++) {
const part = this.particles[p];
const lind = part._model._indicesLength;
for (let i = 0; i < lind; i++) {
const f = i % 3;
if (f == 0) {
const pickedData = { idx: part.idx, faceId: faceId };
this.pickedParticles[faceId] = pickedData;
faceId++;
}
}
}
}
if (this._multimaterialEnabled) {
this.setMultiMaterial(this._materials);
}
if (!this._expandable) {
// free memory
if (!this._depthSort && !this._multimaterialEnabled && !this._autoFixFaceOrientation) {
(<any>this._indices) = null;
}
(<any>this._positions) = null;
(<any>this._normals) = null;
(<any>this._uvs) = null;
(<any>this._colors) = null;
if (!this._updatable) {
this.particles.length = 0;
}
}
this._isNotBuilt = false;
this.recomputeNormals = false;
this._recomputeInvisibles = true;
return this.mesh;
}
private _getUVKind(mesh: Mesh, uvKind: number) {
if (uvKind === -1) {
if ((mesh.material as StandardMaterial)?.diffuseTexture) {
uvKind = (mesh.material as StandardMaterial).diffuseTexture!.coordinatesIndex;
} else if ((mesh.material as PBRMaterial)?.albedoTexture) {
uvKind = (mesh.material as PBRMaterial).albedoTexture!.coordinatesIndex;
}
}
return "uv" + (uvKind ? uvKind + 1 : "");
}
/**
* Digests the mesh and generates as many solid particles in the system as wanted. Returns the SPS.
* These particles will have the same geometry than the mesh parts and will be positioned at the same localisation than the mesh original places.
* Thus the particles generated from `digest()` have their property `position` set yet.
* @param mesh ( Mesh ) is the mesh to be digested
* @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
* {delta} (optional integer, default 0) is the random extra number of facets per particle , each particle will have between `facetNb` and `facetNb + delta` facets
* {number} (optional positive integer) is the wanted number of particles : each particle is built with `mesh_total_facets / number` facets
* {storage} (optional existing array) is an array where the particles will be stored for a further use instead of being inserted in the SPS.
* {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
* @param options.facetNb
* @param options.number
* @param options.delta
* @param options.storage
* @param options.uvKind
* @returns the current SPS
*/
public digest(mesh: Mesh, options?: { facetNb?: number; number?: number; delta?: number; storage?: []; uvKind?: number }): SolidParticleSystem {
let size: number = (options && options.facetNb) || 1;
let number: number = (options && options.number) || 0;
let delta: number = (options && options.delta) || 0;
const meshPos = <FloatArray>mesh.getVerticesData(VertexBuffer.PositionKind);
const meshInd = <IndicesArray>mesh.getIndices();
const meshUV = <FloatArray>mesh.getVerticesData(this._getUVKind(mesh, options?.uvKind ?? 0));
const meshCol = <FloatArray>mesh.getVerticesData(VertexBuffer.ColorKind);
const meshNor = <FloatArray>mesh.getVerticesData(VertexBuffer.NormalKind);
const storage = options && options.storage ? options.storage : null;
let f: number = 0; // facet counter
const totalFacets: number = meshInd.length / 3; // a facet is a triangle, so 3 indices
// compute size from number
if (number) {
number = number > totalFacets ? totalFacets : number;
size = Math.round(totalFacets / number);
delta = 0;
} else {
size = size > totalFacets ? totalFacets : size;
}
const facetPos: number[] = []; // submesh positions
const facetNor: number[] = [];
const facetInd: number[] = []; // submesh indices
const facetUV: number[] = []; // submesh UV
const facetCol: number[] = []; // submesh colors
const barycenter: Vector3 = Vector3.Zero();
const sizeO: number = size;
while (f < totalFacets) {
size = sizeO + Math.floor((1 + delta) * Math.random());
if (f > totalFacets - size) {
size = totalFacets - f;
}
// reset temp arrays
facetPos.length = 0;
facetNor.length = 0;
facetInd.length = 0;
facetUV.length = 0;
facetCol.length = 0;
// iterate over "size" facets
let fi: number = 0;
for (let j = f * 3; j < (f + size) * 3; j++) {
facetInd.push(fi);
const i: number = meshInd[j];
const i3: number = i * 3;
facetPos.push(meshPos[i3], meshPos[i3 + 1], meshPos[i3 + 2]);
facetNor.push(meshNor[i3], meshNor[i3 + 1], meshNor[i3 + 2]);
if (meshUV) {
const i2: number = i * 2;
facetUV.push(meshUV[i2], meshUV[i2 + 1]);
}
if (meshCol) {
const i4: number = i * 4;
facetCol.push(meshCol[i4], meshCol[i4 + 1], meshCol[i4 + 2], meshCol[i4 + 3]);
}
fi++;
}
// create a model shape for each single particle
let idx: number = this.nbParticles;
const shape: Vector3[] = this._posToShape(facetPos);
const shapeUV: number[] = this._uvsToShapeUV(facetUV);
const shapeInd = facetInd.slice();
const shapeCol = facetCol.slice();
const shapeNor = facetNor.slice();
// compute the barycenter of the shape
barycenter.copyFromFloats(0, 0, 0);
let v: number;
for (v = 0; v < shape.length; v++) {
barycenter.addInPlace(shape[v]);
}
barycenter.scaleInPlace(1 / shape.length);
// shift the shape from its barycenter to the origin
// and compute the BBox required for intersection.
const minimum: Vector3 = new Vector3(Infinity, Infinity, Infinity);
const maximum: Vector3 = new Vector3(-Infinity, -Infinity, -Infinity);
for (v = 0; v < shape.length; v++) {
shape[v].subtractInPlace(barycenter);
minimum.minimizeInPlaceFromFloats(shape[v].x, shape[v].y, shape[v].z);
maximum.maximizeInPlaceFromFloats(shape[v].x, shape[v].y, shape[v].z);
}
let bInfo;
if (this._particlesIntersect) {
bInfo = new BoundingInfo(minimum, maximum);
}
let material = null;
if (this._useModelMaterial) {
material = mesh.material ? mesh.material : this._setDefaultMaterial();
}
const modelShape = new ModelShape(this._shapeCounter, shape, shapeInd, shapeNor, shapeCol, shapeUV, null, null, material);
// add the particle in the SPS
const currentPos = this._positions.length;
const currentInd = this._indices.length;
this._meshBuilder(
this._index,
currentInd,
shape,
this._positions,
shapeInd,
this._indices,
facetUV,
this._uvs,
shapeCol,
this._colors,
shapeNor,
this._normals,
idx,
0,
null,
modelShape
);
this._addParticle(idx, this._lastParticleId, currentPos, currentInd, modelShape, this._shapeCounter, 0, bInfo, storage);
// initialize the particle position
this.particles[this.nbParticles].position.addInPlace(barycenter);
if (!storage) {
this._index += shape.length;
idx++;
this.nbParticles++;
this._lastParticleId++;
}
this._shapeCounter++;
f += size;
}
this._isNotBuilt = true; // buildMesh() is now expected for setParticles() to work
return this;
}
/**
* Unrotate the fixed normals in case the mesh was built with pre-rotated particles, ex : use of positionFunction in addShape()
* @internal
*/
protected _unrotateFixedNormals() {
let index = 0;
let idx = 0;
const tmpNormal = TmpVectors.Vector3[0];
const quaternion = TmpVectors.Quaternion[0];
const invertedRotMatrix = TmpVectors.Matrix[0];
for (let p = 0; p < this.particles.length; p++) {
const particle = this.particles[p];
const shape = particle._model._shape;
// computing the inverse of the rotation matrix from the quaternion
// is equivalent to computing the matrix of the inverse quaternion, i.e of the conjugate quaternion
if (particle.rotationQuaternion) {
particle.rotationQuaternion.conjugateToRef(quaternion);
} else {
const rotation = particle.rotation;
Quaternion.RotationYawPitchRollToRef(rotation.y, rotation.x, rotation.z, quaternion);
quaternion.conjugateInPlace();
}
quaternion.toRotationMatrix(invertedRotMatrix);
for (let pt = 0; pt < shape.length; pt++) {
idx = index + pt * 3;
Vector3.TransformNormalFromFloatsToRef(this._normals32[idx], this._normals32[idx + 1], this._normals32[idx + 2], invertedRotMatrix, tmpNormal);
tmpNormal.toArray(this._fixedNormal32, idx);
}
index = idx + 3;
}
}
/**
* Resets the temporary working copy particle
* @internal
*/
protected _resetCopy() {
const copy = this._copy;
copy.position.setAll(0);
copy.rotation.setAll(0);
copy.rotationQuaternion = null;
copy.scaling.setAll(1);
copy.uvs.copyFromFloats(0.0, 0.0, 1.0, 1.0);
copy.color = null;
copy.translateFromPivot = false;
copy.shapeId = 0;
copy.materialIndex = null;
}
/**
* Inserts the shape model geometry in the global SPS mesh by updating the positions, indices, normals, colors, uvs arrays
* @param p the current index in the positions array to be updated
* @param ind the current index in the indices array
* @param shape a Vector3 array, the shape geometry
* @param positions the positions array to be updated
* @param meshInd the shape indices array
* @param indices the indices array to be updated
* @param meshUV the shape uv array
* @param uvs the uv array to be updated
* @param meshCol the shape color array
* @param colors the color array to be updated
* @param meshNor the shape normals array
* @param normals the normals array to be updated
* @param idx the particle index
* @param idxInShape the particle index in its shape
* @param options the addShape() method passed options
* @param model
* @model the particle model
* @internal
*/
protected _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 {
let i;
let u = 0;
let c = 0;
let n = 0;
this._resetCopy();
const copy = this._copy;
const storeApart = options && options.storage ? true : false;
copy.idx = idx;
copy.idxInShape = idxInShape;
copy.shapeId = model.shapeId;
if (this._useModelMaterial) {
const materialId = model._material!.uniqueId;
const materialIndexesById = this._materialIndexesById;
if (!Object.prototype.hasOwnProperty.call(materialIndexesById, materialId)) {
materialIndexesById[materialId] = this._materials.length;
this._materials.push(model._material!);
}
const matIdx = materialIndexesById[materialId];
copy.materialIndex = matIdx;
}
if (options && options.positionFunction) {
// call to custom positionFunction
options.positionFunction(copy, idx, idxInShape);
this._mustUnrotateFixedNormals = true;
}
// in case the particle geometry must NOT be inserted in the SPS mesh geometry
if (storeApart) {
return copy;
}
const rotMatrix = TmpVectors.Matrix[0];
const tmpVertex = this._tmpVertex;
const tmpVector = tmpVertex.position;
const tmpColor = tmpVertex.color;
const tmpUV = tmpVertex.uv;
const tmpRotated = TmpVectors.Vector3[1];
const pivotBackTranslation = TmpVectors.Vector3[2];
const scaledPivot = TmpVectors.Vector3[3];
Matrix.IdentityToRef(rotMatrix);
copy.getRotationMatrix(rotMatrix);
copy.pivot.multiplyToRef(copy.scaling, scaledPivot);
if (copy.translateFromPivot) {
pivotBackTranslation.setAll(0.0);
} else {
pivotBackTranslation.copyFrom(scaledPivot);
}
const someVertexFunction = options && options.vertexFunction;
for (i = 0; i < shape.length; i++) {
tmpVector.copyFrom(shape[i]);
if (copy.color) {
tmpColor.copyFrom(copy.color);
}
if (meshUV) {
tmpUV.copyFromFloats(meshUV[u], meshUV[u + 1]);
}
if (someVertexFunction) {
options.vertexFunction(copy, tmpVertex, i);
}
tmpVector.multiplyInPlace(copy.scaling).subtractInPlace(scaledPivot);
Vector3.TransformCoordinatesToRef(tmpVector, rotMatrix, tmpRotated);
tmpRotated.addInPlace(pivotBackTranslation).addInPlace(copy.position);
positions.push(tmpRotated.x, tmpRotated.y, tmpRotated.z);
if (meshUV) {
const copyUvs = copy.uvs;
uvs.push((copyUvs.z - copyUvs.x) * tmpUV.x + copyUvs.x, (copyUvs.w - copyUvs.y) * tmpUV.y + copyUvs.y);
u += 2;
}
if (copy.color) {
this._color.copyFrom(tmpColor);
} else {
const color = this._color;
if (meshCol && meshCol[c] !== undefined) {
color.r = meshCol[c];
color.g = meshCol[c + 1];
color.b = meshCol[c + 2];
color.a = meshCol[c + 3];
} else {
color.r = 1.0;
color.g = 1.0;
color.b = 1.0;
color.a = 1.0;
}
}
colors.push(this._color.r, this._color.g, this._color.b, this._color.a);
c += 4;
if (!this.recomputeNormals && meshNor) {
Vector3.TransformNormalFromFloatsToRef(meshNor[n], meshNor[n + 1], meshNor[n + 2], rotMatrix, tmpVector);
normals.push(tmpVector.x, tmpVector.y, tmpVector.z);
n += 3;
}
}
for (i = 0; i < meshInd.length; i++) {
const current_ind = p + meshInd[i];
indices.push(current_ind);
if (current_ind > 65535) {
this._needs32Bits = true;
}
}
if (this._depthSort || this._multimaterialEnabled) {
const matIndex = copy.materialIndex !== null ? copy.materialIndex : 0;
this.depthSortedParticles.push(new DepthSortedParticle(idx, ind, meshInd.length, matIndex));
}
return copy;
}
/**
* Returns a shape Vector3 array from positions float array
* @param positions float array
* @returns a vector3 array
* @internal
*/
protected _posToShape(positions: number[] | Float32Array): Vector3[] {
const shape = [];
for (let i = 0; i < positions.length; i += 3) {
shape.push(Vector3.FromArray(positions, i));
}
return shape;
}
/**
* Returns a shapeUV array from a float uvs (array deep copy)
* @param uvs as a float array
* @returns a shapeUV array
* @internal
*/
protected _uvsToShapeUV(uvs: number[] | Float32Array): number[] {
const shapeUV = [];
if (uvs) {
for (let i = 0; i < uvs.length; i++) {
shapeUV.push(uvs[i]);
}
}
return shapeUV;
}
/**
* Adds a new particle object in the particles array
* @param idx particle index in particles array
* @param id particle id
* @param idxpos positionIndex : the starting index of the particle vertices in the SPS "positions" array
* @param idxind indiceIndex : he starting index of the particle indices in the SPS "indices" array
* @param model particle ModelShape object
* @param shapeId model shape identifier
* @param idxInShape index of the particle in the current model
* @param bInfo model bounding info object
* @param storage target storage array, if any
* @internal
*/
protected _addParticle(
idx: number,
id: number,
idxpos: number,
idxind: number,
model: ModelShape,
shapeId: number,
idxInShape: number,
bInfo: Nullable<BoundingInfo> = null,
storage: Nullable<[]> = null
): SolidParticle {
const sp = new SolidParticle(idx, id, idxpos, idxind, model, shapeId, idxInShape, this, bInfo);
const target = storage ? storage : this.particles;
target.push(sp);
return sp;
}
/**
* Adds some particles to the SPS from the model shape. Returns the shape id.
* Please read the doc : https://doc.babylonjs.com/features/featuresDeepDive/particles/solid_particle_system/immutable_sps
* @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.
* @param nb (positive integer) the number of particles to be created from this model
* @param options {positionFunction} is an optional javascript function to called for each particle on SPS creation.
* {vertexFunction} is an optional javascript function to called for each vertex of each particle on SPS creation
* {storage} (optional existing array) is an array where the particles will be stored for a further use instead of being inserted in the SPS.
* @param options.positionFunction
* @param options.vertexFunction
* @param options.storage
* @returns the number of shapes in the system
*/
public addShape(mesh: Mesh, nb: number, options?: { positionFunction?: any; vertexFunction?: any; storage?: [] }): number {
const meshPos = <FloatArray>mesh.getVerticesData(VertexBuffer.PositionKind);
const meshInd = <IndicesArray>mesh.getIndices();
const meshUV = <FloatArray>mesh.getVerticesData(VertexBuffer.UVKind);
const meshCol = <FloatArray>mesh.getVerticesData(VertexBuffer.ColorKind);
const meshNor = <FloatArray>mesh.getVerticesData(VertexBuffer.NormalKind);
this.recomputeNormals = meshNor ? false : true;
const indices = Array.from(meshInd);
const shapeNormals = meshNor ? Array.from(meshNor) : [];
const shapeColors = meshCol ? Array.from(meshCol) : [];
const storage = options && options.storage ? options.storage : null;
let bbInfo: Nullable<BoundingInfo> = null;
if (this._particlesIntersect) {
bbInfo = mesh.getBoundingInfo();
}
const shape = this._posToShape(meshPos);
const shapeUV = this._uvsToShapeUV(meshUV);
const posfunc = options ? options.positionFunction : null;
const vtxfunc = options ? options.vertexFunction : null;
let material = null;
if (this._useModelMaterial) {
material = mesh.material ? mesh.material : this._setDefaultMaterial();
}
const modelShape = new ModelShape(this._shapeCounter, shape, indices, shapeNormals, shapeColors, shapeUV, posfunc, vtxfunc, material);
// particles
for (let i = 0; i < nb; i++) {
this._insertNewParticle(this.nbParticles, i, modelShape, shape, meshInd, meshUV, meshCol, meshNor, bbInfo, storage, options);
}
this._shapeCounter++;
this._isNotBuilt = true; // buildMesh() call is now expected for setParticles() to work
return this._shapeCounter - 1;
}
/**
* Rebuilds a particle back to its just built status : if needed, recomputes the custom positions and vertices
* @internal
*/
protected _rebuildParticle(particle: SolidParticle, reset: boolean = false): void {
this._resetCopy();
const copy = this._copy;
if (particle._model._positionFunction) {
// recall to stored custom positionFunction
particle._model._positionFunction(copy, particle.idx, particle.idxInShape);
}
const rotMatrix = TmpVectors.Matrix[0];
const tmpVertex = TmpVectors.Vector3[0];
const tmpRotated = TmpVectors.Vector3[1];
const pivotBackTranslation = TmpVectors.Vector3[2];
const scaledPivot = TmpVectors.Vector3[3];
copy.getRotationMatrix(rotMatrix);
particle.pivot.multiplyToRef(particle.scaling, scaledPivot);
if (copy.translateFromPivot) {
pivotBackTranslation.copyFromFloats(0.0, 0.0, 0.0);
} else {
pivotBackTranslation.copyFrom(scaledPivot);
}
const shape = particle._model._shape;
for (let pt = 0; pt < shape.length; pt++) {
tmpVertex.copyFrom(shape[pt]);
if (particle._model._vertexFunction) {
particle._model._vertexFunction(copy, tmpVertex, pt); // recall to stored vertexFunction
}
tmpVertex.multiplyInPlace(copy.scaling).subtractInPlace(scaledPivot);
Vector3.TransformCoordinatesToRef(tmpVertex, rotMatrix, tmpRotated);
tmpRotated
.addInPlace(pivotBackTranslation)
.addInPlace(copy.position)
.toArray(this._positions32, particle._pos + pt * 3);
}
if (reset) {
particle.position.setAll(0.0);
particle.rotation.setAll(0.0);
particle.rotationQuaternion = null;
particle.scaling.setAll(1.0);
particle.uvs.setAll(0.0);
particle.pivot.setAll(0.0);
particle.translateFromPivot = false;
particle.parentId = null;
}
}
/**
* Rebuilds the whole mesh and updates the VBO : custom positions and vertices are recomputed if needed.
* @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.
* @returns the SPS.
*/
public rebuildMesh(reset: boolean = false): SolidParticleSystem {
for (let p = 0; p < this.particles.length; p++) {
this._rebuildParticle(this.particles[p], reset);
}
this.mesh.updateVerticesData(VertexBuffer.PositionKind, this._positions32, false, false);
return this;
}
/** Removes the particles from the start-th to the end-th included from an expandable SPS (required).
* Returns an array with the removed particles.
* 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.
* The SPS can't be empty so at least one particle needs to remain in place.
* Under the hood, the VertexData array, so the VBO buffer, is recreated each call.
* @param start index of the first particle to remove
* @param end index of the last particle to remove (included)
* @returns an array populated with the removed particles
*/
public removeParticles(start: number, end: number): SolidParticle[] {
const nb = end - start + 1;
if (!this._expandable || nb <= 0 || nb >= this.nbParticles || !this._updatable) {
return [];
}
const particles = this.particles;
const currentNb = this.nbParticles;
if (end < currentNb - 1) {
// update the particle indexes in the positions array in case they're remaining particles after the last removed
const firstRemaining = end + 1;
const shiftPos = particles[firstRemaining]._pos - particles[start]._pos;
const shifInd = particles[firstRemaining]._ind - particles[start]._ind;
for (let i = firstRemaining; i < currentNb; i++) {
const part = particles[i];
part._pos -= shiftPos;
part._ind -= shifInd;
}
}
const removed = particles.splice(start, nb);
this._positions.length = 0;
this._indices.length = 0;
this._colors.length = 0;
this._uvs.length = 0;
this._normals.length = 0;
this._index = 0;
this._idxOfId.length = 0;
if (this._depthSort || this._multimaterialEnabled) {
this.depthSortedParticles = [];
}
let ind = 0;
const particlesLength = particles.length;
for (let p = 0; p < particlesLength; p++) {
const particle = particles[p];
const model = particle._model;
const shape = model._shape;
const modelIndices = model._indices;
const modelNormals = model._normals;
const modelColors = model._shapeColors;
const modelUVs = model._shapeUV;
particle.idx = p;
this._idxOfId[particle.id] = p;
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
);
this._index += shape.length;
ind += modelIndices.length;
}
this.nbParticles -= nb;
this._isNotBuilt = true; // buildMesh() call is now expected for setParticles() to work
return removed;
}
/**
* Inserts some pre-created particles in the solid particle system so that they can be managed by setParticles().
* @param solidParticleArray an array populated with Solid Particles objects
* @returns the SPS
*/
public insertParticlesFromArray(solidParticleArray: SolidParticle[]): SolidParticleSystem {
if (!this._expandable) {
return this;
}
let idxInShape = 0;
let currentShapeId = solidParticleArray[0].shapeId;
const nb = solidParticleArray.length;
for (let i = 0; i < nb; i++) {
const sp = solidParticleArray[i];
const model = sp._model;
const shape = model._shape;
const meshInd = model._indices;
const meshUV = model._shapeUV;
const meshCol = model._shapeColors;
const meshNor = model._normals;
const noNor = meshNor ? false : true;
this.recomputeNormals = noNor || this.recomputeNormals;
const bbInfo = sp.getBoundingInfo();
const newPart = this._insertNewParticle(this.nbParticles, idxInShape, model, shape, meshInd, meshUV, meshCol, meshNor, bbInfo, null, null);
sp.copyToRef(newPart!);
idxInShape++;
if (currentShapeId != sp.shapeId) {
currentShapeId = sp.shapeId;
idxInShape = 0;
}
}
this._isNotBuilt = true; // buildMesh() call is now expected for setParticles() to work
return this;
}
/**
* Creates a new particle and modifies the SPS mesh geometry :
* - calls _meshBuilder() to increase the SPS mesh geometry step by step
* - calls _addParticle() to populate the particle array
* factorized code from addShape() and insertParticlesFromArray()
* @param idx particle index in the particles array
* @param i particle index in its shape
* @param modelShape particle ModelShape object
* @param shape shape vertex array
* @param meshInd shape indices array
* @param meshUV shape uv array
* @param meshCol shape color array
* @param meshNor shape normals array
* @param bbInfo shape bounding info