Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
epoxy
GitHub Repository: epoxy/proj11
Path: blob/master/SLICK_HOME/src/org/newdawn/slick/particles/ParticleSystem.java
1457 views
1
package org.newdawn.slick.particles;
2
3
import java.io.ByteArrayInputStream;
4
import java.io.ByteArrayOutputStream;
5
import java.io.IOException;
6
import java.security.AccessController;
7
import java.security.PrivilegedAction;
8
import java.util.ArrayList;
9
import java.util.HashMap;
10
import java.util.Iterator;
11
12
import org.newdawn.slick.Color;
13
import org.newdawn.slick.Image;
14
import org.newdawn.slick.SlickException;
15
import org.newdawn.slick.opengl.TextureImpl;
16
import org.newdawn.slick.opengl.renderer.Renderer;
17
import org.newdawn.slick.opengl.renderer.SGL;
18
import org.newdawn.slick.util.Log;
19
20
/**
21
* A particle syste responsible for maintaining a set of data about individual
22
* particles which are created and controlled by assigned emitters. This pseudo
23
* chaotic nature hopes to give more organic looking effects
24
*
25
* @author kevin
26
*/
27
public class ParticleSystem {
28
/** The renderer to use for all GL operations */
29
protected static SGL GL = Renderer.get();
30
31
/** The blending mode for the glowy style */
32
public static final int BLEND_ADDITIVE = 1;
33
/** The blending mode for the normal style */
34
public static final int BLEND_COMBINE = 2;
35
36
/** The default number of particles in the system */
37
private static final int DEFAULT_PARTICLES = 100;
38
39
/**
40
* Set the path from which images should be loaded
41
*
42
* @param path
43
* The path from which images should be loaded
44
*/
45
public static void setRelativePath(String path) {
46
ConfigurableEmitter.setRelativePath(path);
47
}
48
49
/**
50
* A pool of particles being used by a specific emitter
51
*
52
* @author void
53
*/
54
private class ParticlePool
55
{
56
/** The particles being rendered and maintained */
57
public Particle[] particles;
58
/** The list of particles left to be used, if this size() == 0 then the particle engine was too small for the effect */
59
public ArrayList available;
60
61
/**
62
* Create a new particle pool contiaining a set of particles
63
*
64
* @param system The system that owns the particles over all
65
* @param maxParticles The maximum number of particles in the pool
66
*/
67
public ParticlePool( ParticleSystem system, int maxParticles )
68
{
69
particles = new Particle[ maxParticles ];
70
available = new ArrayList();
71
72
for( int i=0; i<particles.length; i++ )
73
{
74
particles[i] = createParticle( system );
75
}
76
77
reset(system);
78
}
79
80
/**
81
* Rest the list of particles
82
*
83
* @param system The system in which the particle belong
84
*/
85
public void reset(ParticleSystem system) {
86
available.clear();
87
88
for( int i=0; i<particles.length; i++ )
89
{
90
available.add(particles[i]);
91
}
92
}
93
}
94
95
/**
96
* A map from emitter to a the particle pool holding the particles it uses
97
* void: this is now sorted by emitters to allow emitter specfic state to be set for
98
* each emitter. actually this is used to allow setting an individual blend mode for
99
* each emitter
100
*/
101
protected HashMap particlesByEmitter = new HashMap();
102
/** The maximum number of particles allows per emitter */
103
protected int maxParticlesPerEmitter;
104
105
/** The list of emittered producing and controlling particles */
106
protected ArrayList emitters = new ArrayList();
107
108
/** The dummy particle to return should no more particles be available */
109
protected Particle dummy;
110
/** The blending mode */
111
private int blendingMode = BLEND_COMBINE;
112
/** The number of particles in use */
113
private int pCount;
114
/** True if we're going to use points to render the particles */
115
private boolean usePoints;
116
/** The x coordinate at which this system should be rendered */
117
private float x;
118
/** The x coordinate at which this system should be rendered */
119
private float y;
120
/** True if we should remove completed emitters */
121
private boolean removeCompletedEmitters = true;
122
123
/** The default image for the particles */
124
private Image sprite;
125
/** True if the particle system is visible */
126
private boolean visible = true;
127
/** The name of the default image */
128
private String defaultImageName;
129
/** The mask used to make the particle image background transparent if any */
130
private Color mask;
131
132
/**
133
* Create a new particle system
134
*
135
* @param defaultSprite The sprite to render for each particle
136
*/
137
public ParticleSystem(Image defaultSprite) {
138
this(defaultSprite, DEFAULT_PARTICLES);
139
}
140
141
/**
142
* Create a new particle system
143
*
144
* @param defaultSpriteRef The sprite to render for each particle
145
*/
146
public ParticleSystem(String defaultSpriteRef) {
147
this(defaultSpriteRef, DEFAULT_PARTICLES);
148
}
149
150
/**
151
* Reset the state of the system
152
*/
153
public void reset() {
154
Iterator pools = particlesByEmitter.values().iterator();
155
while (pools.hasNext()) {
156
ParticlePool pool = (ParticlePool) pools.next();
157
pool.reset(this);
158
}
159
160
for (int i=0;i<emitters.size();i++) {
161
ParticleEmitter emitter = (ParticleEmitter) emitters.get(i);
162
emitter.resetState();
163
}
164
}
165
166
/**
167
* Check if this system is currently visible, i.e. it's actually
168
* rendered
169
*
170
* @return True if the particle system is rendered
171
*/
172
public boolean isVisible() {
173
return visible;
174
}
175
176
/**
177
* Indicate whether the particle system should be visible, i.e. whether
178
* it'll actually render
179
*
180
* @param visible True if the particle system should render
181
*/
182
public void setVisible(boolean visible) {
183
this.visible = visible;
184
}
185
186
/**
187
* Indicate if completed emitters should be removed
188
*
189
* @param remove True if completed emitters should be removed
190
*/
191
public void setRemoveCompletedEmitters(boolean remove) {
192
removeCompletedEmitters = remove;
193
}
194
195
/**
196
* Indicate if this engine should use points to render the particles
197
*
198
* @param usePoints True if points should be used to render the particles
199
*/
200
public void setUsePoints(boolean usePoints) {
201
this.usePoints = usePoints;
202
}
203
204
/**
205
* Check if this engine should use points to render the particles
206
*
207
* @return True if the engine should use points to render the particles
208
*/
209
public boolean usePoints() {
210
return usePoints;
211
}
212
213
/**
214
* Create a new particle system
215
*
216
* @param defaultSpriteRef The sprite to render for each particle
217
* @param maxParticles The number of particles available in the system
218
*/
219
public ParticleSystem(String defaultSpriteRef, int maxParticles) {
220
this(defaultSpriteRef, maxParticles, null);
221
}
222
223
/**
224
* Create a new particle system
225
*
226
* @param defaultSpriteRef The sprite to render for each particle
227
* @param maxParticles The number of particles available in the system
228
* @param mask The mask used to make the sprite image transparent
229
*/
230
public ParticleSystem(String defaultSpriteRef, int maxParticles, Color mask) {
231
this.maxParticlesPerEmitter= maxParticles;
232
this.mask = mask;
233
234
setDefaultImageName(defaultSpriteRef);
235
dummy = createParticle(this);
236
}
237
238
/**
239
* Create a new particle system
240
*
241
* @param defaultSprite The sprite to render for each particle
242
* @param maxParticles The number of particles available in the system
243
*/
244
public ParticleSystem(Image defaultSprite, int maxParticles) {
245
this.maxParticlesPerEmitter= maxParticles;
246
247
sprite = defaultSprite;
248
dummy = createParticle(this);
249
}
250
251
/**
252
* Set the default image name
253
*
254
* @param ref The default image name
255
*/
256
public void setDefaultImageName(String ref) {
257
defaultImageName = ref;
258
sprite = null;
259
}
260
261
/**
262
* Get the blending mode in use
263
*
264
* @see #BLEND_COMBINE
265
* @see #BLEND_ADDITIVE
266
* @return The blending mode in use
267
*/
268
public int getBlendingMode() {
269
return blendingMode;
270
}
271
272
/**
273
* Create a particle specific to this system, override for your own implementations.
274
* These particles will be cached and reused within this system.
275
*
276
* @param system The system owning this particle
277
* @return The newly created particle.
278
*/
279
protected Particle createParticle(ParticleSystem system) {
280
return new Particle(system);
281
}
282
283
/**
284
* Set the blending mode for the particles
285
*
286
* @param mode The mode for blending particles together
287
*/
288
public void setBlendingMode(int mode) {
289
this.blendingMode = mode;
290
}
291
292
/**
293
* Get the number of emitters applied to the system
294
*
295
* @return The number of emitters applied to the system
296
*/
297
public int getEmitterCount() {
298
return emitters.size();
299
}
300
301
/**
302
* Get an emitter a specified index int he list contained within this system
303
*
304
* @param index The index of the emitter to retrieve
305
* @return The particle emitter
306
*/
307
public ParticleEmitter getEmitter(int index) {
308
return (ParticleEmitter) emitters.get(index);
309
}
310
311
/**
312
* Add a particle emitter to be used on this system
313
*
314
* @param emitter The emitter to be added
315
*/
316
public void addEmitter(ParticleEmitter emitter) {
317
emitters.add(emitter);
318
319
ParticlePool pool= new ParticlePool( this, maxParticlesPerEmitter );
320
particlesByEmitter.put( emitter, pool );
321
}
322
323
/**
324
* Remove a particle emitter that is currently used in the system
325
*
326
* @param emitter The emitter to be removed
327
*/
328
public void removeEmitter(ParticleEmitter emitter) {
329
emitters.remove(emitter);
330
particlesByEmitter.remove( emitter );
331
}
332
333
/**
334
* Remove all the emitters from the system
335
*/
336
public void removeAllEmitters() {
337
for (int i=0;i<emitters.size();i++) {
338
removeEmitter((ParticleEmitter) emitters.get(i));
339
i--;
340
}
341
}
342
343
/**
344
* Get the x coordiante of the position of the system
345
*
346
* @return The x coordinate of the position of the system
347
*/
348
public float getPositionX() {
349
return x;
350
}
351
352
/**
353
* Get the y coordiante of the position of the system
354
*
355
* @return The y coordinate of the position of the system
356
*/
357
public float getPositionY() {
358
return y;
359
}
360
361
/**
362
* Set the position at which this system should render relative to the current
363
* graphics context setup
364
*
365
* @param x The x coordinate at which this system should be centered
366
* @param y The y coordinate at which this system should be centered
367
*/
368
public void setPosition(float x, float y) {
369
this.x = x;
370
this.y = y;
371
}
372
373
/**
374
* Render the particles in the system
375
*/
376
public void render() {
377
render(x,y);
378
}
379
380
/**
381
* Render the particles in the system
382
*
383
* @param x The x coordinate to render the particle system at (in the current coordinate space)
384
* @param y The y coordinate to render the particle system at (in the current coordiante space)
385
*/
386
public void render(float x, float y) {
387
if ((sprite == null) && (defaultImageName != null)) {
388
loadSystemParticleImage();
389
}
390
391
if (!visible) {
392
return;
393
}
394
395
GL.glTranslatef(x,y,0);
396
397
if (blendingMode == BLEND_ADDITIVE) {
398
GL.glBlendFunc(SGL.GL_SRC_ALPHA, SGL.GL_ONE);
399
}
400
if (usePoints()) {
401
GL.glEnable( SGL.GL_POINT_SMOOTH );
402
TextureImpl.bindNone();
403
}
404
405
// iterate over all emitters
406
for( int emitterIdx=0; emitterIdx<emitters.size(); emitterIdx++ )
407
{
408
// get emitter
409
ParticleEmitter emitter = (ParticleEmitter) emitters.get(emitterIdx);
410
411
// check for additive override and enable when set
412
if (emitter.useAdditive()) {
413
GL.glBlendFunc(SGL.GL_SRC_ALPHA, SGL.GL_ONE);
414
}
415
416
// now get the particle pool for this emitter and render all particles that are in use
417
ParticlePool pool = (ParticlePool) particlesByEmitter.get(emitter);
418
Image image = emitter.getImage();
419
if (image == null) {
420
image = this.sprite;
421
}
422
423
if (!emitter.isOriented() && !emitter.usePoints(this)) {
424
image.startUse();
425
}
426
427
for (int i = 0; i < pool.particles.length; i++)
428
{
429
if (pool.particles[i].inUse())
430
pool.particles[i].render();
431
}
432
433
if (!emitter.isOriented() && !emitter.usePoints(this)) {
434
image.endUse();
435
}
436
437
// reset additive blend mode
438
if (emitter.useAdditive()) {
439
GL.glBlendFunc(SGL.GL_SRC_ALPHA, SGL.GL_ONE_MINUS_SRC_ALPHA);
440
}
441
}
442
443
if (usePoints()) {
444
GL.glDisable( SGL.GL_POINT_SMOOTH );
445
}
446
if (blendingMode == BLEND_ADDITIVE) {
447
GL.glBlendFunc(SGL.GL_SRC_ALPHA, SGL.GL_ONE_MINUS_SRC_ALPHA);
448
}
449
450
Color.white.bind();
451
GL.glTranslatef(-x,-y,0);
452
}
453
454
/**
455
* Load the system particle image as the extension permissions
456
*/
457
private void loadSystemParticleImage() {
458
AccessController.doPrivileged(new PrivilegedAction() {
459
public Object run() {
460
try {
461
if (mask != null) {
462
sprite = new Image(defaultImageName, mask);
463
} else {
464
sprite = new Image(defaultImageName);
465
}
466
} catch (SlickException e) {
467
Log.error(e);
468
defaultImageName = null;
469
}
470
return null; // nothing to return
471
}
472
});
473
}
474
475
/**
476
* Update the system, request the assigned emitters update the particles
477
*
478
* @param delta The amount of time thats passed since last update in milliseconds
479
*/
480
public void update(int delta) {
481
if ((sprite == null) && (defaultImageName != null)) {
482
loadSystemParticleImage();
483
}
484
485
ArrayList removeMe = new ArrayList();
486
for (int i=0;i<emitters.size();i++) {
487
ParticleEmitter emitter = (ParticleEmitter) emitters.get(i);
488
if (emitter.isEnabled()) {
489
emitter.update(this, delta);
490
if (removeCompletedEmitters) {
491
if (emitter.completed()) {
492
removeMe.add(emitter);
493
particlesByEmitter.remove(emitter);
494
}
495
}
496
}
497
}
498
emitters.removeAll(removeMe);
499
500
pCount = 0;
501
502
if (!particlesByEmitter.isEmpty())
503
{
504
Iterator it= particlesByEmitter.values().iterator();
505
while (it.hasNext())
506
{
507
ParticlePool pool= (ParticlePool)it.next();
508
for (int i=0;i<pool.particles.length;i++) {
509
if (pool.particles[i].life > 0) {
510
pool.particles[i].update(delta);
511
pCount++;
512
}
513
}
514
}
515
}
516
}
517
518
/**
519
* Get the number of particles in use in this system
520
*
521
* @return The number of particles in use in this system
522
*/
523
public int getParticleCount() {
524
return pCount;
525
}
526
527
/**
528
* Get a new particle from the system. This should be used by emitters to
529
* request particles
530
*
531
* @param emitter The emitter requesting the particle
532
* @param life The time the new particle should live for
533
* @return A particle from the system
534
*/
535
public Particle getNewParticle(ParticleEmitter emitter, float life)
536
{
537
ParticlePool pool = (ParticlePool) particlesByEmitter.get(emitter);
538
ArrayList available = pool.available;
539
if (available.size() > 0)
540
{
541
Particle p = (Particle) available.remove(available.size()-1);
542
p.init(emitter, life);
543
p.setImage(sprite);
544
545
return p;
546
}
547
548
Log.warn("Ran out of particles (increase the limit)!");
549
return dummy;
550
}
551
552
/**
553
* Release a particle back to the system once it has expired
554
*
555
* @param particle The particle to be released
556
*/
557
public void release(Particle particle) {
558
if (particle != dummy)
559
{
560
ParticlePool pool = (ParticlePool)particlesByEmitter.get( particle.getEmitter() );
561
pool.available.add(particle);
562
}
563
}
564
565
/**
566
* Release all the particles owned by the specified emitter
567
*
568
* @param emitter The emitter owning the particles that should be released
569
*/
570
public void releaseAll(ParticleEmitter emitter) {
571
if( !particlesByEmitter.isEmpty() )
572
{
573
Iterator it= particlesByEmitter.values().iterator();
574
while( it.hasNext())
575
{
576
ParticlePool pool= (ParticlePool)it.next();
577
for (int i=0;i<pool.particles.length;i++) {
578
if (pool.particles[i].inUse()) {
579
if (pool.particles[i].getEmitter() == emitter) {
580
pool.particles[i].setLife(-1);
581
release(pool.particles[i]);
582
}
583
}
584
}
585
}
586
}
587
}
588
589
/**
590
* Move all the particles owned by the specified emitter
591
*
592
* @param emitter The emitter owning the particles that should be released
593
* @param x The amount on the x axis to move the particles
594
* @param y The amount on the y axis to move the particles
595
*/
596
public void moveAll(ParticleEmitter emitter, float x, float y) {
597
ParticlePool pool = (ParticlePool) particlesByEmitter.get(emitter);
598
for (int i=0;i<pool.particles.length;i++) {
599
if (pool.particles[i].inUse()) {
600
pool.particles[i].move(x, y);
601
}
602
}
603
}
604
605
/**
606
* Create a duplicate of this system. This would have been nicer as a different interface
607
* but may cause to much API change headache. Maybe next full version release it should be
608
* rethought.
609
*
610
* TODO: Consider refactor at next point release
611
*
612
* @return A copy of this particle system
613
* @throws SlickException Indicates a failure during copy or a invalid particle system to be duplicated
614
*/
615
public ParticleSystem duplicate() throws SlickException {
616
for (int i=0;i<emitters.size();i++) {
617
if (!(emitters.get(i) instanceof ConfigurableEmitter)) {
618
throw new SlickException("Only systems contianing configurable emitters can be duplicated");
619
}
620
}
621
622
ParticleSystem theCopy = null;
623
try {
624
ByteArrayOutputStream bout = new ByteArrayOutputStream();
625
ParticleIO.saveConfiguredSystem(bout, this);
626
ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
627
theCopy = ParticleIO.loadConfiguredSystem(bin);
628
} catch (IOException e) {
629
Log.error("Failed to duplicate particle system");
630
throw new SlickException("Unable to duplicated particle system", e);
631
}
632
633
return theCopy;
634
}
635
}
636
637