Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
epoxy
GitHub Repository: epoxy/proj11
Path: blob/master/SLICK_HOME/src/org/newdawn/slick/particles/ConfigurableEmitter.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.util.ArrayList;
7
8
import org.newdawn.slick.Color;
9
import org.newdawn.slick.Image;
10
import org.newdawn.slick.SlickException;
11
import org.newdawn.slick.geom.Vector2f;
12
import org.newdawn.slick.util.FastTrig;
13
import org.newdawn.slick.util.Log;
14
15
/**
16
* An emitter than can be externally configured. This configuration can also be
17
* saved/loaded using the ParticleIO class.
18
*
19
* @see ParticleIO
20
*
21
* @author kevin
22
*/
23
public class ConfigurableEmitter implements ParticleEmitter {
24
/** The path from which the images should be loaded */
25
private static String relativePath = "";
26
27
/**
28
* Set the path from which images should be loaded
29
*
30
* @param path
31
* The path from which images should be loaded
32
*/
33
public static void setRelativePath(String path) {
34
if (!path.endsWith("/")) {
35
path += "/";
36
}
37
relativePath = path;
38
}
39
40
/** The spawn interval range property - how often spawn happens */
41
public Range spawnInterval = new Range(100, 100);
42
/** The spawn count property - how many particles are spawned each time */
43
public Range spawnCount = new Range(5, 5);
44
/** The initial life of the new pixels */
45
public Range initialLife = new Range(1000, 1000);
46
/** The initial size of the new pixels */
47
public Range initialSize = new Range(10, 10);
48
/** The offset from the x position */
49
public Range xOffset = new Range(0, 0);
50
/** The offset from the y position */
51
public Range yOffset = new Range(0, 0);
52
/** The spread of the particles */
53
public RandomValue spread = new RandomValue(360);
54
/** The angular offset */
55
public Value angularOffset = new SimpleValue(0);
56
/** The initial distance of the particles */
57
public Range initialDistance = new Range(0, 0);
58
/** The speed particles fly out */
59
public Range speed = new Range(50, 50);
60
/** The growth factor on the particles */
61
public Value growthFactor = new SimpleValue(0);
62
/** The factor of gravity to apply */
63
public Value gravityFactor = new SimpleValue(0);
64
/** The factor of wind to apply */
65
public Value windFactor = new SimpleValue(0);
66
/** The length of the effect */
67
public Range length = new Range(1000, 1000);
68
/**
69
* The color range
70
*
71
* @see ColorRecord
72
*/
73
public ArrayList colors = new ArrayList();
74
/** The starting alpha value */
75
public Value startAlpha = new SimpleValue(255);
76
/** The ending alpha value */
77
public Value endAlpha = new SimpleValue(0);
78
79
/** Whiskas - Interpolated value for alpha */
80
public LinearInterpolator alpha;
81
/** Whiskas - Interpolated value for size */
82
public LinearInterpolator size;
83
/** Whiskas - Interpolated value for velocity */
84
public LinearInterpolator velocity;
85
/** Whiskas - Interpolated value for y axis scaling */
86
public LinearInterpolator scaleY;
87
88
/** The number of particles that will be emitted */
89
public Range emitCount = new Range(1000, 1000);
90
/** The points indicate */
91
public int usePoints = Particle.INHERIT_POINTS;
92
93
/** True if the quads should be orieted based on velocity */
94
public boolean useOriented = false;
95
/**
96
* True if the additivie blending mode should be used for particles owned by
97
* this emitter
98
*/
99
public boolean useAdditive = false;
100
101
/** The name attribute */
102
public String name;
103
/** The name of the image in use */
104
public String imageName = "";
105
/** The image being used for the particles */
106
private Image image;
107
/** True if the image needs updating */
108
private boolean updateImage;
109
110
/** True if the emitter is enabled */
111
private boolean enabled = true;
112
/** The x coordinate of the position of this emitter */
113
private float x;
114
/** The y coordinate of the position of this emitter */
115
private float y;
116
/** The time in milliseconds til the next spawn */
117
private int nextSpawn = 0;
118
119
/** The timeout counting down to spawn */
120
private int timeout;
121
/** The number of particles in use by this emitter */
122
private int particleCount;
123
/** The system this emitter is being updated to */
124
private ParticleSystem engine;
125
/** The number of particles that are left ot emit */
126
private int leftToEmit;
127
128
/** True if we're wrapping up */
129
private boolean wrapUp = false;
130
/** True if the system has completed due to a wrap up */
131
private boolean completed = false;
132
133
/**
134
* Create a new emitter configurable externally
135
*
136
* @param name
137
* The name of emitter
138
*/
139
public ConfigurableEmitter(String name) {
140
this.name = name;
141
leftToEmit = (int) emitCount.random();
142
timeout = (int) (length.random());
143
144
colors.add(new ColorRecord(0, Color.white));
145
colors.add(new ColorRecord(1, Color.red));
146
147
ArrayList curve = new ArrayList();
148
curve.add(new Vector2f(0.0f, 0.0f));
149
curve.add(new Vector2f(1.0f, 255.0f));
150
alpha = new LinearInterpolator(curve, 0, 255);
151
152
curve = new ArrayList();
153
curve.add(new Vector2f(0.0f, 0.0f));
154
curve.add(new Vector2f(1.0f, 255.0f));
155
size = new LinearInterpolator(curve, 0, 255);
156
157
curve = new ArrayList();
158
curve.add(new Vector2f(0.0f, 0.0f));
159
curve.add(new Vector2f(1.0f, 1.0f));
160
velocity = new LinearInterpolator(curve, 0, 1);
161
162
curve = new ArrayList();
163
curve.add(new Vector2f(0.0f, 0.0f));
164
curve.add(new Vector2f(1.0f, 1.0f));
165
scaleY = new LinearInterpolator(curve, 0, 1);
166
}
167
168
/**
169
* Set the name of the image to use on a per particle basis. The complete
170
* reference to the image is required (based on the relative path)
171
*
172
* @see #setRelativePath(String)
173
*
174
* @param imageName
175
* The name of the image to use on a per particle reference
176
*/
177
public void setImageName(String imageName) {
178
if (imageName.length() == 0) {
179
imageName = null;
180
}
181
182
this.imageName = imageName;
183
if (imageName == null) {
184
image = null;
185
} else {
186
updateImage = true;
187
}
188
}
189
190
/**
191
* The name of the image to load
192
*
193
* @return The name of the image to load
194
*/
195
public String getImageName() {
196
return imageName;
197
}
198
199
/**
200
* @see java.lang.Object#toString()
201
*/
202
public String toString() {
203
return "[" + name + "]";
204
}
205
206
/**
207
* Set the position of this particle source
208
*
209
* @param x
210
* The x coodinate of that this emitter should spawn at
211
* @param y
212
* The y coodinate of that this emitter should spawn at
213
*/
214
public void setPosition(float x, float y) {
215
this.x = x;
216
this.y = y;
217
}
218
219
/**
220
* Get the base x coordiante for spawning particles
221
*
222
* @return The x coordinate for spawning particles
223
*/
224
public float getX() {
225
return x;
226
}
227
228
/**
229
* Get the base y coordiante for spawning particles
230
*
231
* @return The y coordinate for spawning particles
232
*/
233
public float getY() {
234
return y;
235
}
236
237
/**
238
* @see org.newdawn.slick.particles.ParticleEmitter#isEnabled()
239
*/
240
public boolean isEnabled() {
241
return enabled;
242
}
243
244
/**
245
* @see org.newdawn.slick.particles.ParticleEmitter#setEnabled(boolean)
246
*/
247
public void setEnabled(boolean enabled) {
248
this.enabled = enabled;
249
}
250
251
/**
252
* @see org.newdawn.slick.particles.ParticleEmitter#update(org.newdawn.slick.particles.ParticleSystem,
253
* int)
254
*/
255
public void update(ParticleSystem system, int delta) {
256
this.engine = system;
257
258
if (updateImage) {
259
updateImage = false;
260
try {
261
image = new Image(relativePath + imageName);
262
} catch (SlickException e) {
263
image = null;
264
Log.error(e);
265
}
266
}
267
268
if ((wrapUp) ||
269
((length.isEnabled()) && (timeout < 0)) ||
270
((emitCount.isEnabled() && (leftToEmit <= 0)))) {
271
if (particleCount == 0) {
272
completed = true;
273
}
274
}
275
particleCount = 0;
276
277
if (wrapUp) {
278
return;
279
}
280
281
if (length.isEnabled()) {
282
if (timeout < 0) {
283
return;
284
}
285
timeout -= delta;
286
}
287
if (emitCount.isEnabled()) {
288
if (leftToEmit <= 0) {
289
return;
290
}
291
}
292
293
nextSpawn -= delta;
294
if (nextSpawn < 0) {
295
nextSpawn = (int) spawnInterval.random();
296
int count = (int) spawnCount.random();
297
298
for (int i = 0; i < count; i++) {
299
Particle p = system.getNewParticle(this, initialLife.random());
300
p.setSize(initialSize.random());
301
p.setPosition(x + xOffset.random(), y + yOffset.random());
302
p.setVelocity(0, 0, 0);
303
304
float dist = initialDistance.random();
305
float power = speed.random();
306
if ((dist != 0) || (power != 0)) {
307
float s = spread.getValue(0);
308
float ang = (s + angularOffset.getValue(0) - (spread
309
.getValue() / 2)) - 90;
310
float xa = (float) FastTrig.cos(Math.toRadians(ang)) * dist;
311
float ya = (float) FastTrig.sin(Math.toRadians(ang)) * dist;
312
p.adjustPosition(xa, ya);
313
314
float xv = (float) FastTrig.cos(Math.toRadians(ang));
315
float yv = (float) FastTrig.sin(Math.toRadians(ang));
316
p.setVelocity(xv, yv, power * 0.001f);
317
}
318
319
if (image != null) {
320
p.setImage(image);
321
}
322
323
ColorRecord start = (ColorRecord) colors.get(0);
324
p.setColor(start.col.r, start.col.g, start.col.b, startAlpha
325
.getValue(0) / 255.0f);
326
p.setUsePoint(usePoints);
327
p.setOriented(useOriented);
328
329
if (emitCount.isEnabled()) {
330
leftToEmit--;
331
if (leftToEmit <= 0) {
332
break;
333
}
334
}
335
}
336
}
337
}
338
339
/**
340
* @see org.newdawn.slick.particles.ParticleEmitter#updateParticle(org.newdawn.slick.particles.Particle,
341
* int)
342
*/
343
public void updateParticle(Particle particle, int delta) {
344
particleCount++;
345
346
particle.adjustVelocity(windFactor.getValue(0) * 0.00005f * delta, gravityFactor
347
.getValue(0) * 0.00005f * delta);
348
349
float offset = particle.getLife() / particle.getOriginalLife();
350
float inv = 1 - offset;
351
float colOffset = 0;
352
float colInv = 1;
353
354
Color startColor = null;
355
Color endColor = null;
356
for (int i = 0; i < colors.size() - 1; i++) {
357
ColorRecord rec1 = (ColorRecord) colors.get(i);
358
ColorRecord rec2 = (ColorRecord) colors.get(i + 1);
359
360
if ((inv >= rec1.pos) && (inv <= rec2.pos)) {
361
startColor = rec1.col;
362
endColor = rec2.col;
363
364
float step = rec2.pos - rec1.pos;
365
colOffset = inv - rec1.pos;
366
colOffset /= step;
367
colOffset = 1 - colOffset;
368
colInv = 1 - colOffset;
369
}
370
}
371
372
if (startColor != null) {
373
float r = (startColor.r * colOffset) + (endColor.r * colInv);
374
float g = (startColor.g * colOffset) + (endColor.g * colInv);
375
float b = (startColor.b * colOffset) + (endColor.b * colInv);
376
377
float a;
378
if (alpha.isActive()) {
379
a = alpha.getValue(inv) / 255.0f;
380
} else {
381
a = ((startAlpha.getValue(0) / 255.0f) * offset)
382
+ ((endAlpha.getValue(0) / 255.0f) * inv);
383
}
384
particle.setColor(r, g, b, a);
385
}
386
387
if (size.isActive()) {
388
float s = size.getValue(inv);
389
particle.setSize(s);
390
} else {
391
particle.adjustSize(delta * growthFactor.getValue(0) * 0.001f);
392
}
393
394
if (velocity.isActive()) {
395
particle.setSpeed(velocity.getValue(inv));
396
}
397
398
if (scaleY.isActive()) {
399
particle.setScaleY(scaleY.getValue(inv));
400
}
401
}
402
403
/**
404
* Check if this emitter has completed it's cycle
405
*
406
* @return True if the emitter has completed it's cycle
407
*/
408
public boolean completed() {
409
if (engine == null) {
410
return false;
411
}
412
413
if (length.isEnabled()) {
414
if (timeout > 0) {
415
return false;
416
}
417
return completed;
418
}
419
if (emitCount.isEnabled()) {
420
if (leftToEmit > 0) {
421
return false;
422
}
423
return completed;
424
}
425
426
if (wrapUp) {
427
return completed;
428
}
429
430
return false;
431
}
432
433
/**
434
* Cause the emitter to replay it's circle
435
*/
436
public void replay() {
437
reset();
438
nextSpawn = 0;
439
leftToEmit = (int) emitCount.random();
440
timeout = (int) (length.random());
441
}
442
443
/**
444
* Release all the particles held by this emitter
445
*/
446
public void reset() {
447
completed = false;
448
if (engine != null) {
449
engine.releaseAll(this);
450
}
451
}
452
453
/**
454
* Check if the replay has died out - used by the editor
455
*/
456
public void replayCheck() {
457
if (completed()) {
458
if (engine != null) {
459
if (engine.getParticleCount() == 0) {
460
replay();
461
}
462
}
463
}
464
}
465
466
/**
467
* Create a duplicate of this emitter.
468
* The duplicate should be added to a ParticleSystem to be used.
469
* @return a copy if no IOException occurred, null otherwise
470
*/
471
public ConfigurableEmitter duplicate() {
472
ConfigurableEmitter theCopy = null;
473
try {
474
ByteArrayOutputStream bout = new ByteArrayOutputStream();
475
ParticleIO.saveEmitter(bout, this);
476
ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
477
theCopy = ParticleIO.loadEmitter(bin);
478
} catch (IOException e) {
479
Log.error("Slick: ConfigurableEmitter.duplicate(): caught exception " + e.toString());
480
return null;
481
}
482
return theCopy;
483
}
484
485
/**
486
* a general interface to provide a general value :]
487
*
488
* @author void
489
*/
490
public interface Value {
491
/**
492
* get the current value that might depend from the given time
493
*
494
* @param time
495
* @return the current value
496
*/
497
public float getValue(float time);
498
}
499
500
/**
501
* A configurable simple single value
502
*
503
* @author void
504
*/
505
public class SimpleValue implements Value {
506
/** The value configured */
507
private float value;
508
/** The next value */
509
private float next;
510
511
/**
512
* Create a new configurable new value
513
*
514
* @param value
515
* The initial value
516
*/
517
private SimpleValue(float value) {
518
this.value = value;
519
}
520
521
/**
522
* Get the currently configured value
523
*
524
* @return The currently configured value
525
*/
526
public float getValue(float time) {
527
return value;
528
}
529
530
/**
531
* Set the configured value
532
*
533
* @param value
534
* The configured value
535
*/
536
public void setValue(float value) {
537
this.value = value;
538
}
539
}
540
541
/**
542
* A configurable simple linear random value
543
*
544
* @author void
545
*/
546
public class RandomValue implements Value {
547
/** The value configured */
548
private float value;
549
550
/**
551
* Create a new configurable new value
552
*
553
* @param value
554
* The initial value
555
*/
556
private RandomValue(float value) {
557
this.value = value;
558
}
559
560
/**
561
* Get the currently configured value
562
*
563
* @return The currently configured value
564
*/
565
public float getValue(float time) {
566
return (float) (Math.random() * value);
567
}
568
569
/**
570
* Set the configured value
571
*
572
* @param value
573
* The configured value
574
*/
575
public void setValue(float value) {
576
this.value = value;
577
}
578
579
/**
580
* get the configured value
581
*
582
* @return the configured value
583
*/
584
public float getValue() {
585
return value;
586
}
587
}
588
589
/**
590
* A value computed based on linear interpolation between a set of points
591
*
592
* @author void
593
*/
594
public class LinearInterpolator implements Value {
595
/** The list of points to interpolate between */
596
private ArrayList curve;
597
/** True if this interpolation value is active */
598
private boolean active;
599
/** The minimum value in the data set */
600
private int min;
601
/** The maximum value in the data set */
602
private int max;
603
604
/**
605
* Create a new interpolated value
606
*
607
* @param curve The set of points to interpolate between
608
* @param min The minimum value in the dataset
609
* @param max The maximum value possible in the dataset
610
*/
611
public LinearInterpolator(ArrayList curve, int min, int max) {
612
this.curve = curve;
613
this.min = min;
614
this.max = max;
615
this.active = false;
616
}
617
618
/**
619
* Set the collection of data points to interpolate between
620
*
621
* @param curve The list of data points to interpolate between
622
*/
623
public void setCurve(ArrayList curve) {
624
this.curve = curve;
625
}
626
627
/**
628
* The list of data points to interpolate between
629
*
630
* @return A list of Vector2f of the data points to interpolate between
631
*/
632
public ArrayList getCurve() {
633
return curve;
634
}
635
636
/**
637
* Get the value to use at a given time value
638
*
639
* @param t The time value (expecting t in [0,1])
640
* @return The value to use at the specified time
641
*/
642
public float getValue(float t) {
643
// first: determine the segment we are in
644
Vector2f p0 = (Vector2f) curve.get(0);
645
for (int i = 1; i < curve.size(); i++) {
646
Vector2f p1 = (Vector2f) curve.get(i);
647
648
if (t >= p0.getX() && t <= p1.getX()) {
649
// found the segment
650
float st = (t - p0.getX())
651
/ (p1.getX() - p0.getX());
652
float r = p0.getY() + st
653
* (p1.getY() - p0.getY());
654
// System.out.println( "t: " + t + ", " + p0.x + ", " + p0.y
655
// + " : " + p1.x + ", " + p1.y + " => " + r );
656
657
return r;
658
}
659
660
p0 = p1;
661
}
662
return 0;
663
}
664
665
/**
666
* Check if this interpolated value should be used
667
*
668
* @return True if this value is in use
669
*/
670
public boolean isActive() {
671
return active;
672
}
673
674
/**
675
* Indicate if this interpoalte value should be used
676
*
677
* @param active True if this value should be used
678
*/
679
public void setActive(boolean active) {
680
this.active = active;
681
}
682
683
/**
684
* Get the maxmimum value possible in this data set
685
*
686
* @return The maximum value possible in this data set
687
*/
688
public int getMax() {
689
return max;
690
}
691
692
/**
693
* Set the maximum value possible in this data set
694
*
695
* @param max The maximum value possible in this data set
696
*/
697
public void setMax(int max) {
698
this.max = max;
699
}
700
701
/**
702
* Get the minimum value possible in this data set
703
*
704
* @return The minimum value possible in this data set
705
*/
706
public int getMin() {
707
return min;
708
}
709
710
/**
711
* Set the minimum value possible in this data set
712
*
713
* @param min The minimum value possible in this data set
714
*/
715
public void setMin(int min) {
716
this.min = min;
717
}
718
}
719
720
/**
721
* A single element in the colour range of this emitter
722
*
723
* @author kevin
724
*/
725
public class ColorRecord {
726
/** The position in the life cycle */
727
public float pos;
728
/** The color at this position */
729
public Color col;
730
731
/**
732
* Create a new record
733
*
734
* @param pos
735
* The position in the life cycle (0 = start, 1 = end)
736
* @param col
737
* The color applied at this position
738
*/
739
public ColorRecord(float pos, Color col) {
740
this.pos = pos;
741
this.col = col;
742
}
743
}
744
745
/**
746
* Add a point in the colour cycle
747
*
748
* @param pos
749
* The position in the life cycle (0 = start, 1 = end)
750
* @param col
751
* The color applied at this position
752
*/
753
public void addColorPoint(float pos, Color col) {
754
colors.add(new ColorRecord(pos, col));
755
}
756
757
/**
758
* A simple bean describing a range of values
759
*
760
* @author kevin
761
*/
762
public class Range {
763
/** The maximum value in the range */
764
private float max;
765
/** The minimum value in the range */
766
private float min;
767
/** True if this range application is enabled */
768
private boolean enabled = false;
769
770
/**
771
* Create a new configurable range
772
*
773
* @param min
774
* The minimum value of the range
775
* @param max
776
* The maximum value of the range
777
*/
778
private Range(float min, float max) {
779
this.min = min;
780
this.max = max;
781
}
782
783
/**
784
* Generate a random number in the range
785
*
786
* @return The random number from the range
787
*/
788
public float random() {
789
return (float) (min + (Math.random() * (max - min)));
790
}
791
792
/**
793
* Check if this configuration option is enabled
794
*
795
* @return True if the range is enabled
796
*/
797
public boolean isEnabled() {
798
return enabled;
799
}
800
801
/**
802
* Indicate if this option should be enabled
803
*
804
* @param enabled
805
* True if this option should be enabled
806
*/
807
public void setEnabled(boolean enabled) {
808
this.enabled = enabled;
809
}
810
811
/**
812
* Get the maximum value for this range
813
*
814
* @return The maximum value for this range
815
*/
816
public float getMax() {
817
return max;
818
}
819
820
/**
821
* Set the maxmium value for this range
822
*
823
* @param max
824
* The maximum value for this range
825
*/
826
public void setMax(float max) {
827
this.max = max;
828
}
829
830
/**
831
* Get the minimum value for this range
832
*
833
* @return The minimum value for this range
834
*/
835
public float getMin() {
836
return min;
837
}
838
839
/**
840
* Set the minimum value for this range
841
*
842
* @param min
843
* The minimum value for this range
844
*/
845
public void setMin(float min) {
846
this.min = min;
847
}
848
}
849
850
public boolean useAdditive() {
851
return useAdditive;
852
}
853
854
public boolean isOriented() {
855
return this.useOriented;
856
}
857
858
public boolean usePoints(ParticleSystem system) {
859
return (this.usePoints == Particle.INHERIT_POINTS) && (system.usePoints()) ||
860
(this.usePoints == Particle.USE_POINTS);
861
}
862
863
public Image getImage() {
864
return image;
865
}
866
867
public void wrapUp() {
868
wrapUp = true;
869
}
870
871
public void resetState() {
872
replay();
873
}
874
}
875
876