Needle Engine

Changes between version 3.2.1-alpha and 3.2.2-alpha
Files changed (5) hide show
  1. src/engine/engine_rendererdata.ts +2 -1
  2. src/engine/extensions/NEEDLE_lighting_settings.ts +1 -1
  3. src/engine-components/ParticleSystem.ts +2 -1
  4. src/engine-components/ParticleSystemModules.ts +1483 -1400
  5. src/engine-components/SceneSwitcher.ts +9 -7
src/engine/engine_rendererdata.ts CHANGED
@@ -136,7 +136,8 @@
136
136
  }
137
137
  }
138
138
 
139
- disableReflection() {
139
+ disableReflection(sourceId? : SourceIdentifier) {
140
+ if(sourceId && sourceId !== this._currentReflectionId) return;
140
141
  const scene = this.context.scene;
141
142
  scene.environment = null;
142
143
  }
src/engine/extensions/NEEDLE_lighting_settings.ts CHANGED
@@ -160,6 +160,6 @@
160
160
  if (this._lightProbeObj) this._lightProbeObj.removeFromParent();
161
161
  if(this._ambientLightObj) this._ambientLightObj.removeFromParent();
162
162
  if (this.sourceId)
163
- this.context.rendererData.disableReflection();
163
+ this.context.rendererData.disableReflection(this.sourceId);
164
164
  }
165
165
  }
src/engine-components/ParticleSystem.ts CHANGED
@@ -414,6 +414,7 @@
414
414
  particle[$gravitySpeed] = 1;
415
415
 
416
416
  particle[$velocityLerpFactor] = Math.random();
417
+ this.system.velocityOverLifetime?.init(particle);
417
418
 
418
419
  this._gravityDirection.set(0, -1, 0);
419
420
  if (this.system.main.simulationSpace === ParticleSystemSimulationSpace.Local)
@@ -463,7 +464,7 @@
463
464
  // limit or modify speed
464
465
  const velocity = this.system.velocityOverLifetime;
465
466
  if (velocity.enabled) {
466
- velocity.apply(0, particle.position, particle.velocity, delta, particle.age, particle.life);
467
+ velocity.apply(particle, 0, particle.position, particle.velocity, delta, particle.age, particle.life);
467
468
  }
468
469
 
469
470
  const limitVelocityOverLifetime = this.system.limitVelocityOverLifetime;
