1/*******************************************************************************
2 * Copyright 2011 See AUTHORS file.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *   http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 ******************************************************************************/
16
17package com.badlogic.gdx.graphics.g2d;
18
19import java.io.BufferedReader;
20import java.io.IOException;
21import java.io.Writer;
22
23import com.badlogic.gdx.graphics.GL20;
24import com.badlogic.gdx.graphics.Texture;
25import com.badlogic.gdx.math.MathUtils;
26import com.badlogic.gdx.math.Rectangle;
27import com.badlogic.gdx.math.collision.BoundingBox;
28
29public class ParticleEmitter {
30	static private final int UPDATE_SCALE = 1 << 0;
31	static private final int UPDATE_ANGLE = 1 << 1;
32	static private final int UPDATE_ROTATION = 1 << 2;
33	static private final int UPDATE_VELOCITY = 1 << 3;
34	static private final int UPDATE_WIND = 1 << 4;
35	static private final int UPDATE_GRAVITY = 1 << 5;
36	static private final int UPDATE_TINT = 1 << 6;
37
38	private RangedNumericValue delayValue = new RangedNumericValue();
39	private ScaledNumericValue lifeOffsetValue = new ScaledNumericValue();
40	private RangedNumericValue durationValue = new RangedNumericValue();
41	private ScaledNumericValue lifeValue = new ScaledNumericValue();
42	private ScaledNumericValue emissionValue = new ScaledNumericValue();
43	private ScaledNumericValue scaleValue = new ScaledNumericValue();
44	private ScaledNumericValue rotationValue = new ScaledNumericValue();
45	private ScaledNumericValue velocityValue = new ScaledNumericValue();
46	private ScaledNumericValue angleValue = new ScaledNumericValue();
47	private ScaledNumericValue windValue = new ScaledNumericValue();
48	private ScaledNumericValue gravityValue = new ScaledNumericValue();
49	private ScaledNumericValue transparencyValue = new ScaledNumericValue();
50	private GradientColorValue tintValue = new GradientColorValue();
51	private RangedNumericValue xOffsetValue = new ScaledNumericValue();
52	private RangedNumericValue yOffsetValue = new ScaledNumericValue();
53	private ScaledNumericValue spawnWidthValue = new ScaledNumericValue();
54	private ScaledNumericValue spawnHeightValue = new ScaledNumericValue();
55	private SpawnShapeValue spawnShapeValue = new SpawnShapeValue();
56
57	private float accumulator;
58	private Sprite sprite;
59	private Particle[] particles;
60	private int minParticleCount, maxParticleCount = 4;
61	private float x, y;
62	private String name;
63	private String imagePath;
64	private int activeCount;
65	private boolean[] active;
66	private boolean firstUpdate;
67	private boolean flipX, flipY;
68	private int updateFlags;
69	private boolean allowCompletion;
70	private BoundingBox bounds;
71
72	private int emission, emissionDiff, emissionDelta;
73	private int lifeOffset, lifeOffsetDiff;
74	private int life, lifeDiff;
75	private float spawnWidth, spawnWidthDiff;
76	private float spawnHeight, spawnHeightDiff;
77	public float duration = 1, durationTimer;
78	private float delay, delayTimer;
79
80	private boolean attached;
81	private boolean continuous;
82	private boolean aligned;
83	private boolean behind;
84	private boolean additive = true;
85	private boolean premultipliedAlpha = false;
86	boolean cleansUpBlendFunction = true;
87
88	public ParticleEmitter () {
89		initialize();
90	}
91
92	public ParticleEmitter (BufferedReader reader) throws IOException {
93		initialize();
94		load(reader);
95	}
96
97	public ParticleEmitter (ParticleEmitter emitter) {
98		sprite = emitter.sprite;
99		name = emitter.name;
100		imagePath = emitter.imagePath;
101		setMaxParticleCount(emitter.maxParticleCount);
102		minParticleCount = emitter.minParticleCount;
103		delayValue.load(emitter.delayValue);
104		durationValue.load(emitter.durationValue);
105		emissionValue.load(emitter.emissionValue);
106		lifeValue.load(emitter.lifeValue);
107		lifeOffsetValue.load(emitter.lifeOffsetValue);
108		scaleValue.load(emitter.scaleValue);
109		rotationValue.load(emitter.rotationValue);
110		velocityValue.load(emitter.velocityValue);
111		angleValue.load(emitter.angleValue);
112		windValue.load(emitter.windValue);
113		gravityValue.load(emitter.gravityValue);
114		transparencyValue.load(emitter.transparencyValue);
115		tintValue.load(emitter.tintValue);
116		xOffsetValue.load(emitter.xOffsetValue);
117		yOffsetValue.load(emitter.yOffsetValue);
118		spawnWidthValue.load(emitter.spawnWidthValue);
119		spawnHeightValue.load(emitter.spawnHeightValue);
120		spawnShapeValue.load(emitter.spawnShapeValue);
121		attached = emitter.attached;
122		continuous = emitter.continuous;
123		aligned = emitter.aligned;
124		behind = emitter.behind;
125		additive = emitter.additive;
126		premultipliedAlpha = emitter.premultipliedAlpha;
127		cleansUpBlendFunction = emitter.cleansUpBlendFunction;
128	}
129
130	private void initialize () {
131		durationValue.setAlwaysActive(true);
132		emissionValue.setAlwaysActive(true);
133		lifeValue.setAlwaysActive(true);
134		scaleValue.setAlwaysActive(true);
135		transparencyValue.setAlwaysActive(true);
136		spawnShapeValue.setAlwaysActive(true);
137		spawnWidthValue.setAlwaysActive(true);
138		spawnHeightValue.setAlwaysActive(true);
139	}
140
141	public void setMaxParticleCount (int maxParticleCount) {
142		this.maxParticleCount = maxParticleCount;
143		active = new boolean[maxParticleCount];
144		activeCount = 0;
145		particles = new Particle[maxParticleCount];
146	}
147
148	public void addParticle () {
149		int activeCount = this.activeCount;
150		if (activeCount == maxParticleCount) return;
151		boolean[] active = this.active;
152		for (int i = 0, n = active.length; i < n; i++) {
153			if (!active[i]) {
154				activateParticle(i);
155				active[i] = true;
156				this.activeCount = activeCount + 1;
157				break;
158			}
159		}
160	}
161
162	public void addParticles (int count) {
163		count = Math.min(count, maxParticleCount - activeCount);
164		if (count == 0) return;
165		boolean[] active = this.active;
166		int index = 0, n = active.length;
167		outer:
168		for (int i = 0; i < count; i++) {
169			for (; index < n; index++) {
170				if (!active[index]) {
171					activateParticle(index);
172					active[index++] = true;
173					continue outer;
174				}
175			}
176			break;
177		}
178		this.activeCount += count;
179	}
180
181	public void update (float delta) {
182		accumulator += delta * 1000;
183		if (accumulator < 1) return;
184		int deltaMillis = (int)accumulator;
185		accumulator -= deltaMillis;
186
187		if (delayTimer < delay) {
188			delayTimer += deltaMillis;
189		} else {
190			boolean done = false;
191			if (firstUpdate) {
192				firstUpdate = false;
193				addParticle();
194			}
195
196			if (durationTimer < duration)
197				durationTimer += deltaMillis;
198			else {
199				if (!continuous || allowCompletion)
200					done = true;
201				else
202					restart();
203			}
204
205			if (!done) {
206				emissionDelta += deltaMillis;
207				float emissionTime = emission + emissionDiff * emissionValue.getScale(durationTimer / (float)duration);
208				if (emissionTime > 0) {
209					emissionTime = 1000 / emissionTime;
210					if (emissionDelta >= emissionTime) {
211						int emitCount = (int)(emissionDelta / emissionTime);
212						emitCount = Math.min(emitCount, maxParticleCount - activeCount);
213						emissionDelta -= emitCount * emissionTime;
214						emissionDelta %= emissionTime;
215						addParticles(emitCount);
216					}
217				}
218				if (activeCount < minParticleCount) addParticles(minParticleCount - activeCount);
219			}
220		}
221
222		boolean[] active = this.active;
223		int activeCount = this.activeCount;
224		Particle[] particles = this.particles;
225		for (int i = 0, n = active.length; i < n; i++) {
226			if (active[i] && !updateParticle(particles[i], delta, deltaMillis)) {
227				active[i] = false;
228				activeCount--;
229			}
230		}
231		this.activeCount = activeCount;
232	}
233
234	public void draw (Batch batch) {
235		if (premultipliedAlpha) {
236			batch.setBlendFunction(GL20.GL_ONE, GL20.GL_ONE_MINUS_SRC_ALPHA);
237		} else if (additive) {
238			batch.setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE);
239		} else {
240			batch.setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
241		}
242		Particle[] particles = this.particles;
243		boolean[] active = this.active;
244
245		for (int i = 0, n = active.length; i < n; i++) {
246			if (active[i]) particles[i].draw(batch);
247		}
248
249		if (cleansUpBlendFunction && (additive || premultipliedAlpha))
250			batch.setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
251
252	}
253
254	/** Updates and draws the particles. This is slightly more efficient than calling {@link #update(float)} and
255	 * {@link #draw(Batch)} separately. */
256	public void draw (Batch batch, float delta) {
257		accumulator += delta * 1000;
258		if (accumulator < 1) {
259			draw(batch);
260			return;
261		}
262		int deltaMillis = (int)accumulator;
263		accumulator -= deltaMillis;
264
265		if (premultipliedAlpha) {
266			batch.setBlendFunction(GL20.GL_ONE, GL20.GL_ONE_MINUS_SRC_ALPHA);
267		} else if (additive) {
268			batch.setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE);
269		} else {
270			batch.setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
271		}
272
273		Particle[] particles = this.particles;
274		boolean[] active = this.active;
275		int activeCount = this.activeCount;
276		for (int i = 0, n = active.length; i < n; i++) {
277			if (active[i]) {
278				Particle particle = particles[i];
279				if (updateParticle(particle, delta, deltaMillis))
280					particle.draw(batch);
281				else {
282					active[i] = false;
283					activeCount--;
284				}
285			}
286		}
287		this.activeCount = activeCount;
288
289		if (cleansUpBlendFunction && (additive || premultipliedAlpha))
290			batch.setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
291
292		if (delayTimer < delay) {
293			delayTimer += deltaMillis;
294			return;
295		}
296
297		if (firstUpdate) {
298			firstUpdate = false;
299			addParticle();
300		}
301
302		if (durationTimer < duration)
303			durationTimer += deltaMillis;
304		else {
305			if (!continuous || allowCompletion) return;
306			restart();
307		}
308
309		emissionDelta += deltaMillis;
310		float emissionTime = emission + emissionDiff * emissionValue.getScale(durationTimer / (float)duration);
311		if (emissionTime > 0) {
312			emissionTime = 1000 / emissionTime;
313			if (emissionDelta >= emissionTime) {
314				int emitCount = (int)(emissionDelta / emissionTime);
315				emitCount = Math.min(emitCount, maxParticleCount - activeCount);
316				emissionDelta -= emitCount * emissionTime;
317				emissionDelta %= emissionTime;
318				addParticles(emitCount);
319			}
320		}
321		if (activeCount < minParticleCount) addParticles(minParticleCount - activeCount);
322	}
323
324	public void start () {
325		firstUpdate = true;
326		allowCompletion = false;
327		restart();
328	}
329
330	public void reset () {
331		emissionDelta = 0;
332		durationTimer = duration;
333		boolean[] active = this.active;
334		for (int i = 0, n = active.length; i < n; i++)
335			active[i] = false;
336		activeCount = 0;
337		start();
338	}
339
340	private void restart () {
341		delay = delayValue.active ? delayValue.newLowValue() : 0;
342		delayTimer = 0;
343
344		durationTimer -= duration;
345		duration = durationValue.newLowValue();
346
347		emission = (int)emissionValue.newLowValue();
348		emissionDiff = (int)emissionValue.newHighValue();
349		if (!emissionValue.isRelative()) emissionDiff -= emission;
350
351		life = (int)lifeValue.newLowValue();
352		lifeDiff = (int)lifeValue.newHighValue();
353		if (!lifeValue.isRelative()) lifeDiff -= life;
354
355		lifeOffset = lifeOffsetValue.active ? (int)lifeOffsetValue.newLowValue() : 0;
356		lifeOffsetDiff = (int)lifeOffsetValue.newHighValue();
357		if (!lifeOffsetValue.isRelative()) lifeOffsetDiff -= lifeOffset;
358
359		spawnWidth = spawnWidthValue.newLowValue();
360		spawnWidthDiff = spawnWidthValue.newHighValue();
361		if (!spawnWidthValue.isRelative()) spawnWidthDiff -= spawnWidth;
362
363		spawnHeight = spawnHeightValue.newLowValue();
364		spawnHeightDiff = spawnHeightValue.newHighValue();
365		if (!spawnHeightValue.isRelative()) spawnHeightDiff -= spawnHeight;
366
367		updateFlags = 0;
368		if (angleValue.active && angleValue.timeline.length > 1) updateFlags |= UPDATE_ANGLE;
369		if (velocityValue.active) updateFlags |= UPDATE_VELOCITY;
370		if (scaleValue.timeline.length > 1) updateFlags |= UPDATE_SCALE;
371		if (rotationValue.active && rotationValue.timeline.length > 1) updateFlags |= UPDATE_ROTATION;
372		if (windValue.active) updateFlags |= UPDATE_WIND;
373		if (gravityValue.active) updateFlags |= UPDATE_GRAVITY;
374		if (tintValue.timeline.length > 1) updateFlags |= UPDATE_TINT;
375	}
376
377	protected Particle newParticle (Sprite sprite) {
378		return new Particle(sprite);
379	}
380
381	private void activateParticle (int index) {
382		Particle particle = particles[index];
383		if (particle == null) {
384			particles[index] = particle = newParticle(sprite);
385			particle.flip(flipX, flipY);
386		}
387
388		float percent = durationTimer / (float)duration;
389		int updateFlags = this.updateFlags;
390
391		particle.currentLife = particle.life = life + (int)(lifeDiff * lifeValue.getScale(percent));
392
393		if (velocityValue.active) {
394			particle.velocity = velocityValue.newLowValue();
395			particle.velocityDiff = velocityValue.newHighValue();
396			if (!velocityValue.isRelative()) particle.velocityDiff -= particle.velocity;
397		}
398
399		particle.angle = angleValue.newLowValue();
400		particle.angleDiff = angleValue.newHighValue();
401		if (!angleValue.isRelative()) particle.angleDiff -= particle.angle;
402		float angle = 0;
403		if ((updateFlags & UPDATE_ANGLE) == 0) {
404			angle = particle.angle + particle.angleDiff * angleValue.getScale(0);
405			particle.angle = angle;
406			particle.angleCos = MathUtils.cosDeg(angle);
407			particle.angleSin = MathUtils.sinDeg(angle);
408		}
409
410		float spriteWidth = sprite.getWidth();
411		particle.scale = scaleValue.newLowValue() / spriteWidth;
412		particle.scaleDiff = scaleValue.newHighValue() / spriteWidth;
413		if (!scaleValue.isRelative()) particle.scaleDiff -= particle.scale;
414		particle.setScale(particle.scale + particle.scaleDiff * scaleValue.getScale(0));
415
416		if (rotationValue.active) {
417			particle.rotation = rotationValue.newLowValue();
418			particle.rotationDiff = rotationValue.newHighValue();
419			if (!rotationValue.isRelative()) particle.rotationDiff -= particle.rotation;
420			float rotation = particle.rotation + particle.rotationDiff * rotationValue.getScale(0);
421			if (aligned) rotation += angle;
422			particle.setRotation(rotation);
423		}
424
425		if (windValue.active) {
426			particle.wind = windValue.newLowValue();
427			particle.windDiff = windValue.newHighValue();
428			if (!windValue.isRelative()) particle.windDiff -= particle.wind;
429		}
430
431		if (gravityValue.active) {
432			particle.gravity = gravityValue.newLowValue();
433			particle.gravityDiff = gravityValue.newHighValue();
434			if (!gravityValue.isRelative()) particle.gravityDiff -= particle.gravity;
435		}
436
437		float[] color = particle.tint;
438		if (color == null) particle.tint = color = new float[3];
439		float[] temp = tintValue.getColor(0);
440		color[0] = temp[0];
441		color[1] = temp[1];
442		color[2] = temp[2];
443
444		particle.transparency = transparencyValue.newLowValue();
445		particle.transparencyDiff = transparencyValue.newHighValue() - particle.transparency;
446
447		// Spawn.
448		float x = this.x;
449		if (xOffsetValue.active) x += xOffsetValue.newLowValue();
450		float y = this.y;
451		if (yOffsetValue.active) y += yOffsetValue.newLowValue();
452		switch (spawnShapeValue.shape) {
453		case square: {
454			float width = spawnWidth + (spawnWidthDiff * spawnWidthValue.getScale(percent));
455			float height = spawnHeight + (spawnHeightDiff * spawnHeightValue.getScale(percent));
456			x += MathUtils.random(width) - width / 2;
457			y += MathUtils.random(height) - height / 2;
458			break;
459		}
460		case ellipse: {
461			float width = spawnWidth + (spawnWidthDiff * spawnWidthValue.getScale(percent));
462			float height = spawnHeight + (spawnHeightDiff * spawnHeightValue.getScale(percent));
463			float radiusX = width / 2;
464			float radiusY = height / 2;
465			if (radiusX == 0 || radiusY == 0) break;
466			float scaleY = radiusX / (float)radiusY;
467			if (spawnShapeValue.edges) {
468				float spawnAngle;
469				switch (spawnShapeValue.side) {
470				case top:
471					spawnAngle = -MathUtils.random(179f);
472					break;
473				case bottom:
474					spawnAngle = MathUtils.random(179f);
475					break;
476				default:
477					spawnAngle = MathUtils.random(360f);
478					break;
479				}
480				float cosDeg = MathUtils.cosDeg(spawnAngle);
481				float sinDeg = MathUtils.sinDeg(spawnAngle);
482				x += cosDeg * radiusX;
483				y += sinDeg * radiusX / scaleY;
484				if ((updateFlags & UPDATE_ANGLE) == 0) {
485					particle.angle = spawnAngle;
486					particle.angleCos = cosDeg;
487					particle.angleSin = sinDeg;
488				}
489			} else {
490				float radius2 = radiusX * radiusX;
491				while (true) {
492					float px = MathUtils.random(width) - radiusX;
493					float py = MathUtils.random(height) - radiusY;
494					if (px * px + py * py <= radius2) {
495						x += px;
496						y += py / scaleY;
497						break;
498					}
499				}
500			}
501			break;
502		}
503		case line: {
504			float width = spawnWidth + (spawnWidthDiff * spawnWidthValue.getScale(percent));
505			float height = spawnHeight + (spawnHeightDiff * spawnHeightValue.getScale(percent));
506			if (width != 0) {
507				float lineX = width * MathUtils.random();
508				x += lineX;
509				y += lineX * (height / (float)width);
510			} else
511				y += height * MathUtils.random();
512			break;
513		}
514		}
515
516		float spriteHeight = sprite.getHeight();
517		particle.setBounds(x - spriteWidth / 2, y - spriteHeight / 2, spriteWidth, spriteHeight);
518
519		int offsetTime = (int)(lifeOffset + lifeOffsetDiff * lifeOffsetValue.getScale(percent));
520		if (offsetTime > 0) {
521			if (offsetTime >= particle.currentLife) offsetTime = particle.currentLife - 1;
522			updateParticle(particle, offsetTime / 1000f, offsetTime);
523		}
524	}
525
526	private boolean updateParticle (Particle particle, float delta, int deltaMillis) {
527		int life = particle.currentLife - deltaMillis;
528		if (life <= 0) return false;
529		particle.currentLife = life;
530
531		float percent = 1 - particle.currentLife / (float)particle.life;
532		int updateFlags = this.updateFlags;
533
534		if ((updateFlags & UPDATE_SCALE) != 0)
535			particle.setScale(particle.scale + particle.scaleDiff * scaleValue.getScale(percent));
536
537		if ((updateFlags & UPDATE_VELOCITY) != 0) {
538			float velocity = (particle.velocity + particle.velocityDiff * velocityValue.getScale(percent)) * delta;
539
540			float velocityX, velocityY;
541			if ((updateFlags & UPDATE_ANGLE) != 0) {
542				float angle = particle.angle + particle.angleDiff * angleValue.getScale(percent);
543				velocityX = velocity * MathUtils.cosDeg(angle);
544				velocityY = velocity * MathUtils.sinDeg(angle);
545				if ((updateFlags & UPDATE_ROTATION) != 0) {
546					float rotation = particle.rotation + particle.rotationDiff * rotationValue.getScale(percent);
547					if (aligned) rotation += angle;
548					particle.setRotation(rotation);
549				}
550			} else {
551				velocityX = velocity * particle.angleCos;
552				velocityY = velocity * particle.angleSin;
553				if (aligned || (updateFlags & UPDATE_ROTATION) != 0) {
554					float rotation = particle.rotation + particle.rotationDiff * rotationValue.getScale(percent);
555					if (aligned) rotation += particle.angle;
556					particle.setRotation(rotation);
557				}
558			}
559
560			if ((updateFlags & UPDATE_WIND) != 0)
561				velocityX += (particle.wind + particle.windDiff * windValue.getScale(percent)) * delta;
562
563			if ((updateFlags & UPDATE_GRAVITY) != 0)
564				velocityY += (particle.gravity + particle.gravityDiff * gravityValue.getScale(percent)) * delta;
565
566			particle.translate(velocityX, velocityY);
567		} else {
568			if ((updateFlags & UPDATE_ROTATION) != 0)
569				particle.setRotation(particle.rotation + particle.rotationDiff * rotationValue.getScale(percent));
570		}
571
572		float[] color;
573		if ((updateFlags & UPDATE_TINT) != 0)
574			color = tintValue.getColor(percent);
575		else
576			color = particle.tint;
577
578		if (premultipliedAlpha) {
579			float alphaMultiplier = additive ? 0 : 1;
580			float a = particle.transparency + particle.transparencyDiff * transparencyValue.getScale(percent);
581			particle.setColor(color[0] * a, color[1] * a, color[2] * a, a * alphaMultiplier);
582		} else {
583			particle.setColor(color[0], color[1], color[2],
584				particle.transparency + particle.transparencyDiff * transparencyValue.getScale(percent));
585		}
586		return true;
587	}
588
589	public void setPosition (float x, float y) {
590		if (attached) {
591			float xAmount = x - this.x;
592			float yAmount = y - this.y;
593			boolean[] active = this.active;
594			for (int i = 0, n = active.length; i < n; i++)
595				if (active[i]) particles[i].translate(xAmount, yAmount);
596		}
597		this.x = x;
598		this.y = y;
599	}
600
601	public void setSprite (Sprite sprite) {
602		this.sprite = sprite;
603		if (sprite == null) return;
604		float originX = sprite.getOriginX();
605		float originY = sprite.getOriginY();
606		Texture texture = sprite.getTexture();
607		for (int i = 0, n = particles.length; i < n; i++) {
608			Particle particle = particles[i];
609			if (particle == null) break;
610			particle.setTexture(texture);
611			particle.setOrigin(originX, originY);
612		}
613	}
614
615	/** Ignores the {@link #setContinuous(boolean) continuous} setting until the emitter is started again. This allows the emitter
616	 * to stop smoothly. */
617	public void allowCompletion () {
618		allowCompletion = true;
619		durationTimer = duration;
620	}
621
622	public Sprite getSprite () {
623		return sprite;
624	}
625
626	public String getName () {
627		return name;
628	}
629
630	public void setName (String name) {
631		this.name = name;
632	}
633
634	public ScaledNumericValue getLife () {
635		return lifeValue;
636	}
637
638	public ScaledNumericValue getScale () {
639		return scaleValue;
640	}
641
642	public ScaledNumericValue getRotation () {
643		return rotationValue;
644	}
645
646	public GradientColorValue getTint () {
647		return tintValue;
648	}
649
650	public ScaledNumericValue getVelocity () {
651		return velocityValue;
652	}
653
654	public ScaledNumericValue getWind () {
655		return windValue;
656	}
657
658	public ScaledNumericValue getGravity () {
659		return gravityValue;
660	}
661
662	public ScaledNumericValue getAngle () {
663		return angleValue;
664	}
665
666	public ScaledNumericValue getEmission () {
667		return emissionValue;
668	}
669
670	public ScaledNumericValue getTransparency () {
671		return transparencyValue;
672	}
673
674	public RangedNumericValue getDuration () {
675		return durationValue;
676	}
677
678	public RangedNumericValue getDelay () {
679		return delayValue;
680	}
681
682	public ScaledNumericValue getLifeOffset () {
683		return lifeOffsetValue;
684	}
685
686	public RangedNumericValue getXOffsetValue () {
687		return xOffsetValue;
688	}
689
690	public RangedNumericValue getYOffsetValue () {
691		return yOffsetValue;
692	}
693
694	public ScaledNumericValue getSpawnWidth () {
695		return spawnWidthValue;
696	}
697
698	public ScaledNumericValue getSpawnHeight () {
699		return spawnHeightValue;
700	}
701
702	public SpawnShapeValue getSpawnShape () {
703		return spawnShapeValue;
704	}
705
706	public boolean isAttached () {
707		return attached;
708	}
709
710	public void setAttached (boolean attached) {
711		this.attached = attached;
712	}
713
714	public boolean isContinuous () {
715		return continuous;
716	}
717
718	public void setContinuous (boolean continuous) {
719		this.continuous = continuous;
720	}
721
722	public boolean isAligned () {
723		return aligned;
724	}
725
726	public void setAligned (boolean aligned) {
727		this.aligned = aligned;
728	}
729
730	public boolean isAdditive () {
731		return additive;
732	}
733
734	public void setAdditive (boolean additive) {
735		this.additive = additive;
736	}
737
738	/** @return Whether this ParticleEmitter automatically returns the {@link com.badlogic.gdx.graphics.g2d.Batch Batch}'s blend
739	 *         function to the alpha-blending default (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) when done drawing. */
740	public boolean cleansUpBlendFunction () {
741		return cleansUpBlendFunction;
742	}
743
744	/** Set whether to automatically return the {@link com.badlogic.gdx.graphics.g2d.Batch Batch}'s blend function to the
745	 * alpha-blending default (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) when done drawing. Is true by default. If set to false, the
746	 * Batch's blend function is left as it was for drawing this ParticleEmitter, which prevents the Batch from being flushed
747	 * repeatedly if consecutive ParticleEmitters with the same additive or pre-multiplied alpha state are drawn in a row.
748	 * <p>
749	 * IMPORTANT: If set to false and if the next object to use this Batch expects alpha blending, you are responsible for setting
750	 * the Batch's blend function to (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) before that next object is drawn.
751	 * @param cleansUpBlendFunction */
752	public void setCleansUpBlendFunction (boolean cleansUpBlendFunction) {
753		this.cleansUpBlendFunction = cleansUpBlendFunction;
754	}
755
756	public boolean isBehind () {
757		return behind;
758	}
759
760	public void setBehind (boolean behind) {
761		this.behind = behind;
762	}
763
764	public boolean isPremultipliedAlpha () {
765		return premultipliedAlpha;
766	}
767
768	public void setPremultipliedAlpha (boolean premultipliedAlpha) {
769		this.premultipliedAlpha = premultipliedAlpha;
770	}
771
772	public int getMinParticleCount () {
773		return minParticleCount;
774	}
775
776	public void setMinParticleCount (int minParticleCount) {
777		this.minParticleCount = minParticleCount;
778	}
779
780	public int getMaxParticleCount () {
781		return maxParticleCount;
782	}
783
784	public boolean isComplete () {
785		if (continuous) return false;
786		if (delayTimer < delay) return false;
787		return durationTimer >= duration && activeCount == 0;
788	}
789
790	public float getPercentComplete () {
791		if (delayTimer < delay) return 0;
792		return Math.min(1, durationTimer / (float)duration);
793	}
794
795	public float getX () {
796		return x;
797	}
798
799	public float getY () {
800		return y;
801	}
802
803	public int getActiveCount () {
804		return activeCount;
805	}
806
807	public String getImagePath () {
808		return imagePath;
809	}
810
811	public void setImagePath (String imagePath) {
812		this.imagePath = imagePath;
813	}
814
815	public void setFlip (boolean flipX, boolean flipY) {
816		this.flipX = flipX;
817		this.flipY = flipY;
818		if (particles == null) return;
819		for (int i = 0, n = particles.length; i < n; i++) {
820			Particle particle = particles[i];
821			if (particle != null) particle.flip(flipX, flipY);
822		}
823	}
824
825	public void flipY () {
826		angleValue.setHigh(-angleValue.getHighMin(), -angleValue.getHighMax());
827		angleValue.setLow(-angleValue.getLowMin(), -angleValue.getLowMax());
828
829		gravityValue.setHigh(-gravityValue.getHighMin(), -gravityValue.getHighMax());
830		gravityValue.setLow(-gravityValue.getLowMin(), -gravityValue.getLowMax());
831
832		windValue.setHigh(-windValue.getHighMin(), -windValue.getHighMax());
833		windValue.setLow(-windValue.getLowMin(), -windValue.getLowMax());
834
835		rotationValue.setHigh(-rotationValue.getHighMin(), -rotationValue.getHighMax());
836		rotationValue.setLow(-rotationValue.getLowMin(), -rotationValue.getLowMax());
837
838		yOffsetValue.setLow(-yOffsetValue.getLowMin(), -yOffsetValue.getLowMax());
839	}
840
841	/** Returns the bounding box for all active particles. z axis will always be zero. */
842	public BoundingBox getBoundingBox () {
843		if (bounds == null) bounds = new BoundingBox();
844
845		Particle[] particles = this.particles;
846		boolean[] active = this.active;
847		BoundingBox bounds = this.bounds;
848
849		bounds.inf();
850		for (int i = 0, n = active.length; i < n; i++)
851			if (active[i]) {
852				Rectangle r = particles[i].getBoundingRectangle();
853				bounds.ext(r.x, r.y, 0);
854				bounds.ext(r.x + r.width, r.y + r.height, 0);
855			}
856
857		return bounds;
858	}
859
860	public void save (Writer output) throws IOException {
861		output.write(name + "\n");
862		output.write("- Delay -\n");
863		delayValue.save(output);
864		output.write("- Duration - \n");
865		durationValue.save(output);
866		output.write("- Count - \n");
867		output.write("min: " + minParticleCount + "\n");
868		output.write("max: " + maxParticleCount + "\n");
869		output.write("- Emission - \n");
870		emissionValue.save(output);
871		output.write("- Life - \n");
872		lifeValue.save(output);
873		output.write("- Life Offset - \n");
874		lifeOffsetValue.save(output);
875		output.write("- X Offset - \n");
876		xOffsetValue.save(output);
877		output.write("- Y Offset - \n");
878		yOffsetValue.save(output);
879		output.write("- Spawn Shape - \n");
880		spawnShapeValue.save(output);
881		output.write("- Spawn Width - \n");
882		spawnWidthValue.save(output);
883		output.write("- Spawn Height - \n");
884		spawnHeightValue.save(output);
885		output.write("- Scale - \n");
886		scaleValue.save(output);
887		output.write("- Velocity - \n");
888		velocityValue.save(output);
889		output.write("- Angle - \n");
890		angleValue.save(output);
891		output.write("- Rotation - \n");
892		rotationValue.save(output);
893		output.write("- Wind - \n");
894		windValue.save(output);
895		output.write("- Gravity - \n");
896		gravityValue.save(output);
897		output.write("- Tint - \n");
898		tintValue.save(output);
899		output.write("- Transparency - \n");
900		transparencyValue.save(output);
901		output.write("- Options - \n");
902		output.write("attached: " + attached + "\n");
903		output.write("continuous: " + continuous + "\n");
904		output.write("aligned: " + aligned + "\n");
905		output.write("additive: " + additive + "\n");
906		output.write("behind: " + behind + "\n");
907		output.write("premultipliedAlpha: " + premultipliedAlpha + "\n");
908		output.write("- Image Path -\n");
909		output.write(imagePath + "\n");
910	}
911
912	public void load (BufferedReader reader) throws IOException {
913		try {
914			name = readString(reader, "name");
915			reader.readLine();
916			delayValue.load(reader);
917			reader.readLine();
918			durationValue.load(reader);
919			reader.readLine();
920			setMinParticleCount(readInt(reader, "minParticleCount"));
921			setMaxParticleCount(readInt(reader, "maxParticleCount"));
922			reader.readLine();
923			emissionValue.load(reader);
924			reader.readLine();
925			lifeValue.load(reader);
926			reader.readLine();
927			lifeOffsetValue.load(reader);
928			reader.readLine();
929			xOffsetValue.load(reader);
930			reader.readLine();
931			yOffsetValue.load(reader);
932			reader.readLine();
933			spawnShapeValue.load(reader);
934			reader.readLine();
935			spawnWidthValue.load(reader);
936			reader.readLine();
937			spawnHeightValue.load(reader);
938			reader.readLine();
939			scaleValue.load(reader);
940			reader.readLine();
941			velocityValue.load(reader);
942			reader.readLine();
943			angleValue.load(reader);
944			reader.readLine();
945			rotationValue.load(reader);
946			reader.readLine();
947			windValue.load(reader);
948			reader.readLine();
949			gravityValue.load(reader);
950			reader.readLine();
951			tintValue.load(reader);
952			reader.readLine();
953			transparencyValue.load(reader);
954			reader.readLine();
955			attached = readBoolean(reader, "attached");
956			continuous = readBoolean(reader, "continuous");
957			aligned = readBoolean(reader, "aligned");
958			additive = readBoolean(reader, "additive");
959			behind = readBoolean(reader, "behind");
960
961			// Backwards compatibility
962			String line = reader.readLine();
963			if (line.startsWith("premultipliedAlpha")) {
964				premultipliedAlpha = readBoolean(line);
965				reader.readLine();
966			}
967			setImagePath(reader.readLine());
968		} catch (RuntimeException ex) {
969			if (name == null) throw ex;
970			throw new RuntimeException("Error parsing emitter: " + name, ex);
971		}
972	}
973
974	static String readString (String line) throws IOException {
975		return line.substring(line.indexOf(":") + 1).trim();
976	}
977
978	static String readString (BufferedReader reader, String name) throws IOException {
979		String line = reader.readLine();
980		if (line == null) throw new IOException("Missing value: " + name);
981		return readString(line);
982	}
983
984	static boolean readBoolean (String line) throws IOException {
985		return Boolean.parseBoolean(readString(line));
986	}
987
988	static boolean readBoolean (BufferedReader reader, String name) throws IOException {
989		return Boolean.parseBoolean(readString(reader, name));
990	}
991
992	static int readInt (BufferedReader reader, String name) throws IOException {
993		return Integer.parseInt(readString(reader, name));
994	}
995
996	static float readFloat (BufferedReader reader, String name) throws IOException {
997		return Float.parseFloat(readString(reader, name));
998	}
999
1000	public static class Particle extends Sprite {
1001		protected int life, currentLife;
1002		protected float scale, scaleDiff;
1003		protected float rotation, rotationDiff;
1004		protected float velocity, velocityDiff;
1005		protected float angle, angleDiff;
1006		protected float angleCos, angleSin;
1007		protected float transparency, transparencyDiff;
1008		protected float wind, windDiff;
1009		protected float gravity, gravityDiff;
1010		protected float[] tint;
1011
1012		public Particle (Sprite sprite) {
1013			super(sprite);
1014		}
1015	}
1016
1017	static public class ParticleValue {
1018		boolean active;
1019		boolean alwaysActive;
1020
1021		public void setAlwaysActive (boolean alwaysActive) {
1022			this.alwaysActive = alwaysActive;
1023		}
1024
1025		public boolean isAlwaysActive () {
1026			return alwaysActive;
1027		}
1028
1029		public boolean isActive () {
1030			return alwaysActive || active;
1031		}
1032
1033		public void setActive (boolean active) {
1034			this.active = active;
1035		}
1036
1037		public void save (Writer output) throws IOException {
1038			if (!alwaysActive)
1039				output.write("active: " + active + "\n");
1040			else
1041				active = true;
1042		}
1043
1044		public void load (BufferedReader reader) throws IOException {
1045			if (!alwaysActive)
1046				active = readBoolean(reader, "active");
1047			else
1048				active = true;
1049		}
1050
1051		public void load (ParticleValue value) {
1052			active = value.active;
1053			alwaysActive = value.alwaysActive;
1054		}
1055	}
1056
1057	static public class NumericValue extends ParticleValue {
1058		private float value;
1059
1060		public float getValue () {
1061			return value;
1062		}
1063
1064		public void setValue (float value) {
1065			this.value = value;
1066		}
1067
1068		public void save (Writer output) throws IOException {
1069			super.save(output);
1070			if (!active) return;
1071			output.write("value: " + value + "\n");
1072		}
1073
1074		public void load (BufferedReader reader) throws IOException {
1075			super.load(reader);
1076			if (!active) return;
1077			value = readFloat(reader, "value");
1078		}
1079
1080		public void load (NumericValue value) {
1081			super.load(value);
1082			this.value = value.value;
1083		}
1084	}
1085
1086	static public class RangedNumericValue extends ParticleValue {
1087		private float lowMin, lowMax;
1088
1089		public float newLowValue () {
1090			return lowMin + (lowMax - lowMin) * MathUtils.random();
1091		}
1092
1093		public void setLow (float value) {
1094			lowMin = value;
1095			lowMax = value;
1096		}
1097
1098		public void setLow (float min, float max) {
1099			lowMin = min;
1100			lowMax = max;
1101		}
1102
1103		public float getLowMin () {
1104			return lowMin;
1105		}
1106
1107		public void setLowMin (float lowMin) {
1108			this.lowMin = lowMin;
1109		}
1110
1111		public float getLowMax () {
1112			return lowMax;
1113		}
1114
1115		public void setLowMax (float lowMax) {
1116			this.lowMax = lowMax;
1117		}
1118
1119		public void save (Writer output) throws IOException {
1120			super.save(output);
1121			if (!active) return;
1122			output.write("lowMin: " + lowMin + "\n");
1123			output.write("lowMax: " + lowMax + "\n");
1124		}
1125
1126		public void load (BufferedReader reader) throws IOException {
1127			super.load(reader);
1128			if (!active) return;
1129			lowMin = readFloat(reader, "lowMin");
1130			lowMax = readFloat(reader, "lowMax");
1131		}
1132
1133		public void load (RangedNumericValue value) {
1134			super.load(value);
1135			lowMax = value.lowMax;
1136			lowMin = value.lowMin;
1137		}
1138	}
1139
1140	static public class ScaledNumericValue extends RangedNumericValue {
1141		private float[] scaling = {1};
1142		float[] timeline = {0};
1143		private float highMin, highMax;
1144		private boolean relative;
1145
1146		public float newHighValue () {
1147			return highMin + (highMax - highMin) * MathUtils.random();
1148		}
1149
1150		public void setHigh (float value) {
1151			highMin = value;
1152			highMax = value;
1153		}
1154
1155		public void setHigh (float min, float max) {
1156			highMin = min;
1157			highMax = max;
1158		}
1159
1160		public float getHighMin () {
1161			return highMin;
1162		}
1163
1164		public void setHighMin (float highMin) {
1165			this.highMin = highMin;
1166		}
1167
1168		public float getHighMax () {
1169			return highMax;
1170		}
1171
1172		public void setHighMax (float highMax) {
1173			this.highMax = highMax;
1174		}
1175
1176		public float[] getScaling () {
1177			return scaling;
1178		}
1179
1180		public void setScaling (float[] values) {
1181			this.scaling = values;
1182		}
1183
1184		public float[] getTimeline () {
1185			return timeline;
1186		}
1187
1188		public void setTimeline (float[] timeline) {
1189			this.timeline = timeline;
1190		}
1191
1192		public boolean isRelative () {
1193			return relative;
1194		}
1195
1196		public void setRelative (boolean relative) {
1197			this.relative = relative;
1198		}
1199
1200		public float getScale (float percent) {
1201			int endIndex = -1;
1202			float[] timeline = this.timeline;
1203			int n = timeline.length;
1204			for (int i = 1; i < n; i++) {
1205				float t = timeline[i];
1206				if (t > percent) {
1207					endIndex = i;
1208					break;
1209				}
1210			}
1211			if (endIndex == -1) return scaling[n - 1];
1212			float[] scaling = this.scaling;
1213			int startIndex = endIndex - 1;
1214			float startValue = scaling[startIndex];
1215			float startTime = timeline[startIndex];
1216			return startValue + (scaling[endIndex] - startValue) * ((percent - startTime) / (timeline[endIndex] - startTime));
1217		}
1218
1219		public void save (Writer output) throws IOException {
1220			super.save(output);
1221			if (!active) return;
1222			output.write("highMin: " + highMin + "\n");
1223			output.write("highMax: " + highMax + "\n");
1224			output.write("relative: " + relative + "\n");
1225			output.write("scalingCount: " + scaling.length + "\n");
1226			for (int i = 0; i < scaling.length; i++)
1227				output.write("scaling" + i + ": " + scaling[i] + "\n");
1228			output.write("timelineCount: " + timeline.length + "\n");
1229			for (int i = 0; i < timeline.length; i++)
1230				output.write("timeline" + i + ": " + timeline[i] + "\n");
1231		}
1232
1233		public void load (BufferedReader reader) throws IOException {
1234			super.load(reader);
1235			if (!active) return;
1236			highMin = readFloat(reader, "highMin");
1237			highMax = readFloat(reader, "highMax");
1238			relative = readBoolean(reader, "relative");
1239			scaling = new float[readInt(reader, "scalingCount")];
1240			for (int i = 0; i < scaling.length; i++)
1241				scaling[i] = readFloat(reader, "scaling" + i);
1242			timeline = new float[readInt(reader, "timelineCount")];
1243			for (int i = 0; i < timeline.length; i++)
1244				timeline[i] = readFloat(reader, "timeline" + i);
1245		}
1246
1247		public void load (ScaledNumericValue value) {
1248			super.load(value);
1249			highMax = value.highMax;
1250			highMin = value.highMin;
1251			scaling = new float[value.scaling.length];
1252			System.arraycopy(value.scaling, 0, scaling, 0, scaling.length);
1253			timeline = new float[value.timeline.length];
1254			System.arraycopy(value.timeline, 0, timeline, 0, timeline.length);
1255			relative = value.relative;
1256		}
1257	}
1258
1259	static public class GradientColorValue extends ParticleValue {
1260		static private float[] temp = new float[4];
1261
1262		private float[] colors = {1, 1, 1};
1263		float[] timeline = {0};
1264
1265		public GradientColorValue () {
1266			alwaysActive = true;
1267		}
1268
1269		public float[] getTimeline () {
1270			return timeline;
1271		}
1272
1273		public void setTimeline (float[] timeline) {
1274			this.timeline = timeline;
1275		}
1276
1277		/** @return the r, g and b values for every timeline position */
1278		public float[] getColors () {
1279			return colors;
1280		}
1281
1282		/** @param colors the r, g and b values for every timeline position */
1283		public void setColors (float[] colors) {
1284			this.colors = colors;
1285		}
1286
1287		public float[] getColor (float percent) {
1288			int startIndex = 0, endIndex = -1;
1289			float[] timeline = this.timeline;
1290			int n = timeline.length;
1291			for (int i = 1; i < n; i++) {
1292				float t = timeline[i];
1293				if (t > percent) {
1294					endIndex = i;
1295					break;
1296				}
1297				startIndex = i;
1298			}
1299			float startTime = timeline[startIndex];
1300			startIndex *= 3;
1301			float r1 = colors[startIndex];
1302			float g1 = colors[startIndex + 1];
1303			float b1 = colors[startIndex + 2];
1304			if (endIndex == -1) {
1305				temp[0] = r1;
1306				temp[1] = g1;
1307				temp[2] = b1;
1308				return temp;
1309			}
1310			float factor = (percent - startTime) / (timeline[endIndex] - startTime);
1311			endIndex *= 3;
1312			temp[0] = r1 + (colors[endIndex] - r1) * factor;
1313			temp[1] = g1 + (colors[endIndex + 1] - g1) * factor;
1314			temp[2] = b1 + (colors[endIndex + 2] - b1) * factor;
1315			return temp;
1316		}
1317
1318		public void save (Writer output) throws IOException {
1319			super.save(output);
1320			if (!active) return;
1321			output.write("colorsCount: " + colors.length + "\n");
1322			for (int i = 0; i < colors.length; i++)
1323				output.write("colors" + i + ": " + colors[i] + "\n");
1324			output.write("timelineCount: " + timeline.length + "\n");
1325			for (int i = 0; i < timeline.length; i++)
1326				output.write("timeline" + i + ": " + timeline[i] + "\n");
1327		}
1328
1329		public void load (BufferedReader reader) throws IOException {
1330			super.load(reader);
1331			if (!active) return;
1332			colors = new float[readInt(reader, "colorsCount")];
1333			for (int i = 0; i < colors.length; i++)
1334				colors[i] = readFloat(reader, "colors" + i);
1335			timeline = new float[readInt(reader, "timelineCount")];
1336			for (int i = 0; i < timeline.length; i++)
1337				timeline[i] = readFloat(reader, "timeline" + i);
1338		}
1339
1340		public void load (GradientColorValue value) {
1341			super.load(value);
1342			colors = new float[value.colors.length];
1343			System.arraycopy(value.colors, 0, colors, 0, colors.length);
1344			timeline = new float[value.timeline.length];
1345			System.arraycopy(value.timeline, 0, timeline, 0, timeline.length);
1346		}
1347	}
1348
1349	static public class SpawnShapeValue extends ParticleValue {
1350		SpawnShape shape = SpawnShape.point;
1351		boolean edges;
1352		SpawnEllipseSide side = SpawnEllipseSide.both;
1353
1354		public SpawnShape getShape () {
1355			return shape;
1356		}
1357
1358		public void setShape (SpawnShape shape) {
1359			this.shape = shape;
1360		}
1361
1362		public boolean isEdges () {
1363			return edges;
1364		}
1365
1366		public void setEdges (boolean edges) {
1367			this.edges = edges;
1368		}
1369
1370		public SpawnEllipseSide getSide () {
1371			return side;
1372		}
1373
1374		public void setSide (SpawnEllipseSide side) {
1375			this.side = side;
1376		}
1377
1378		public void save (Writer output) throws IOException {
1379			super.save(output);
1380			if (!active) return;
1381			output.write("shape: " + shape + "\n");
1382			if (shape == SpawnShape.ellipse) {
1383				output.write("edges: " + edges + "\n");
1384				output.write("side: " + side + "\n");
1385			}
1386		}
1387
1388		public void load (BufferedReader reader) throws IOException {
1389			super.load(reader);
1390			if (!active) return;
1391			shape = SpawnShape.valueOf(readString(reader, "shape"));
1392			if (shape == SpawnShape.ellipse) {
1393				edges = readBoolean(reader, "edges");
1394				side = SpawnEllipseSide.valueOf(readString(reader, "side"));
1395			}
1396		}
1397
1398		public void load (SpawnShapeValue value) {
1399			super.load(value);
1400			shape = value.shape;
1401			edges = value.edges;
1402			side = value.side;
1403		}
1404	}
1405
1406	static public enum SpawnShape {
1407		point, line, square, ellipse
1408	}
1409
1410	static public enum SpawnEllipseSide {
1411		both, top, bottom
1412	}
1413}
1414