@needle-tools/engine 3.2.3-alpha → 3.2.4-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/needle-engine.js +24012 -22890
  3. package/dist/needle-engine.min.js +363 -363
  4. package/dist/needle-engine.umd.cjs +369 -369
  5. package/lib/engine/api.d.ts +33 -11
  6. package/lib/engine/api.js +33 -11
  7. package/lib/engine/api.js.map +1 -1
  8. package/lib/engine/engine_addressables.d.ts +3 -3
  9. package/lib/engine/engine_addressables.js +0 -1
  10. package/lib/engine/engine_addressables.js.map +1 -1
  11. package/lib/engine/engine_context.d.ts +11 -12
  12. package/lib/engine/engine_context.js +16 -17
  13. package/lib/engine/engine_context.js.map +1 -1
  14. package/lib/engine/engine_fileloader.d.ts +2 -2
  15. package/lib/engine/engine_fileloader.js +2 -2
  16. package/lib/engine/engine_fileloader.js.map +1 -1
  17. package/lib/engine/engine_gameobject.d.ts +8 -8
  18. package/lib/engine/engine_gameobject.js +1 -1
  19. package/lib/engine/engine_gameobject.js.map +1 -1
  20. package/lib/engine/engine_gizmos.d.ts +2 -3
  21. package/lib/engine/engine_gizmos.js +3 -3
  22. package/lib/engine/engine_gizmos.js.map +1 -1
  23. package/lib/engine/engine_gltf_builtin_components.js +4 -5
  24. package/lib/engine/engine_gltf_builtin_components.js.map +1 -1
  25. package/lib/engine/engine_input.d.ts +7 -7
  26. package/lib/engine/engine_input.js +12 -12
  27. package/lib/engine/engine_input.js.map +1 -1
  28. package/lib/engine/engine_license.js +2 -2
  29. package/lib/engine/engine_license.js.map +1 -1
  30. package/lib/engine/engine_mainloop_utils.d.ts +1 -1
  31. package/lib/engine/engine_mainloop_utils.js +1 -1
  32. package/lib/engine/engine_mainloop_utils.js.map +1 -1
  33. package/lib/engine/engine_networking_auto.d.ts +1 -1
  34. package/lib/engine/engine_networking_auto.js.map +1 -1
  35. package/lib/engine/engine_networking_files.d.ts +4 -4
  36. package/lib/engine/engine_networking_files.js +4 -5
  37. package/lib/engine/engine_networking_files.js.map +1 -1
  38. package/lib/engine/engine_three_utils.d.ts +19 -19
  39. package/lib/engine/engine_three_utils.js +17 -18
  40. package/lib/engine/engine_three_utils.js.map +1 -1
  41. package/lib/engine/extensions/NEEDLE_components.d.ts +6 -5
  42. package/lib/engine/extensions/NEEDLE_components.js.map +1 -1
  43. package/lib/engine/extensions/NEEDLE_lighting_settings.js +14 -6
  44. package/lib/engine/extensions/NEEDLE_lighting_settings.js.map +1 -1
  45. package/lib/engine/extensions/NEEDLE_techniques_webgl.d.ts +3 -4
  46. package/lib/engine/extensions/NEEDLE_techniques_webgl.js +25 -26
  47. package/lib/engine/extensions/NEEDLE_techniques_webgl.js.map +1 -1
  48. package/lib/engine-components/Animation.d.ts +6 -7
  49. package/lib/engine-components/Animation.js +5 -6
  50. package/lib/engine-components/Animation.js.map +1 -1
  51. package/lib/engine-components/Animator.d.ts +3 -4
  52. package/lib/engine-components/Animator.js.map +1 -1
  53. package/lib/engine-components/AnimatorController.js +3 -4
  54. package/lib/engine-components/AnimatorController.js.map +1 -1
  55. package/lib/engine-components/AudioListener.d.ts +2 -2
  56. package/lib/engine-components/AudioListener.js +2 -2
  57. package/lib/engine-components/AudioListener.js.map +1 -1
  58. package/lib/engine-components/AudioSource.d.ts +2 -2
  59. package/lib/engine-components/AudioSource.js +4 -4
  60. package/lib/engine-components/AudioSource.js.map +1 -1
  61. package/lib/engine-components/AvatarLoader.d.ts +7 -7
  62. package/lib/engine-components/AvatarLoader.js +3 -4
  63. package/lib/engine-components/AvatarLoader.js.map +1 -1
  64. package/lib/engine-components/AxesHelper.js.map +1 -1
  65. package/lib/engine-components/BasicIKConstraint.js +2 -2
  66. package/lib/engine-components/BasicIKConstraint.js.map +1 -1
  67. package/lib/engine-components/BoxHelperComponent.d.ts +5 -5
  68. package/lib/engine-components/BoxHelperComponent.js +6 -6
  69. package/lib/engine-components/BoxHelperComponent.js.map +1 -1
  70. package/lib/engine-components/Component.d.ts +45 -47
  71. package/lib/engine-components/Component.js +16 -17
  72. package/lib/engine-components/Component.js.map +1 -1
  73. package/lib/engine-components/DragControls.d.ts +3 -3
  74. package/lib/engine-components/DragControls.js +22 -24
  75. package/lib/engine-components/DragControls.js.map +1 -1
  76. package/lib/engine-components/api.d.ts +3 -0
  77. package/lib/engine-components/api.js +3 -0
  78. package/lib/engine-components/api.js.map +1 -1
  79. package/lib/engine-components/ui/Utils.d.ts +4 -3
  80. package/lib/engine-components/ui/Utils.js.map +1 -1
  81. package/lib/engine-components-experimental/api.d.ts +1 -0
  82. package/lib/engine-components-experimental/api.js +2 -0
  83. package/lib/engine-components-experimental/api.js.map +1 -0
  84. package/lib/engine-components-experimental/networking/PlayerSync.d.ts +2 -1
  85. package/lib/engine-components-experimental/networking/PlayerSync.js +18 -1
  86. package/lib/engine-components-experimental/networking/PlayerSync.js.map +1 -1
  87. package/lib/needle-engine.d.ts +1 -5
  88. package/lib/needle-engine.js +4 -5
  89. package/lib/needle-engine.js.map +1 -1
  90. package/lib/tsconfig.tsbuildinfo +1 -1
  91. package/package.json +1 -1
  92. package/plugins/vite/license.js +2 -2
  93. package/src/engine/api.ts +39 -20
  94. package/src/engine/codegen/register_types.js +2 -2
  95. package/src/engine/engine_addressables.ts +6 -7
  96. package/src/engine/engine_context.ts +31 -28
  97. package/src/engine/engine_fileloader.js +2 -2
  98. package/src/engine/engine_gameobject.ts +21 -21
  99. package/src/engine/engine_gizmos.ts +5 -6
  100. package/src/engine/engine_gltf_builtin_components.ts +9 -10
  101. package/src/engine/engine_input.ts +17 -18
  102. package/src/engine/engine_license.ts +2 -2
  103. package/src/engine/engine_mainloop_utils.ts +5 -5
  104. package/src/engine/engine_networking_auto.ts +1 -1
  105. package/src/engine/engine_networking_files.ts +10 -11
  106. package/src/engine/engine_three_utils.ts +37 -37
  107. package/src/engine/extensions/NEEDLE_components.ts +7 -6
  108. package/src/engine/extensions/NEEDLE_lighting_settings.ts +15 -6
  109. package/src/engine/extensions/NEEDLE_techniques_webgl.ts +29 -30
  110. package/src/engine-components/Animation.ts +17 -19
  111. package/src/engine-components/Animator.ts +4 -5
  112. package/src/engine-components/AnimatorController.ts +16 -17
  113. package/src/engine-components/AudioListener.ts +4 -4
  114. package/src/engine-components/AudioSource.ts +8 -8
  115. package/src/engine-components/AvatarLoader.ts +15 -16
  116. package/src/engine-components/AxesHelper.ts +1 -1
  117. package/src/engine-components/BasicIKConstraint.ts +2 -2
  118. package/src/engine-components/BoxHelperComponent.ts +13 -13
  119. package/src/engine-components/Component.ts +65 -68
  120. package/src/engine-components/DragControls.ts +42 -43
  121. package/src/engine-components/ParticleSystemModules.ts +1483 -1483
  122. package/src/engine-components/api.ts +3 -0
  123. package/src/engine-components/ui/Utils.ts +4 -4
  124. package/src/engine-components-experimental/api.ts +1 -0
  125. package/src/engine-components-experimental/networking/PlayerSync.ts +17 -4
  126. package/src/needle-engine.ts +5 -9
@@ -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 {
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
+ }
1484
1484
  }