Needle Engine

Changes between version 3.2.2-alpha and 3.2.3-alpha
Files changed (5) hide show
  1. src/engine/codegen/register_types.js +2 -2
  2. src/engine/engine_input.ts +6 -3
  3. src/engine-components/ParticleSystemModules.ts +1483 -1483
  4. src/engine-components/SpriteRenderer.ts +4 -0
  5. src/engine-components/VideoPlayer.ts +1 -1
src/engine/codegen/register_types.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { TypeStore } from "./../engine_typestore"
2
-
2
+
3
3
  // Import types
4
4
  import { __Ignore } from "../../engine-components/codegen/components";
5
5
  import { AlignmentConstraint } from "../../engine-components/AlignmentConstraint";
@@ -184,7 +184,7 @@
184
184
  import { XRGrabRendering } from "../../engine-components/WebXRGrabRendering";
185
185
  import { XRRig } from "../../engine-components/WebXRRig";
186
186
  import { XRState } from "../../engine-components/XRFlag";
187
-
187
+
188
188
  // Register types
189
189
  TypeStore.add("__Ignore", __Ignore);
190
190
  TypeStore.add("AlignmentConstraint", AlignmentConstraint);
src/engine/engine_input.ts CHANGED
@@ -243,28 +243,31 @@
243
243
  return null;
244
244
  }
245
245
  isKeyDown(keyCode: KeyCode | string) {
246
+ if (!this.context.application.isVisible || !this.context.application.hasFocus) return false;
246
247
  const codes = this.getCodeForCommonKeyName(keyCode);
247
248
  if (codes !== null) {
248
249
  for (const code of codes) if (this.isKeyDown(code)) return true;
249
250
  return false;
250
251
  }
251
- return this.context.application.isVisible && this.context.application.hasFocus && this.keysPressed[keyCode]?.startFrame === this.context.time.frameCount && this.keysPressed[keyCode].pressed;
252
+ return this.keysPressed[keyCode]?.startFrame === this.context.time.frameCount && this.keysPressed[keyCode].pressed;
252
253
  }
253
254
  isKeyUp(keyCode: KeyCode | string) {
255
+ if (!this.context.application.isVisible || !this.context.application.hasFocus) return false;
254
256
  const codes = this.getCodeForCommonKeyName(keyCode);
255
257
  if (codes !== null) {
256
258
  for (const code of codes) if (this.isKeyUp(code)) return true;
257
259
  return false;
258
260
  }
259
- return this.context.application.isVisible && this.context.application.hasFocus && this.keysPressed[keyCode]?.frame === this.context.time.frameCount && !this.keysPressed[keyCode].pressed;
261
+ return this.keysPressed[keyCode]?.frame === this.context.time.frameCount && !this.keysPressed[keyCode].pressed;
260
262
  }
261
263
  isKeyPressed(keyCode: KeyCode | string) {
264
+ if (!this.context.application.isVisible || !this.context.application.hasFocus) return false;
262
265
  const codes = this.getCodeForCommonKeyName(keyCode);
263
266
  if (codes !== null) {
264
267
  for (const code of codes) if (this.isKeyPressed(code)) return true;
265
268
  return false;
266
269
  }
267
- return this.context.application.isVisible && this.context.application.hasFocus && this.keysPressed[keyCode]?.pressed;// && time.frameCount - this.keysPressed[keyCode].frame < 100;
270
+ return this.keysPressed[keyCode]?.pressed;// && time.frameCount - this.keysPressed[keyCode].frame < 100;
268
271
  }
269
272
 
270
273
  // utility helper for mapping common names to actual codes; e.g. "Shift" -> "ShiftLeft" and "ShiftRight" or "a" -> "KeyA"
