@rzmps/rzmps 1.0.0
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.
- package/README.md +1048 -0
- package/build/EmissionShape.d.ts +33 -0
- package/build/EmissionShape.js +129 -0
- package/build/Emitter.d.ts +58 -0
- package/build/Emitter.js +166 -0
- package/build/GpuModule.d.ts +28 -0
- package/build/GpuModule.js +28 -0
- package/build/Module.d.ts +19 -0
- package/build/Module.js +31 -0
- package/build/Particle.d.ts +60 -0
- package/build/Particle.js +52 -0
- package/build/ParticleForceField.d.ts +39 -0
- package/build/ParticleForceField.js +101 -0
- package/build/ParticleForceFieldHelper.d.ts +18 -0
- package/build/ParticleForceFieldHelper.js +103 -0
- package/build/ParticleSystem.d.ts +122 -0
- package/build/ParticleSystem.js +609 -0
- package/build/Renderer.d.ts +15 -0
- package/build/Renderer.js +11 -0
- package/build/assets/images/default.png +0 -0
- package/build/collision/ThreeCollisionBackend.d.ts +47 -0
- package/build/collision/ThreeCollisionBackend.js +340 -0
- package/build/enums/Axis.d.ts +6 -0
- package/build/enums/Axis.js +7 -0
- package/build/enums/DynamicValueType.d.ts +6 -0
- package/build/enums/DynamicValueType.js +7 -0
- package/build/enums/EmissionSource copy.d.ts +7 -0
- package/build/enums/EmissionSource copy.js +7 -0
- package/build/enums/EmissionSource.d.ts +7 -0
- package/build/enums/EmissionSource.js +7 -0
- package/build/enums/EndBehavior.d.ts +6 -0
- package/build/enums/EndBehavior.js +7 -0
- package/build/enums/SimulationSpace.d.ts +5 -0
- package/build/enums/SimulationSpace.js +6 -0
- package/build/enums/SpriteMaterialType.d.ts +5 -0
- package/build/enums/SpriteMaterialType.js +6 -0
- package/build/enums/TagSelectionMethod.d.ts +6 -0
- package/build/enums/TagSelectionMethod.js +7 -0
- package/build/enums/TrailMode copy.d.ts +5 -0
- package/build/enums/TrailMode copy.js +7 -0
- package/build/enums/TrailMode.d.ts +5 -0
- package/build/enums/TrailMode.js +7 -0
- package/build/enums/TrailTextureMode.d.ts +7 -0
- package/build/enums/TrailTextureMode.js +9 -0
- package/build/gpu/GpuDynamicValue.d.ts +9 -0
- package/build/gpu/GpuDynamicValue.js +113 -0
- package/build/gpu/GpuMath.d.ts +3 -0
- package/build/gpu/GpuMath.js +12 -0
- package/build/gpu/GpuParticleContext.d.ts +63 -0
- package/build/gpu/GpuParticleContext.js +291 -0
- package/build/helpers/acceptMultiple.d.ts +2 -0
- package/build/helpers/acceptMultiple.js +6 -0
- package/build/helpers/evaluateDynamic.d.ts +2 -0
- package/build/helpers/evaluateDynamic.js +18 -0
- package/build/helpers/evaluateDynamicColor.d.ts +3 -0
- package/build/helpers/evaluateDynamicColor.js +5 -0
- package/build/helpers/evaluateDynamicNumber.d.ts +2 -0
- package/build/helpers/evaluateDynamicNumber.js +4 -0
- package/build/helpers/evaluateDynamicVector3.d.ts +3 -0
- package/build/helpers/evaluateDynamicVector3.js +5 -0
- package/build/helpers/isPointInMesh.d.ts +2 -0
- package/build/helpers/isPointInMesh.js +8 -0
- package/build/helpers/particleRatio.d.ts +2 -0
- package/build/helpers/particleRatio.js +13 -0
- package/build/helpers/tagsIntersect.d.ts +2 -0
- package/build/helpers/tagsIntersect.js +4 -0
- package/build/index.d.ts +37 -0
- package/build/index.js +34 -0
- package/build/interfaces/ICollisionBackend.d.ts +21 -0
- package/build/interfaces/ICollisionBackend.js +1 -0
- package/build/interfaces/IParticleForceField.d.ts +5 -0
- package/build/interfaces/IParticleForceField.js +1 -0
- package/build/interfaces/InitialParticleValues.d.ts +18 -0
- package/build/interfaces/InitialParticleValues.js +1 -0
- package/build/materials/BasicSprite.d.ts +21 -0
- package/build/materials/BasicSprite.js +46 -0
- package/build/materials/UnlitSprite.d.ts +13 -0
- package/build/materials/UnlitSprite.js +31 -0
- package/build/materials/WebGPUBasicSprite.d.ts +8 -0
- package/build/materials/WebGPUBasicSprite.js +65 -0
- package/build/materials/WebGPUUnlitSprite.d.ts +8 -0
- package/build/materials/WebGPUUnlitSprite.js +49 -0
- package/build/modules/Audio.d.ts +60 -0
- package/build/modules/Audio.js +181 -0
- package/build/modules/Collision.d.ts +40 -0
- package/build/modules/Collision.js +162 -0
- package/build/modules/ColorBySpeed.d.ts +18 -0
- package/build/modules/ColorBySpeed.js +26 -0
- package/build/modules/ColorOverLifetime.d.ts +12 -0
- package/build/modules/ColorOverLifetime.js +17 -0
- package/build/modules/ExternalForces.d.ts +21 -0
- package/build/modules/ExternalForces.js +61 -0
- package/build/modules/ForceOverLifetime.d.ts +11 -0
- package/build/modules/ForceOverLifetime.js +12 -0
- package/build/modules/LimitVelocityOverLifetime.d.ts +16 -0
- package/build/modules/LimitVelocityOverLifetime.js +34 -0
- package/build/modules/MassOverLifetime.d.ts +11 -0
- package/build/modules/MassOverLifetime.js +15 -0
- package/build/modules/NoiseModule.d.ts +23 -0
- package/build/modules/NoiseModule.js +45 -0
- package/build/modules/RotationBySpeed.d.ts +14 -0
- package/build/modules/RotationBySpeed.js +19 -0
- package/build/modules/RotationOverLifetime.d.ts +11 -0
- package/build/modules/RotationOverLifetime.js +12 -0
- package/build/modules/ScaleBySpeed.d.ts +14 -0
- package/build/modules/ScaleBySpeed.js +19 -0
- package/build/modules/ScaleOverLifetime.d.ts +11 -0
- package/build/modules/ScaleOverLifetime.js +11 -0
- package/build/modules/TransformByNoise.d.ts +17 -0
- package/build/modules/TransformByNoise.js +32 -0
- package/build/modules/VelocityOverLifetime.d.ts +15 -0
- package/build/modules/VelocityOverLifetime.js +32 -0
- package/build/renderers/LightRenderer.d.ts +54 -0
- package/build/renderers/LightRenderer.js +141 -0
- package/build/renderers/MeshRenderer.d.ts +27 -0
- package/build/renderers/MeshRenderer.js +70 -0
- package/build/renderers/SpriteRenderer.d.ts +95 -0
- package/build/renderers/SpriteRenderer.js +456 -0
- package/build/renderers/TrailRenderer.d.ts +71 -0
- package/build/renderers/TrailRenderer.js +400 -0
- package/build/shaders/BasicSprite.frag.js +1 -0
- package/build/shaders/BasicSprite.vert.js +1 -0
- package/build/shaders/UnlitSprite.frag.js +1 -0
- package/build/shaders/UnlitSprite.vert.js +1 -0
- package/build/types/DynamicValue.d.ts +2 -0
- package/build/types/DynamicValue.js +1 -0
- package/build/types/EnumStringLiteral.d.ts +1 -0
- package/build/types/EnumStringLiteral.js +1 -0
- package/build/types/Multiple.d.ts +2 -0
- package/build/types/Multiple.js +1 -0
- package/build/types/Tag.d.ts +1 -0
- package/build/types/Tag.js +1 -0
- package/package.json +39 -0
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import Emitter from './Emitter';
|
|
3
|
+
import acceptMultiple from './helpers/acceptMultiple';
|
|
4
|
+
import SpriteRenderer from './renderers/SpriteRenderer';
|
|
5
|
+
import evaluateDynamicNumber from './helpers/evaluateDynamicNumber';
|
|
6
|
+
import particleRatio from './helpers/particleRatio';
|
|
7
|
+
import { EndBehavior } from './enums/EndBehavior';
|
|
8
|
+
import { SimulationSpace } from './enums/SimulationSpace';
|
|
9
|
+
class ParticleSystem extends THREE.Object3D {
|
|
10
|
+
get simulationSpace() {
|
|
11
|
+
return this._simulationSpace;
|
|
12
|
+
}
|
|
13
|
+
set simulationSpace(value) {
|
|
14
|
+
if (value === this._simulationSpace)
|
|
15
|
+
return;
|
|
16
|
+
this.convertParticlesToSimulationSpace(value);
|
|
17
|
+
this._simulationSpace = value;
|
|
18
|
+
this.subSystems.forEach((_options, subSystem) => {
|
|
19
|
+
subSystem.simulationSpace = value;
|
|
20
|
+
});
|
|
21
|
+
this.syncRendererParents();
|
|
22
|
+
}
|
|
23
|
+
get scene() { return this._scene; }
|
|
24
|
+
get sceneCamera() { return this._camera; }
|
|
25
|
+
get sceneRenderer() { return this._renderer; }
|
|
26
|
+
get isSubSystem() { return !!this._subSystemParent; }
|
|
27
|
+
get destroyed() { return this._destroyed; }
|
|
28
|
+
constructor(options = {}) {
|
|
29
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
30
|
+
super();
|
|
31
|
+
this.particles = [];
|
|
32
|
+
this.emitters = [];
|
|
33
|
+
this.modules = [];
|
|
34
|
+
this.renderers = [];
|
|
35
|
+
this.subSystems = new Map();
|
|
36
|
+
this._simulationSpace = SimulationSpace.Local;
|
|
37
|
+
this.deltaTime = 0;
|
|
38
|
+
this._deathListeners = [];
|
|
39
|
+
this._spawnListeners = [];
|
|
40
|
+
this._collisionListeners = [];
|
|
41
|
+
this._emissionRuns = [];
|
|
42
|
+
this._nextEmissionRunId = 0;
|
|
43
|
+
this._playing = true;
|
|
44
|
+
this._paused = false;
|
|
45
|
+
this._elapsedTime = 0;
|
|
46
|
+
this._ended = false;
|
|
47
|
+
this._destroyed = false;
|
|
48
|
+
this._rendererObjects = new Set();
|
|
49
|
+
this._worldRendererRoot = new THREE.Group();
|
|
50
|
+
this.emitters = (_b = acceptMultiple((_a = options.emitters) !== null && _a !== void 0 ? _a : new Emitter())) !== null && _b !== void 0 ? _b : [];
|
|
51
|
+
this.renderers = (_d = acceptMultiple((_c = options.renderers) !== null && _c !== void 0 ? _c : new SpriteRenderer())) !== null && _d !== void 0 ? _d : [];
|
|
52
|
+
this.modules = (_e = acceptMultiple(options.modules)) !== null && _e !== void 0 ? _e : [];
|
|
53
|
+
this.simulationSpeed = (_f = options.simulationSpeed) !== null && _f !== void 0 ? _f : 1;
|
|
54
|
+
this.duration = (_g = options.duration) !== null && _g !== void 0 ? _g : 10;
|
|
55
|
+
this.looping = (_h = options.looping) !== null && _h !== void 0 ? _h : true;
|
|
56
|
+
this.endBehavior = (_j = options.endBehavior) !== null && _j !== void 0 ? _j : EndBehavior.None;
|
|
57
|
+
// If gravity is passed in, gravityModifier will be set to 1.
|
|
58
|
+
// In effect, this means gravity will be turned off by default,
|
|
59
|
+
// but if either gravity or gravityModifier are specified, they will be used.
|
|
60
|
+
this.gravity = (_k = options.gravity) !== null && _k !== void 0 ? _k : new THREE.Vector3(0, -9.81, 0);
|
|
61
|
+
this.gravityModifier = (_l = options.gravityModifier) !== null && _l !== void 0 ? _l : (options.gravity ? 1 : 0);
|
|
62
|
+
this._simulationSpace = (_m = options.simulationSpace) !== null && _m !== void 0 ? _m : this._simulationSpace;
|
|
63
|
+
this._worldRendererRoot.name = 'ParticleSystem World Renderers';
|
|
64
|
+
this.lastFrame = Date.now();
|
|
65
|
+
this.emitters.forEach((e) => e.setup(this));
|
|
66
|
+
this.renderers.forEach((r) => r.setup(this));
|
|
67
|
+
// Store these here so that other renderers/modules can access easily
|
|
68
|
+
// Dummy mesh that renders nothing allows us to catch the onBeforeRender hook
|
|
69
|
+
const dummy = new THREE.Mesh();
|
|
70
|
+
this.add(dummy);
|
|
71
|
+
dummy.onBeforeRender = (renderer, scene, camera) => {
|
|
72
|
+
this._renderer = renderer;
|
|
73
|
+
this._scene = scene;
|
|
74
|
+
this._camera = camera;
|
|
75
|
+
};
|
|
76
|
+
// Cleanup on remove from scene. Anything done here should not be permanent.
|
|
77
|
+
// If the system is removed from one scene and added to another, it should
|
|
78
|
+
// still function.
|
|
79
|
+
this.addEventListener('removed', () => this.cleanup());
|
|
80
|
+
}
|
|
81
|
+
/*
|
|
82
|
+
* PROCESSING
|
|
83
|
+
*/
|
|
84
|
+
update() {
|
|
85
|
+
if (this._destroyed)
|
|
86
|
+
return;
|
|
87
|
+
// Subsystems are owned and ticked by parent, avoid double update
|
|
88
|
+
if (this._subSystemParent)
|
|
89
|
+
return;
|
|
90
|
+
// Tick delta time before checking for pause so that it stays consistent betwee frames, not between processed frames
|
|
91
|
+
this._calculateDeltaTime();
|
|
92
|
+
// Check for pauses
|
|
93
|
+
if (this._paused)
|
|
94
|
+
return;
|
|
95
|
+
this.syncRendererParents();
|
|
96
|
+
if (this._playing)
|
|
97
|
+
this._updateSystemTime();
|
|
98
|
+
if (this._playing)
|
|
99
|
+
this.emitters.forEach((emitter) => {
|
|
100
|
+
const particles = emitter.update(this.particles, this.getEmitterContext());
|
|
101
|
+
particles.forEach((p) => this._notifySpawn(p));
|
|
102
|
+
});
|
|
103
|
+
// Particle processing
|
|
104
|
+
this._processParticles();
|
|
105
|
+
// Subsystems
|
|
106
|
+
this._updateSubSystems();
|
|
107
|
+
this._handleEndBehavior();
|
|
108
|
+
}
|
|
109
|
+
_calculateDeltaTime() {
|
|
110
|
+
const now = Date.now();
|
|
111
|
+
this.deltaTime = this._scaleDeltaTime((now - this.lastFrame) / 1000);
|
|
112
|
+
this.lastFrame = now;
|
|
113
|
+
}
|
|
114
|
+
_scaleDeltaTime(deltaTime) {
|
|
115
|
+
const speed = Number.isFinite(this.simulationSpeed)
|
|
116
|
+
? Math.max(0, this.simulationSpeed)
|
|
117
|
+
: 1;
|
|
118
|
+
return deltaTime * speed;
|
|
119
|
+
}
|
|
120
|
+
_processParticles() {
|
|
121
|
+
// Moudle preparation
|
|
122
|
+
this.modules
|
|
123
|
+
.flatMap((module) => module.withDependents())
|
|
124
|
+
.forEach((module) => module.prepare(this, this.deltaTime));
|
|
125
|
+
// Run pre modules
|
|
126
|
+
this.modules
|
|
127
|
+
.flatMap((module) => module.withDependents())
|
|
128
|
+
.filter((module) => module.priority < 0)
|
|
129
|
+
.forEach((module) => module.modify(this.particles, this.deltaTime));
|
|
130
|
+
// Update particles
|
|
131
|
+
this._updateParticles();
|
|
132
|
+
// Run post modules
|
|
133
|
+
this.modules
|
|
134
|
+
.flatMap((module) => module.withDependents())
|
|
135
|
+
.filter((module) => module.priority >= 0)
|
|
136
|
+
.sort((a, b) => a.priority - b.priority)
|
|
137
|
+
.forEach((module) => module.modify(this.particles, this.deltaTime));
|
|
138
|
+
this.renderers.forEach((renderer) => {
|
|
139
|
+
renderer.update(this.particles, this, this.deltaTime);
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
_updateParticles() {
|
|
143
|
+
// Iterate over a copy of particles so that we can safely splice particles on death inside the loop
|
|
144
|
+
[...this.particles].forEach((p, index) => {
|
|
145
|
+
// Apply gravity
|
|
146
|
+
p.velocity.addScaledVector(this.gravity, this.deltaTime * evaluateDynamicNumber(this.gravityModifier, p.time, p.id));
|
|
147
|
+
// Update time
|
|
148
|
+
p.realtime += this.deltaTime * 1000;
|
|
149
|
+
p.time = (p.realtime / 1000) / p.lifetime;
|
|
150
|
+
// Update transform
|
|
151
|
+
p.position.addScaledVector(p.velocity, this.deltaTime * p.speed);
|
|
152
|
+
p.rotation.addScaledVector(p.angularVelocity, this.deltaTime * p.speed);
|
|
153
|
+
p.scale.addScaledVector(p.scalarVelocity, this.deltaTime * p.speed);
|
|
154
|
+
// Update velocities
|
|
155
|
+
p.velocity.addScaledVector(p.acceleration, this.deltaTime * p.speed);
|
|
156
|
+
p.angularVelocity.addScaledVector(p.angularAcceleration, this.deltaTime * p.speed);
|
|
157
|
+
p.scalarVelocity.addScaledVector(p.scalarAcceleration, this.deltaTime * p.speed);
|
|
158
|
+
// Kill old particles
|
|
159
|
+
if (p.realtime > p.lifetime * 1000) {
|
|
160
|
+
this._notifyDeath(p);
|
|
161
|
+
this.particles.splice(index, 1);
|
|
162
|
+
this.subSystems.forEach((_options, subSystem) => {
|
|
163
|
+
subSystem.emitters.forEach((emitter) => emitter.clearContext(p.id));
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
_updateSubSystems() {
|
|
169
|
+
this.subSystems.forEach((options, subSystem) => {
|
|
170
|
+
subSystem._updateAsSubSystem(this.particles, options, this.deltaTime);
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
_updateAsSubSystem(parentParticles, options, parentDeltaTime) {
|
|
174
|
+
if (!this._playing || this._paused)
|
|
175
|
+
return;
|
|
176
|
+
this.syncRendererParents();
|
|
177
|
+
this.deltaTime = this._scaleDeltaTime(parentDeltaTime);
|
|
178
|
+
this._updateSystemTime();
|
|
179
|
+
if (options.emitContinuous) {
|
|
180
|
+
parentParticles
|
|
181
|
+
.filter((particle) => this._canEmitForParticle(particle, options))
|
|
182
|
+
.forEach((particle) => {
|
|
183
|
+
const transform = this._particleEmissionTransform(particle, options.inheritScale);
|
|
184
|
+
this.emitters.forEach((emitter) => {
|
|
185
|
+
const particles = emitter.updateAt(this.particles, {
|
|
186
|
+
key: particle.id,
|
|
187
|
+
transform,
|
|
188
|
+
time: options.inheritLifetime ? particle.time : undefined,
|
|
189
|
+
duration: options.inheritLifetime ? particle.lifetime : undefined,
|
|
190
|
+
looping: options.inheritLifetime ? false : this.looping,
|
|
191
|
+
color: options.inheritColor ? particle.color : undefined,
|
|
192
|
+
alpha: options.inheritAlpha ? particle.alpha : undefined,
|
|
193
|
+
mass: options.inheritMass ? particle.mass : undefined,
|
|
194
|
+
tags: particle.tags,
|
|
195
|
+
});
|
|
196
|
+
particles.forEach((p) => this._notifySpawn(p));
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
this._updateEmissionRuns(options);
|
|
201
|
+
this._processParticles();
|
|
202
|
+
this._updateSubSystems();
|
|
203
|
+
}
|
|
204
|
+
_updateEmissionRuns(options) {
|
|
205
|
+
for (let i = this._emissionRuns.length - 1; i >= 0; i -= 1) {
|
|
206
|
+
const run = this._emissionRuns[i];
|
|
207
|
+
run.realtime += this.deltaTime;
|
|
208
|
+
let finished = true;
|
|
209
|
+
this.emitters.forEach((emitter) => {
|
|
210
|
+
var _a;
|
|
211
|
+
const duration = (_a = run.duration) !== null && _a !== void 0 ? _a : this.duration;
|
|
212
|
+
const elapsed = run.realtime;
|
|
213
|
+
const startTime = 0;
|
|
214
|
+
const time = startTime + (elapsed / duration);
|
|
215
|
+
// Ignore looping emitters
|
|
216
|
+
if (time < 1) {
|
|
217
|
+
finished = false;
|
|
218
|
+
const newParticles = emitter.updateAt(this.particles, {
|
|
219
|
+
key: run.id,
|
|
220
|
+
transform: run.transform,
|
|
221
|
+
time,
|
|
222
|
+
duration,
|
|
223
|
+
elapsedTime: elapsed,
|
|
224
|
+
looping: false,
|
|
225
|
+
color: options.inheritColor ? run.particle.color : undefined,
|
|
226
|
+
});
|
|
227
|
+
newParticles.forEach((particle) => this._notifySpawn(particle));
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
emitter.clearContext(run.id);
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
if (finished) {
|
|
234
|
+
this._emissionRuns.splice(i, 1);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
_canEmitForParticle(particle, options) {
|
|
239
|
+
var _a;
|
|
240
|
+
if (!particleRatio(particle, (_a = options.ratio) !== null && _a !== void 0 ? _a : 1))
|
|
241
|
+
return false;
|
|
242
|
+
return typeof options.shouldEmit === 'function'
|
|
243
|
+
? options.shouldEmit(particle)
|
|
244
|
+
: options.shouldEmit;
|
|
245
|
+
}
|
|
246
|
+
_startEmissionRunAtParticle(particle, options) {
|
|
247
|
+
const now = Date.now();
|
|
248
|
+
this._playing = true;
|
|
249
|
+
this._paused = false;
|
|
250
|
+
this._ended = false;
|
|
251
|
+
this.lastFrame = now;
|
|
252
|
+
this._emissionRuns.push({
|
|
253
|
+
id: `event_${this._nextEmissionRunId++}`,
|
|
254
|
+
transform: this._particleEmissionTransform(particle),
|
|
255
|
+
startTime: now,
|
|
256
|
+
realtime: 0,
|
|
257
|
+
// If lifetime is inherited, use the parent's lifetime instead
|
|
258
|
+
// of the subsystem emitter's configured duration.
|
|
259
|
+
duration: options.inheritLifetime
|
|
260
|
+
? particle.lifetime
|
|
261
|
+
: this.duration,
|
|
262
|
+
// Pass particle info -- clone so reference doesn't get destroyed
|
|
263
|
+
particle: {
|
|
264
|
+
position: particle.position.clone(),
|
|
265
|
+
rotation: particle.rotation.clone(),
|
|
266
|
+
scale: particle.scale.clone(),
|
|
267
|
+
color: particle.color.clone(),
|
|
268
|
+
tags: particle.tags ? [...particle.tags] : undefined,
|
|
269
|
+
alpha: particle.alpha,
|
|
270
|
+
lifetime: particle.lifetime,
|
|
271
|
+
mass: particle.mass,
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
_particleEmissionTransform(particle, inheritScale = false) {
|
|
276
|
+
const quaternion = new THREE.Quaternion().setFromEuler(new THREE.Euler(particle.rotation.x, particle.rotation.y, particle.rotation.z));
|
|
277
|
+
return new THREE.Matrix4().compose(particle.position, quaternion, inheritScale ? particle.scale : new THREE.Vector3(1, 1, 1));
|
|
278
|
+
}
|
|
279
|
+
/*
|
|
280
|
+
* CONTROL
|
|
281
|
+
*/
|
|
282
|
+
// Start emitting
|
|
283
|
+
start() {
|
|
284
|
+
const now = Date.now();
|
|
285
|
+
this._playing = true;
|
|
286
|
+
this._paused = false;
|
|
287
|
+
this._ended = false;
|
|
288
|
+
this._elapsedTime = 0;
|
|
289
|
+
this.lastFrame = now;
|
|
290
|
+
this.emitters.forEach((emitter) => emitter.reset());
|
|
291
|
+
this.subSystems.forEach((_options, subSystem) => {
|
|
292
|
+
subSystem.start();
|
|
293
|
+
});
|
|
294
|
+
this._emissionRuns.forEach((run) => {
|
|
295
|
+
this.emitters.forEach((emitter) => emitter.clearContext(run.id));
|
|
296
|
+
});
|
|
297
|
+
this._emissionRuns.length = 0;
|
|
298
|
+
}
|
|
299
|
+
// Pause emission and simulation
|
|
300
|
+
pause() {
|
|
301
|
+
if (!this._playing || this._paused)
|
|
302
|
+
return;
|
|
303
|
+
this._paused = true;
|
|
304
|
+
this.subSystems.forEach((_options, subSystem) => {
|
|
305
|
+
subSystem.pause();
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
resume() {
|
|
309
|
+
if (!this._playing || !this._paused)
|
|
310
|
+
return;
|
|
311
|
+
this._paused = false;
|
|
312
|
+
this.lastFrame = Date.now();
|
|
313
|
+
const now = Date.now();
|
|
314
|
+
this.particles.forEach((p) => {
|
|
315
|
+
p.startTime = now - p.realtime;
|
|
316
|
+
});
|
|
317
|
+
this._emissionRuns.forEach((e) => {
|
|
318
|
+
e.startTime = now - e.realtime * 1000;
|
|
319
|
+
});
|
|
320
|
+
this.subSystems.forEach((_options, subSystem) => {
|
|
321
|
+
subSystem.resume();
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
// Stop emission and optionally clear particles
|
|
325
|
+
stop(clearParticles) {
|
|
326
|
+
this._playing = false;
|
|
327
|
+
this._paused = false;
|
|
328
|
+
this._ended = true;
|
|
329
|
+
this.subSystems.forEach((_options, subSystem) => {
|
|
330
|
+
subSystem.stop(clearParticles);
|
|
331
|
+
});
|
|
332
|
+
this._emissionRuns.forEach((run) => {
|
|
333
|
+
this.emitters.forEach((emitter) => {
|
|
334
|
+
emitter.clearContext(run.id);
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
this._emissionRuns.length = 0;
|
|
338
|
+
if (clearParticles)
|
|
339
|
+
this.clearParticles();
|
|
340
|
+
}
|
|
341
|
+
destroy() {
|
|
342
|
+
if (this._destroyed)
|
|
343
|
+
return;
|
|
344
|
+
this._destroyed = true;
|
|
345
|
+
this.stop(true);
|
|
346
|
+
this.subSystems.forEach((_options, subSystem) => {
|
|
347
|
+
subSystem.destroy();
|
|
348
|
+
});
|
|
349
|
+
this.renderers.forEach((renderer) => {
|
|
350
|
+
renderer.destroy();
|
|
351
|
+
});
|
|
352
|
+
this.removeFromParent();
|
|
353
|
+
this.cleanup();
|
|
354
|
+
this.dispatchEvent({ type: 'destroyed' });
|
|
355
|
+
}
|
|
356
|
+
// Clear particles
|
|
357
|
+
clearParticles() {
|
|
358
|
+
this.particles.length = 0;
|
|
359
|
+
this.renderers.forEach((renderer) => {
|
|
360
|
+
renderer.update(this.particles, this);
|
|
361
|
+
});
|
|
362
|
+
this.subSystems.forEach((_options, subSystem) => {
|
|
363
|
+
subSystem.clearParticles();
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
/*
|
|
367
|
+
* ACCESSING
|
|
368
|
+
*/
|
|
369
|
+
addEmitter(emitter) {
|
|
370
|
+
this.emitters.push(emitter);
|
|
371
|
+
emitter.setup(this);
|
|
372
|
+
return this;
|
|
373
|
+
}
|
|
374
|
+
removeEmitter(emitter) {
|
|
375
|
+
const index = this.emitters.indexOf(emitter);
|
|
376
|
+
if (index !== -1) {
|
|
377
|
+
this.emitters.splice(index, 1);
|
|
378
|
+
this.remove(emitter.source);
|
|
379
|
+
}
|
|
380
|
+
return this;
|
|
381
|
+
}
|
|
382
|
+
addModule(module) {
|
|
383
|
+
this.modules.push(module);
|
|
384
|
+
return this;
|
|
385
|
+
}
|
|
386
|
+
removeModule(module) {
|
|
387
|
+
const index = this.modules.indexOf(module);
|
|
388
|
+
if (index !== -1) {
|
|
389
|
+
this.modules.splice(index, 1);
|
|
390
|
+
module.cleanup();
|
|
391
|
+
}
|
|
392
|
+
return this;
|
|
393
|
+
}
|
|
394
|
+
addRenderer(renderer) {
|
|
395
|
+
this.renderers.push(renderer);
|
|
396
|
+
renderer.setup(this);
|
|
397
|
+
this.syncRendererParents();
|
|
398
|
+
return this;
|
|
399
|
+
}
|
|
400
|
+
removeRenderer(renderer) {
|
|
401
|
+
const index = this.renderers.indexOf(renderer);
|
|
402
|
+
if (index !== -1) {
|
|
403
|
+
this.modules.splice(index, 1);
|
|
404
|
+
renderer.destroy();
|
|
405
|
+
}
|
|
406
|
+
return this;
|
|
407
|
+
}
|
|
408
|
+
addRendererObject(object) {
|
|
409
|
+
this._rendererObjects.add(object);
|
|
410
|
+
this.getRendererParent().add(object);
|
|
411
|
+
}
|
|
412
|
+
addSubSystem(subSystem, options) {
|
|
413
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
414
|
+
// No adding recursive self
|
|
415
|
+
if (subSystem === this)
|
|
416
|
+
return this;
|
|
417
|
+
// Replace existing parent
|
|
418
|
+
if (subSystem._subSystemParent && subSystem._subSystemParent !== this) {
|
|
419
|
+
subSystem._subSystemParent.removeSubSystem(subSystem);
|
|
420
|
+
}
|
|
421
|
+
// Configure emission condition
|
|
422
|
+
const emitOnCollision = (_a = options.emitOnCollision) !== null && _a !== void 0 ? _a : false;
|
|
423
|
+
const emitOnSpawn = (_b = options.emitOnSpawn) !== null && _b !== void 0 ? _b : false;
|
|
424
|
+
const emitOnDeath = (_c = options.emitOnDeath) !== null && _c !== void 0 ? _c : false;
|
|
425
|
+
const emitContinuous = (_d = options.emitContinuous) !== null && _d !== void 0 ? _d : !(emitOnCollision || emitOnDeath || emitOnSpawn);
|
|
426
|
+
this.subSystems.set(subSystem, {
|
|
427
|
+
shouldEmit: (_e = options.shouldEmit) !== null && _e !== void 0 ? _e : true,
|
|
428
|
+
ratio: THREE.MathUtils.clamp((_f = options.ratio) !== null && _f !== void 0 ? _f : 1, 0, 1),
|
|
429
|
+
emitContinuous,
|
|
430
|
+
emitOnCollision,
|
|
431
|
+
emitOnSpawn,
|
|
432
|
+
emitOnDeath,
|
|
433
|
+
inheritScale: (_g = options.inheritScale) !== null && _g !== void 0 ? _g : true,
|
|
434
|
+
inheritLifetime: (_h = options.inheritLifetime) !== null && _h !== void 0 ? _h : emitContinuous,
|
|
435
|
+
inheritColor: (_j = options.inheritColor) !== null && _j !== void 0 ? _j : true,
|
|
436
|
+
inheritAlpha: (_k = options.inheritAlpha) !== null && _k !== void 0 ? _k : true,
|
|
437
|
+
inheritMass: (_l = options.inheritMass) !== null && _l !== void 0 ? _l : true,
|
|
438
|
+
});
|
|
439
|
+
subSystem._subSystemParent = this;
|
|
440
|
+
subSystem.simulationSpace = this.simulationSpace;
|
|
441
|
+
// Identity local transform makes the subsystem share this system's space.
|
|
442
|
+
subSystem.position.set(0, 0, 0);
|
|
443
|
+
subSystem.rotation.set(0, 0, 0);
|
|
444
|
+
subSystem.scale.set(1, 1, 1);
|
|
445
|
+
this.add(subSystem);
|
|
446
|
+
return this;
|
|
447
|
+
}
|
|
448
|
+
removeSubSystem(subSystem) {
|
|
449
|
+
if (!this.subSystems.delete(subSystem))
|
|
450
|
+
return this;
|
|
451
|
+
subSystem._subSystemParent = undefined;
|
|
452
|
+
if (subSystem.parent === this)
|
|
453
|
+
this.remove(subSystem);
|
|
454
|
+
return this;
|
|
455
|
+
}
|
|
456
|
+
/*
|
|
457
|
+
* LISTENERS
|
|
458
|
+
*/
|
|
459
|
+
onDeath(listener) {
|
|
460
|
+
this._deathListeners.push(listener);
|
|
461
|
+
return this;
|
|
462
|
+
}
|
|
463
|
+
removeDeathListener(listener) {
|
|
464
|
+
this._deathListeners = this._deathListeners.filter((value) => value !== listener);
|
|
465
|
+
return this;
|
|
466
|
+
}
|
|
467
|
+
onSpawn(listener) {
|
|
468
|
+
this._spawnListeners.push(listener);
|
|
469
|
+
return this;
|
|
470
|
+
}
|
|
471
|
+
removeSpawnListener(listener) {
|
|
472
|
+
this._spawnListeners = this._spawnListeners.filter((value) => value !== listener);
|
|
473
|
+
return this;
|
|
474
|
+
}
|
|
475
|
+
onCollision(listener) {
|
|
476
|
+
this._collisionListeners.push(listener);
|
|
477
|
+
return this;
|
|
478
|
+
}
|
|
479
|
+
removeCollisionListener(listener) {
|
|
480
|
+
this._collisionListeners = this._collisionListeners.filter((value) => value !== listener);
|
|
481
|
+
return this;
|
|
482
|
+
}
|
|
483
|
+
// Called by Collision after a hit is found.
|
|
484
|
+
notifyCollision(particle, collision) {
|
|
485
|
+
this._collisionListeners.forEach((listener) => listener(particle, collision));
|
|
486
|
+
this.subSystems.forEach((options, subSystem) => {
|
|
487
|
+
if (options.emitOnCollision && this._canEmitForParticle(particle, options)) {
|
|
488
|
+
subSystem._startEmissionRunAtParticle(particle, options);
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
_notifyDeath(particle) {
|
|
493
|
+
this._deathListeners.forEach((listener) => listener(particle));
|
|
494
|
+
this.subSystems.forEach((options, subSystem) => {
|
|
495
|
+
if (options.emitOnDeath && this._canEmitForParticle(particle, options)) {
|
|
496
|
+
subSystem._startEmissionRunAtParticle(particle, options);
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
_notifySpawn(particle) {
|
|
501
|
+
this._spawnListeners.forEach((listener) => listener(particle));
|
|
502
|
+
this.subSystems.forEach((options, subSystem) => {
|
|
503
|
+
if (options.emitOnSpawn && this._canEmitForParticle(particle, options)) {
|
|
504
|
+
subSystem._startEmissionRunAtParticle(particle, options);
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
cleanup() {
|
|
509
|
+
this.modules
|
|
510
|
+
.flatMap((module) => module.withDependents())
|
|
511
|
+
.forEach((module) => module.cleanup());
|
|
512
|
+
this._worldRendererRoot.removeFromParent();
|
|
513
|
+
}
|
|
514
|
+
getEmitterContext() {
|
|
515
|
+
const context = {
|
|
516
|
+
time: this.duration === 0 ? 1 : this._elapsedTime / this.duration,
|
|
517
|
+
duration: this.duration,
|
|
518
|
+
elapsedTime: this._elapsedTime,
|
|
519
|
+
looping: this.looping,
|
|
520
|
+
};
|
|
521
|
+
if (this.simulationSpace !== SimulationSpace.World)
|
|
522
|
+
return context;
|
|
523
|
+
this.updateWorldMatrix(true, false);
|
|
524
|
+
return Object.assign(Object.assign({}, context), { transform: this.matrixWorld.clone() });
|
|
525
|
+
}
|
|
526
|
+
_updateSystemTime() {
|
|
527
|
+
this._elapsedTime += this.deltaTime;
|
|
528
|
+
if (this.looping) {
|
|
529
|
+
while (this.duration > 0 && this._elapsedTime >= this.duration) {
|
|
530
|
+
this._elapsedTime -= this.duration;
|
|
531
|
+
this.emitters.forEach((emitter) => emitter.reset());
|
|
532
|
+
}
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
// Importantly, this isn't allowed to "end" if it has a parent
|
|
536
|
+
// If the parent is alive, this must stay alive
|
|
537
|
+
if (this._elapsedTime >= this.duration && !this.isSubSystem) {
|
|
538
|
+
this._elapsedTime = this.duration;
|
|
539
|
+
this._playing = false;
|
|
540
|
+
this._ended = true;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
_handleEndBehavior() {
|
|
544
|
+
if (!this._ended || this.looping || this._destroyed)
|
|
545
|
+
return;
|
|
546
|
+
if (this.endBehavior === EndBehavior.DestroyImmediate) {
|
|
547
|
+
this.destroy();
|
|
548
|
+
}
|
|
549
|
+
else if (this.endBehavior === EndBehavior.Destroy && this.particles.length === 0) {
|
|
550
|
+
this.destroy();
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
getRendererParent() {
|
|
554
|
+
if (this.simulationSpace !== SimulationSpace.World)
|
|
555
|
+
return this;
|
|
556
|
+
return this._worldRendererRoot;
|
|
557
|
+
}
|
|
558
|
+
syncRendererParents() {
|
|
559
|
+
const worldRendererParent = this.getWorldRendererParent();
|
|
560
|
+
if (this.simulationSpace === SimulationSpace.World) {
|
|
561
|
+
if (worldRendererParent && this._worldRendererRoot.parent !== worldRendererParent) {
|
|
562
|
+
worldRendererParent.add(this._worldRendererRoot);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
else if (this._worldRendererRoot.parent) {
|
|
566
|
+
this._worldRendererRoot.removeFromParent();
|
|
567
|
+
}
|
|
568
|
+
const parent = this.getRendererParent();
|
|
569
|
+
this._rendererObjects.forEach((object) => {
|
|
570
|
+
if (object.parent !== parent)
|
|
571
|
+
parent.add(object);
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
getWorldRendererParent() {
|
|
575
|
+
let parent = this.parent;
|
|
576
|
+
while (parent instanceof ParticleSystem) {
|
|
577
|
+
parent = parent.parent;
|
|
578
|
+
}
|
|
579
|
+
return parent !== null && parent !== void 0 ? parent : undefined;
|
|
580
|
+
}
|
|
581
|
+
convertParticlesToSimulationSpace(space) {
|
|
582
|
+
this.updateWorldMatrix(true, false);
|
|
583
|
+
if (space === SimulationSpace.World) {
|
|
584
|
+
this.particles.forEach((particle) => {
|
|
585
|
+
this.localToWorld(particle.position);
|
|
586
|
+
this.localDirectionToWorld(particle.velocity);
|
|
587
|
+
this.localDirectionToWorld(particle.acceleration);
|
|
588
|
+
this.localDirectionToWorld(particle.scalarVelocity);
|
|
589
|
+
this.localDirectionToWorld(particle.scalarAcceleration);
|
|
590
|
+
particle.cacheStartValues();
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
else {
|
|
594
|
+
const normalMatrix = new THREE.Matrix3().getNormalMatrix(this.matrixWorld).invert();
|
|
595
|
+
this.particles.forEach((particle) => {
|
|
596
|
+
this.worldToLocal(particle.position);
|
|
597
|
+
particle.velocity.applyMatrix3(normalMatrix);
|
|
598
|
+
particle.acceleration.applyMatrix3(normalMatrix);
|
|
599
|
+
particle.scalarVelocity.applyMatrix3(normalMatrix);
|
|
600
|
+
particle.scalarAcceleration.applyMatrix3(normalMatrix);
|
|
601
|
+
particle.cacheStartValues();
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
localDirectionToWorld(vector) {
|
|
606
|
+
vector.applyMatrix3(new THREE.Matrix3().getNormalMatrix(this.matrixWorld));
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
export default ParticleSystem;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import Particle from './Particle';
|
|
2
|
+
import ParticleSystem from './ParticleSystem';
|
|
3
|
+
import { StrictMultiple } from './types/Multiple';
|
|
4
|
+
import { Tag } from './types/Tag';
|
|
5
|
+
export interface RendererOptions {
|
|
6
|
+
tags: StrictMultiple<Tag>;
|
|
7
|
+
}
|
|
8
|
+
export default abstract class Renderer {
|
|
9
|
+
tags?: Tag[];
|
|
10
|
+
constructor(options?: Partial<RendererOptions>);
|
|
11
|
+
abstract setup(system: ParticleSystem): void;
|
|
12
|
+
protected abstract _update(particles: Particle[], system: ParticleSystem, deltaTime: number): void;
|
|
13
|
+
update(particles: Particle[], system: ParticleSystem, deltaTime?: number): void;
|
|
14
|
+
abstract destroy(): void;
|
|
15
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import acceptMultiple from './helpers/acceptMultiple';
|
|
2
|
+
import tagsIntersect from './helpers/tagsIntersect';
|
|
3
|
+
export default class Renderer {
|
|
4
|
+
constructor(options = {}) {
|
|
5
|
+
this.tags = acceptMultiple(options.tags);
|
|
6
|
+
}
|
|
7
|
+
update(particles, system, deltaTime = 0) {
|
|
8
|
+
// Call update on particles in the group
|
|
9
|
+
this._update(particles.filter((p) => { var _a; return !this.tags || tagsIntersect(this.tags, (_a = p.tags) !== null && _a !== void 0 ? _a : []); }), system, deltaTime);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import { Octree } from 'three/examples/jsm/math/Octree.js';
|
|
3
|
+
import { ICollisionBackend, CollisionHit, CollisionQuery } from '../interfaces/ICollisionBackend';
|
|
4
|
+
export interface ThreeCollisionBackendOptions {
|
|
5
|
+
world?: THREE.Object3D;
|
|
6
|
+
staticObjects?: THREE.Object3D[];
|
|
7
|
+
dynamicObjects?: THREE.Object3D[];
|
|
8
|
+
staticAfter?: number;
|
|
9
|
+
timeQuality?: number;
|
|
10
|
+
refreshQuality?: number;
|
|
11
|
+
objectFilter?: (object: THREE.Object3D) => boolean;
|
|
12
|
+
staticObjectFilter?: (object: THREE.Object3D) => boolean;
|
|
13
|
+
dynamicObjectFilter?: (object: THREE.Object3D) => boolean;
|
|
14
|
+
}
|
|
15
|
+
declare class ThreeCollisionBackend implements ICollisionBackend {
|
|
16
|
+
staticOctree: Octree;
|
|
17
|
+
dynamicOctree: Octree;
|
|
18
|
+
world?: THREE.Object3D;
|
|
19
|
+
staticObjects?: THREE.Object3D[];
|
|
20
|
+
dynamicObjects?: THREE.Object3D[];
|
|
21
|
+
objectFilter?: (object: THREE.Object3D) => boolean;
|
|
22
|
+
staticObjectFilter?: (object: THREE.Object3D) => boolean;
|
|
23
|
+
dynamicObjectFilter?: (object: THREE.Object3D) => boolean;
|
|
24
|
+
staticAfter: number;
|
|
25
|
+
timeQuality: number;
|
|
26
|
+
refreshQuality: number;
|
|
27
|
+
private trackedObjects;
|
|
28
|
+
private elapsedTime;
|
|
29
|
+
private dynamicUpdateAccumulator;
|
|
30
|
+
private refreshAccumulator;
|
|
31
|
+
constructor(options?: ThreeCollisionBackendOptions);
|
|
32
|
+
update(deltaTime: number): void;
|
|
33
|
+
collide(query: CollisionQuery): CollisionHit | null;
|
|
34
|
+
private initialize;
|
|
35
|
+
private refreshObjects;
|
|
36
|
+
private hasExplicitLists;
|
|
37
|
+
private collectExplicitObjects;
|
|
38
|
+
private collectWorldObjects;
|
|
39
|
+
private collectRoots;
|
|
40
|
+
private trackObject;
|
|
41
|
+
private rebuildStaticOctree;
|
|
42
|
+
private rebuildDynamicOctree;
|
|
43
|
+
private buildOctree;
|
|
44
|
+
private addMeshToOctree;
|
|
45
|
+
private matrixChanged;
|
|
46
|
+
}
|
|
47
|
+
export default ThreeCollisionBackend;
|