src/engine-components/ParticleSystemModules.ts CHANGED
@@ -1,1401 +1,1484 @@
1
- import { Color, Matrix4, Object3D, PointLightShadow, Quaternion, Vector3, Vector2, Euler, Vector4, DirectionalLightHelper } from "three";
2
- import { Mathf } from "../engine/engine_math";
3
- import { serializable } from "../engine/engine_serialization";
4
- import { RGBAColor } from "./js-extensions/RGBAColor";
5
- import { AnimationCurve } from "./AnimationCurve";
6
- import { Vec2, Vec3 } from "../engine/engine_types";
7
- import { Context } from "../engine/engine_setup";
8
- import { EmitterShape, FrameOverLife, Particle, ShapeJSON } from "three.quarks";
9
- import { createNoise4D, NoiseFunction4D } from 'simplex-noise';
10
- import { Gizmos } from "../engine/engine_gizmos";
11
- import { getParam } from "../engine/engine_utils";
12
-
13
- const debug = getParam("debugparticles");
14
-
15
- declare type Color4 = { r: number, g: number, b: number, a: number };
16
- declare type ColorKey = { time: number, color: Color4 };
17
- declare type AlphaKey = { time: number, alpha: number };
18
-
19
- export interface IParticleSystem {
20
- get currentParticles(): number;
21
- get maxParticles(): number;
22
- get time(): number;
23
- get deltaTime(): number;
24
- get duration(): number;
25
- readonly main: MainModule;
26
- get container(): Object3D;
27
- get worldspace(): boolean;
28
- get worldPos(): Vector3;
29
- get worldQuaternion(): Quaternion;
30
- get worldQuaternionInverted(): Quaternion;
31
- get worldScale(): Vector3;
32
- get matrixWorld(): Matrix4;
33
- }
34
-
35
-
36
- export enum ParticleSystemRenderMode {
37
- Billboard = 0,
38
- Stretch = 1,
39
- HorizontalBillboard = 2,
40
- VerticalBillboard = 3,
41
- Mesh = 4,
42
- // None = 5,
43
- }
44
-
45
-
46
- export class Gradient {
47
- @serializable()
48
- alphaKeys!: Array<AlphaKey>;
49
- @serializable()
50
- colorKeys!: Array<ColorKey>;
51
-
52
- get duration(): number {
53
- return 1;
54
- }
55
-
56
- evaluate(time: number, target: RGBAColor) {
57
-
58
- // target.r = this.colorKeys[0].color.r;
59
- // target.g = this.colorKeys[0].color.g;
60
- // target.b = this.colorKeys[0].color.b;
61
- // target.alpha = this.alphaKeys[0].alpha;
62
- // return;
63
-
64
- let closestAlpha: AlphaKey | undefined = undefined;
65
- let closestAlphaIndex = 0;
66
- let closestColor: ColorKey | null = null;
67
- let closestColorIndex = 0;
68
- for (let i = 0; i < this.alphaKeys.length; i++) {
69
- const key = this.alphaKeys[i];
70
- if (key.time < time || !closestAlpha) {
71
- closestAlpha = key;
72
- closestAlphaIndex = i;
73
- }
74
- }
75
- for (let i = 0; i < this.colorKeys.length; i++) {
76
- const key = this.colorKeys[i];
77
- if (key.time < time || !closestColor) {
78
- closestColor = key;
79
- closestColorIndex = i;
80
- }
81
- }
82
- if (closestColor) {
83
- const hasNextColor = closestColorIndex + 1 < this.colorKeys.length;
84
- if (hasNextColor) {
85
- const nextColor = this.colorKeys[closestColorIndex + 1];
86
- const t = Mathf.remap(time, closestColor.time, nextColor.time, 0, 1);
87
- target.r = Mathf.lerp(closestColor.color.r, nextColor.color.r, t);
88
- target.g = Mathf.lerp(closestColor.color.g, nextColor.color.g, t);
89
- target.b = Mathf.lerp(closestColor.color.b, nextColor.color.b, t);
90
- }
91
- else {
92
- target.r = closestColor.color.r;
93
- target.g = closestColor.color.g;
94
- target.b = closestColor.color.b;
95
- }
96
- }
97
- if (closestAlpha) {
98
- const hasNextAlpha = closestAlphaIndex + 1 < this.alphaKeys.length;
99
- if (hasNextAlpha) {
100
- const nextAlpha = this.alphaKeys[closestAlphaIndex + 1];
101
- const t = Mathf.remap(time, closestAlpha.time, nextAlpha.time, 0, 1);
102
- target.alpha = Mathf.lerp(closestAlpha.alpha, nextAlpha.alpha, t);
103
- }
104
- else {
105
- target.alpha = closestAlpha.alpha;
106
- }
107
- }
108
- return target;
109
- }
110
- }
111
-
112
- export enum ParticleSystemCurveMode {
113
- Constant = 0,
114
- Curve = 1,
115
- TwoCurves = 2,
116
- TwoConstants = 3
117
- }
118
-
119
- export enum ParticleSystemGradientMode {
120
- Color = 0,
121
- Gradient = 1,
122
- TwoColors = 2,
123
- TwoGradients = 3,
124
- RandomColor = 4,
125
- }
126
-
127
- export enum ParticleSystemSimulationSpace {
128
- Local = 0,
129
- World = 1,
130
- Custom = 2
131
- }
132
-
133
- export enum ParticleSystemShapeType {
134
- Sphere = 0,
135
- SphereShell = 1,
136
- Hemisphere = 2,
137
- HemisphereShell = 3,
138
- Cone = 4,
139
- Box = 5,
140
- Mesh = 6,
141
- ConeShell = 7,
142
- ConeVolume = 8,
143
- ConeVolumeShell = 9,
144
- Circle = 10,
145
- CircleEdge = 11,
146
- SingleSidedEdge = 12,
147
- MeshRenderer = 13,
148
- SkinnedMeshRenderer = 14,
149
- BoxShell = 15,
150
- BoxEdge = 16,
151
- Donut = 17,
152
- Rectangle = 18,
153
- Sprite = 19,
154
- SpriteRenderer = 20
155
- }
156
-
157
- export enum ParticleSystemShapeMultiModeValue {
158
- Random = 0,
159
- Loop = 1,
160
- PingPong = 2,
161
- BurstSpread = 3,
162
- }
163
-
164
- export class MinMaxCurve {
165
- @serializable()
166
- mode!: ParticleSystemCurveMode;
167
- @serializable()
168
- constant!: number;
169
- @serializable()
170
- constantMin!: number;
171
- @serializable()
172
- constantMax!: number;
173
- @serializable(AnimationCurve)
174
- curve?: AnimationCurve;
175
- @serializable(AnimationCurve)
176
- curveMin?: AnimationCurve;
177
- @serializable(AnimationCurve)
178
- curveMax?: AnimationCurve;
179
- @serializable()
180
- curveMultiplier?: number;
181
-
182
- evaluate(t01: number, lerpFactor?: number): number {
183
- const t = lerpFactor === undefined ? Math.random() : lerpFactor;
184
- switch (this.mode) {
185
- case ParticleSystemCurveMode.Constant:
186
- return this.constant;
187
- case ParticleSystemCurveMode.Curve:
188
- t01 = Mathf.clamp01(t01);
189
- return this.curve!.evaluate(t01) * this.curveMultiplier!;
190
- case ParticleSystemCurveMode.TwoCurves:
191
- const t1 = t01 * this.curveMin!.duration;
192
- const t2 = t01 * this.curveMax!.duration;
193
- return Mathf.lerp(this.curveMin!.evaluate(t1), this.curveMax!.evaluate(t2), t % 1) * this.curveMultiplier!;
194
- case ParticleSystemCurveMode.TwoConstants:
195
- return Mathf.lerp(this.constantMin, this.constantMax, t % 1)
196
- default:
197
- this.curveMax!.evaluate(t01) * this.curveMultiplier!;
198
- break;
199
- }
200
- return 0;
201
- }
202
-
203
- getMax(): number {
204
- switch (this.mode) {
205
- case ParticleSystemCurveMode.Constant:
206
- return this.constant;
207
- case ParticleSystemCurveMode.Curve:
208
- return this.getMaxFromCurve(this.curve!) * this.curveMultiplier!;
209
- case ParticleSystemCurveMode.TwoCurves:
210
- return Math.max(this.getMaxFromCurve(this.curveMin), this.getMaxFromCurve(this.curveMax)) * this.curveMultiplier!;
211
- case ParticleSystemCurveMode.TwoConstants:
212
- return Math.max(this.constantMin, this.constantMax);
213
- default:
214
- return 0;
215
- }
216
- }
217
-
218
- private getMaxFromCurve(curve?: AnimationCurve) {
219
- if (!curve) return 0;
220
- let maxNumber = Number.MIN_VALUE;
221
- for (let i = 0; i < curve!.keys.length; i++) {
222
- const key = curve!.keys[i];
223
- if (key.value > maxNumber) {
224
- maxNumber = key.value;
225
- }
226
- }
227
- return maxNumber;
228
- }
229
- }
230
-
231
- export class MinMaxGradient {
232
- mode!: ParticleSystemGradientMode;
233
- @serializable(RGBAColor)
234
- color!: RGBAColor;
235
- @serializable(RGBAColor)
236
- colorMin!: RGBAColor;
237
- @serializable(RGBAColor)
238
- colorMax!: RGBAColor;
239
- @serializable(Gradient)
240
- gradient!: Gradient;
241
- @serializable(Gradient)
242
- gradientMin!: Gradient;
243
- @serializable(Gradient)
244
- gradientMax!: Gradient;
245
-
246
- private static _temp: RGBAColor = new RGBAColor(0, 0, 0, 1);
247
- private static _temp2: RGBAColor = new RGBAColor(0, 0, 0, 1);
248
-
249
- evaluate(t01: number, lerpFactor?: number): RGBAColor {
250
- const t = lerpFactor === undefined ? Math.random() : lerpFactor;
251
- switch (this.mode) {
252
- case ParticleSystemGradientMode.Color:
253
- return this.color;
254
- case ParticleSystemGradientMode.Gradient:
255
- this.gradient.evaluate(t01, MinMaxGradient._temp);
256
- return MinMaxGradient._temp
257
- case ParticleSystemGradientMode.TwoColors:
258
- const col1 = MinMaxGradient._temp.lerpColors(this.colorMin, this.colorMax, t);
259
- return col1;
260
- case ParticleSystemGradientMode.TwoGradients:
261
- this.gradientMin.evaluate(t01, MinMaxGradient._temp);
262
- this.gradientMax.evaluate(t01, MinMaxGradient._temp2);
263
- return MinMaxGradient._temp.lerp(MinMaxGradient._temp2, t);
264
-
265
- }
266
- // console.warn("Not implemented", ParticleSystemGradientMode[this.mode]);
267
- MinMaxGradient._temp.set(0xff00ff)
268
- MinMaxGradient._temp.alpha = 1;
269
- return MinMaxGradient._temp;
270
- }
271
- }
272
-
273
- declare type ParticleSystemScalingMode = {
274
- Hierarchy: number;
275
- Local: number;
276
- Shape: number;
277
- }
278
-
279
- export class MainModule {
280
- cullingMode!: number;
281
- duration!: number;
282
- emitterVelocityMode!: number;
283
- flipRotation!: number;
284
- @serializable(MinMaxCurve)
285
- gravityModifier!: MinMaxCurve;
286
- gravityModifierMultiplier!: number;
287
- loop!: boolean;
288
- maxParticles!: number;
289
- playOnAwake!: boolean;
290
- prewarm!: boolean;
291
- ringBufferLoopRange!: { x: number, y: number };
292
- ringBufferMode!: boolean;
293
- scalingMode!: ParticleSystemScalingMode;
294
- simulationSpace!: ParticleSystemSimulationSpace;
295
- simulationSpeed!: number;
296
- @serializable(MinMaxGradient)
297
- startColor!: MinMaxGradient;
298
- @serializable(MinMaxCurve)
299
- startDelay!: MinMaxCurve;
300
- startDelayMultiplier!: number;
301
- @serializable(MinMaxCurve)
302
- startLifetime!: MinMaxCurve;
303
- startLifetimeMultiplier!: number;
304
- @serializable(MinMaxCurve)
305
- startRotation!: MinMaxCurve;
306
- startRotationMultiplier!: number;
307
- startRotation3D!: boolean;
308
- @serializable(MinMaxCurve)
309
- startRotationX!: MinMaxCurve;
310
- startRotationXMultiplier!: number;
311
- @serializable(MinMaxCurve)
312
- startRotationY!: MinMaxCurve;
313
- startRotationYMultiplier!: number;
314
- @serializable(MinMaxCurve)
315
- startRotationZ!: MinMaxCurve;
316
- startRotationZMultiplier!: number;
317
- @serializable(MinMaxCurve)
318
- startSize!: MinMaxCurve;
319
- startSize3D!: boolean;
320
- startSizeMultiplier!: number;
321
- @serializable(MinMaxCurve)
322
- startSizeX!: MinMaxCurve;
323
- startSizeXMultiplier!: number;
324
- @serializable(MinMaxCurve)
325
- startSizeY!: MinMaxCurve;
326
- startSizeYMultiplier!: number;
327
- @serializable(MinMaxCurve)
328
- startSizeZ!: MinMaxCurve;
329
- startSizeZMultiplier!: number;
330
- @serializable(MinMaxCurve)
331
- startSpeed!: MinMaxCurve;
332
- startSpeedMultiplier!: number;
333
- stopAction!: number;
334
- useUnscaledTime!: boolean;
335
- }
336
-
337
-
338
- export class ParticleBurst {
339
- cycleCount!: number;
340
- maxCount!: number;
341
- minCount!: number;
342
- probability!: number;
343
- repeatInterval!: number;
344
- time!: number;
345
- count!: {
346
- constant: number;
347
- constantMax: number;
348
- constantMin: number;
349
- curve?: AnimationCurve;
350
- curveMax?: AnimationCurve;
351
- curveMin?: AnimationCurve;
352
- curveMultiplier?: number;
353
- mode: ParticleSystemCurveMode;
354
- }
355
-
356
-
357
- private _performed: number = 0;
358
-
359
-
360
- reset() {
361
- this._performed = 0;
362
- }
363
- run(time: number): number {
364
- if (time <= this.time) {
365
- this.reset();
366
- return 0;
367
- }
368
- let amount = 0;
369
- if (this.cycleCount === 0 || this._performed < this.cycleCount) {
370
- const nextTime = this.time + this.repeatInterval * this._performed;
371
- if (time >= nextTime) {
372
- this._performed += 1;
373
- if (Math.random() < this.probability) {
374
- switch (this.count.mode) {
375
- case ParticleSystemCurveMode.Constant:
376
- amount = this.count.constant;
377
- break;
378
- case ParticleSystemCurveMode.TwoConstants:
379
- amount = Mathf.lerp(this.count.constantMin, this.count.constantMax, Math.random());
380
- break;
381
- case ParticleSystemCurveMode.Curve:
382
- amount = this.count.curve!.evaluate(Math.random());
383
- break;
384
- case ParticleSystemCurveMode.TwoCurves:
385
- const t = Math.random();
386
- amount = Mathf.lerp(this.count.curveMin!.evaluate(t), this.count.curveMax!.evaluate(t), Math.random());
387
- break;
388
- }
389
- }
390
- }
391
- }
392
- return amount;
393
- }
394
- }
395
-
396
- export class EmissionModule {
397
-
398
- @serializable()
399
- enabled!: boolean;
400
-
401
-
402
- get burstCount() {
403
- return this.bursts?.length ?? 0;
404
- }
405
-
406
- @serializable()
407
- bursts!: ParticleBurst[];
408
-
409
- @serializable(MinMaxCurve)
410
- rateOverTime!: MinMaxCurve;
411
- @serializable()
412
- rateOverTimeMultiplier!: number;
413
-
414
- @serializable(MinMaxCurve)
415
- rateOverDistance!: MinMaxCurve;
416
- @serializable()
417
- rateOverDistanceMultiplier!: number;
418
-
419
-
420
- /** set from system */
421
- system!: IParticleSystem;
422
-
423
- reset() {
424
- this.bursts?.forEach(b => b.reset());
425
- }
426
-
427
- getBurst() {
428
- let amount = 0;
429
- if (this.burstCount > 0) {
430
- for (let i = 0; i < this.burstCount; i++) {
431
- const burst = this.bursts[i];
432
- if (burst.time >= this.system.time) {
433
- burst.reset();
434
- }
435
- amount += Math.round(burst.run(this.system.time));
436
- }
437
- }
438
- return amount;
439
- }
440
- }
441
-
442
- export class ColorOverLifetimeModule {
443
- enabled!: boolean;
444
- @serializable(MinMaxGradient)
445
- color!: MinMaxGradient;
446
- }
447
-
448
- export class SizeOverLifetimeModule {
449
- enabled!: boolean;
450
- separateAxes!: boolean;
451
- @serializable(MinMaxCurve)
452
- size!: MinMaxCurve;
453
- sizeMultiplier!: number;
454
- @serializable(MinMaxCurve)
455
- x!: MinMaxCurve;
456
- xMultiplier!: number;
457
- @serializable(MinMaxCurve)
458
- y!: MinMaxCurve;
459
- yMultiplier!: number;
460
- @serializable(MinMaxCurve)
461
- z!: MinMaxCurve;
462
- zMultiplier!: number;
463
-
464
- private _time: number = 0;
465
- private _temp = new Vector3();
466
-
467
- evaluate(t01: number, target?: Vec3, lerpFactor?: number) {
468
- if (!target) target = this._temp;
469
-
470
- if (!this.enabled) {
471
- target.x = target.y = target.z = 1;
472
- return target;
473
- }
474
-
475
- if (!this.separateAxes) {
476
- const scale = this.size.evaluate(t01, lerpFactor) * this.sizeMultiplier;
477
- target.x = scale;
478
- // target.y = scale;
479
- // target.z = scale;
480
- }
481
- else {
482
- target.x = this.x.evaluate(t01, lerpFactor) * this.xMultiplier;
483
- target.y = this.y.evaluate(t01, lerpFactor) * this.yMultiplier;
484
- target.z = this.z.evaluate(t01, lerpFactor) * this.zMultiplier;
485
- }
486
- return target;
487
- }
488
- }
489
-
490
- export class ShapeModule implements EmitterShape {
491
-
492
- // Emittershape start
493
- get type(): string {
494
- return ParticleSystemShapeType[this.shapeType];
495
- }
496
- initialize(particle: Particle): void {
497
- this.getPosition();
498
- particle.position.copy(this._vector);
499
- }
500
- toJSON(): ShapeJSON {
501
- return this;
502
- }
503
- clone(): EmitterShape {
504
- return new ShapeModule();
505
- }
506
- // EmitterShape end
507
-
508
- @serializable()
509
- shapeType: ParticleSystemShapeType = ParticleSystemShapeType.Box;
510
- @serializable()
511
- enabled: boolean = true;
512
- @serializable()
513
- alignToDirection: boolean = false;
514
- @serializable()
515
- angle: number = 0;
516
- @serializable()
517
- arc: number = 360;
518
- @serializable()
519
- arcSpread!: number;
520
- @serializable()
521
- arcSpeedMultiplier!: number;
522
- @serializable()
523
- arcMode!: ParticleSystemShapeMultiModeValue;
524
-
525
-
526
- @serializable(Vector3)
527
- boxThickness!: Vector3;
528
- @serializable(Vector3)
529
- position!: Vector3;
530
- @serializable(Vector3)
531
- rotation!: Vector3;
532
- private _rotation: Euler = new Euler();
533
- @serializable(Vector3)
534
- scale!: Vector3;
535
-
536
- @serializable()
537
- radius!: number;
538
- @serializable()
539
- radiusThickness!: number;
540
- @serializable()
541
- sphericalDirectionAmount!: number;
542
- @serializable()
543
- randomDirectionAmount!: number;
544
- @serializable()
545
- randomPositionAmount!: number;
546
-
547
- private system!: IParticleSystem;
548
- private _space?: ParticleSystemSimulationSpace;
549
- private readonly _worldSpaceMatrix: Matrix4 = new Matrix4();
550
- private readonly _worldSpaceMatrixInverse: Matrix4 = new Matrix4();
551
-
552
- constructor() {
553
- if (debug)
554
- console.log(this);
555
- }
556
-
557
- update(system: IParticleSystem, _context: Context, simulationSpace: ParticleSystemSimulationSpace, obj: Object3D) {
558
- this.system = system;
559
- this._space = simulationSpace;
560
- if (simulationSpace === ParticleSystemSimulationSpace.World) {
561
- this._worldSpaceMatrix.copy(obj.matrixWorld);
562
- // set scale to 1
563
- this._worldSpaceMatrix.elements[0] = 1;
564
- this._worldSpaceMatrix.elements[5] = 1;
565
- this._worldSpaceMatrix.elements[10] = 1;
566
- this._worldSpaceMatrixInverse.copy(this._worldSpaceMatrix).invert();
567
- }
568
- }
569
-
570
- private updateRotation() {
571
- const isRotated = this.rotation.x !== 0 || this.rotation.y !== 0 || this.rotation.z !== 0;
572
- if (isRotated) {
573
- this._rotation.x = Mathf.toRadians(this.rotation.x);
574
- this._rotation.y = -Mathf.toRadians(this.rotation.y);
575
- this._rotation.z = -Mathf.toRadians(this.rotation.z);
576
- }
577
- return isRotated;
578
- }
579
-
580
- /** nebula implementations: */
581
-
582
- /** initializer implementation */
583
- private _vector: Vector3 = new Vector3(0, 0, 0);
584
- private _temp: Vector3 = new Vector3(0, 0, 0);
585
- /** called by nebula on initialize */
586
- get vector() {
587
- return this._vector;
588
- }
589
- getPosition(): void {
590
- this._vector.set(0, 0, 0);
591
- const pos = this._temp.copy(this.position);
592
- const isWorldSpace = this._space === ParticleSystemSimulationSpace.World;
593
- if (isWorldSpace) {
594
- pos.applyQuaternion(this.system.worldQuaternion);
595
- }
596
- let radius = this.radius;
597
- if (isWorldSpace) radius *= this.system.worldScale.x;
598
- if (this.enabled) {
599
- switch (this.shapeType) {
600
- case ParticleSystemShapeType.Box:
601
- if (debug) Gizmos.DrawBox(this.position, this.scale, 0xdddddd, 1);
602
- this._vector.x = Math.random() * this.scale.x - this.scale.x / 2;
603
- this._vector.y = Math.random() * this.scale.y - this.scale.y / 2;
604
- this._vector.z = Math.random() * this.scale.z - this.scale.z / 2;
605
- this._vector.add(pos);
606
- break;
607
- case ParticleSystemShapeType.Cone:
608
- this.randomConePoint(this.position, this.angle, radius, this.radiusThickness, this.arc, this.arcMode, this._vector);
609
- break;
610
- case ParticleSystemShapeType.Sphere:
611
- this.randomSpherePoint(this.position, radius, this.radiusThickness, this.arc, this._vector);
612
- break;
613
- case ParticleSystemShapeType.Circle:
614
- this.randomCirclePoint(this.position, radius, this.radiusThickness, this.arc, this._vector);
615
- break;
616
- default:
617
- this._vector.set(0, 0, 0);
618
- break;
619
- // case ParticleSystemShapeType.Hemisphere:
620
- // randomSpherePoint(this.position.x, this.position.y, this.position.z, this.radius, this.radiusThickness, 180, this._vector);
621
- // break;
622
- }
623
-
624
- this.randomizePosition(this._vector, this.randomPositionAmount);
625
- }
626
-
627
- if (this.updateRotation())
628
- this._vector.applyEuler(this._rotation);
629
-
630
- if (isWorldSpace) {
631
- this._vector.applyQuaternion(this.system.worldQuaternion);
632
- this._vector.add(this.system.worldPos);
633
- }
634
-
635
- if (debug) {
636
- Gizmos.DrawSphere(this._vector, .03, 0xff0000, .5, true);
637
- }
638
- }
639
-
640
-
641
-
642
- private _dir: Vector3 = new Vector3();
643
-
644
- getDirection(pos: Vec3): Vector3 {
645
- if (!this.enabled) {
646
- this._dir.set(0, 0, 1);
647
- return this._dir;
648
- }
649
- switch (this.shapeType) {
650
- case ParticleSystemShapeType.Box:
651
- this._dir.set(0, 0, 1);
652
- break;
653
- case ParticleSystemShapeType.Cone:
654
- this._dir.set(0, 0, 1);
655
- // apply cone angle
656
- // this._dir.applyAxisAngle(new Vector3(0, 1, 0), Mathf.toRadians(this.angle));
657
- break;
658
- case ParticleSystemShapeType.Circle:
659
- case ParticleSystemShapeType.Sphere:
660
- const rx = pos.x;
661
- const ry = pos.y;
662
- const rz = pos.z;
663
- this._dir.set(rx, ry, rz)
664
- if (this.system?.worldspace)
665
- this._dir.sub(this.system.worldPos)
666
- break;
667
- default:
668
- this._dir.set(0, 0, 1);
669
- break;
670
- }
671
- if (this._space === ParticleSystemSimulationSpace.World) {
672
- this._dir.applyQuaternion(this.system.worldQuaternion);
673
- }
674
- if (this.updateRotation())
675
- this._dir.applyEuler(this._rotation);
676
- this._dir.normalize();
677
- this.spherizeDirection(this._dir, this.sphericalDirectionAmount);
678
- this.randomizeDirection(this._dir, this.randomDirectionAmount);
679
- if (debug) {
680
- Gizmos.DrawSphere(pos, .01, 0x883300, .5, true);
681
- Gizmos.DrawDirection(pos, this._dir, 0x883300, .5, true);
682
- }
683
- return this._dir;
684
- }
685
-
686
- private static _randomQuat = new Quaternion();
687
- private static _tempVec = new Vector3();
688
-
689
- private randomizePosition(pos: Vector3, amount: number) {
690
- if (amount <= 0) return;
691
- const rp = ShapeModule._tempVec;
692
- rp.set(Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1);
693
- rp.x *= amount * this.scale.x;
694
- rp.y *= amount * this.scale.y;
695
- rp.z *= amount * this.scale.z;
696
- pos.add(rp);
697
- }
698
-
699
- private randomizeDirection(direction: Vector3, amount: number) {
700
- if (amount === 0) return;
701
- const randomQuat = ShapeModule._randomQuat;
702
- const tempVec = ShapeModule._tempVec;
703
- tempVec.set(Math.random() - .5, Math.random() - .5, Math.random() - .5).normalize();
704
- randomQuat.setFromAxisAngle(tempVec, amount * Math.random() * Math.PI);
705
- direction.applyQuaternion(randomQuat);
706
- }
707
-
708
- private spherizeDirection(dir: Vector3, amount: number) {
709
- if (amount === 0) return;
710
- const theta = Math.random() * Math.PI * 2;
711
- const phi = Math.acos(1 - Math.random() * 2);
712
- const x = Math.sin(phi) * Math.cos(theta);
713
- const y = Math.sin(phi) * Math.sin(theta);
714
- const z = Math.cos(phi);
715
- const v = new Vector3(x, y, z);
716
- dir.lerp(v, amount);
717
- }
718
-
719
- private randomSpherePoint(pos: Vec3, radius: number, thickness: number, arc: number, vec: Vec3) {
720
- const u = Math.random();
721
- const v = Math.random();
722
- const theta = 2 * Math.PI * u * (arc / 360);
723
- const phi = Math.acos(2 * v - 1);
724
- const r = Mathf.lerp(1, 1 - (Math.pow(1 - Math.random(), Math.PI)), thickness) * (radius);
725
- const x = pos.x + this.scale.x * (-r * Math.sin(phi) * Math.cos(theta));
726
- const y = pos.y + this.scale.y * (r * Math.sin(phi) * Math.sin(theta));
727
- const z = pos.z + this.scale.z * (r * Math.cos(phi));
728
- vec.x = x;
729
- vec.y = y;
730
- vec.z = z;
731
- }
732
-
733
- private randomCirclePoint(pos:Vec3, radius:number, thickness:number, arg:number, vec:Vec3){
734
- const u = Math.random();
735
- const theta = 2 * Math.PI * u * (arg / 360);
736
- const r = Mathf.lerp(1, 1 - (Math.pow(1 - Math.random(), Math.PI)), thickness) * (radius);
737
- const x = pos.x + this.scale.x * r * Math.cos(theta);
738
- const y = pos.y + this.scale.y * r * Math.sin(theta);
739
- const z = pos.z;
740
- vec.x = x;
741
- vec.y = y;
742
- vec.z = z;
743
- }
744
-
745
- private _loopTime: number = 0;
746
- private _loopDirection: number = 1;
747
-
748
- private randomConePoint(pos: Vec3, _angle: number, radius: number, thickness: number, arc: number, arcMode: ParticleSystemShapeMultiModeValue, vec: Vec3) {
749
- let u = 0;
750
- let v = 0;
751
- switch (arcMode) {
752
- case ParticleSystemShapeMultiModeValue.Random:
753
- u = Math.random();
754
- v = Math.random();
755
- break;
756
- case ParticleSystemShapeMultiModeValue.PingPong:
757
- if (this._loopTime > 1) this._loopDirection = -1;
758
- if (this._loopTime < 0) this._loopDirection = 1;
759
- // continue with loop
760
-
761
- case ParticleSystemShapeMultiModeValue.Loop:
762
- u = .5;
763
- v = Math.random()
764
- this._loopTime += this.system.deltaTime * this._loopDirection;
765
- break;
766
- }
767
-
768
- let theta = 2 * Math.PI * u * (arc / 360);
769
- switch (arcMode) {
770
- case ParticleSystemShapeMultiModeValue.PingPong:
771
- case ParticleSystemShapeMultiModeValue.Loop:
772
- theta += Math.PI + .5;
773
- theta += this._loopTime * Math.PI * 2;
774
- theta %= Mathf.toRadians(arc);
775
- break;
776
- }
777
-
778
- const phi = Math.acos(2 * v - 1);
779
- const r = Mathf.lerp(1, 1 - (Math.pow(1 - Math.random(), Math.PI)), thickness) * radius;
780
- const x = pos.x + (-r * Math.sin(phi) * Math.cos(theta));
781
- const y = pos.y + (r * Math.sin(phi) * Math.sin(theta));
782
- const z = pos.z;
783
- vec.x = x * this.scale.x;
784
- vec.y = y * this.scale.y;
785
- vec.z = z * this.scale.z;
786
- }
787
- }
788
-
789
-
790
-
791
-
792
-
793
- export class NoiseModule {
794
- @serializable()
795
- damping!: boolean;
796
- @serializable()
797
- enabled!: boolean;
798
- @serializable()
799
- frequency!: number;
800
- @serializable()
801
- octaveCount!: number;
802
- @serializable()
803
- octaveMultiplier!: number;
804
- @serializable()
805
- octaveScale!: number;
806
- @serializable(MinMaxCurve)
807
- positionAmount!: MinMaxCurve;
808
- @serializable()
809
- quality!: number;
810
-
811
- @serializable(MinMaxCurve)
812
- remap!: MinMaxCurve;
813
- @serializable()
814
- remapEnabled!: boolean;
815
- @serializable()
816
- remapMultiplier!: number;
817
- @serializable(MinMaxCurve)
818
- remapX!: MinMaxCurve;
819
- @serializable()
820
- remapXMultiplier!: number;
821
- @serializable(MinMaxCurve)
822
- remapY!: MinMaxCurve;
823
- @serializable()
824
- remapYMultiplier!: number;
825
- @serializable(MinMaxCurve)
826
- remapZ!: MinMaxCurve;
827
- @serializable()
828
- remapZMultiplier!: number;
829
-
830
- @serializable()
831
- scrollSpeedMultiplier!: number;
832
- @serializable()
833
- separateAxes!: boolean;
834
- @serializable()
835
- strengthMultiplier!: number;
836
- @serializable(MinMaxCurve)
837
- strengthX!: MinMaxCurve;
838
- @serializable()
839
- strengthXMultiplier!: number;
840
- @serializable(MinMaxCurve)
841
- strengthY!: MinMaxCurve;
842
- @serializable()
843
- strengthYMultiplier!: number;
844
- @serializable(MinMaxCurve)
845
- strengthZ!: MinMaxCurve;
846
- @serializable()
847
- strengthZMultiplier!: number;
848
-
849
-
850
- private _noise?: NoiseFunction4D;
851
- private _time: number = 0;
852
-
853
- update(context: Context) {
854
- this._time += context.time.deltaTime * this.scrollSpeedMultiplier;
855
- }
856
-
857
- /** nebula implementations: */
858
- private _temp: Vector3 = new Vector3();
859
- apply(_index: number, pos: Vec3, vel: Vec3, _deltaTime: number, age: number, life: number) {
860
- if (!this.enabled) return;
861
- if (!this._noise) {
862
- this._noise = createNoise4D(() => 0);
863
- }
864
- const temp = this._temp.set(pos.x, pos.y, pos.z).multiplyScalar(this.frequency);
865
- const nx = this._noise(temp.x, temp.y, temp.z, this._time);
866
- const ny = this._noise(temp.x, temp.y, temp.z, this._time + 1000 * this.frequency);
867
- const nz = this._noise(temp.x, temp.y, temp.z, this._time + 2000 * this.frequency);
868
- this._temp.set(nx, ny, nz).normalize()
869
-
870
- const t = age / life;
871
- let strengthFactor = this.positionAmount.evaluate(t);
872
- if (!this.separateAxes) {
873
- if (this.strengthX) {
874
- strengthFactor *= this.strengthX.evaluate(t) * 1.5;
875
- }
876
- // strengthFactor *= this.strengthMultiplier;
877
- // strengthFactor *= deltaTime;
878
- this._temp.multiplyScalar(strengthFactor);
879
- }
880
- else {
881
- this._temp.x *= strengthFactor * this.strengthXMultiplier
882
- this._temp.y *= strengthFactor * this.strengthYMultiplier;
883
- this._temp.z *= strengthFactor * this.strengthZMultiplier;
884
- }
885
- // this._temp.setLength(strengthFactor * deltaTime);
886
- vel.x += this._temp.x;
887
- vel.y += this._temp.y;
888
- vel.z += this._temp.z;
889
- }
890
- }
891
-
892
- export enum ParticleSystemTrailMode {
893
- PerParticle,
894
- Ribbon,
895
- }
896
-
897
- export enum ParticleSystemTrailTextureMode {
898
- Stretch = 0,
899
- Tile = 1,
900
- DistributePerSegment = 2,
901
- RepeatPerSegment = 3,
902
- }
903
-
904
- export class TrailModule {
905
-
906
- @serializable()
907
- enabled!: boolean;
908
-
909
- @serializable()
910
- attachRibbonToTransform = false;
911
-
912
- @serializable(MinMaxGradient)
913
- colorOverLifetime!: MinMaxGradient;
914
-
915
- @serializable(MinMaxGradient)
916
- colorOverTrail!: MinMaxGradient;
917
-
918
- @serializable()
919
- dieWithParticles: boolean = true;
920
-
921
- @serializable()
922
- inheritParticleColor: boolean = true;
923
-
924
- @serializable(MinMaxCurve)
925
- lifetime!: MinMaxCurve;
926
- @serializable()
927
- lifetimeMultiplier!: number;
928
-
929
- @serializable()
930
- minVertexDistance: number = .2;
931
-
932
- @serializable()
933
- mode: ParticleSystemTrailMode = ParticleSystemTrailMode.PerParticle;
934
-
935
- @serializable()
936
- ratio: number = 1;
937
-
938
- @serializable()
939
- ribbonCount: number = 1;
940
-
941
- @serializable()
942
- shadowBias: number = 0;
943
-
944
- @serializable()
945
- sizeAffectsLifetime: boolean = false;
946
-
947
- @serializable()
948
- sizeAffectsWidth: boolean = false;
949
-
950
- @serializable()
951
- splitSubEmitterRibbons: boolean = false;
952
-
953
- @serializable()
954
- textureMode: ParticleSystemTrailTextureMode = ParticleSystemTrailTextureMode.Stretch;
955
-
956
- @serializable(MinMaxCurve)
957
- widthOverTrail!: MinMaxCurve;
958
- @serializable()
959
- widthOverTrailMultiplier!: number;
960
-
961
- @serializable()
962
- worldSpace: boolean = false;
963
-
964
- getWidth(size: number, _life01: number, pos01: number) {
965
- let res = this.widthOverTrail.evaluate(pos01);
966
- if (pos01 === 0) res = size;
967
- size *= res;
968
- return size;
969
- }
970
-
971
- getColor(color: Vector4, life01: number, pos01: number) {
972
- const overTrail = this.colorOverTrail.evaluate(pos01);
973
- const overLife = this.colorOverLifetime.evaluate(life01);
974
- color.x *= overTrail.r * overLife.r;
975
- color.y *= overTrail.g * overLife.g;
976
- color.z *= overTrail.b * overLife.b;
977
- color.w *= overTrail.alpha * overLife.alpha;
978
- }
979
- }
980
-
981
- export class VelocityOverLifetimeModule {
982
- @serializable()
983
- enabled!: boolean;
984
-
985
- /* orbital settings */
986
-
987
-
988
- @serializable()
989
- space: ParticleSystemSimulationSpace = ParticleSystemSimulationSpace.Local;
990
-
991
- @serializable(MinMaxCurve)
992
- speedModifier!: MinMaxCurve;
993
- @serializable()
994
- speedModifierMultiplier!: number;
995
- @serializable(MinMaxCurve)
996
- x!: MinMaxCurve;
997
- @serializable()
998
- xMultiplier!: number;
999
- @serializable(MinMaxCurve)
1000
- y!: MinMaxCurve;
1001
- @serializable()
1002
- yMultiplier!: number;
1003
- @serializable(MinMaxCurve)
1004
- z!: MinMaxCurve;
1005
- @serializable()
1006
- zMultiplier!: number;
1007
-
1008
- private _system?: IParticleSystem;
1009
- // private _worldRotation: Quaternion = new Quaternion();
1010
-
1011
- update(system: IParticleSystem) {
1012
- this._system = system;
1013
- }
1014
-
1015
- private _temp: Vector3 = new Vector3();
1016
-
1017
- apply(_index: number, _pos: Vec3, vel: Vec3, _dt: number, age: number, life: number) {
1018
- if (!this.enabled) return;
1019
- const t = age / life;
1020
-
1021
- const speed = this.speedModifier.evaluate(t) * this.speedModifierMultiplier;
1022
- const x = this.x.evaluate(t);
1023
- const y = this.y.evaluate(t);
1024
- const z = this.z.evaluate(t);
1025
- this._temp.set(-x, y, z);
1026
- if (this._system) {
1027
- if (this.space === ParticleSystemSimulationSpace.World) {
1028
- this._temp.applyQuaternion(this._system.worldQuaternionInverted);
1029
- }
1030
- if (this._system.main.simulationSpace === ParticleSystemSimulationSpace.World) {
1031
- this._temp.applyQuaternion(this._system.worldQuaternion);
1032
- }
1033
- }
1034
- vel.x += this._temp.x;
1035
- vel.y += this._temp.y;
1036
- vel.z += this._temp.z;
1037
- vel.x *= speed;
1038
- vel.y *= speed;
1039
- vel.z *= speed;
1040
- }
1041
- }
1042
-
1043
-
1044
-
1045
- enum ParticleSystemAnimationTimeMode {
1046
- Lifetime,
1047
- Speed,
1048
- FPS,
1049
- }
1050
-
1051
- enum ParticleSystemAnimationMode {
1052
- Grid,
1053
- Sprites,
1054
- }
1055
-
1056
- enum ParticleSystemAnimationRowMode {
1057
- Custom,
1058
- Random,
1059
- MeshIndex,
1060
- }
1061
-
1062
- enum ParticleSystemAnimationType {
1063
- WholeSheet,
1064
- SingleRow,
1065
- }
1066
-
1067
- export class TextureSheetAnimationModule {
1068
-
1069
- @serializable()
1070
- animation!: ParticleSystemAnimationType;
1071
-
1072
- @serializable()
1073
- enabled!: boolean;
1074
-
1075
- @serializable()
1076
- cycleCount!: number;
1077
-
1078
- @serializable(MinMaxCurve)
1079
- frameOverTime!: MinMaxCurve;
1080
- @serializable()
1081
- frameOverTimeMultiplier!: number;
1082
-
1083
- @serializable()
1084
- numTilesX!: number;
1085
- @serializable()
1086
- numTilesY!: number;
1087
-
1088
- @serializable(MinMaxCurve)
1089
- startFrame!: MinMaxCurve;
1090
- @serializable()
1091
- startFrameMultiplier!: number;
1092
-
1093
- @serializable()
1094
- rowMode!: ParticleSystemAnimationRowMode;
1095
- @serializable()
1096
- rowIndex!: number;
1097
-
1098
- @serializable()
1099
- spriteCount!: number;
1100
-
1101
- @serializable()
1102
- timeMode!: ParticleSystemAnimationTimeMode;
1103
-
1104
- private sampleOnceAtStart(): boolean {
1105
- if (this.timeMode === ParticleSystemAnimationTimeMode.Lifetime) {
1106
- switch (this.frameOverTime.mode) {
1107
- case ParticleSystemCurveMode.Constant:
1108
- case ParticleSystemCurveMode.TwoConstants:
1109
- case ParticleSystemCurveMode.TwoCurves:
1110
- case ParticleSystemCurveMode.Curve:
1111
- return true;
1112
- }
1113
- }
1114
- return false;
1115
- }
1116
-
1117
- getStartIndex(): number {
1118
- if (this.sampleOnceAtStart()) {
1119
- const start = Math.random();
1120
- return start * (this.numTilesX * this.numTilesY);
1121
- }
1122
- return 0;
1123
- }
1124
-
1125
- evaluate(t01: number): number | undefined {
1126
- if (this.sampleOnceAtStart()) {
1127
- return undefined;
1128
- }
1129
- return this.getIndex(t01);
1130
- }
1131
-
1132
- private getIndex(t01: number): number {
1133
- const tiles = this.numTilesX * this.numTilesY;
1134
- t01 = t01 * this.cycleCount;
1135
- let index = this.frameOverTime.evaluate(t01 % 1);
1136
- index *= this.frameOverTimeMultiplier;
1137
- index *= tiles;
1138
- index = index % tiles;
1139
- index = Math.floor(index);
1140
- return index;
1141
- }
1142
- }
1143
-
1144
-
1145
- export class RotationOverLifetimeModule {
1146
- @serializable()
1147
- enabled!: boolean;
1148
-
1149
- @serializable()
1150
- separateAxes!: boolean;
1151
-
1152
- @serializable(MinMaxCurve)
1153
- x!: MinMaxCurve;
1154
- @serializable()
1155
- xMultiplier!: number;
1156
- @serializable(MinMaxCurve)
1157
- y!: MinMaxCurve;
1158
- @serializable()
1159
- yMultiplier!: number;
1160
- @serializable(MinMaxCurve)
1161
- z!: MinMaxCurve;
1162
- @serializable()
1163
- zMultiplier!: number;
1164
-
1165
- evaluate(t01: number, t: number): number {
1166
- if (!this.enabled) return 0;
1167
- if (!this.separateAxes) {
1168
- const rot = this.z.evaluate(t01, t) * -1;
1169
- return rot;
1170
- }
1171
- return 0;
1172
- }
1173
- }
1174
-
1175
- export class RotationBySpeedModule {
1176
- @serializable()
1177
- enabled!: boolean;
1178
-
1179
- @serializable()
1180
- range!: Vec2;
1181
-
1182
- @serializable()
1183
- separateAxes!: boolean;
1184
-
1185
- @serializable(MinMaxCurve)
1186
- x!: MinMaxCurve;
1187
- @serializable()
1188
- xMultiplier!: number;
1189
- @serializable(MinMaxCurve)
1190
- y!: MinMaxCurve;
1191
- @serializable()
1192
- yMultiplier!: number;
1193
- @serializable(MinMaxCurve)
1194
- z!: MinMaxCurve;
1195
- @serializable()
1196
- zMultiplier!: number;
1197
-
1198
- evaluate(_t01: number, speed: number): number {
1199
- if (!this.enabled) return 0;
1200
- if (!this.separateAxes) {
1201
- const t = Mathf.lerp(this.range.x, this.range.y, speed);
1202
- const rot = this.z.evaluate(t) * -1;
1203
- return rot;
1204
- }
1205
- return 0;
1206
- }
1207
- }
1208
-
1209
-
1210
- export class LimitVelocityOverLifetimeModule {
1211
- @serializable()
1212
- enabled!: boolean;
1213
-
1214
- @serializable()
1215
- dampen!: number;
1216
-
1217
- @serializable(MinMaxCurve)
1218
- drag!: MinMaxCurve;
1219
- @serializable()
1220
- dragMultiplier!: number;
1221
-
1222
- @serializable(MinMaxCurve)
1223
- limit!: MinMaxCurve;
1224
- @serializable()
1225
- limitMultiplier!: number;
1226
-
1227
- @serializable()
1228
- separateAxes!: boolean;
1229
-
1230
- @serializable(MinMaxCurve)
1231
- limitX!: MinMaxCurve;
1232
- @serializable()
1233
- limitXMultiplier!: number;
1234
- @serializable(MinMaxCurve)
1235
- limitY!: MinMaxCurve;
1236
- @serializable()
1237
- limitYMultiplier!: number;
1238
- @serializable(MinMaxCurve)
1239
- limitZ!: MinMaxCurve;
1240
- @serializable()
1241
- limitZMultiplier!: number;
1242
-
1243
- @serializable()
1244
- multiplyDragByParticleSize: boolean = false;
1245
- @serializable()
1246
- multiplyDragByParticleVelocity: boolean = false;
1247
-
1248
- @serializable()
1249
- space!: ParticleSystemSimulationSpace;
1250
-
1251
- private _temp: Vector3 = new Vector3();
1252
- private _temp2: Vector3 = new Vector3();
1253
-
1254
- apply(_position: Vec3, baseVelocity: Vector3, currentVelocity: Vector3, _size: number, t01: number, _dt: number, _scale: number) {
1255
- if (!this.enabled) return;
1256
- // if (this.separateAxes) {
1257
- // // const maxX = this.limitX.evaluate(t01) * this.limitXMultiplier;
1258
- // // const maxY = this.limitY.evaluate(t01) * this.limitYMultiplier;
1259
- // // const maxZ = this.limitZ.evaluate(t01) * this.limitZMultiplier;
1260
-
1261
- // }
1262
- // else
1263
- {
1264
- const max = this.limit.evaluate(t01) * this.limitMultiplier;
1265
- const speed = baseVelocity.length();
1266
- if (speed > max) {
1267
- this._temp.copy(baseVelocity).normalize().multiplyScalar(max);
1268
- let t = this.dampen * .5;
1269
- // t *= scale;
1270
- baseVelocity.x = Mathf.lerp(baseVelocity.x, this._temp.x, t);
1271
- baseVelocity.y = Mathf.lerp(baseVelocity.y, this._temp.y, t);
1272
- baseVelocity.z = Mathf.lerp(baseVelocity.z, this._temp.z, t);
1273
-
1274
- // this._temp2.set(0, 0, 0);
1275
- currentVelocity.x = Mathf.lerp(currentVelocity.x, this._temp.x, t);
1276
- currentVelocity.y = Mathf.lerp(currentVelocity.y, this._temp.y, t);
1277
- currentVelocity.z = Mathf.lerp(currentVelocity.z, this._temp.z, t);
1278
- }
1279
- // vel.multiplyScalar(dragFactor);
1280
- }
1281
- // vel.x *= 0.3;
1282
- // vel.y *= 0.3;
1283
- // vel.z *= 0.3;
1284
- }
1285
- }
1286
-
1287
-
1288
- export enum ParticleSystemInheritVelocityMode {
1289
- Initial,
1290
- Current,
1291
- }
1292
-
1293
- export class InheritVelocityModule {
1294
-
1295
- @serializable()
1296
- enabled!: boolean;
1297
-
1298
- @serializable(MinMaxCurve)
1299
- curve!: MinMaxCurve;
1300
- @serializable()
1301
- curveMultiplier!: number;
1302
-
1303
- @serializable()
1304
- mode!: ParticleSystemInheritVelocityMode;
1305
-
1306
- system!: IParticleSystem;
1307
- private _lastWorldPosition!: Vector3;
1308
- private _velocity: Vector3 = new Vector3();
1309
- private _temp: Vector3 = new Vector3();
1310
-
1311
- update(_context: Context) {
1312
- if (!this.enabled) return;
1313
- if (this.system.worldspace === false) return;
1314
- if (this._lastWorldPosition) {
1315
- this._velocity.copy(this.system.worldPos).sub(this._lastWorldPosition).multiplyScalar(1 / this.system.deltaTime);
1316
- this._lastWorldPosition.copy(this.system.worldPos);
1317
- }
1318
- else {
1319
- this._velocity.set(0, 0, 0);
1320
- this._lastWorldPosition = this.system.worldPos.clone();
1321
- }
1322
- }
1323
-
1324
- // TODO: make work for subsystems
1325
- applyInitial(vel: Vector3) {
1326
- if (!this.enabled) return;
1327
- if (this.system.worldspace === false) return;
1328
- if (this.mode === ParticleSystemInheritVelocityMode.Initial) {
1329
- const factor = this.curve.evaluate(Math.random(), Math.random());
1330
- this._temp.copy(this._velocity).multiplyScalar(factor);
1331
- vel.add(this._temp);
1332
- }
1333
- }
1334
-
1335
- applyCurrent(vel: Vector3, t01: number, lerpFactor: number) {
1336
- if (!this.enabled) return;
1337
- if (this.system.worldspace === false) return;
1338
- if (this.mode === ParticleSystemInheritVelocityMode.Current) {
1339
- const factor = this.curve.evaluate(t01, lerpFactor);
1340
- this._temp.copy(this._velocity).multiplyScalar(factor);
1341
- vel.add(this._temp);
1342
- }
1343
- }
1344
- }
1345
-
1346
-
1347
- export class SizeBySpeedModule {
1348
- @serializable()
1349
- enabled!: boolean;
1350
-
1351
- @serializable(Vector2)
1352
- range!: Vector2;
1353
- @serializable()
1354
- separateAxes!: boolean;
1355
-
1356
- @serializable(MinMaxCurve)
1357
- size!: MinMaxCurve;
1358
- @serializable()
1359
- sizeMultiplier!: number;
1360
-
1361
- @serializable(MinMaxCurve)
1362
- x!: MinMaxCurve;
1363
- @serializable()
1364
- xMultiplier!: number;
1365
- @serializable(MinMaxCurve)
1366
- y!: MinMaxCurve;
1367
- @serializable()
1368
- yMultiplier!: number;
1369
- @serializable(MinMaxCurve)
1370
- z!: MinMaxCurve;
1371
- @serializable()
1372
- zMultiplier!: number;
1373
-
1374
- evaluate(vel: Vector3, _t01: number, lerpFactor: number, size: number): number {
1375
-
1376
- const speed = vel.length();
1377
- const x = Mathf.remap(speed, this.range.x, this.range.y, 0, 1);
1378
- const factor = this.size.evaluate(x, lerpFactor);
1379
- // return size;
1380
- return size * factor;
1381
- }
1382
- }
1383
-
1384
- export class ColorBySpeedModule {
1385
- @serializable()
1386
- enabled!: boolean;
1387
- @serializable(Vector2)
1388
- range!: Vector2;
1389
- @serializable(MinMaxGradient)
1390
- color!: MinMaxGradient;
1391
-
1392
- evaluate(vel: Vector3, lerpFactor: number, color: Vector4) {
1393
- const speed = vel.length();
1394
- const x = Mathf.remap(speed, this.range.x, this.range.y, 0, 1);
1395
- const res = this.color.evaluate(x, lerpFactor);
1396
- color.x *= res.r;
1397
- color.y *= res.g;
1398
- color.z *= res.b;
1399
- color.w *= res.alpha;
1400
- }
1
+ import { Color, Matrix4, Object3D, PointLightShadow, Quaternion, Vector3, Vector2, Euler, Vector4, DirectionalLightHelper } from "three";
2
+ import { Mathf } from "../engine/engine_math";
3
+ import { serializable } from "../engine/engine_serialization";
4
+ import { RGBAColor } from "./js-extensions/RGBAColor";
5
+ import { AnimationCurve } from "./AnimationCurve";
6
+ import { Vec2, Vec3 } from "../engine/engine_types";
7
+ import { Context } from "../engine/engine_setup";
8
+ import { EmitterShape, FrameOverLife, Particle, ShapeJSON } from "three.quarks";
9
+ import { createNoise4D, NoiseFunction4D } from 'simplex-noise';
10
+ import { Gizmos } from "../engine/engine_gizmos";
11
+ import { getParam } from "../engine/engine_utils";
12
+
13
+ const debug = getParam("debugparticles");
14
+
15
+ declare type Color4 = { r: number, g: number, b: number, a: number };
16
+ declare type ColorKey = { time: number, color: Color4 };
17
+ declare type AlphaKey = { time: number, alpha: number };
18
+
19
+ export interface IParticleSystem {
20
+ get currentParticles(): number;
21
+ get maxParticles(): number;
22
+ get time(): number;
23
+ get deltaTime(): number;
24
+ get duration(): number;
25
+ readonly main: MainModule;
26
+ get container(): Object3D;
27
+ get worldspace(): boolean;
28
+ get worldPos(): Vector3;
29
+ get worldQuaternion(): Quaternion;
30
+ get worldQuaternionInverted(): Quaternion;
31
+ get worldScale(): Vector3;
32
+ get matrixWorld(): Matrix4;
33
+ }
34
+
35
+
36
+ export enum ParticleSystemRenderMode {
37
+ Billboard = 0,
38
+ Stretch = 1,
39
+ HorizontalBillboard = 2,
40
+ VerticalBillboard = 3,
41
+ Mesh = 4,
42
+ // None = 5,
43
+ }
44
+
45
+
46
+ export class Gradient {
47
+ @serializable()
48
+ alphaKeys!: Array<AlphaKey>;
49
+ @serializable()
50
+ colorKeys!: Array<ColorKey>;
51
+
52
+ get duration(): number {
53
+ return 1;
54
+ }
55
+
56
+ evaluate(time: number, target: RGBAColor) {
57
+
58
+ // target.r = this.colorKeys[0].color.r;
59
+ // target.g = this.colorKeys[0].color.g;
60
+ // target.b = this.colorKeys[0].color.b;
61
+ // target.alpha = this.alphaKeys[0].alpha;
62
+ // return;
63
+
64
+ let closestAlpha: AlphaKey | undefined = undefined;
65
+ let closestAlphaIndex = 0;
66
+ let closestColor: ColorKey | null = null;
67
+ let closestColorIndex = 0;
68
+ for (let i = 0; i < this.alphaKeys.length; i++) {
69
+ const key = this.alphaKeys[i];
70
+ if (key.time < time || !closestAlpha) {
71
+ closestAlpha = key;
72
+ closestAlphaIndex = i;
73
+ }
74
+ }
75
+ for (let i = 0; i < this.colorKeys.length; i++) {
76
+ const key = this.colorKeys[i];
77
+ if (key.time < time || !closestColor) {
78
+ closestColor = key;
79
+ closestColorIndex = i;
80
+ }
81
+ }
82
+ if (closestColor) {
83
+ const hasNextColor = closestColorIndex + 1 < this.colorKeys.length;
84
+ if (hasNextColor) {
85
+ const nextColor = this.colorKeys[closestColorIndex + 1];
86
+ const t = Mathf.remap(time, closestColor.time, nextColor.time, 0, 1);
87
+ target.r = Mathf.lerp(closestColor.color.r, nextColor.color.r, t);
88
+ target.g = Mathf.lerp(closestColor.color.g, nextColor.color.g, t);
89
+ target.b = Mathf.lerp(closestColor.color.b, nextColor.color.b, t);
90
+ }
91
+ else {
92
+ target.r = closestColor.color.r;
93
+ target.g = closestColor.color.g;
94
+ target.b = closestColor.color.b;
95
+ }
96
+ }
97
+ if (closestAlpha) {
98
+ const hasNextAlpha = closestAlphaIndex + 1 < this.alphaKeys.length;
99
+ if (hasNextAlpha) {
100
+ const nextAlpha = this.alphaKeys[closestAlphaIndex + 1];
101
+ const t = Mathf.remap(time, closestAlpha.time, nextAlpha.time, 0, 1);
102
+ target.alpha = Mathf.lerp(closestAlpha.alpha, nextAlpha.alpha, t);
103
+ }
104
+ else {
105
+ target.alpha = closestAlpha.alpha;
106
+ }
107
+ }
108
+ return target;
109
+ }
110
+ }
111
+
112
+ export enum ParticleSystemCurveMode {
113
+ Constant = 0,
114
+ Curve = 1,
115
+ TwoCurves = 2,
116
+ TwoConstants = 3
117
+ }
118
+
119
+ export enum ParticleSystemGradientMode {
120
+ Color = 0,
121
+ Gradient = 1,
122
+ TwoColors = 2,
123
+ TwoGradients = 3,
124
+ RandomColor = 4,
125
+ }
126
+
127
+ export enum ParticleSystemSimulationSpace {
128
+ Local = 0,
129
+ World = 1,
130
+ Custom = 2
131
+ }
132
+
133
+ export enum ParticleSystemShapeType {
134
+ Sphere = 0,
135
+ SphereShell = 1,
136
+ Hemisphere = 2,
137
+ HemisphereShell = 3,
138
+ Cone = 4,
139
+ Box = 5,
140
+ Mesh = 6,
141
+ ConeShell = 7,
142
+ ConeVolume = 8,
143
+ ConeVolumeShell = 9,
144
+ Circle = 10,
145
+ CircleEdge = 11,
146
+ SingleSidedEdge = 12,
147
+ MeshRenderer = 13,
148
+ SkinnedMeshRenderer = 14,
149
+ BoxShell = 15,
150
+ BoxEdge = 16,
151
+ Donut = 17,
152
+ Rectangle = 18,
153
+ Sprite = 19,
154
+ SpriteRenderer = 20
155
+ }
156
+
157
+ export enum ParticleSystemShapeMultiModeValue {
158
+ Random = 0,
159
+ Loop = 1,
160
+ PingPong = 2,
161
+ BurstSpread = 3,
162
+ }
163
+
164
+ export class MinMaxCurve {
165
+ @serializable()
166
+ mode!: ParticleSystemCurveMode;
167
+ @serializable()
168
+ constant!: number;
169
+ @serializable()
170
+ constantMin!: number;
171
+ @serializable()
172
+ constantMax!: number;
173
+ @serializable(AnimationCurve)
174
+ curve?: AnimationCurve;
175
+ @serializable(AnimationCurve)
176
+ curveMin?: AnimationCurve;
177
+ @serializable(AnimationCurve)
178
+ curveMax?: AnimationCurve;
179
+ @serializable()
180
+ curveMultiplier?: number;
181
+
182
+ evaluate(t01: number, lerpFactor?: number): number {
183
+ const t = lerpFactor === undefined ? Math.random() : lerpFactor;
184
+ switch (this.mode) {
185
+ case ParticleSystemCurveMode.Constant:
186
+ return this.constant;
187
+ case ParticleSystemCurveMode.Curve:
188
+ t01 = Mathf.clamp01(t01);
189
+ return this.curve!.evaluate(t01) * this.curveMultiplier!;
190
+ case ParticleSystemCurveMode.TwoCurves:
191
+ const t1 = t01 * this.curveMin!.duration;
192
+ const t2 = t01 * this.curveMax!.duration;
193
+ return Mathf.lerp(this.curveMin!.evaluate(t1), this.curveMax!.evaluate(t2), t % 1) * this.curveMultiplier!;
194
+ case ParticleSystemCurveMode.TwoConstants:
195
+ return Mathf.lerp(this.constantMin, this.constantMax, t % 1)
196
+ default:
197
+ this.curveMax!.evaluate(t01) * this.curveMultiplier!;
198
+ break;
199
+ }
200
+ return 0;
201
+ }
202
+
203
+ getMax(): number {
204
+ switch (this.mode) {
205
+ case ParticleSystemCurveMode.Constant:
206
+ return this.constant;
207
+ case ParticleSystemCurveMode.Curve:
208
+ return this.getMaxFromCurve(this.curve!) * this.curveMultiplier!;
209
+ case ParticleSystemCurveMode.TwoCurves:
210
+ return Math.max(this.getMaxFromCurve(this.curveMin), this.getMaxFromCurve(this.curveMax)) * this.curveMultiplier!;
211
+ case ParticleSystemCurveMode.TwoConstants:
212
+ return Math.max(this.constantMin, this.constantMax);
213
+ default:
214
+ return 0;
215
+ }
216
+ }
217
+
218
+ private getMaxFromCurve(curve?: AnimationCurve) {
219
+ if (!curve) return 0;
220
+ let maxNumber = Number.MIN_VALUE;
221
+ for (let i = 0; i < curve!.keys.length; i++) {
222
+ const key = curve!.keys[i];
223
+ if (key.value > maxNumber) {
224
+ maxNumber = key.value;
225
+ }
226
+ }
227
+ return maxNumber;
228
+ }
229
+ }
230
+
231
+ export class MinMaxGradient {
232
+ mode!: ParticleSystemGradientMode;
233
+ @serializable(RGBAColor)
234
+ color!: RGBAColor;
235
+ @serializable(RGBAColor)
236
+ colorMin!: RGBAColor;
237
+ @serializable(RGBAColor)
238
+ colorMax!: RGBAColor;
239
+ @serializable(Gradient)
240
+ gradient!: Gradient;
241
+ @serializable(Gradient)
242
+ gradientMin!: Gradient;
243
+ @serializable(Gradient)
244
+ gradientMax!: Gradient;
245
+
246
+ private static _temp: RGBAColor = new RGBAColor(0, 0, 0, 1);
247
+ private static _temp2: RGBAColor = new RGBAColor(0, 0, 0, 1);
248
+
249
+ evaluate(t01: number, lerpFactor?: number): RGBAColor {
250
+ const t = lerpFactor === undefined ? Math.random() : lerpFactor;
251
+ switch (this.mode) {
252
+ case ParticleSystemGradientMode.Color:
253
+ return this.color;
254
+ case ParticleSystemGradientMode.Gradient:
255
+ this.gradient.evaluate(t01, MinMaxGradient._temp);
256
+ return MinMaxGradient._temp
257
+ case ParticleSystemGradientMode.TwoColors:
258
+ const col1 = MinMaxGradient._temp.lerpColors(this.colorMin, this.colorMax, t);
259
+ return col1;
260
+ case ParticleSystemGradientMode.TwoGradients:
261
+ this.gradientMin.evaluate(t01, MinMaxGradient._temp);
262
+ this.gradientMax.evaluate(t01, MinMaxGradient._temp2);
263
+ return MinMaxGradient._temp.lerp(MinMaxGradient._temp2, t);
264
+
265
+ }
266
+ // console.warn("Not implemented", ParticleSystemGradientMode[this.mode]);
267
+ MinMaxGradient._temp.set(0xff00ff)
268
+ MinMaxGradient._temp.alpha = 1;
269
+ return MinMaxGradient._temp;
270
+ }
271
+ }
272
+
273
+ declare type ParticleSystemScalingMode = {
274
+ Hierarchy: number;
275
+ Local: number;
276
+ Shape: number;
277
+ }
278
+
279
+ export class MainModule {
280
+ cullingMode!: number;
281
+ duration!: number;
282
+ emitterVelocityMode!: number;
283
+ flipRotation!: number;
284
+ @serializable(MinMaxCurve)
285
+ gravityModifier!: MinMaxCurve;
286
+ gravityModifierMultiplier!: number;
287
+ loop!: boolean;
288
+ maxParticles!: number;
289
+ playOnAwake!: boolean;
290
+ prewarm!: boolean;
291
+ ringBufferLoopRange!: { x: number, y: number };
292
+ ringBufferMode!: boolean;
293
+ scalingMode!: ParticleSystemScalingMode;
294
+ simulationSpace!: ParticleSystemSimulationSpace;
295
+ simulationSpeed!: number;
296
+ @serializable(MinMaxGradient)
297
+ startColor!: MinMaxGradient;
298
+ @serializable(MinMaxCurve)
299
+ startDelay!: MinMaxCurve;
300
+ startDelayMultiplier!: number;
301
+ @serializable(MinMaxCurve)
302
+ startLifetime!: MinMaxCurve;
303
+ startLifetimeMultiplier!: number;
304
+ @serializable(MinMaxCurve)
305
+ startRotation!: MinMaxCurve;
306
+ startRotationMultiplier!: number;
307
+ startRotation3D!: boolean;
308
+ @serializable(MinMaxCurve)
309
+ startRotationX!: MinMaxCurve;
310
+ startRotationXMultiplier!: number;
311
+ @serializable(MinMaxCurve)
312
+ startRotationY!: MinMaxCurve;
313
+ startRotationYMultiplier!: number;
314
+ @serializable(MinMaxCurve)
315
+ startRotationZ!: MinMaxCurve;
316
+ startRotationZMultiplier!: number;
317
+ @serializable(MinMaxCurve)
318
+ startSize!: MinMaxCurve;
319
+ startSize3D!: boolean;
320
+ startSizeMultiplier!: number;
321
+ @serializable(MinMaxCurve)
322
+ startSizeX!: MinMaxCurve;
323
+ startSizeXMultiplier!: number;
324
+ @serializable(MinMaxCurve)
325
+ startSizeY!: MinMaxCurve;
326
+ startSizeYMultiplier!: number;
327
+ @serializable(MinMaxCurve)
328
+ startSizeZ!: MinMaxCurve;
329
+ startSizeZMultiplier!: number;
330
+ @serializable(MinMaxCurve)
331
+ startSpeed!: MinMaxCurve;
332
+ startSpeedMultiplier!: number;
333
+ stopAction!: number;
334
+ useUnscaledTime!: boolean;
335
+ }
336
+
337
+
338
+ export class ParticleBurst {
339
+ cycleCount!: number;
340
+ maxCount!: number;
341
+ minCount!: number;
342
+ probability!: number;
343
+ repeatInterval!: number;
344
+ time!: number;
345
+ count!: {
346
+ constant: number;
347
+ constantMax: number;
348
+ constantMin: number;
349
+ curve?: AnimationCurve;
350
+ curveMax?: AnimationCurve;
351
+ curveMin?: AnimationCurve;
352
+ curveMultiplier?: number;
353
+ mode: ParticleSystemCurveMode;
354
+ }
355
+
356
+
357
+ private _performed: number = 0;
358
+
359
+
360
+ reset() {
361
+ this._performed = 0;
362
+ }
363
+ run(time: number): number {
364
+ if (time <= this.time) {
365
+ this.reset();
366
+ return 0;
367
+ }
368
+ let amount = 0;
369
+ if (this.cycleCount === 0 || this._performed < this.cycleCount) {
370
+ const nextTime = this.time + this.repeatInterval * this._performed;
371
+ if (time >= nextTime) {
372
+ this._performed += 1;
373
+ if (Math.random() < this.probability) {
374
+ switch (this.count.mode) {
375
+ case ParticleSystemCurveMode.Constant:
376
+ amount = this.count.constant;
377
+ break;
378
+ case ParticleSystemCurveMode.TwoConstants:
379
+ amount = Mathf.lerp(this.count.constantMin, this.count.constantMax, Math.random());
380
+ break;
381
+ case ParticleSystemCurveMode.Curve:
382
+ amount = this.count.curve!.evaluate(Math.random());
383
+ break;
384
+ case ParticleSystemCurveMode.TwoCurves:
385
+ const t = Math.random();
386
+ amount = Mathf.lerp(this.count.curveMin!.evaluate(t), this.count.curveMax!.evaluate(t), Math.random());
387
+ break;
388
+ }
389
+ }
390
+ }
391
+ }
392
+ return amount;
393
+ }
394
+ }
395
+
396
+ export class EmissionModule {
397
+
398
+ @serializable()
399
+ enabled!: boolean;
400
+
401
+
402
+ get burstCount() {
403
+ return this.bursts?.length ?? 0;
404
+ }
405
+
406
+ @serializable()
407
+ bursts!: ParticleBurst[];
408
+
409
+ @serializable(MinMaxCurve)
410
+ rateOverTime!: MinMaxCurve;
411
+ @serializable()
412
+ rateOverTimeMultiplier!: number;
413
+
414
+ @serializable(MinMaxCurve)
415
+ rateOverDistance!: MinMaxCurve;
416
+ @serializable()
417
+ rateOverDistanceMultiplier!: number;
418
+
419
+
420
+ /** set from system */
421
+ system!: IParticleSystem;
422
+
423
+ reset() {
424
+ this.bursts?.forEach(b => b.reset());
425
+ }
426
+
427
+ getBurst() {
428
+ let amount = 0;
429
+ if (this.burstCount > 0) {
430
+ for (let i = 0; i < this.burstCount; i++) {
431
+ const burst = this.bursts[i];
432
+ if (burst.time >= this.system.time) {
433
+ burst.reset();
434
+ }
435
+ amount += Math.round(burst.run(this.system.time));
436
+ }
437
+ }
438
+ return amount;
439
+ }
440
+ }
441
+
442
+ export class ColorOverLifetimeModule {
443
+ enabled!: boolean;
444
+ @serializable(MinMaxGradient)
445
+ color!: MinMaxGradient;
446
+ }
447
+
448
+ export class SizeOverLifetimeModule {
449
+ enabled!: boolean;
450
+ separateAxes!: boolean;
451
+ @serializable(MinMaxCurve)
452
+ size!: MinMaxCurve;
453
+ sizeMultiplier!: number;
454
+ @serializable(MinMaxCurve)
455
+ x!: MinMaxCurve;
456
+ xMultiplier!: number;
457
+ @serializable(MinMaxCurve)
458
+ y!: MinMaxCurve;
459
+ yMultiplier!: number;
460
+ @serializable(MinMaxCurve)
461
+ z!: MinMaxCurve;
462
+ zMultiplier!: number;
463
+
464
+ private _time: number = 0;
465
+ private _temp = new Vector3();
466
+
467
+ evaluate(t01: number, target?: Vec3, lerpFactor?: number) {
468
+ if (!target) target = this._temp;
469
+
470
+ if (!this.enabled) {
471
+ target.x = target.y = target.z = 1;
472
+ return target;
473
+ }
474
+
475
+ if (!this.separateAxes) {
476
+ const scale = this.size.evaluate(t01, lerpFactor) * this.sizeMultiplier;
477
+ target.x = scale;
478
+ // target.y = scale;
479
+ // target.z = scale;
480
+ }
481
+ else {
482
+ target.x = this.x.evaluate(t01, lerpFactor) * this.xMultiplier;
483
+ target.y = this.y.evaluate(t01, lerpFactor) * this.yMultiplier;
484
+ target.z = this.z.evaluate(t01, lerpFactor) * this.zMultiplier;
485
+ }
486
+ return target;
487
+ }
488
+ }
489
+
490
+ export class ShapeModule implements EmitterShape {
491
+
492
+ // Emittershape start
493
+ get type(): string {
494
+ return ParticleSystemShapeType[this.shapeType];
495
+ }
496
+ initialize(particle: Particle): void {
497
+ this.getPosition();
498
+ particle.position.copy(this._vector);
499
+ }
500
+ toJSON(): ShapeJSON {
501
+ return this;
502
+ }
503
+ clone(): EmitterShape {
504
+ return new ShapeModule();
505
+ }
506
+ // EmitterShape end
507
+
508
+ @serializable()
509
+ shapeType: ParticleSystemShapeType = ParticleSystemShapeType.Box;
510
+ @serializable()
511
+ enabled: boolean = true;
512
+ @serializable()
513
+ alignToDirection: boolean = false;
514
+ @serializable()
515
+ angle: number = 0;
516
+ @serializable()
517
+ arc: number = 360;
518
+ @serializable()
519
+ arcSpread!: number;
520
+ @serializable()
521
+ arcSpeedMultiplier!: number;
522
+ @serializable()
523
+ arcMode!: ParticleSystemShapeMultiModeValue;
524
+
525
+
526
+ @serializable(Vector3)
527
+ boxThickness!: Vector3;
528
+ @serializable(Vector3)
529
+ position!: Vector3;
530
+ @serializable(Vector3)
531
+ rotation!: Vector3;
532
+ private _rotation: Euler = new Euler();
533
+ @serializable(Vector3)
534
+ scale!: Vector3;
535
+
536
+ @serializable()
537
+ radius!: number;
538
+ @serializable()
539
+ radiusThickness!: number;
540
+ @serializable()
541
+ sphericalDirectionAmount!: number;
542
+ @serializable()
543
+ randomDirectionAmount!: number;
544
+ @serializable()
545
+ randomPositionAmount!: number;
546
+
547
+ private system!: IParticleSystem;
548
+ private _space?: ParticleSystemSimulationSpace;
549
+ private readonly _worldSpaceMatrix: Matrix4 = new Matrix4();
550
+ private readonly _worldSpaceMatrixInverse: Matrix4 = new Matrix4();
551
+
552
+ constructor() {
553
+ if (debug)
554
+ console.log(this);
555
+ }
556
+
557
+ update(system: IParticleSystem, _context: Context, simulationSpace: ParticleSystemSimulationSpace, obj: Object3D) {
558
+ this.system = system;
559
+ this._space = simulationSpace;
560
+ if (simulationSpace === ParticleSystemSimulationSpace.World) {
561
+ this._worldSpaceMatrix.copy(obj.matrixWorld);
562
+ // set scale to 1
563
+ this._worldSpaceMatrix.elements[0] = 1;
564
+ this._worldSpaceMatrix.elements[5] = 1;
565
+ this._worldSpaceMatrix.elements[10] = 1;
566
+ this._worldSpaceMatrixInverse.copy(this._worldSpaceMatrix).invert();
567
+ }
568
+ }
569
+
570
+ private applyRotation(vector: Vector3) {
571
+ const isRotated = this.rotation.x !== 0 || this.rotation.y !== 0 || this.rotation.z !== 0;
572
+ if (isRotated) {
573
+ // console.log(this._rotation);
574
+ // TODO: we need to convert this to threejs euler
575
+ this._rotation.x = Mathf.toRadians(this.rotation.x);
576
+ this._rotation.y = Mathf.toRadians(this.rotation.y);
577
+ this._rotation.z = Mathf.toRadians(this.rotation.z);
578
+ this._rotation.order = 'ZYX';
579
+ vector.applyEuler(this._rotation);
580
+ // this._quat.setFromEuler(this._rotation);
581
+ // // this._quat.invert();
582
+ // this._quat.x *= -1;
583
+ // // this._quat.y *= -1;
584
+ // // this._quat.z *= -1;
585
+ // this._quat.w *= -1;
586
+ // vector.applyQuaternion(this._quat);
587
+
588
+ }
589
+ return isRotated;
590
+ }
591
+
592
+ /** nebula implementations: */
593
+
594
+ /** initializer implementation */
595
+ private _vector: Vector3 = new Vector3(0, 0, 0);
596
+ private _temp: Vector3 = new Vector3(0, 0, 0);
597
+ /** called by nebula on initialize */
598
+ get vector() {
599
+ return this._vector;
600
+ }
601
+ getPosition(): void {
602
+ this._vector.set(0, 0, 0);
603
+ const pos = this._temp.copy(this.position);
604
+ const isWorldSpace = this._space === ParticleSystemSimulationSpace.World;
605
+ if (isWorldSpace) {
606
+ pos.applyQuaternion(this.system.worldQuaternion);
607
+ }
608
+ let radius = this.radius;
609
+ if (isWorldSpace) radius *= this.system.worldScale.x;
610
+ if (this.enabled) {
611
+ switch (this.shapeType) {
612
+ case ParticleSystemShapeType.Box:
613
+ if (debug) Gizmos.DrawBox(this.position, this.scale, 0xdddddd, 1);
614
+ this._vector.x = Math.random() * this.scale.x - this.scale.x / 2;
615
+ this._vector.y = Math.random() * this.scale.y - this.scale.y / 2;
616
+ this._vector.z = Math.random() * this.scale.z - this.scale.z / 2;
617
+ this._vector.add(pos);
618
+ break;
619
+ case ParticleSystemShapeType.Cone:
620
+ this.randomConePoint(this.position, this.angle, radius, this.radiusThickness, this.arc, this.arcMode, this._vector);
621
+ break;
622
+ case ParticleSystemShapeType.Sphere:
623
+ this.randomSpherePoint(this.position, radius, this.radiusThickness, this.arc, this._vector);
624
+ break;
625
+ case ParticleSystemShapeType.Circle:
626
+ this.randomCirclePoint(this.position, radius, this.radiusThickness, this.arc, this._vector);
627
+ break;
628
+ default:
629
+ this._vector.set(0, 0, 0);
630
+ break;
631
+ // case ParticleSystemShapeType.Hemisphere:
632
+ // randomSpherePoint(this.position.x, this.position.y, this.position.z, this.radius, this.radiusThickness, 180, this._vector);
633
+ // break;
634
+ }
635
+
636
+ this.randomizePosition(this._vector, this.randomPositionAmount);
637
+ }
638
+
639
+ this.applyRotation(this._vector);
640
+
641
+ if (isWorldSpace) {
642
+ this._vector.applyQuaternion(this.system.worldQuaternion);
643
+ this._vector.add(this.system.worldPos);
644
+ }
645
+
646
+ if (debug) {
647
+ Gizmos.DrawSphere(this._vector, .03, 0xff0000, .5, true);
648
+ }
649
+ }
650
+
651
+
652
+
653
+ private _dir: Vector3 = new Vector3();
654
+
655
+ getDirection(pos: Vec3): Vector3 {
656
+ if (!this.enabled) {
657
+ this._dir.set(0, 0, 1);
658
+ return this._dir;
659
+ }
660
+ switch (this.shapeType) {
661
+ case ParticleSystemShapeType.Box:
662
+ this._dir.set(0, 0, 1);
663
+ break;
664
+ case ParticleSystemShapeType.Cone:
665
+ this._dir.set(0, 0, 1);
666
+ // apply cone angle
667
+ // this._dir.applyAxisAngle(new Vector3(0, 1, 0), Mathf.toRadians(this.angle));
668
+ break;
669
+ case ParticleSystemShapeType.Circle:
670
+ case ParticleSystemShapeType.Sphere:
671
+ const rx = pos.x;
672
+ const ry = pos.y;
673
+ const rz = pos.z;
674
+ this._dir.set(rx, ry, rz)
675
+ if (this.system?.worldspace)
676
+ this._dir.sub(this.system.worldPos)
677
+ else
678
+ this._dir.sub(this.position)
679
+ break;
680
+ default:
681
+ this._dir.set(0, 0, 1);
682
+ break;
683
+ }
684
+ if (this._space === ParticleSystemSimulationSpace.World) {
685
+ this._dir.applyQuaternion(this.system.worldQuaternion);
686
+ }
687
+ this.applyRotation(this._dir);
688
+ this._dir.normalize();
689
+ this.spherizeDirection(this._dir, this.sphericalDirectionAmount);
690
+ this.randomizeDirection(this._dir, this.randomDirectionAmount);
691
+ if (debug) {
692
+ Gizmos.DrawSphere(pos, .01, 0x883300, .5, true);
693
+ Gizmos.DrawDirection(pos, this._dir, 0x883300, .5, true);
694
+ }
695
+ return this._dir;
696
+ }
697
+
698
+ private static _randomQuat = new Quaternion();
699
+ private static _tempVec = new Vector3();
700
+
701
+ private randomizePosition(pos: Vector3, amount: number) {
702
+ if (amount <= 0) return;
703
+ const rp = ShapeModule._tempVec;
704
+ rp.set(Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1);
705
+ rp.x *= amount * this.scale.x;
706
+ rp.y *= amount * this.scale.y;
707
+ rp.z *= amount * this.scale.z;
708
+ pos.add(rp);
709
+ }
710
+
711
+ private randomizeDirection(direction: Vector3, amount: number) {
712
+ if (amount === 0) return;
713
+ const randomQuat = ShapeModule._randomQuat;
714
+ const tempVec = ShapeModule._tempVec;
715
+ tempVec.set(Math.random() - .5, Math.random() - .5, Math.random() - .5).normalize();
716
+ randomQuat.setFromAxisAngle(tempVec, amount * Math.random() * Math.PI);
717
+ direction.applyQuaternion(randomQuat);
718
+ }
719
+
720
+ private spherizeDirection(dir: Vector3, amount: number) {
721
+ if (amount === 0) return;
722
+ const theta = Math.random() * Math.PI * 2;
723
+ const phi = Math.acos(1 - Math.random() * 2);
724
+ const x = Math.sin(phi) * Math.cos(theta);
725
+ const y = Math.sin(phi) * Math.sin(theta);
726
+ const z = Math.cos(phi);
727
+ const v = new Vector3(x, y, z);
728
+ dir.lerp(v, amount);
729
+ }
730
+
731
+ private randomSpherePoint(pos: Vec3, radius: number, thickness: number, arc: number, vec: Vec3) {
732
+ const u = Math.random();
733
+ const v = Math.random();
734
+ const theta = 2 * Math.PI * u * (arc / 360);
735
+ const phi = Math.acos(2 * v - 1);
736
+ const r = Mathf.lerp(1, 1 - (Math.pow(1 - Math.random(), Math.PI)), thickness) * (radius);
737
+ const x = pos.x + this.scale.x * (-r * Math.sin(phi) * Math.cos(theta));
738
+ const y = pos.y + this.scale.y * (r * Math.sin(phi) * Math.sin(theta));
739
+ const z = pos.z + this.scale.z * (r * Math.cos(phi));
740
+ vec.x = x;
741
+ vec.y = y;
742
+ vec.z = z;
743
+ }
744
+
745
+ private randomCirclePoint(pos:Vec3, radius:number, thickness:number, arg:number, vec:Vec3){
746
+ const u = Math.random();
747
+ const theta = 2 * Math.PI * u * (arg / 360);
748
+ const r = Mathf.lerp(1, 1 - (Math.pow(1 - Math.random(), Math.PI)), thickness) * (radius);
749
+ const x = pos.x + this.scale.x * r * Math.cos(theta);
750
+ const y = pos.y + this.scale.y * r * Math.sin(theta);
751
+ const z = pos.z;
752
+ vec.x = x;
753
+ vec.y = y;
754
+ vec.z = z;
755
+ }
756
+
757
+ private _loopTime: number = 0;
758
+ private _loopDirection: number = 1;
759
+
760
+ private randomConePoint(pos: Vec3, _angle: number, radius: number, thickness: number, arc: number, arcMode: ParticleSystemShapeMultiModeValue, vec: Vec3) {
761
+ let u = 0;
762
+ let v = 0;
763
+ switch (arcMode) {
764
+ case ParticleSystemShapeMultiModeValue.Random:
765
+ u = Math.random();
766
+ v = Math.random();
767
+ break;
768
+ case ParticleSystemShapeMultiModeValue.PingPong:
769
+ if (this._loopTime > 1) this._loopDirection = -1;
770
+ if (this._loopTime < 0) this._loopDirection = 1;
771
+ // continue with loop
772
+
773
+ case ParticleSystemShapeMultiModeValue.Loop:
774
+ u = .5;
775
+ v = Math.random()
776
+ this._loopTime += this.system.deltaTime * this._loopDirection;
777
+ break;
778
+ }
779
+
780
+ let theta = 2 * Math.PI * u * (arc / 360);
781
+ switch (arcMode) {
782
+ case ParticleSystemShapeMultiModeValue.PingPong:
783
+ case ParticleSystemShapeMultiModeValue.Loop:
784
+ theta += Math.PI + .5;
785
+ theta += this._loopTime * Math.PI * 2;
786
+ theta %= Mathf.toRadians(arc);
787
+ break;
788
+ }
789
+
790
+ const phi = Math.acos(2 * v - 1);
791
+ const r = Mathf.lerp(1, 1 - (Math.pow(1 - Math.random(), Math.PI)), thickness) * radius;
792
+ const x = pos.x + (-r * Math.sin(phi) * Math.cos(theta));
793
+ const y = pos.y + (r * Math.sin(phi) * Math.sin(theta));
794
+ const z = pos.z;
795
+ vec.x = x * this.scale.x;
796
+ vec.y = y * this.scale.y;
797
+ vec.z = z * this.scale.z;
798
+ }
799
+ }
800
+
801
+
802
+
803
+
804
+
805
+ export class NoiseModule {
806
+ @serializable()
807
+ damping!: boolean;
808
+ @serializable()
809
+ enabled!: boolean;
810
+ @serializable()
811
+ frequency!: number;
812
+ @serializable()
813
+ octaveCount!: number;
814
+ @serializable()
815
+ octaveMultiplier!: number;
816
+ @serializable()
817
+ octaveScale!: number;
818
+ @serializable(MinMaxCurve)
819
+ positionAmount!: MinMaxCurve;
820
+ @serializable()
821
+ quality!: number;
822
+
823
+ @serializable(MinMaxCurve)
824
+ remap!: MinMaxCurve;
825
+ @serializable()
826
+ remapEnabled!: boolean;
827
+ @serializable()
828
+ remapMultiplier!: number;
829
+ @serializable(MinMaxCurve)
830
+ remapX!: MinMaxCurve;
831
+ @serializable()
832
+ remapXMultiplier!: number;
833
+ @serializable(MinMaxCurve)
834
+ remapY!: MinMaxCurve;
835
+ @serializable()
836
+ remapYMultiplier!: number;
837
+ @serializable(MinMaxCurve)
838
+ remapZ!: MinMaxCurve;
839
+ @serializable()
840
+ remapZMultiplier!: number;
841
+
842
+ @serializable()
843
+ scrollSpeedMultiplier!: number;
844
+ @serializable()
845
+ separateAxes!: boolean;
846
+ @serializable()
847
+ strengthMultiplier!: number;
848
+ @serializable(MinMaxCurve)
849
+ strengthX!: MinMaxCurve;
850
+ @serializable()
851
+ strengthXMultiplier!: number;
852
+ @serializable(MinMaxCurve)
853
+ strengthY!: MinMaxCurve;
854
+ @serializable()
855
+ strengthYMultiplier!: number;
856
+ @serializable(MinMaxCurve)
857
+ strengthZ!: MinMaxCurve;
858
+ @serializable()
859
+ strengthZMultiplier!: number;
860
+
861
+
862
+ private _noise?: NoiseFunction4D;
863
+ private _time: number = 0;
864
+
865
+ update(context: Context) {
866
+ this._time += context.time.deltaTime * this.scrollSpeedMultiplier;
867
+ }
868
+
869
+ /** nebula implementations: */
870
+ private _temp: Vector3 = new Vector3();
871
+ apply(_index: number, pos: Vec3, vel: Vec3, _deltaTime: number, age: number, life: number) {
872
+ if (!this.enabled) return;
873
+ if (!this._noise) {
874
+ this._noise = createNoise4D(() => 0);
875
+ }
876
+ const temp = this._temp.set(pos.x, pos.y, pos.z).multiplyScalar(this.frequency);
877
+ const nx = this._noise(temp.x, temp.y, temp.z, this._time);
878
+ const ny = this._noise(temp.x, temp.y, temp.z, this._time + 1000 * this.frequency);
879
+ const nz = this._noise(temp.x, temp.y, temp.z, this._time + 2000 * this.frequency);
880
+ this._temp.set(nx, ny, nz).normalize()
881
+
882
+ const t = age / life;
883
+ let strengthFactor = this.positionAmount.evaluate(t);
884
+ if (!this.separateAxes) {
885
+ if (this.strengthX) {
886
+ strengthFactor *= this.strengthX.evaluate(t) * 1.5;
887
+ }
888
+ // strengthFactor *= this.strengthMultiplier;
889
+ // strengthFactor *= deltaTime;
890
+ this._temp.multiplyScalar(strengthFactor);
891
+ }
892
+ else {
893
+ this._temp.x *= strengthFactor * this.strengthXMultiplier
894
+ this._temp.y *= strengthFactor * this.strengthYMultiplier;
895
+ this._temp.z *= strengthFactor * this.strengthZMultiplier;
896
+ }
897
+ // this._temp.setLength(strengthFactor * deltaTime);
898
+ vel.x += this._temp.x;
899
+ vel.y += this._temp.y;
900
+ vel.z += this._temp.z;
901
+ }
902
+ }
903
+
904
+ export enum ParticleSystemTrailMode {
905
+ PerParticle,
906
+ Ribbon,
907
+ }
908
+
909
+ export enum ParticleSystemTrailTextureMode {
910
+ Stretch = 0,
911
+ Tile = 1,
912
+ DistributePerSegment = 2,
913
+ RepeatPerSegment = 3,
914
+ }
915
+
916
+ export class TrailModule {
917
+
918
+ @serializable()
919
+ enabled!: boolean;
920
+
921
+ @serializable()
922
+ attachRibbonToTransform = false;
923
+
924
+ @serializable(MinMaxGradient)
925
+ colorOverLifetime!: MinMaxGradient;
926
+
927
+ @serializable(MinMaxGradient)
928
+ colorOverTrail!: MinMaxGradient;
929
+
930
+ @serializable()
931
+ dieWithParticles: boolean = true;
932
+
933
+ @serializable()
934
+ inheritParticleColor: boolean = true;
935
+
936
+ @serializable(MinMaxCurve)
937
+ lifetime!: MinMaxCurve;
938
+ @serializable()
939
+ lifetimeMultiplier!: number;
940
+
941
+ @serializable()
942
+ minVertexDistance: number = .2;
943
+
944
+ @serializable()
945
+ mode: ParticleSystemTrailMode = ParticleSystemTrailMode.PerParticle;
946
+
947
+ @serializable()
948
+ ratio: number = 1;
949
+
950
+ @serializable()
951
+ ribbonCount: number = 1;
952
+
953
+ @serializable()
954
+ shadowBias: number = 0;
955
+
956
+ @serializable()
957
+ sizeAffectsLifetime: boolean = false;
958
+
959
+ @serializable()
960
+ sizeAffectsWidth: boolean = false;
961
+
962
+ @serializable()
963
+ splitSubEmitterRibbons: boolean = false;
964
+
965
+ @serializable()
966
+ textureMode: ParticleSystemTrailTextureMode = ParticleSystemTrailTextureMode.Stretch;
967
+
968
+ @serializable(MinMaxCurve)
969
+ widthOverTrail!: MinMaxCurve;
970
+ @serializable()
971
+ widthOverTrailMultiplier!: number;
972
+
973
+ @serializable()
974
+ worldSpace: boolean = false;
975
+
976
+ getWidth(size: number, _life01: number, pos01: number) {
977
+ let res = this.widthOverTrail.evaluate(pos01);
978
+ if (pos01 === 0) res = size;
979
+ size *= res;
980
+ return size;
981
+ }
982
+
983
+ getColor(color: Vector4, life01: number, pos01: number) {
984
+ const overTrail = this.colorOverTrail.evaluate(pos01);
985
+ const overLife = this.colorOverLifetime.evaluate(life01);
986
+ color.x *= overTrail.r * overLife.r;
987
+ color.y *= overTrail.g * overLife.g;
988
+ color.z *= overTrail.b * overLife.b;
989
+ color.w *= overTrail.alpha * overLife.alpha;
990
+ }
991
+ }
992
+
993
+ export class VelocityOverLifetimeModule {
994
+ @serializable()
995
+ enabled!: boolean;
996
+
997
+ @serializable()
998
+ space: ParticleSystemSimulationSpace = ParticleSystemSimulationSpace.Local;
999
+
1000
+ @serializable(MinMaxCurve)
1001
+ orbitalX!: MinMaxCurve;
1002
+ @serializable(MinMaxCurve)
1003
+ orbitalY!: MinMaxCurve;
1004
+ @serializable(MinMaxCurve)
1005
+ orbitalZ!: MinMaxCurve;
1006
+
1007
+ @serializable()
1008
+ orbitalXMultiplier!: number;
1009
+ @serializable()
1010
+ orbitalYMultiplier!: number;
1011
+ @serializable()
1012
+ orbitalZMultiplier!: number;
1013
+
1014
+ @serializable()
1015
+ orbitalOffsetX!: number;
1016
+ @serializable()
1017
+ orbitalOffsetY!: number;
1018
+ @serializable()
1019
+ orbitalOffsetZ!: number;
1020
+
1021
+ @serializable(MinMaxCurve)
1022
+ speedModifier!: MinMaxCurve;
1023
+ @serializable()
1024
+ speedModifierMultiplier!: number;
1025
+ @serializable(MinMaxCurve)
1026
+ x!: MinMaxCurve;
1027
+ @serializable()
1028
+ xMultiplier!: number;
1029
+ @serializable(MinMaxCurve)
1030
+ y!: MinMaxCurve;
1031
+ @serializable()
1032
+ yMultiplier!: number;
1033
+ @serializable(MinMaxCurve)
1034
+ z!: MinMaxCurve;
1035
+ @serializable()
1036
+ zMultiplier!: number;
1037
+
1038
+ private _system?: IParticleSystem;
1039
+ // private _worldRotation: Quaternion = new Quaternion();
1040
+
1041
+ update(system: IParticleSystem) {
1042
+ this._system = system;
1043
+ }
1044
+
1045
+ private _temp: Vector3 = new Vector3();
1046
+ private _temp2: Vector3 = new Vector3();
1047
+ private _temp3: Vector3 = new Vector3();
1048
+ private _hasOrbital = false;
1049
+ private _index = 0;
1050
+ private _orbitalMatrix: Matrix4 = new Matrix4();
1051
+
1052
+ init(particle: object) {
1053
+ if (this._index == 0) particle["debug"] = true;
1054
+ this._index += 1;
1055
+ particle["orbitx"] = this.orbitalX.evaluate(Math.random());
1056
+ particle["orbity"] = this.orbitalY.evaluate(Math.random());
1057
+ particle["orbitz"] = this.orbitalZ.evaluate(Math.random());
1058
+ // console.log(particle["orbitx"], particle["orbity"], particle["orbitz"])
1059
+ this._hasOrbital = particle["orbitx"] != 0 || particle["orbity"] != 0 || particle["orbitz"] != 0;
1060
+ }
1061
+
1062
+ apply(_particle: object, _index: number, _pos: Vec3, vel: Vec3, _dt: number, age: number, life: number) {
1063
+ if (!this.enabled) return;
1064
+ const t = age / life;
1065
+
1066
+ const speed = this.speedModifier.evaluate(t) * this.speedModifierMultiplier;
1067
+ const x = this.x.evaluate(t);
1068
+ const y = this.y.evaluate(t);
1069
+ const z = this.z.evaluate(t);
1070
+ this._temp.set(-x, y, z);
1071
+ if (this._system) {
1072
+ // if (this.space === ParticleSystemSimulationSpace.World) {
1073
+ // this._temp.applyQuaternion(this._system.worldQuaternionInverted);
1074
+ // }
1075
+ // if (this._system.main.simulationSpace === ParticleSystemSimulationSpace.World) {
1076
+ // this._temp.applyQuaternion(this._system.worldQuaternion);
1077
+ // }
1078
+ }
1079
+
1080
+ if (this._hasOrbital) {
1081
+ const position = this._system?.worldPos;
1082
+ if (position) {
1083
+
1084
+ // TODO: we absolutely need to fix this, this is a hack for a specific usecase and doesnt work yet correctly
1085
+ // https://github.com/needle-tools/needle-tiny/issues/710
1086
+
1087
+ const pos = this._temp2.set(_pos.x, _pos.y, _pos.z);
1088
+
1089
+ const ox = this.orbitalXMultiplier;// particle["orbitx"];
1090
+ const oy = this.orbitalYMultiplier;// particle["orbity"];
1091
+ const oz = this.orbitalZMultiplier;// particle["orbitz"];
1092
+ const angle = speed * Math.PI * 2 * 10; // < Oh god
1093
+
1094
+ const cosX = Math.cos(angle * ox);
1095
+ const sinX = Math.sin(angle * ox);
1096
+ const cosY = Math.cos(angle * oy);
1097
+ const sinY = Math.sin(angle * oy);
1098
+ const cosZ = Math.cos(angle * oz);
1099
+ const sinZ = Math.sin(angle * oz);
1100
+
1101
+ const newX = pos.x * (cosY * cosZ) + pos.y * (cosY * sinZ) + pos.z * (-sinY);
1102
+ const newY = pos.x * (sinX * sinY * cosZ - cosX * sinZ) + pos.y * (sinX * sinY * sinZ + cosX * cosZ) + pos.z * (sinX * cosY);
1103
+ const newZ = pos.x * (cosX * sinY * cosZ + sinX * sinZ) + pos.y * (cosX * sinY * sinZ - sinX * cosZ) + pos.z * (cosX * cosY);
1104
+
1105
+ // pos.x += this.orbitalOffsetX;
1106
+ // pos.y += this.orbitalOffsetY;
1107
+ // pos.z += this.orbitalOffsetZ;
1108
+ const v = this._temp3.set(pos.x - newX, pos.y - newY, pos.z - newZ);
1109
+ v.normalize();
1110
+ v.multiplyScalar(.2 / _dt * (Math.max(this.orbitalXMultiplier, this.orbitalYMultiplier, this.orbitalZMultiplier)));
1111
+ vel.x += v.x;
1112
+ vel.y += v.y;
1113
+ vel.z += v.z;
1114
+ }
1115
+ }
1116
+
1117
+ vel.x += this._temp.x;
1118
+ vel.y += this._temp.y;
1119
+ vel.z += this._temp.z;
1120
+ vel.x *= speed;
1121
+ vel.y *= speed;
1122
+ vel.z *= speed;
1123
+ }
1124
+ }
1125
+
1126
+
1127
+
1128
+ enum ParticleSystemAnimationTimeMode {
1129
+ Lifetime,
1130
+ Speed,
1131
+ FPS,
1132
+ }
1133
+
1134
+ enum ParticleSystemAnimationMode {
1135
+ Grid,
1136
+ Sprites,
1137
+ }
1138
+
1139
+ enum ParticleSystemAnimationRowMode {
1140
+ Custom,
1141
+ Random,
1142
+ MeshIndex,
1143
+ }
1144
+
1145
+ enum ParticleSystemAnimationType {
1146
+ WholeSheet,
1147
+ SingleRow,
1148
+ }
1149
+
1150
+ export class TextureSheetAnimationModule {
1151
+
1152
+ @serializable()
1153
+ animation!: ParticleSystemAnimationType;
1154
+
1155
+ @serializable()
1156
+ enabled!: boolean;
1157
+
1158
+ @serializable()
1159
+ cycleCount!: number;
1160
+
1161
+ @serializable(MinMaxCurve)
1162
+ frameOverTime!: MinMaxCurve;
1163
+ @serializable()
1164
+ frameOverTimeMultiplier!: number;
1165
+
1166
+ @serializable()
1167
+ numTilesX!: number;
1168
+ @serializable()
1169
+ numTilesY!: number;
1170
+
1171
+ @serializable(MinMaxCurve)
1172
+ startFrame!: MinMaxCurve;
1173
+ @serializable()
1174
+ startFrameMultiplier!: number;
1175
+
1176
+ @serializable()
1177
+ rowMode!: ParticleSystemAnimationRowMode;
1178
+ @serializable()
1179
+ rowIndex!: number;
1180
+
1181
+ @serializable()
1182
+ spriteCount!: number;
1183
+
1184
+ @serializable()
1185
+ timeMode!: ParticleSystemAnimationTimeMode;
1186
+
1187
+ private sampleOnceAtStart(): boolean {
1188
+ if (this.timeMode === ParticleSystemAnimationTimeMode.Lifetime) {
1189
+ switch (this.frameOverTime.mode) {
1190
+ case ParticleSystemCurveMode.Constant:
1191
+ case ParticleSystemCurveMode.TwoConstants:
1192
+ case ParticleSystemCurveMode.TwoCurves:
1193
+ case ParticleSystemCurveMode.Curve:
1194
+ return true;
1195
+ }
1196
+ }
1197
+ return false;
1198
+ }
1199
+
1200
+ getStartIndex(): number {
1201
+ if (this.sampleOnceAtStart()) {
1202
+ const start = Math.random();
1203
+ return start * (this.numTilesX * this.numTilesY);
1204
+ }
1205
+ return 0;
1206
+ }
1207
+
1208
+ evaluate(t01: number): number | undefined {
1209
+ if (this.sampleOnceAtStart()) {
1210
+ return undefined;
1211
+ }
1212
+ return this.getIndex(t01);
1213
+ }
1214
+
1215
+ private getIndex(t01: number): number {
1216
+ const tiles = this.numTilesX * this.numTilesY;
1217
+ t01 = t01 * this.cycleCount;
1218
+ let index = this.frameOverTime.evaluate(t01 % 1);
1219
+ index *= this.frameOverTimeMultiplier;
1220
+ index *= tiles;
1221
+ index = index % tiles;
1222