• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.badlogic.gdx.graphics.g2d;
18 
19 import java.io.BufferedReader;
20 import java.io.IOException;
21 import java.io.Writer;
22 
23 import com.badlogic.gdx.graphics.GL20;
24 import com.badlogic.gdx.graphics.Texture;
25 import com.badlogic.gdx.math.MathUtils;
26 import com.badlogic.gdx.math.Rectangle;
27 import com.badlogic.gdx.math.collision.BoundingBox;
28 
29 public 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 
ParticleEmitter()88 	public ParticleEmitter () {
89 		initialize();
90 	}
91 
ParticleEmitter(BufferedReader reader)92 	public ParticleEmitter (BufferedReader reader) throws IOException {
93 		initialize();
94 		load(reader);
95 	}
96 
ParticleEmitter(ParticleEmitter emitter)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 
initialize()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 
setMaxParticleCount(int maxParticleCount)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 
addParticle()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 
addParticles(int count)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 
update(float delta)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 
draw(Batch batch)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. */
draw(Batch batch, float delta)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 
start()324 	public void start () {
325 		firstUpdate = true;
326 		allowCompletion = false;
327 		restart();
328 	}
329 
reset()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 
restart()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 
newParticle(Sprite sprite)377 	protected Particle newParticle (Sprite sprite) {
378 		return new Particle(sprite);
379 	}
380 
activateParticle(int index)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 
updateParticle(Particle particle, float delta, int deltaMillis)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 
setPosition(float x, float y)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 
setSprite(Sprite sprite)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. */
allowCompletion()617 	public void allowCompletion () {
618 		allowCompletion = true;
619 		durationTimer = duration;
620 	}
621 
getSprite()622 	public Sprite getSprite () {
623 		return sprite;
624 	}
625 
getName()626 	public String getName () {
627 		return name;
628 	}
629 
setName(String name)630 	public void setName (String name) {
631 		this.name = name;
632 	}
633 
getLife()634 	public ScaledNumericValue getLife () {
635 		return lifeValue;
636 	}
637 
getScale()638 	public ScaledNumericValue getScale () {
639 		return scaleValue;
640 	}
641 
getRotation()642 	public ScaledNumericValue getRotation () {
643 		return rotationValue;
644 	}
645 
getTint()646 	public GradientColorValue getTint () {
647 		return tintValue;
648 	}
649 
getVelocity()650 	public ScaledNumericValue getVelocity () {
651 		return velocityValue;
652 	}
653 
getWind()654 	public ScaledNumericValue getWind () {
655 		return windValue;
656 	}
657 
getGravity()658 	public ScaledNumericValue getGravity () {
659 		return gravityValue;
660 	}
661 
getAngle()662 	public ScaledNumericValue getAngle () {
663 		return angleValue;
664 	}
665 
getEmission()666 	public ScaledNumericValue getEmission () {
667 		return emissionValue;
668 	}
669 
getTransparency()670 	public ScaledNumericValue getTransparency () {
671 		return transparencyValue;
672 	}
673 
getDuration()674 	public RangedNumericValue getDuration () {
675 		return durationValue;
676 	}
677 
getDelay()678 	public RangedNumericValue getDelay () {
679 		return delayValue;
680 	}
681 
getLifeOffset()682 	public ScaledNumericValue getLifeOffset () {
683 		return lifeOffsetValue;
684 	}
685 
getXOffsetValue()686 	public RangedNumericValue getXOffsetValue () {
687 		return xOffsetValue;
688 	}
689 
getYOffsetValue()690 	public RangedNumericValue getYOffsetValue () {
691 		return yOffsetValue;
692 	}
693 
getSpawnWidth()694 	public ScaledNumericValue getSpawnWidth () {
695 		return spawnWidthValue;
696 	}
697 
getSpawnHeight()698 	public ScaledNumericValue getSpawnHeight () {
699 		return spawnHeightValue;
700 	}
701 
getSpawnShape()702 	public SpawnShapeValue getSpawnShape () {
703 		return spawnShapeValue;
704 	}
705 
isAttached()706 	public boolean isAttached () {
707 		return attached;
708 	}
709 
setAttached(boolean attached)710 	public void setAttached (boolean attached) {
711 		this.attached = attached;
712 	}
713 
isContinuous()714 	public boolean isContinuous () {
715 		return continuous;
716 	}
717 
setContinuous(boolean continuous)718 	public void setContinuous (boolean continuous) {
719 		this.continuous = continuous;
720 	}
721 
isAligned()722 	public boolean isAligned () {
723 		return aligned;
724 	}
725 
setAligned(boolean aligned)726 	public void setAligned (boolean aligned) {
727 		this.aligned = aligned;
728 	}
729 
isAdditive()730 	public boolean isAdditive () {
731 		return additive;
732 	}
733 
setAdditive(boolean additive)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. */
cleansUpBlendFunction()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 */
setCleansUpBlendFunction(boolean cleansUpBlendFunction)752 	public void setCleansUpBlendFunction (boolean cleansUpBlendFunction) {
753 		this.cleansUpBlendFunction = cleansUpBlendFunction;
754 	}
755 
isBehind()756 	public boolean isBehind () {
757 		return behind;
758 	}
759 
setBehind(boolean behind)760 	public void setBehind (boolean behind) {
761 		this.behind = behind;
762 	}
763 
isPremultipliedAlpha()764 	public boolean isPremultipliedAlpha () {
765 		return premultipliedAlpha;
766 	}
767 
setPremultipliedAlpha(boolean premultipliedAlpha)768 	public void setPremultipliedAlpha (boolean premultipliedAlpha) {
769 		this.premultipliedAlpha = premultipliedAlpha;
770 	}
771 
getMinParticleCount()772 	public int getMinParticleCount () {
773 		return minParticleCount;
774 	}
775 
setMinParticleCount(int minParticleCount)776 	public void setMinParticleCount (int minParticleCount) {
777 		this.minParticleCount = minParticleCount;
778 	}
779 
getMaxParticleCount()780 	public int getMaxParticleCount () {
781 		return maxParticleCount;
782 	}
783 
isComplete()784 	public boolean isComplete () {
785 		if (continuous) return false;
786 		if (delayTimer < delay) return false;
787 		return durationTimer >= duration && activeCount == 0;
788 	}
789 
getPercentComplete()790 	public float getPercentComplete () {
791 		if (delayTimer < delay) return 0;
792 		return Math.min(1, durationTimer / (float)duration);
793 	}
794 
getX()795 	public float getX () {
796 		return x;
797 	}
798 
getY()799 	public float getY () {
800 		return y;
801 	}
802 
getActiveCount()803 	public int getActiveCount () {
804 		return activeCount;
805 	}
806 
getImagePath()807 	public String getImagePath () {
808 		return imagePath;
809 	}
810 
setImagePath(String imagePath)811 	public void setImagePath (String imagePath) {
812 		this.imagePath = imagePath;
813 	}
814 
setFlip(boolean flipX, boolean flipY)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 
flipY()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. */
getBoundingBox()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 
save(Writer output)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 
load(BufferedReader reader)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 
readString(String line)974 	static String readString (String line) throws IOException {
975 		return line.substring(line.indexOf(":") + 1).trim();
976 	}
977 
readString(BufferedReader reader, String name)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 
readBoolean(String line)984 	static boolean readBoolean (String line) throws IOException {
985 		return Boolean.parseBoolean(readString(line));
986 	}
987 
readBoolean(BufferedReader reader, String name)988 	static boolean readBoolean (BufferedReader reader, String name) throws IOException {
989 		return Boolean.parseBoolean(readString(reader, name));
990 	}
991 
readInt(BufferedReader reader, String name)992 	static int readInt (BufferedReader reader, String name) throws IOException {
993 		return Integer.parseInt(readString(reader, name));
994 	}
995 
readFloat(BufferedReader reader, String name)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 
Particle(Sprite sprite)1012 		public Particle (Sprite sprite) {
1013 			super(sprite);
1014 		}
1015 	}
1016 
1017 	static public class ParticleValue {
1018 		boolean active;
1019 		boolean alwaysActive;
1020 
setAlwaysActive(boolean alwaysActive)1021 		public void setAlwaysActive (boolean alwaysActive) {
1022 			this.alwaysActive = alwaysActive;
1023 		}
1024 
isAlwaysActive()1025 		public boolean isAlwaysActive () {
1026 			return alwaysActive;
1027 		}
1028 
isActive()1029 		public boolean isActive () {
1030 			return alwaysActive || active;
1031 		}
1032 
setActive(boolean active)1033 		public void setActive (boolean active) {
1034 			this.active = active;
1035 		}
1036 
save(Writer output)1037 		public void save (Writer output) throws IOException {
1038 			if (!alwaysActive)
1039 				output.write("active: " + active + "\n");
1040 			else
1041 				active = true;
1042 		}
1043 
load(BufferedReader reader)1044 		public void load (BufferedReader reader) throws IOException {
1045 			if (!alwaysActive)
1046 				active = readBoolean(reader, "active");
1047 			else
1048 				active = true;
1049 		}
1050 
load(ParticleValue value)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 
getValue()1060 		public float getValue () {
1061 			return value;
1062 		}
1063 
setValue(float value)1064 		public void setValue (float value) {
1065 			this.value = value;
1066 		}
1067 
save(Writer output)1068 		public void save (Writer output) throws IOException {
1069 			super.save(output);
1070 			if (!active) return;
1071 			output.write("value: " + value + "\n");
1072 		}
1073 
load(BufferedReader reader)1074 		public void load (BufferedReader reader) throws IOException {
1075 			super.load(reader);
1076 			if (!active) return;
1077 			value = readFloat(reader, "value");
1078 		}
1079 
load(NumericValue value)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 
newLowValue()1089 		public float newLowValue () {
1090 			return lowMin + (lowMax - lowMin) * MathUtils.random();
1091 		}
1092 
setLow(float value)1093 		public void setLow (float value) {
1094 			lowMin = value;
1095 			lowMax = value;
1096 		}
1097 
setLow(float min, float max)1098 		public void setLow (float min, float max) {
1099 			lowMin = min;
1100 			lowMax = max;
1101 		}
1102 
getLowMin()1103 		public float getLowMin () {
1104 			return lowMin;
1105 		}
1106 
setLowMin(float lowMin)1107 		public void setLowMin (float lowMin) {
1108 			this.lowMin = lowMin;
1109 		}
1110 
getLowMax()1111 		public float getLowMax () {
1112 			return lowMax;
1113 		}
1114 
setLowMax(float lowMax)1115 		public void setLowMax (float lowMax) {
1116 			this.lowMax = lowMax;
1117 		}
1118 
save(Writer output)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 
load(BufferedReader reader)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 
load(RangedNumericValue value)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 
newHighValue()1146 		public float newHighValue () {
1147 			return highMin + (highMax - highMin) * MathUtils.random();
1148 		}
1149 
setHigh(float value)1150 		public void setHigh (float value) {
1151 			highMin = value;
1152 			highMax = value;
1153 		}
1154 
setHigh(float min, float max)1155 		public void setHigh (float min, float max) {
1156 			highMin = min;
1157 			highMax = max;
1158 		}
1159 
getHighMin()1160 		public float getHighMin () {
1161 			return highMin;
1162 		}
1163 
setHighMin(float highMin)1164 		public void setHighMin (float highMin) {
1165 			this.highMin = highMin;
1166 		}
1167 
getHighMax()1168 		public float getHighMax () {
1169 			return highMax;
1170 		}
1171 
setHighMax(float highMax)1172 		public void setHighMax (float highMax) {
1173 			this.highMax = highMax;
1174 		}
1175 
getScaling()1176 		public float[] getScaling () {
1177 			return scaling;
1178 		}
1179 
setScaling(float[] values)1180 		public void setScaling (float[] values) {
1181 			this.scaling = values;
1182 		}
1183 
getTimeline()1184 		public float[] getTimeline () {
1185 			return timeline;
1186 		}
1187 
setTimeline(float[] timeline)1188 		public void setTimeline (float[] timeline) {
1189 			this.timeline = timeline;
1190 		}
1191 
isRelative()1192 		public boolean isRelative () {
1193 			return relative;
1194 		}
1195 
setRelative(boolean relative)1196 		public void setRelative (boolean relative) {
1197 			this.relative = relative;
1198 		}
1199 
getScale(float percent)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 
save(Writer output)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 
load(BufferedReader reader)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 
load(ScaledNumericValue value)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 
GradientColorValue()1265 		public GradientColorValue () {
1266 			alwaysActive = true;
1267 		}
1268 
getTimeline()1269 		public float[] getTimeline () {
1270 			return timeline;
1271 		}
1272 
setTimeline(float[] timeline)1273 		public void setTimeline (float[] timeline) {
1274 			this.timeline = timeline;
1275 		}
1276 
1277 		/** @return the r, g and b values for every timeline position */
getColors()1278 		public float[] getColors () {
1279 			return colors;
1280 		}
1281 
1282 		/** @param colors the r, g and b values for every timeline position */
setColors(float[] colors)1283 		public void setColors (float[] colors) {
1284 			this.colors = colors;
1285 		}
1286 
getColor(float percent)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 
save(Writer output)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 
load(BufferedReader reader)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 
load(GradientColorValue value)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 
getShape()1354 		public SpawnShape getShape () {
1355 			return shape;
1356 		}
1357 
setShape(SpawnShape shape)1358 		public void setShape (SpawnShape shape) {
1359 			this.shape = shape;
1360 		}
1361 
isEdges()1362 		public boolean isEdges () {
1363 			return edges;
1364 		}
1365 
setEdges(boolean edges)1366 		public void setEdges (boolean edges) {
1367 			this.edges = edges;
1368 		}
1369 
getSide()1370 		public SpawnEllipseSide getSide () {
1371 			return side;
1372 		}
1373 
setSide(SpawnEllipseSide side)1374 		public void setSide (SpawnEllipseSide side) {
1375 			this.side = side;
1376 		}
1377 
save(Writer output)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 
load(BufferedReader reader)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 
load(SpawnShapeValue value)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