@needle-tools/engine 2.42.0-pre → 2.44.0-pre

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 (62) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/needle-engine.d.ts +224 -81
  3. package/dist/needle-engine.js +3836 -422
  4. package/dist/needle-engine.js.map +4 -4
  5. package/dist/needle-engine.min.js +40 -40
  6. package/dist/needle-engine.min.js.map +4 -4
  7. package/lib/engine/engine_element_loading.js +22 -6
  8. package/lib/engine/engine_element_loading.js.map +1 -1
  9. package/lib/engine/engine_input.js +17 -6
  10. package/lib/engine/engine_input.js.map +1 -1
  11. package/lib/engine/engine_serialization_builtin_serializer.js +60 -58
  12. package/lib/engine/engine_serialization_builtin_serializer.js.map +1 -1
  13. package/lib/engine/engine_three_utils.d.ts +1 -0
  14. package/lib/engine/engine_three_utils.js +10 -0
  15. package/lib/engine/engine_three_utils.js.map +1 -1
  16. package/lib/engine/engine_time.js +2 -0
  17. package/lib/engine/engine_time.js.map +1 -1
  18. package/lib/engine/extensions/NEEDLE_techniques_webgl.js +42 -0
  19. package/lib/engine/extensions/NEEDLE_techniques_webgl.js.map +1 -1
  20. package/lib/engine-components/Animation.d.ts +3 -1
  21. package/lib/engine-components/Animation.js +25 -1
  22. package/lib/engine-components/Animation.js.map +1 -1
  23. package/lib/engine-components/AnimatorController.js +4 -1
  24. package/lib/engine-components/AnimatorController.js.map +1 -1
  25. package/lib/engine-components/Camera.d.ts +3 -0
  26. package/lib/engine-components/Camera.js +17 -9
  27. package/lib/engine-components/Camera.js.map +1 -1
  28. package/lib/engine-components/ParticleSystem.d.ts +33 -7
  29. package/lib/engine-components/ParticleSystem.js +464 -249
  30. package/lib/engine-components/ParticleSystem.js.map +1 -1
  31. package/lib/engine-components/ParticleSystemBehaviours.d.ts +0 -0
  32. package/lib/engine-components/ParticleSystemBehaviours.js +2 -0
  33. package/lib/engine-components/ParticleSystemBehaviours.js.map +1 -0
  34. package/lib/engine-components/ParticleSystemModules.d.ts +123 -20
  35. package/lib/engine-components/ParticleSystemModules.js +461 -63
  36. package/lib/engine-components/ParticleSystemModules.js.map +1 -1
  37. package/lib/engine-components/WebXRController.d.ts +1 -0
  38. package/lib/engine-components/WebXRController.js +2 -1
  39. package/lib/engine-components/WebXRController.js.map +1 -1
  40. package/lib/engine-components/codegen/components.d.ts +6 -0
  41. package/lib/engine-components/codegen/components.js +6 -0
  42. package/lib/engine-components/codegen/components.js.map +1 -1
  43. package/lib/engine-components/js-extensions/RGBAColor.d.ts +1 -0
  44. package/lib/engine-components/js-extensions/RGBAColor.js +7 -0
  45. package/lib/engine-components/js-extensions/RGBAColor.js.map +1 -1
  46. package/package.json +2 -1
  47. package/src/engine/codegen/register_types.js +24 -0
  48. package/src/engine/engine_element_loading.ts +22 -5
  49. package/src/engine/engine_input.ts +16 -7
  50. package/src/engine/engine_serialization_builtin_serializer.ts +59 -57
  51. package/src/engine/engine_three_utils.ts +11 -2
  52. package/src/engine/engine_time.ts +1 -0
  53. package/src/engine/extensions/NEEDLE_techniques_webgl.ts +43 -1
  54. package/src/engine-components/Animation.ts +18 -2
  55. package/src/engine-components/AnimatorController.ts +5 -1
  56. package/src/engine-components/Camera.ts +17 -10
  57. package/src/engine-components/ParticleSystem.ts +526 -303
  58. package/src/engine-components/ParticleSystemBehaviours.ts +0 -0
  59. package/src/engine-components/ParticleSystemModules.ts +408 -66
  60. package/src/engine-components/WebXRController.ts +2 -1
  61. package/src/engine-components/codegen/components.ts +6 -0
  62. package/src/engine-components/js-extensions/RGBAColor.ts +7 -0
@@ -1,18 +1,19 @@
1
1
  import { Behaviour, GameObject } from "./Component";
2
2
  import * as THREE from "three";
3
- import { MainModule, EmissionModule, ShapeModule, ParticleSystemShapeType, MinMaxCurve, MinMaxGradient, ColorOverLifetimeModule, SizeOverLifetimeModule, NoiseModule, ParticleSystemSimulationSpace, ParticleBurst, IParticleSystem, ParticleSystemRenderMode } from "./ParticleSystemModules"
3
+ import { MainModule, EmissionModule, ShapeModule, ParticleSystemShapeType, MinMaxCurve, MinMaxGradient, ColorOverLifetimeModule, SizeOverLifetimeModule, NoiseModule, ParticleSystemSimulationSpace, ParticleBurst, IParticleSystem, ParticleSystemRenderMode, TrailModule, VelocityOverLifetimeModule, TextureSheetAnimationModule, RotationOverLifetimeModule, LimitVelocityOverLifetimeModule, RotationBySpeedModule } from "./ParticleSystemModules"
4
4
  import { getParam } from "../engine/engine_utils";
5
5
 
6
6
  // https://github.dev/creativelifeform/three-nebula
7
- import System, { Emitter, Position, Life, SpriteRenderer, Particle, Body, MeshRenderer, } from 'three-nebula';
7
+ // import System, { Emitter, Position, Life, SpriteRenderer, Particle, Body, MeshRenderer, } from 'three-nebula';
8
8
 
9
9
  import { serializeable } from "../engine/engine_serialization";
10
- import { Time } from "../engine/engine_time";
11
- import { Context } from "../engine/engine_setup";
12
10
  import { RGBAColor } from "./js-extensions/RGBAColor";