src/engine-components/ParticleSystemModules.ts CHANGED
@@ -1,1484 +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 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
- index = Math.floor(index);
1223
- return index;
1224
- }
1225
- }
1226
-
1227
-
1228
- export class RotationOverLifetimeModule {
1229
- @serializable()
1230
- enabled!: boolean;
1231
-
1232
- @serializable()
1233
- separateAxes!: boolean;
1234
-
1235
- @serializable(MinMaxCurve)
1236
- x!: MinMaxCurve;
1237
- @serializable()
1238
- xMultiplier!: number;
1239
- @serializable(MinMaxCurve)
1240
- y!: MinMaxCurve;
1241
- @serializable()
1242
- yMultiplier!: number;
1243
- @serializable(MinMaxCurve)
1244
- z!: MinMaxCurve;
1245
- @serializable()
1246
- zMultiplier!: number;
1247
-
1248
- evaluate(t01: number, t: number): number {
1249
- if (!this.enabled) return 0;
1250
- if (!this.separateAxes) {
1251
- const rot = this.z.evaluate(t01, t) * -1;
1252
- return rot;
1253
- }
1254
- return 0;
1255
- }
1256
- }
1257
-
1258
- export class RotationBySpeedModule {
1259
- @serializable()
1260
- enabled!: boolean;
1261
-
1262
- @serializable()
1263
- range!: Vec2;
1264
-
1265
- @serializable()
1266
- separateAxes!: boolean;
1267
-
1268
- @serializable(MinMaxCurve)
1269
- x!: MinMaxCurve;
1270
- @serializable()
1271
- xMultiplier!: number;
1272
- @serializable(MinMaxCurve)
1273
- y!: MinMaxCurve;
1274
- @serializable()
1275
- yMultiplier!: number;
1276
- @serializable(MinMaxCurve)
1277
- z!: MinMaxCurve;
1278
- @serializable()
1279
- zMultiplier!: number;
1280
-
1281
- evaluate(_t01: number, speed: number): number {
1282
- if (!this.enabled) return 0;
1283
- if (!this.separateAxes) {
1284
- const t = Mathf.lerp(this.range.x, this.range.y, speed);
1285
- const rot = this.z.evaluate(t) * -1;
1286
- return rot;
1287
- }
1288
- return 0;
1289
- }
1290
- }
1291
-
1292
-
1293
- export class LimitVelocityOverLifetimeModule {
1294
- @serializable()
1295
- enabled!: boolean;
1296
-
1297
- @serializable()
1298
- dampen!: number;
1299
-
1300
- @serializable(MinMaxCurve)
1301
- drag!: MinMaxCurve;
1302
- @serializable()
1303
- dragMultiplier!: number;
1304
-
1305
- @serializable(MinMaxCurve)
1306
- limit!: MinMaxCurve;
1307
- @serializable()
1308
- limitMultiplier!: number;
1309
-
1310
- @serializable()
1311
- separateAxes!: boolean;
1312
-
1313
- @serializable(MinMaxCurve)
1314
- limitX!: MinMaxCurve;
1315
- @serializable()
1316
- limitXMultiplier!: number;
1317
- @serializable(MinMaxCurve)
1318
- limitY!: MinMaxCurve;
1319
- @serializable()
1320
- limitYMultiplier!: number;
1321
- @serializable(MinMaxCurve)
1322
- limitZ!: MinMaxCurve;
1323
- @serializable()
1324
- limitZMultiplier!: number;
1325
-
1326
- @serializable()
1327
- multiplyDragByParticleSize: boolean = false;
1328
- @serializable()
1329
- multiplyDragByParticleVelocity: boolean = false;
1330
-
1331
- @serializable()
1332
- space!: ParticleSystemSimulationSpace;
1333
-
1334
- private _temp: Vector3 = new Vector3();
1335
- private _temp2: Vector3 = new Vector3();
1336
-
1337
- apply(_position: Vec3, baseVelocity: Vector3, currentVelocity: Vector3, _size: number, t01: number, _dt: number, _scale: number) {
1338
- if (!this.enabled) return;
1339
- // if (this.separateAxes) {
1340
- // // const maxX = this.limitX.evaluate(t01) * this.limitXMultiplier;
1341
- // // const maxY = this.limitY.evaluate(t01) * this.limitYMultiplier;
1342
- // // const maxZ = this.limitZ.evaluate(t01) * this.limitZMultiplier;
1343
-
1344
- // }
1345
- // else
1346
- {
1347
- const max = this.limit.evaluate(t01) * this.limitMultiplier;
1348
- const speed = baseVelocity.length();
1349
- if (speed > max) {
1350
- this._temp.copy(baseVelocity).normalize().multiplyScalar(max);
1351
- let t = this.dampen * .5;
1352
- // t *= scale;
1353
- baseVelocity.x = Mathf.lerp(baseVelocity.x, this._temp.x, t);
1354
- baseVelocity.y = Mathf.lerp(baseVelocity.y, this._temp.y, t);
1355
- baseVelocity.z = Mathf.lerp(baseVelocity.z, this._temp.z, t);
1356
-
1357
- // this._temp2.set(0, 0, 0);
1358
- currentVelocity.x = Mathf.lerp(currentVelocity.x, this._temp.x, t);
1359
- currentVelocity.y = Mathf.lerp(currentVelocity.y, this._temp.y, t);
1360
- currentVelocity.z = Mathf.lerp(currentVelocity.z, this._temp.z, t);
1361
- }
1362
- // vel.multiplyScalar(dragFactor);
1363
- }
1364
- // vel.x *= 0.3;
1365
- // vel.y *= 0.3;
1366
- // vel.z *= 0.3;
1367
- }
1368
- }
1369
-
1370
-
1371
- export enum ParticleSystemInheritVelocityMode {
1372
- Initial,
1373
- Current,
1374
- }
1375
-
1376
- export class InheritVelocityModule {
1377
-
1378
- @serializable()
1379
- enabled!: boolean;
1380
-
1381
- @serializable(MinMaxCurve)
1382
- curve!: MinMaxCurve;
1383
- @serializable()
1384
- curveMultiplier!: number;
1385
-
1386
- @serializable()
1387
- mode!: ParticleSystemInheritVelocityMode;
1388
-
1389
- system!: IParticleSystem;
1390
- private _lastWorldPosition!: Vector3;
1391
- private _velocity: Vector3 = new Vector3();
1392
- private _temp: Vector3 = new Vector3();
1393
-
1394
- update(_context: Context) {
1395
- if (!this.enabled) return;
1396
- if (this.system.worldspace === false) return;
1397
- if (this._lastWorldPosition) {
1398
- this._velocity.copy(this.system.worldPos).sub(this._lastWorldPosition).multiplyScalar(1 / this.system.deltaTime);
1399
- this._lastWorldPosition.copy(this.system.worldPos);
1400
- }
1401
- else {
1402
- this._velocity.set(0, 0, 0);
1403
- this._lastWorldPosition = this.system.worldPos.clone();
1404
- }
1405
- }
1406
-
1407
- // TODO: make work for subsystems
1408
- applyInitial(vel: Vector3) {
1409
- if (!this.enabled) return;
1410
- if (this.system.worldspace === false) return;
1411
- if (this.mode === ParticleSystemInheritVelocityMode.Initial) {
1412
- const factor = this.curve.evaluate(Math.random(), Math.random());
1413
- this._temp.copy(this._velocity).multiplyScalar(factor);
1414
- vel.add(this._temp);
1415
- }
1416
- }
1417
-
1418
- applyCurrent(vel: Vector3, t01: number, lerpFactor: number) {
1419
- if (!this.enabled) return;
1420
- if (this.system.worldspace === false) return;
1421
- if (this.mode === ParticleSystemInheritVelocityMode.Current) {
1422
- const factor = this.curve.evaluate(t01, lerpFactor);
1423
- this._temp.copy(this._velocity).multiplyScalar(factor);
1424
- vel.add(this._temp);
1425
- }
1426
- }
1427
- }
1428
-
1429
-
1430
- export class SizeBySpeedModule {
1431
- @serializable()
1432
- enabled!: boolean;
1433
-
1434
- @serializable(Vector2)
1435
- range!: Vector2;
1436
- @serializable()
1437
- separateAxes!: boolean;
1438
-
1439
- @serializable(MinMaxCurve)
1440
- size!: MinMaxCurve;
1441
- @serializable()
1442
- sizeMultiplier!: number;
1443
-
1444
- @serializable(MinMaxCurve)
1445
- x!: MinMaxCurve;
1446
- @serializable()
1447
- xMultiplier!: number;
1448
- @serializable(MinMaxCurve)
1449
- y!: MinMaxCurve;
1450
- @serializable()
1451
- yMultiplier!: number;
1452
- @serializable(MinMaxCurve)
1453
- z!: MinMaxCurve;
1454
- @serializable()
1455
- zMultiplier!: number;
1456
-
1457
- evaluate(vel: Vector3, _t01: number, lerpFactor: number, size: number): number {
1458
-
1459
- const speed = vel.length();
1460
- const x = Mathf.remap(speed, this.range.x, this.range.y, 0, 1);
1461
- const factor = this.size.evaluate(x, lerpFactor);
1462
- // return size;
1463
- return size * factor;
1464
- }
1465
- }
1466
-
1467
- export class ColorBySpeedModule {
1468
- @serializable()
1469
- enabled!: boolean;
1470
- @serializable(Vector2)
1471
- range!: Vector2;
1472
- @serializable(MinMaxGradient)
1473
- color!: MinMaxGradient;
1474
-
1475
- evaluate(vel: Vector3, lerpFactor: number, color: Vector4) {
1476
- const speed = vel.length();
1477
- const x = Mathf.remap(speed, this.range.x, this.range.y, 0, 1);
1478
- const res = this.color.evaluate(x, lerpFactor);
1479
- color.x *= res.r;
1480
- color.y *= res.g;
1481
- color.z *= res.b;
1482
- color.w *= res.alpha;
1483
- }
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 {