13
- import { AxesHelper, BufferGeometry, Material, Mesh, Object3D, Sprite, SpriteMaterial, Vector3 } from "three";
14
- import { getWorldQuaternion, getWorldScale } from "../engine/engine_three_utils";
11
+ import { AxesHelper, BufferGeometry, Color, Material, Mesh, MeshStandardMaterial, Object3D, OneMinusDstAlphaFactor, Quaternion, Sprite, SpriteMaterial, Vector3, Vector4 } from "three";
12
+ import { getWorldPosition, getWorldQuaternion, getWorldScale } from "../engine/engine_three_utils";
15
13
  import { assign } from "../engine/engine_serialization_core";
14
+ import { BatchedParticleRenderer, Behavior, BillBoardSettings, BurstParameters, ColorGenerator, ConstantColor, ConstantValue, EmissionState, EmitterShape, FunctionColorGenerator, FunctionJSON, FunctionValueGenerator, MeshSettings, Particle, ParticleEmitter, ParticleSystem as _ParticleSystem, ParticleSystemParameters, PointEmitter, RenderMode, RotationGenerator, SizeOverLife, TrailBatch, TrailSettings, ValueGenerator } from "three.quarks";
15
+ import { createFlatTexture } from "../engine/engine_shaders";
16
+ import { Mathf } from "../engine/engine_math";
16
17
 
17
18
  const debug = getParam("debugparticles");
18
19
 
@@ -28,6 +29,11 @@ export class ParticleSystemRenderer extends Behaviour {
28
29
  // @serializeable(Mesh)
29
30
  particleMesh?: Mesh | string;
30
31
 
32
+ get transparent(): boolean {
33
+ const res = this.particleMaterial?.transparent ?? false;
34
+ // console.log(res, this.particleMaterial);
35
+ return res;
36
+ }
31
37
 
32
38
  getMesh() {
33
39
  let geo: BufferGeometry | null = null;
@@ -40,405 +46,622 @@ export class ParticleSystemRenderer extends Behaviour {
40
46
  }
41
47
  }
42
48
 
43
- export class ParticleSystem extends Behaviour implements IParticleSystem {
49
+ class MinMaxCurveFunction implements FunctionValueGenerator {
44
50
 
45
- @serializeable(ColorOverLifetimeModule)
46
- readonly colorOverLifetime!: ColorOverLifetimeModule;
51
+ private _curve: MinMaxCurve;
47
52
 
48
- @serializeable(MainModule)
49
- readonly main!: MainModule;
53
+ constructor(curve: MinMaxCurve) { this._curve = curve; }
50
54
 
51
- @serializeable(EmissionModule)
52
- readonly emission!: EmissionModule;
55
+ type: "function" = "function";
53
56
 
54
- @serializeable(SizeOverLifetimeModule)
55
- readonly sizeOverLifetime!: SizeOverLifetimeModule;
57
+ genValue(t: number): number {
58
+ return this._curve.evaluate(t, Math.random());
59
+ }
60
+ toJSON(): FunctionJSON {
61
+ throw new Error("Method not implemented.");
62
+ }
63
+ clone(): FunctionValueGenerator {
64
+ throw new Error("Method not implemented.");
65
+ }
66
+ }
56
67
 
57
- @serializeable(ShapeModule)
58
- readonly shape!: ShapeModule;
68
+ class MinMaxGradientFunction implements FunctionColorGenerator {
59
69
 
60
- @serializeable(NoiseModule)
61
- readonly noise!: NoiseModule;
70
+ private _curve: MinMaxGradient;
62
71
 
63
- get renderer(): ParticleSystemRenderer {
64
- return this._renderer;
72
+ constructor(curve: MinMaxGradient) { this._curve = curve; }
73
+
74
+ type: "function" = "function";
75
+
76
+ genColor(color: THREE.Vector4, t: number): THREE.Vector4 {
77
+ const col = this._curve.evaluate(t, Math.random());
78
+ // TODO: incoming color should probably be blended?
79
+ color.set(col.r, col.g, col.b, col.alpha);
80
+ return color;
81
+ }
82
+ toJSON(): FunctionJSON {
83
+ throw new Error("Method not implemented.");
84
+ }
85
+ clone(): FunctionColorGenerator {
86
+ throw new Error("Method not implemented.");
65
87
  }
66
88
 
67
- get currentParticles() {
68
- return this._system?.getCount() ?? 0;
89
+ }
90
+
91
+ abstract class BaseValueGenerator implements ValueGenerator {
92
+
93
+ type: "value" = "value";
94
+ toJSON(): FunctionJSON {
95
+ throw new Error("Method not implemented.");
69
96
  }
70
- get maxParticles() {
71
- return this.main.maxParticles;
97
+ clone(): ValueGenerator {
98
+ throw new Error("Method not implemented.");
72
99
  }
73
- get time() {
74
- return this._time;
100
+
101
+ abstract genValue(): number;
102
+
103
+ readonly system: ParticleSystem;
104
+
105
+ constructor(system: ParticleSystem) {
106
+ this.system = system;
75
107
  }
76
- get duration() {
77
- return this.main.duration;
108
+ }
109
+
110
+ class TextureSheetStartFrameGenerator extends BaseValueGenerator {
111
+ genValue(): number {
112
+ return this.system.textureSheetAnimation.getStartIndex();
78
113
  }
79
114
 
80
- private _renderer!: ParticleSystemRenderer;
81
- private _system!: System;
82
- private _emitter: Emitter;
83
- private _size!: SizeBehaviour;
84
- private _container?: Object3D;
85
- private _time: number = 0;
115
+ }
86
116
 
87
- /** called from deserialization */
88
- private set bursts(arr: ParticleBurst[]) {
89
- for (let i = 0; i < arr.length; i++) {
90
- const burst = arr[i];
91
- if ((burst instanceof ParticleBurst) === false) {
92
- const instance = new ParticleBurst();
93
- assign(instance, burst);
94
- arr[i] = instance;
95
- }
117
+ class ParticleSystemEmissionOverTime extends BaseValueGenerator {
118
+
119
+ private _lastPosition: Vector3 = new Vector3();
120
+ private _lastDistance: number = 0;
121
+
122
+ update() {
123
+ const currentPosition = getWorldPosition(this.system.gameObject);
124
+ this._lastDistance = this._lastPosition.distanceTo(currentPosition)
125
+ this._lastPosition.copy(currentPosition);
126
+ }
127
+
128
+ genValue(): number {
129
+ if (this.system.currentParticles >= this.system.maxParticles) return 0;
130
+ // emission over time
131
+ let emission = this.system.emission.rateOverTime.evaluate(this.system.time / this.system.duration, Math.random());
132
+ // if(this.system.currentParticles + emission > this.system.maxParticles)
133
+ // emission = (this.system.maxParticles - this.system.currentParticles);
134
+ // const res = Mathf.clamp(emission, 0, this.system.maxParticles - this.system.currentParticles);
135
+
136
+ if (this.system.deltaTime > 0) {
137
+ const distanceEmission = this.system.emission.rateOverDistance.evaluate(this.system.time / this.system.duration, Math.random());
138
+ const meterPerSecond = this._lastDistance / this.system.deltaTime;
139
+ let distanceEmissionValue = meterPerSecond * distanceEmission;
140
+ if (!Number.isFinite(distanceEmissionValue)) distanceEmissionValue = 0;
141
+ emission += distanceEmissionValue;
96
142
  }
97
- this._bursts = arr;
143
+ const burst = this.system.emission.getBurst();
144
+ if (burst > 0)
145
+ emission += burst / this.system.deltaTime;
146
+
147
+ const maxEmission = (this.system.maxParticles - this.system.currentParticles);
148
+ return Mathf.clamp(emission, 0, maxEmission / this.system.deltaTime);
98
149
  }
99
- private _bursts?: ParticleBurst[];
150
+ }
100
151
 
101
- awake(): void {
102
- this._renderer = this.gameObject.getComponent(ParticleSystemRenderer) as ParticleSystemRenderer;
103
- this._system = new System();
152
+ class ParticleSystemEmissionOverDistance extends BaseValueGenerator {
104
153
 
105
- if (this.main.simulationSpace == ParticleSystemSimulationSpace.Local) {
106
- this._container = new Object3D();
107
- this._container.matrixAutoUpdate = false;
108
- this.gameObject.add(this._container);
109
- }
154
+ genValue(): number {
155
+ // this seems not be called yet
156
+ return 0;
157
+ // if (this.system.currentParticles >= this.system.maxParticles) return 0;
158
+ // const emission = this.system.emission.rateOverDistance.evaluate(this.system.time / this.system.duration, Math.random());
159
+ // return emission;
160
+ }
161
+ }
110
162
 
111
- const container = this.main.simulationSpace == ParticleSystemSimulationSpace.Local ? this._container : this.context.scene;
163
+ abstract class ParticleSystemBaseBehaviour implements Behavior {
164
+ readonly system: ParticleSystem;
112
165
 
113
- if (debug) {
114
- console.log(this);
115
- this.gameObject.add(new AxesHelper(1))
116
- }
117
- const renderMode = this._renderer.renderMode;
118
- let renderer: any = undefined;
119
-
120
- switch (renderMode) {
121
- case ParticleSystemRenderMode.Mesh:
122
- // https://three-nebula-docs.netlify.app/class/src/renderer/meshrenderer.js~meshrenderer
123
- // mesh renderer example: https://github.com/creativelifeform/three-nebula/blob/master/website/components/Examples/MeshRenderer/init.js
124
- renderer = new MeshRenderer(container, THREE);
125
- break;
126
- case ParticleSystemRenderMode.Billboard:
127
- renderer = new SpriteRenderer(container, THREE);
128
- break;
166
+ get scaleFactorDiff(): number {
167
+ return this.system.worldScale.x - this.system.scale;
168
+ }
169
+
170
+ constructor(ps: ParticleSystem) {
171
+ this.system = ps;
172
+ }
173
+
174
+ abstract type: string;
175
+
176
+ initialize(_particle: Particle): void {
177
+ }
178
+ update(_particle: Particle, _delta: number): void {
179
+ }
180
+ frameUpdate(_delta: number): void {
181
+ }
182
+ toJSON() { throw new Error("Method not implemented."); }
183
+ clone(): Behavior { throw new Error("Method not implemented."); }
184
+ }
185
+
186
+ class TextureSheetAnimationBehaviour extends ParticleSystemBaseBehaviour {
187
+ type: string = "NeedleTextureSheet"
188
+
189
+ update(particle: Particle, _delta: number) {
190
+ const sheet = this.system.textureSheetAnimation;
191
+ if (sheet.enabled) {
192
+ const t01 = particle.age / particle.life;
193
+ const index = sheet.evaluate(t01);;
194
+ if (index !== undefined)
195
+ particle.uvTile = index;
129
196
  }
197
+ }
198
+
199
+ }
200
+
201
+ class RotationBehaviour extends ParticleSystemBaseBehaviour {
202
+ type: string = "NeedleRotation"
203
+
204
+ update(particle: Particle, delta: number) {
205
+ if (particle.rotation === undefined) return;
206
+ if (particle.rotation instanceof Quaternion) {
130
207
 
131
- if (!renderer) {
132
- console.error("No renderer for particle system");
133
- return;
134
208
  }
209
+ else {
210
+ if (this.system.rotationOverLifetime.enabled) {
211
+ particle.rotation += this.system.rotationOverLifetime.evaluate(particle.age / particle.life) * delta;
212
+ }
213
+ // HACK flip y
214
+ else particle.rotation = Math.PI;
135
215
 
136
- renderer.logRendererType = () => { };
137
- if (renderMode === ParticleSystemRenderMode.Billboard) {
138
- if (this.renderer.particleMaterial) {
139
- const sprite = renderer._body as Sprite;
140
- sprite.layers.disableAll();
141
- sprite.layers.set(2); // ignore raycasting particles
142
- sprite.renderOrder = 1;
143
- sprite.material.map = this.renderer.particleMaterial.map;
144
- sprite.material.transparent = true;
145
- // sprite.material.sizeAttenuation = false;
146
- sprite.material.blending = this.renderer.particleMaterial.blending;
147
- sprite.material.blendDst = this.renderer.particleMaterial.blendDst;
148
- sprite.material.blendDstAlpha = this.renderer.particleMaterial.blendDstAlpha;
149
- sprite.material.blendEquation = this.renderer.particleMaterial.blendEquation;
150
- sprite.material.blendEquationAlpha = this.renderer.particleMaterial.blendEquationAlpha;
151
- sprite.material.blendSrc = this.renderer.particleMaterial.blendSrc;
152
- sprite.material.blendSrcAlpha = this.renderer.particleMaterial.blendSrcAlpha;
153
- sprite.material.premultipliedAlpha = this.renderer.particleMaterial.premultipliedAlpha;
154
- sprite.material.depthWrite = false;
155
- sprite.material.depthTest = true;
156
- if (debug) console.log(sprite);
216
+ if (this.system.rotationBySpeed.enabled) {
217
+ const speed = particle.velocity.length();
218
+ particle.rotation += this.system.rotationBySpeed.evaluate(particle.age / particle.life, speed) * delta;
157
219
  }
158
220
  }
221
+ }
222
+ }
159
223
 
160
- this._system.addRenderer(renderer);
224
+ class SizeBehaviour extends ParticleSystemBaseBehaviour {
161
225
 
162
- const initializers: Array<any> = [];
163
- const behaviours: Array<any> = [];
226
+ type: string = "NeedleSize";
164
227
 
165
- const life = new Life();
166
- life.lifePan = new MinMaxCurveSpan(this.main.startLifetime, 1);
167
- initializers.push(life);
228
+ update(particle: Particle, _delta: number): void {
229
+ const t = particle.age / particle.life;
230
+ let size = 1;
231
+ if (this.system.sizeOverLifetime.enabled)
232
+ size *= this.system.sizeOverLifetime.evaluate(t).x;
233
+ const scaleFactor = this.system.worldScale.x / this.system.cameraScale;
234
+ particle.size = particle.startSize * size * scaleFactor;
235
+ // console.log(particle.startSize, size, this.system.scale, this.system.cameraScale);
236
+ }
237
+ }
168
238
 
169
- const shape = new Position(this.shape);
170
- initializers.push(shape);
239
+ const $startVelocity = Symbol("startVelocity");
240
+ const $gravityFactor = Symbol("gravityModifier");
241
+ const temp3 = new Vector3();
242
+ const temp4 = new Quaternion();
171
243
 
172
- const size = this._size = new SizeBehaviour(this.main.startSize, this.main.startSizeMultiplier);
173
- size.sizeOverLifetimeModule = this.sizeOverLifetime;
174
- behaviours.push(size);
244
+ class VelocityBehaviour extends ParticleSystemBaseBehaviour {
245
+ type: string = "NeedleVelocity";
175
246
 
176
- const color = new GradientBehaviour(this.main.startColor, this.colorOverLifetime?.enabled ? this.colorOverLifetime.color : undefined);
177
- behaviours.push(color);
247
+ private _gravityDirection = new Vector3();
178
248
 
179
- const velocity = new VelocityBehaviour(renderer.container, this.shape, this.noise, this.main.startSpeed, 1);
180
- velocity.gravity = this.main.gravityModifier;
181
- velocity.gravityModifierMultiplier = this.main.gravityModifierMultiplier;
182
- behaviours.push(velocity);
249
+ initialize(particle: Particle): void {
250
+ const factor = 1 + this.scaleFactorDiff;
183
251
 
184
- if (renderMode === ParticleSystemRenderMode.Mesh) {
185
- initializers.push(new Body(this._renderer.getMesh()))
186
- }
252
+ particle.startSpeed = this.system.main.startSpeed.evaluate(Math.random(), Math.random()) * factor;
253
+ particle.velocity.copy(this.system.shape.getDirection(particle.position)).multiplyScalar(particle.startSpeed);
254
+ if (!particle[$startVelocity]) particle[$startVelocity] = particle.velocity.clone();
255
+ else particle[$startVelocity].copy(particle.velocity);
187
256
 
188
- const emitter = this._emitter = new Emitter({});
189
- emitter.setInitializers(initializers);
190
- emitter.setBehaviours(behaviours);
191
- emitter.setRate(this.emission);
192
- emitter.damping = 0;
193
- }
257
+ const gravityFactor = this.system.main.gravityModifier.evaluate(Math.random(), Math.random());// * factor;
258
+ particle[$gravityFactor] = gravityFactor;
194
259
 
195
- onEnable() {
196
- this._emitter.emit();
197
- this._system.addEmitter(this._emitter);
198
- this._system.emit({ onStart: () => { }, onUpdate: () => { }, onEnd: () => { }, });
199
- this._time = 0;
200
- }
260
+ this._gravityDirection.set(0, -1, 0);
261
+ if (this.system.main.simulationSpace === ParticleSystemSimulationSpace.Local)
262
+ this._gravityDirection.applyQuaternion(this.system.worldQuaternionInverted);
201
263
 
202
- onDisable() {
203
- this._system.removeEmitter(this._emitter);
204
264
  }
205
265
 
206
- onBeforeRender() {
207
- if (this._bursts) {
208
- this.emission.bursts = this._bursts;
209
- }
266
+ update(particle: Particle, delta: number): void {
210
267
 
211
- // sprite materials must be scaled in AR
212
- const cam = this.context.mainCamera;
213
- if (cam) {
214
- const scale = getWorldScale(cam);
215
- this._size.cameraScale = scale.x;
268
+ const baseVelocity = particle[$startVelocity];
269
+ let gravityFactor = particle[$gravityFactor];
270
+ if (gravityFactor !== 0) {
271
+ // gravityFactor *= -1;
272
+ temp3.copy(this._gravityDirection).multiplyScalar(gravityFactor * delta * Math.PI);
273
+ // Gizmos.DrawDirection(particle.position, temp3, 0xff0000, 0, false, 10);
274
+ baseVelocity.add(temp3);
216
275
  }
276
+ particle.velocity.copy(baseVelocity);
217
277
 
218
- if (this._container && this.gameObject?.parent) {
219
- // this._container.matrix.copy(this.gameObject.matrixWorld);
220
- // this._container.matrixWorld.copy(this.gameObject.matrixWorld);
278
+ const t01 = particle.age / particle.life;
221
279
 
222
- const scale = getWorldScale(this.gameObject.parent);
223
- scale.x = 1 / scale.x;
224
- scale.y = 1 / scale.y;
225
- scale.z = 1 / scale.z;
226
- // console.log(scale);
227
- // this._container.scale.copy(scale);
228
- // // this._container.updateMatrix();
229
- // // this._container.updateMatrixWorld();
230
- // // // set scale to 1
231
- this._container.matrix.identity();
232
- this._container.matrix.scale(scale);
233
- // this._container.matrixWorld.copy(this._container.matrix);
280
+ const noise = this.system.noise;
281
+ if (noise.enabled) {
282
+ noise.apply(0, particle.position, particle.velocity, delta, particle.age, particle.life);
234
283
  }
235
- // this._system.time
236
- // this._system.duration = this.main.duration;
237
- this.emission.system = this;
238
- this.shape.update(this.context, this.main.simulationSpace, this.gameObject);
239
- this.noise.update(this.context);
240
- this._system.update(this.context.time.deltaTime * this.main.simulationSpeed);
241
- this._time += this.context.time.deltaTime;
242
- if (this._time > this.duration) this._time = 0;
243
- }
244
-
245
- }
246
284
 
285
+ const velocity = this.system.velocityOverLifetime;
286
+ if (velocity.enabled) {
287
+ velocity.apply(0, particle.position, particle.velocity, delta, particle.age, particle.life);
288
+ }
247
289
 
248
- class MinMaxCurveSpan {
249
-
250
- readonly time: Time;
251
- readonly curve: MinMaxCurve;
252
- multiplier: number = 1;
290
+ const limitVelocityOverLifetime = this.system.limitVelocityOverLifetime;
291
+ if (limitVelocityOverLifetime.enabled) {
292
+ // const factor = this.system.worldScale.x;
293
+ limitVelocityOverLifetime.apply(particle.position, baseVelocity, particle.velocity, particle.size, t01, delta, 1);
294
+ }
253
295
 
254
- constructor(minMaxCurve: MinMaxCurve, multiplier: number = 1) {
255
- this.time = Context.Current.time;
256
- this.curve = minMaxCurve;
257
- this.multiplier = multiplier;
258
- }
296
+ // const speed = particle.velocity.length();
297
+ // particle.velocity.multiplyScalar(1-delta);
259
298
 
260
- /** called by nebula */
261
- getValue(lerp?: number) {
262
- const res = this.curve.evaluate(this.time.time, lerp) * this.multiplier;
263
- return res;
299
+ // const vel = this.system.velocityOverLifetime.evaluate(particle.age / particle.life);
264
300
  }
265
301
  }
266
302
 
267
- abstract class CustomBehaviour extends Behaviour {
268
- abstract applyBehaviour(target: Particle | Emitter, time: number, index: number);
269
- abstract initialize(particle: Particle);
270
- }
271
-
272
- abstract class MinMaxCurveBehaviour extends CustomBehaviour {
303
+ class ColorBehaviour extends ParticleSystemBaseBehaviour {
304
+ type: string = "NeedleColor";
273
305
 
274
- readonly startCurve: MinMaxCurve;
275
- startMultiplier: number = 1;
306
+ initialize(particle: Particle): void {
307
+ const col = this.system.main.startColor.evaluate(particle.age / particle.life, Math.random());
308
+ particle.startColor.set(col.r, col.g, col.b, col.alpha);
309
+ particle.color.copy(particle.startColor);
310
+ }
276
311
 
277
- constructor(startCurve: MinMaxCurve, startMultiplier: number = 1) {
278
- super();
279
- this.startCurve = startCurve;
280
- this.startMultiplier = startMultiplier;
312
+ update(particle: Particle, _delta: number): void {
313
+ if (this.system.colorOverLifetime.enabled) {
314
+ const t = particle.age / particle.life;
315
+ const col = this.system.colorOverLifetime.color.evaluate(t);
316
+ particle.color.set(col.r, col.g, col.b, col.alpha).multiply(particle.startColor);
317
+ }
281
318
  }
282
319
  }
283
320
 
284
- declare type ParticleWithScale = Particle & { target: Object3D, start_scale: number };
321
+ class ParticleSystemInterface implements ParticleSystemParameters {
285
322
 
286
- class SizeBehaviour extends MinMaxCurveBehaviour {
323
+ private readonly system: ParticleSystem;
324
+ private readonly emission: ParticleSystemEmissionOverTime;
325
+ private get anim(): TextureSheetAnimationModule {
326
+ return this.system.textureSheetAnimation;
327
+ }
287
328
 
288
- sizeOverLifetimeModule!: SizeOverLifetimeModule;
329
+ constructor(system: ParticleSystem) {
330
+ this.system = system;
331
+ this.emission = new ParticleSystemEmissionOverTime(this.system);
332
+ }
289
333
 
290
- cameraScale: number = 1;
334
+ update() {
335
+ this.emission.update();
336
+ }
291
337
 
292
- applyBehaviour(particle: ParticleWithScale, _deltaTime: number, _index: number) {
293
- if (particle.target.type === "Sprite") {
294
- particle.radius = 1 / this.cameraScale;
338
+ autoDestroy?: boolean | undefined;
339
+ get looping() { return this.system.main.loop; }
340
+ get duration() { return this.system.duration; }
341
+ get shape(): EmitterShape { return this.system.shape; }
342
+ get startLife() { return new MinMaxCurveFunction(this.system.main.startLifetime); }
343
+ get startSpeed() { return new MinMaxCurveFunction(this.system.main.startSpeed); }
344
+ get startRotation() { return new MinMaxCurveFunction(this.system.main.startRotation); }
345
+ get startSize() { return new MinMaxCurveFunction(this.system.main.startSize); }
346
+ startLength?: ValueGenerator | FunctionValueGenerator | undefined; /** start length is for trails */
347
+ get startColor() { return new ConstantColor(new Vector4(1, 1, 1, 1)); }
348
+ get emissionOverTime() { return this.emission; }
349
+ /** this is not supported yet */
350
+ get emissionOverDistance() { return new ParticleSystemEmissionOverDistance(this.system); }
351
+ /** not used - burst is controled via emissionOverTime */
352
+ emissionBursts?: BurstParameters[] | undefined;
353
+ onlyUsedByOther?: boolean | undefined;
354
+ readonly behaviors: Behavior[] = [];
355
+ get instancingGeometry() {
356
+ return this.system.renderer.getMesh().geometry;
357
+ }
358
+ get renderMode() {
359
+ if (this.system.trails["enabled"] === true) {
360
+ return RenderMode.Trail;
295
361
  }
296
- else particle.radius = 1;
297
-
298
- // particle.target.scale.set(1,1,1);
299
- if (this.sizeOverLifetimeModule?.enabled) {
300
- const time = particle.age / particle.life;
301
- const scaleVector = particle.target.scale;
302
- this.sizeOverLifetimeModule.evaluate?.call(this.sizeOverLifetimeModule, time, scaleVector);
303
- // scaleVector.set(.1, .1, .1)
304
- particle.scale = particle.start_scale * scaleVector.x;
305
- // console.log(time, particle.scale);
362
+ switch (this.system.renderer.renderMode) {
363
+ case ParticleSystemRenderMode.Billboard: return RenderMode.BillBoard;
364
+ // case ParticleSystemRenderMode.Stretch: return RenderMode.Stretch;
365
+ // case ParticleSystemRenderMode.HorizontalBillboard: return RenderMode.HorizontalBillboard;
366
+ // case ParticleSystemRenderMode.VerticalBillboard: return RenderMode.VerticalBillboard;
367
+ case ParticleSystemRenderMode.Mesh: return RenderMode.LocalSpace;
306
368
  }
369
+ return RenderMode.BillBoard;
307
370
  }
308
- initialize(particle: ParticleWithScale) {
309
- particle.scale = this.startCurve.evaluate(0, Math.random());// * this.startMultiplier;
310
- particle.start_scale = particle.scale;
311
-
371
+ rendererEmitterSettings: TrailSettings = {
372
+ startLength: new ConstantValue(220),
373
+ followLocalOrigin: false,
374
+ };
375
+ get speedFactor() { return this.system.main.simulationSpeed; }
376
+ get texture(): THREE.Texture {
377
+ const mat = this.system.renderer.particleMaterial;
378
+ if (mat && mat["map"]) {
379
+ const tex = mat["map"]!;
380
+ tex.premultiplyAlpha = false;
381
+ tex.encoding = THREE.LinearEncoding;
382
+ return tex;
383
+ }
384
+ return createFlatTexture(new RGBAColor(1, 1, 1, 1), 1)
312
385
  }
386
+ get startTileIndex() { return new TextureSheetStartFrameGenerator(this.system); }
387
+ get uTileCount() { return this.anim.enabled ? this.anim?.numTilesX : undefined }
388
+ get vTileCount() { return this.anim.enabled ? this.anim?.numTilesY : undefined }
389
+ get renderOrder() { return 1; }
390
+ get blending(): THREE.Blending { return this.system.renderer.particleMaterial?.blending ?? THREE.NormalBlending; }
391
+ get transparent() { return this.system.renderer.transparent; }
392
+ get worldSpace() { return this.system.main.simulationSpace === ParticleSystemSimulationSpace.World; }
393
+
313
394
  }
314
395
 
315
- declare type ParticleWithColor = Particle & { color_start: { r: number, g: number, b: number }, alpha_start: number };
396
+ class ParticlesEmissionState implements EmissionState {
397
+
398
+ burstIndex: number = 0;
399
+ burstWaveIndex: number = 0;
400
+ time: number = 0;
401
+ waitEmiting: number = 0;
402
+ }
316
403
 
317
- class GradientBehaviour extends CustomBehaviour {
404
+ export class ParticleSystem extends Behaviour implements IParticleSystem {
318
405
 
319
- private startGradient: MinMaxGradient;
320
- private gradient?: MinMaxGradient;
406
+ play(includeChildren: boolean = false) {
407
+ if (includeChildren) {
408
+ GameObject.foreachComponent(this.gameObject, comp => {
409
+ if (comp instanceof ParticleSystem && comp !== this) {
410
+ comp.play(false);
411
+ }
412
+ }, true)
413
+ }
414
+ this._isPlaying = true;
415
+ this._time = 0;
321
416
 
322
- constructor(startGradient: MinMaxGradient, gradient?: MinMaxGradient) {
323
- super();
324
- this.startGradient = startGradient;
325
- this.gradient = gradient;
417
+ // https://github.com/Alchemist0823/three.quarks/pull/35
418
+ if (this._particleSystem) {
419
+ this._particleSystem["emissionState"].time = 0;
420
+ this._particleSystem["emitEnded"] = false;
421
+ }
422
+ this.emission?.reset();
326
423
  }
327
424
 
328
- /** called from nebula */
329
- applyBehaviour(target: ParticleWithColor | Emitter, _deltaTime: number, _index: number) {
330
- // console.log(target, time, index)
331
- if (target instanceof Particle) {
332
- if (this.gradient) {
333
- const t = target.age / target.life;
334
- const ev = this.gradient.evaluate(t) as RGBAColor;
335
- const startColor = target.color_start;
336
- target.color.r = startColor.r * ev.r;
337
- target.color.g = startColor.g * ev.g;
338
- target.color.b = startColor.b * ev.b;
339
- target.alpha = ev.alpha * target.alpha_start;
340
- }
425
+ pause() {
426
+ this._isPlaying = false;
427
+ }
428
+ stop() {
429
+ this._isPlaying = false;
430
+ this._time = 0;
431
+ }
432
+
433
+ private _state?: ParticlesEmissionState;
434
+ emit(count: number) {
435
+ if (this._particleSystem) {
436
+ count = Math.min(count, this.maxParticles - this.currentParticles);
437
+ if (!this._state) this._state = new ParticlesEmissionState();
438
+ this._state.waitEmiting = count;
439
+ this._state.time = 0;
440
+ const emitEndedState = this._particleSystem["emitEnded"];
441
+ this._particleSystem["emitEnded"] = false;
442
+ this._particleSystem.emit(this.deltaTime, this._state, this._particleSystem.emitter.matrixWorld);
443
+ this._particleSystem["emitEnded"] = emitEndedState;
341
444
  }
342
445
  }
343
446
 
344
- private id = Symbol('gradientId');
345
- private _internalId: number = 0;
447
+ @serializeable(ColorOverLifetimeModule)
448
+ readonly colorOverLifetime!: ColorOverLifetimeModule;
346
449
 
347
- /** called from nebula */
348
- initialize(particle: ParticleWithColor) {
450
+ @serializeable(MainModule)
451
+ readonly main!: MainModule;
349
452
 
350
- // console.log(particle);
351
- // const context = Context.Current;
352
- // const time = context.time.time;
353
- const ev = this.startGradient.evaluate(Math.random()) as RGBAColor;
354
- particle.color.r = ev.r;
355
- particle.color.g = ev.g;
356
- particle.color.b = ev.b;
357
- particle.alpha = ev.alpha
453
+ @serializeable(EmissionModule)
454
+ readonly emission!: EmissionModule;
358
455
 
359
- particle.age = 0;
456
+ @serializeable(SizeOverLifetimeModule)
457
+ readonly sizeOverLifetime!: SizeOverLifetimeModule;
360
458
 
361
- particle.color_start = { r: ev.r, g: ev.g, b: ev.b };
362
- particle.alpha_start = ev.alpha;
459
+ @serializeable(ShapeModule)
460
+ readonly shape!: ShapeModule;
363
461
 
364
- particle.useColor = true;
365
- particle.useAlpha = true;
462
+ @serializeable(NoiseModule)
463
+ readonly noise!: NoiseModule;
366
464
 
367
- if (particle[this.id] === undefined)
368
- particle[this.id] = this._internalId++;
369
- }
370
- }
465
+ // @serializeable(TrailModule)
466
+ readonly trails!: TrailModule;
371
467
 
468
+ @serializeable(VelocityOverLifetimeModule)
469
+ readonly velocityOverLifetime!: VelocityOverLifetimeModule;
372
470
 
373
- declare type ParticleWithVelocity = Particle & { velocity_start: { x: number, y: number, u: number }, gravity: number };
471
+ @serializeable(LimitVelocityOverLifetimeModule)
472
+ readonly limitVelocityOverLifetime!: LimitVelocityOverLifetimeModule;
374
473
 
375
- class VelocityBehaviour extends CustomBehaviour {
474
+ @serializeable(TextureSheetAnimationModule)
475
+ readonly textureSheetAnimation!: TextureSheetAnimationModule;
376
476
 
377
- container: Object3D;
477
+ @serializeable(RotationOverLifetimeModule)
478
+ readonly rotationOverLifetime!: RotationOverLifetimeModule;
378
479
 
379
- shape: ShapeModule;
380
- noise: NoiseModule;
480
+ @serializeable(RotationBySpeedModule)
481
+ readonly rotationBySpeed!: RotationBySpeedModule;
381
482
 
382
- startSpeed: MinMaxCurve;
383
- startMultiplier: number = 1;
483
+ get renderer(): ParticleSystemRenderer {
484
+ return this._renderer;
485
+ }
384
486
 
385
- gravity?: MinMaxCurve;
386
- gravityModifierMultiplier: number = 1;
487
+ get isPlaying() { return this.isPlaying; }
387
488
 
388
- downDirection = new Vector3();
389
- private _temp: Vector3 = new Vector3();
489
+ get currentParticles() {
490
+ return this._particleSystem?.particleNum ?? 0;
491
+ }
492
+ get maxParticles() {
493
+ return this.main.maxParticles;
494
+ }
495
+ get time() {
496
+ return this._time;
497
+ }
498
+ get duration() {
499
+ return this.main.duration;
500
+ }
501
+ get deltaTime() {
502
+ return this.context.time.deltaTime * this.main.simulationSpeed;
503
+ }
504
+ get scale() {
505
+ return this.gameObject.scale.x;
506
+ }
507
+ get cameraScale(): number {
508
+ return this._cameraScale;
509
+ }
510
+ private _cameraScale: number = 1;
390
511
 
391
- constructor(container: Object3D, shape: ShapeModule, noise: NoiseModule, startCurve: MinMaxCurve, startMultiplier: number) {
392
- super();
393
- this.container = container;
394
- this.shape = shape;
395
- this.noise = noise;
396
- this.startSpeed = startCurve;
397
- this.startMultiplier = startMultiplier;
512
+ get container(): Object3D {
513
+ return this._container!;
514
+ }
398
515
 
516
+ get worldspace() {
517
+ return this.main.simulationSpace === ParticleSystemSimulationSpace.World;
399
518
  }
400
519
 
401
- applyBehaviour(target: ParticleWithVelocity | Emitter, deltaTime: number, index: number) {
520
+ private __worldQuaternion = new Quaternion();
521
+ get worldQuaternion(): Quaternion {
522
+ return this.__worldQuaternion;
523
+ }
524
+ private _worldQuaternionInverted = new Quaternion();
525
+ get worldQuaternionInverted(): Quaternion {
526
+ return this._worldQuaternionInverted;
527
+ }
528
+ private _worldScale = new Vector3();
529
+ get worldScale(): Vector3 {
530
+ return this._worldScale;
531
+ }
402
532
 
403
- if (target instanceof Particle) {
404
- // target.velocity.x = target.velocity_start.x;
405
- // target.velocity.y = target.velocity_start.y;
406
- // target.velocity.z = target.velocity_start.z;
407
- if (target.gravity !== 0 && this.gravity) {
408
- // get world down vector
409
- this._temp.copy(this.downDirection);
410
- this._temp.multiplyScalar(target.gravity * deltaTime);
411
- target.velocity.add(this._temp);
412
- }
533
+ private _renderer!: ParticleSystemRenderer;
413
534
 
414
- if (this.noise) {
415
- this.noise.applyNoise(index, target.position, target.velocity, deltaTime, target.age, target.life);
535
+ private _batchSystem?: BatchedParticleRenderer;
536
+ private _particleSystem?: _ParticleSystem;
537
+ private _interface!: ParticleSystemInterface;
538
+
539
+ // private _system!: System;
540
+ // private _emitter: Emitter;
541
+ // private _size!: SizeBehaviour;
542
+ private _container!: Object3D;
543
+ private _time: number = 0;
544
+ private _isPlaying: boolean = true;
545
+
546
+ /** called from deserialization */
547
+ private set bursts(arr: ParticleBurst[]) {
548
+ for (let i = 0; i < arr.length; i++) {
549
+ const burst = arr[i];
550
+ if ((burst instanceof ParticleBurst) === false) {
551
+ const instance = new ParticleBurst();
552
+ assign(instance, burst);
553
+ arr[i] = instance;
416
554
  }
417
555
  }
556
+ this._bursts = arr;
418
557
  }
558
+ private _bursts?: ParticleBurst[];
559
+
560
+ awake(): void {
561
+ this._renderer = this.gameObject.getComponent(ParticleSystemRenderer) as ParticleSystemRenderer;
419
562
 
420
- initialize(particle: ParticleWithVelocity) {
421
- const dir = this.shape.getDirection(particle.position);
422
- // dir.applyQuaternion(quat);
423
- const speed = this.startSpeed.evaluate(0, Math.random()) * this.startMultiplier;
424
- particle.velocity.x = dir.x * speed;
425
- particle.velocity.y = dir.y * speed;
426
- particle.velocity.z = dir.z * speed;
427
- if (!particle.velocity_start)
428
- particle.velocity_start = {};
429
- particle.velocity_start.x = particle.velocity.x;
430
- particle.velocity_start.y = particle.velocity.y;
431
- particle.velocity_start.z = particle.velocity.z;
432
-
433
- if (this.gravity) {
434
- particle.gravity = this.gravity.evaluate(Math.random(), Math.random()) * 9.81;
563
+ this._container = new Object3D();
564
+ this._container.matrixAutoUpdate = false;
565
+ if (this.main.simulationSpace == ParticleSystemSimulationSpace.Local) {
566
+ this.gameObject.add(this._container);
435
567
  }
436
568
  else {
437
- particle.gravity = 0;
569
+ this.context.scene.add(this._container);
438
570
  }
571
+ // else this._container = this.context.scene;
572
+
573
+ this._batchSystem = new BatchedParticleRenderer();
574
+ this._container.add(this._batchSystem);
575
+ this._interface = new ParticleSystemInterface(this);
576
+ this._particleSystem = new _ParticleSystem(this._batchSystem, this._interface);
577
+ this._particleSystem.addBehavior(new SizeBehaviour(this));
578
+ this._particleSystem.addBehavior(new VelocityBehaviour(this));
579
+ this._particleSystem.addBehavior(new ColorBehaviour(this));
580
+ this._particleSystem.addBehavior(new TextureSheetAnimationBehaviour(this));
581
+ this._particleSystem.addBehavior(new RotationBehaviour(this));
582
+ const emitter = this._particleSystem.emitter;
583
+ this.context.scene.add(emitter);
439
584
 
440
- const quat = getWorldQuaternion(this.container);
441
- this.downDirection.set(0, -1, 0).applyQuaternion(quat);
585
+ if (debug) {
586
+ console.log(this);
587
+ this.gameObject.add(new AxesHelper(1))
588
+ }
589
+
590
+ // renderer.logRendererType = () => { };
591
+ // if (renderMode === ParticleSystemRenderMode.Billboard) {
592
+ // if (this.renderer.particleMaterial) {
593
+ // const sprite = renderer._body as Sprite;
594
+ // sprite.layers.disableAll();
595
+ // sprite.layers.set(2); // ignore raycasting particles
596
+ // sprite.renderOrder = 1;
597
+ // sprite.material.map = this.renderer.particleMaterial.map;
598
+ // sprite.material.transparent = true;
599
+ // // sprite.material.sizeAttenuation = false;
600
+ // sprite.material.blending = this.renderer.particleMaterial.blending;
601
+ // sprite.material.blendDst = this.renderer.particleMaterial.blendDst;
602
+ // sprite.material.blendDstAlpha = this.renderer.particleMaterial.blendDstAlpha;
603
+ // sprite.material.blendEquation = this.renderer.particleMaterial.blendEquation;
604
+ // sprite.material.blendEquationAlpha = this.renderer.particleMaterial.blendEquationAlpha;
605
+ // sprite.material.blendSrc = this.renderer.particleMaterial.blendSrc;
606
+ // sprite.material.blendSrcAlpha = this.renderer.particleMaterial.blendSrcAlpha;
607
+ // sprite.material.premultipliedAlpha = this.renderer.particleMaterial.premultipliedAlpha;
608
+ // sprite.material.depthWrite = false;
609
+ // sprite.material.depthTest = true;
610
+ // if (debug) console.log(sprite);
611
+ // }
612
+ // }
613
+
614
+ // setInterval(()=>{
615
+ // this.emit(100);
616
+ // }, 1000)
617
+ }
618
+
619
+ onEnable() {
620
+ this.play();
442
621
  }
443
622
 
444
- }
623
+ onDisable() {
624
+ }
625
+
626
+ onBeforeRender() {
627
+ if (this._bursts) {
628
+ this.emission.bursts = this._bursts;
629
+ delete this._bursts;
630
+ }
631
+ if (!this._isPlaying) return;
632
+
633
+ // sprite materials must be scaled in AR
634
+ const cam = this.context.mainCamera;
635
+ if (cam) {
636
+ const scale = getWorldScale(cam);
637
+ this._cameraScale = scale.x;
638
+ }
639
+
640
+ let source = this._container;
641
+ if (this.worldspace)
642
+ source = this.gameObject;
643
+ getWorldQuaternion(source, this.__worldQuaternion)
644
+ this._worldQuaternionInverted.copy(this.__worldQuaternion).invert();
645
+ getWorldScale(this.gameObject, this._worldScale);
646
+
647
+ if (!this.worldspace && this._container && this.gameObject?.parent) {
648
+ const scale = getWorldScale(this.gameObject.parent);
649
+ scale.x = 1 / scale.x;
650
+ scale.y = 1 / scale.y;
651
+ scale.z = 1 / scale.z;
652
+ this._container.matrix.identity();
653
+ this._container.matrix.scale(scale);
654
+ }
655
+
656
+ this.emission.system = this;
657
+ const dt = this.deltaTime;
658
+ this._interface.update();
659
+ this.shape.update(this, this.context, this.main.simulationSpace, this.gameObject);
660
+ this.noise.update(this.context);
661
+ this.velocityOverLifetime.update(this);
662
+ this._batchSystem?.update(dt);
663
+ this._time += dt;
664
+ if (this._time > this.duration) this._time = 0;
665
+ }
666
+
667
+ }