OSDN Git Service

Revert "- implemented Cloneable instead of own methods - but particle value classes...
authorDaniel Heinrich <dannynullzwo@gmail.com>
Sun, 16 Sep 2012 05:11:51 +0000 (13:11 +0800)
committerDaniel Heinrich <dannynullzwo@gmail.com>
Sun, 16 Sep 2012 05:11:51 +0000 (13:11 +0800)
This reverts commit cc58ace942fb15077cd83b8b9d8e3f1959347f43.

12 files changed:
gdx/src/com/badlogic/gdx/graphics/g2d/ParticleEffect.java [new file with mode: 0644]
gdx/src/com/badlogic/gdx/graphics/g2d/ParticleEffectPool.java [new file with mode: 0644]
gdx/src/com/badlogic/gdx/graphics/g2d/ParticleEmitter.java [new file with mode: 0644]
gdx/src/com/badlogic/gdx/graphics/g2d/ParticleEmitterBox2D.java [new file with mode: 0644]
gdx/src/com/badlogic/gdx/graphics/g2d/particle/Box2DParticleFactory.java
gdx/src/com/badlogic/gdx/graphics/g2d/particle/ParticleEmitter.java
gdx/src/com/badlogic/gdx/graphics/g2d/particle/emitterattributs/GradientColorValue.java [deleted file]
gdx/src/com/badlogic/gdx/graphics/g2d/particle/emitterattributs/NumericValue.java [deleted file]
gdx/src/com/badlogic/gdx/graphics/g2d/particle/emitterattributs/ParticleValue.java [deleted file]
gdx/src/com/badlogic/gdx/graphics/g2d/particle/emitterattributs/RangedNumericValue.java [deleted file]
gdx/src/com/badlogic/gdx/graphics/g2d/particle/emitterattributs/ScaledNumericValue.java [deleted file]
gdx/src/com/badlogic/gdx/graphics/g2d/particle/emitterattributs/SpawnShapeValue.java [deleted file]

diff --git a/gdx/src/com/badlogic/gdx/graphics/g2d/ParticleEffect.java b/gdx/src/com/badlogic/gdx/graphics/g2d/ParticleEffect.java
new file mode 100644 (file)
index 0000000..e92e584
--- /dev/null
@@ -0,0 +1,205 @@
+/*******************************************************************************\r
+ * Copyright 2011 See AUTHORS file.\r
+ * \r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ * \r
+ *   http://www.apache.org/licenses/LICENSE-2.0\r
+ * \r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ ******************************************************************************/\r
+\r
+package com.badlogic.gdx.graphics.g2d;\r
+\r
+import java.io.BufferedReader;\r
+import java.io.File;\r
+import java.io.FileWriter;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.io.InputStreamReader;\r
+import java.io.Writer;\r
+\r
+import com.badlogic.gdx.files.FileHandle;\r
+import com.badlogic.gdx.graphics.Texture;\r
+import com.badlogic.gdx.utils.Array;\r
+import com.badlogic.gdx.utils.Disposable;\r
+import com.badlogic.gdx.utils.GdxRuntimeException;\r
+\r
+/** See <a href="http://www.badlogicgames.com/wordpress/?p=1255">http://www.badlogicgames.com/wordpress/?p=1255</a>\r
+ * @author mzechner */\r
+public class ParticleEffect implements Disposable {\r
+       private final Array<ParticleEmitter> emitters;\r
+\r
+       public ParticleEffect () {\r
+               emitters = new Array(8);\r
+       }\r
+\r
+       public ParticleEffect (ParticleEffect effect) {\r
+               emitters = new Array(true, effect.emitters.size);\r
+               for (int i = 0, n = effect.emitters.size; i < n; i++)\r
+                       emitters.add(new ParticleEmitter(effect.emitters.get(i)));\r
+       }\r
+\r
+       public void start () {\r
+               for (int i = 0, n = emitters.size; i < n; i++)\r
+                       emitters.get(i).start();\r
+       }\r
+\r
+       public void update (float delta) {\r
+               for (int i = 0, n = emitters.size; i < n; i++)\r
+                       emitters.get(i).update(delta);\r
+       }\r
+\r
+       public void draw (SpriteBatch spriteBatch) {\r
+               for (int i = 0, n = emitters.size; i < n; i++)\r
+                       emitters.get(i).draw(spriteBatch);\r
+       }\r
+\r
+       public void draw (SpriteBatch spriteBatch, float delta) {\r
+               for (int i = 0, n = emitters.size; i < n; i++)\r
+                       emitters.get(i).draw(spriteBatch, delta);\r
+       }\r
+\r
+       public void allowCompletion () {\r
+               for (int i = 0, n = emitters.size; i < n; i++)\r
+                       emitters.get(i).allowCompletion();\r
+       }\r
+\r
+       public boolean isComplete () {\r
+               for (int i = 0, n = emitters.size; i < n; i++) {\r
+                       ParticleEmitter emitter = emitters.get(i);\r
+                       if (emitter.isContinuous()) return false;\r
+                       if (!emitter.isComplete()) return false;\r
+               }\r
+               return true;\r
+       }\r
+\r
+       public void setDuration (int duration) {\r
+               for (int i = 0, n = emitters.size; i < n; i++) {\r
+                       ParticleEmitter emitter = emitters.get(i);\r
+                       emitter.setContinuous(false);\r
+                       emitter.duration = duration;\r
+                       emitter.durationTimer = 0;\r
+               }\r
+       }\r
+\r
+       public void setPosition (float x, float y) {\r
+               for (int i = 0, n = emitters.size; i < n; i++)\r
+                       emitters.get(i).setPosition(x, y);\r
+       }\r
+\r
+       public void setFlip (boolean flipX, boolean flipY) {\r
+               for (int i = 0, n = emitters.size; i < n; i++)\r
+                       emitters.get(i).setFlip(flipX, flipY);\r
+       }\r
+\r
+       public Array<ParticleEmitter> getEmitters () {\r
+               return emitters;\r
+       }\r
+\r
+       /** Returns the emitter with the specified name, or null. */\r
+       public ParticleEmitter findEmitter (String name) {\r
+               for (int i = 0, n = emitters.size; i < n; i++) {\r
+                       ParticleEmitter emitter = emitters.get(i);\r
+                       if (emitter.getName().equals(name)) return emitter;\r
+               }\r
+               return null;\r
+       }\r
+\r
+       public void save (File file) {\r
+               Writer output = null;\r
+               try {\r
+                       output = new FileWriter(file);\r
+                       int index = 0;\r
+                       for (int i = 0, n = emitters.size; i < n; i++) {\r
+                               ParticleEmitter emitter = emitters.get(i);\r
+                               if (index++ > 0) output.write("\n\n");\r
+                               emitter.save(output);\r
+                               output.write("- Image Path -\n");\r
+                               output.write(emitter.getImagePath() + "\n");\r
+                       }\r
+               } catch (IOException ex) {\r
+                       throw new GdxRuntimeException("Error saving effect: " + file, ex);\r
+               } finally {\r
+                       try {\r
+                               if (output != null) output.close();\r
+                       } catch (IOException ex) {\r
+                       }\r
+               }\r
+       }\r
+\r
+       public void load (FileHandle effectFile, FileHandle imagesDir) {\r
+               loadEmitters(effectFile);\r
+               loadEmitterImages(imagesDir);\r
+       }\r
+\r
+       public void load (FileHandle effectFile, TextureAtlas atlas) {\r
+               loadEmitters(effectFile);\r
+               loadEmitterImages(atlas);\r
+       }\r
+\r
+       public void loadEmitters (FileHandle effectFile) {\r
+               InputStream input = effectFile.read();\r
+               emitters.clear();\r
+               BufferedReader reader = null;\r
+               try {\r
+                       reader = new BufferedReader(new InputStreamReader(input), 512);\r
+                       while (true) {\r
+                               ParticleEmitter emitter = new ParticleEmitter(reader);\r
+                               reader.readLine();\r
+                               emitter.setImagePath(reader.readLine());\r
+                               emitters.add(emitter);\r
+                               if (reader.readLine() == null) break;\r
+                               if (reader.readLine() == null) break;\r
+                       }\r
+               } catch (IOException ex) {\r
+                       throw new GdxRuntimeException("Error loading effect: " + effectFile, ex);\r
+               } finally {\r
+                       try {\r
+                               if (reader != null) reader.close();\r
+                       } catch (IOException ex) {\r
+                       }\r
+               }\r
+       }\r
+\r
+       public void loadEmitterImages (TextureAtlas atlas) {\r
+               for (int i = 0, n = emitters.size; i < n; i++) {\r
+                       ParticleEmitter emitter = emitters.get(i);\r
+                       String imagePath = emitter.getImagePath();\r
+                       if (imagePath == null) continue;\r
+                       String imageName = new File(imagePath.replace('\\', '/')).getName();\r
+                       int lastDotIndex = imageName.lastIndexOf('.');\r
+                       if (lastDotIndex != -1) imageName = imageName.substring(0, lastDotIndex);\r
+                       Sprite sprite = atlas.createSprite(imageName);\r
+                       if (sprite == null) throw new IllegalArgumentException("SpriteSheet missing image: " + imageName);\r
+                       emitter.setSprite(sprite);\r
+               }\r
+       }\r
+\r
+       public void loadEmitterImages (FileHandle imagesDir) {\r
+               for (int i = 0, n = emitters.size; i < n; i++) {\r
+                       ParticleEmitter emitter = emitters.get(i);\r
+                       String imagePath = emitter.getImagePath();\r
+                       if (imagePath == null) continue;\r
+                       String imageName = new File(imagePath.replace('\\', '/')).getName();\r
+                       emitter.setSprite(new Sprite(loadTexture(imagesDir.child(imageName))));\r
+               }\r
+       }\r
+\r
+       protected Texture loadTexture (FileHandle file) {\r
+               return new Texture(file, false);\r
+       }\r
+\r
+       /** Disposes the texture for each sprite for each ParticleEmitter. */\r
+       public void dispose () {\r
+               for (int i = 0, n = emitters.size; i < n; i++) {\r
+                       ParticleEmitter emitter = emitters.get(i);\r
+                       emitter.getSprite().getTexture().dispose();\r
+               }\r
+       }\r
+}\r
diff --git a/gdx/src/com/badlogic/gdx/graphics/g2d/ParticleEffectPool.java b/gdx/src/com/badlogic/gdx/graphics/g2d/ParticleEffectPool.java
new file mode 100644 (file)
index 0000000..15650b9
--- /dev/null
@@ -0,0 +1,49 @@
+/*******************************************************************************\r
+ * Copyright 2011 See AUTHORS file.\r
+ * \r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ * \r
+ *   http://www.apache.org/licenses/LICENSE-2.0\r
+ * \r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ ******************************************************************************/\r
+\r
+package com.badlogic.gdx.graphics.g2d;\r
+\r
+import com.badlogic.gdx.graphics.g2d.ParticleEffectPool.PooledEffect;\r
+import com.badlogic.gdx.utils.Pool;\r
+\r
+public class ParticleEffectPool extends Pool<PooledEffect> {\r
+       private final ParticleEffect effect;\r
+\r
+       public ParticleEffectPool (ParticleEffect effect, int initialCapacity, int max) {\r
+               super(initialCapacity, max);\r
+               this.effect = effect;\r
+       }\r
+\r
+       protected PooledEffect newObject () {\r
+               return new PooledEffect(effect);\r
+       }\r
+\r
+       public PooledEffect obtain () {\r
+               PooledEffect effect = super.obtain();\r
+               effect.start();\r
+               return effect;\r
+       }\r
+\r
+       public class PooledEffect extends ParticleEffect {\r
+               PooledEffect (ParticleEffect effect) {\r
+                       super(effect);\r
+               }\r
+\r
+               public void free () {\r
+                       ParticleEffectPool.this.free(this);\r
+               }\r
+       }\r
+}\r
diff --git a/gdx/src/com/badlogic/gdx/graphics/g2d/ParticleEmitter.java b/gdx/src/com/badlogic/gdx/graphics/g2d/ParticleEmitter.java
new file mode 100644 (file)
index 0000000..28825e1
--- /dev/null
@@ -0,0 +1,1297 @@
+/*******************************************************************************\r
+ * Copyright 2011 See AUTHORS file.\r
+ * \r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ * \r
+ *   http://www.apache.org/licenses/LICENSE-2.0\r
+ * \r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ ******************************************************************************/\r
+\r
+package com.badlogic.gdx.graphics.g2d;\r
+\r
+import java.io.BufferedReader;\r
+import java.io.IOException;\r
+import java.io.Writer;\r
+\r
+import com.badlogic.gdx.graphics.GL10;\r
+import com.badlogic.gdx.graphics.Texture;\r
+import com.badlogic.gdx.math.MathUtils;\r
+\r
+// BOZO - Javadoc.\r
+// BOZO - Add a duplicate emitter button.\r
+\r
+public class ParticleEmitter {\r
+       static private final int UPDATE_SCALE = 1 << 0;\r
+       static private final int UPDATE_ANGLE = 1 << 1;\r
+       static private final int UPDATE_ROTATION = 1 << 2;\r
+       static private final int UPDATE_VELOCITY = 1 << 3;\r
+       static private final int UPDATE_WIND = 1 << 4;\r
+       static private final int UPDATE_GRAVITY = 1 << 5;\r
+       static private final int UPDATE_TINT = 1 << 6;\r
+\r
+       private RangedNumericValue delayValue = new RangedNumericValue();\r
+       private ScaledNumericValue lifeOffsetValue = new ScaledNumericValue();\r
+       private RangedNumericValue durationValue = new RangedNumericValue();\r
+       private ScaledNumericValue lifeValue = new ScaledNumericValue();\r
+       private ScaledNumericValue emissionValue = new ScaledNumericValue();\r
+       private ScaledNumericValue scaleValue = new ScaledNumericValue();\r
+       private ScaledNumericValue rotationValue = new ScaledNumericValue();\r
+       private ScaledNumericValue velocityValue = new ScaledNumericValue();\r
+       private ScaledNumericValue angleValue = new ScaledNumericValue();\r
+       private ScaledNumericValue windValue = new ScaledNumericValue();\r
+       private ScaledNumericValue gravityValue = new ScaledNumericValue();\r
+       private ScaledNumericValue transparencyValue = new ScaledNumericValue();\r
+       private GradientColorValue tintValue = new GradientColorValue();\r
+       private RangedNumericValue xOffsetValue = new ScaledNumericValue();\r
+       private RangedNumericValue yOffsetValue = new ScaledNumericValue();\r
+       private ScaledNumericValue spawnWidthValue = new ScaledNumericValue();\r
+       private ScaledNumericValue spawnHeightValue = new ScaledNumericValue();\r
+       private SpawnShapeValue spawnShapeValue = new SpawnShapeValue();\r
+\r
+       private float accumulator;\r
+       private Sprite sprite;\r
+       private Particle[] particles;\r
+       private int minParticleCount, maxParticleCount = 4;\r
+       private float x, y;\r
+       private String name;\r
+       private String imagePath;\r
+       private int activeCount;\r
+       private boolean[] active;\r
+       private boolean firstUpdate;\r
+       private boolean flipX, flipY;\r
+       private int updateFlags;\r
+       private boolean allowCompletion;\r
+\r
+       private int emission, emissionDiff, emissionDelta;\r
+       private int lifeOffset, lifeOffsetDiff;\r
+       private int life, lifeDiff;\r
+       private float spawnWidth, spawnWidthDiff;\r
+       private float spawnHeight, spawnHeightDiff;\r
+       public float duration = 1, durationTimer;\r
+       private float delay, delayTimer;\r
+\r
+       private boolean attached;\r
+       private boolean continuous;\r
+       private boolean aligned;\r
+       private boolean behind;\r
+       private boolean additive = true;\r
+\r
+       public ParticleEmitter () {\r
+               initialize();\r
+       }\r
+\r
+       public ParticleEmitter (BufferedReader reader) throws IOException {\r
+               initialize();\r
+               load(reader);\r
+       }\r
+\r
+       public ParticleEmitter (ParticleEmitter emitter) {\r
+               sprite = emitter.sprite;\r
+               name = emitter.name;\r
+               setMaxParticleCount(emitter.maxParticleCount);\r
+               minParticleCount = emitter.minParticleCount;\r
+               delayValue.load(emitter.delayValue);\r
+               durationValue.load(emitter.durationValue);\r
+               emissionValue.load(emitter.emissionValue);\r
+               lifeValue.load(emitter.lifeValue);\r
+               lifeOffsetValue.load(emitter.lifeOffsetValue);\r
+               scaleValue.load(emitter.scaleValue);\r
+               rotationValue.load(emitter.rotationValue);\r
+               velocityValue.load(emitter.velocityValue);\r
+               angleValue.load(emitter.angleValue);\r
+               windValue.load(emitter.windValue);\r
+               gravityValue.load(emitter.gravityValue);\r
+               transparencyValue.load(emitter.transparencyValue);\r
+               tintValue.load(emitter.tintValue);\r
+               xOffsetValue.load(emitter.xOffsetValue);\r
+               yOffsetValue.load(emitter.yOffsetValue);\r
+               spawnWidthValue.load(emitter.spawnWidthValue);\r
+               spawnHeightValue.load(emitter.spawnHeightValue);\r
+               spawnShapeValue.load(emitter.spawnShapeValue);\r
+               attached = emitter.attached;\r
+               continuous = emitter.continuous;\r
+               aligned = emitter.aligned;\r
+               behind = emitter.behind;\r
+               additive = emitter.additive;\r
+       }\r
+\r
+       private void initialize () {\r
+               durationValue.setAlwaysActive(true);\r
+               emissionValue.setAlwaysActive(true);\r
+               lifeValue.setAlwaysActive(true);\r
+               scaleValue.setAlwaysActive(true);\r
+               transparencyValue.setAlwaysActive(true);\r
+               spawnShapeValue.setAlwaysActive(true);\r
+               spawnWidthValue.setAlwaysActive(true);\r
+               spawnHeightValue.setAlwaysActive(true);\r
+       }\r
+\r
+       public void setMaxParticleCount (int maxParticleCount) {\r
+               this.maxParticleCount = maxParticleCount;\r
+               active = new boolean[maxParticleCount];\r
+               activeCount = 0;\r
+               particles = new Particle[maxParticleCount];\r
+       }\r
+\r
+       public void addParticle () {\r
+               int activeCount = this.activeCount;\r
+               if (activeCount == maxParticleCount) return;\r
+               boolean[] active = this.active;\r
+               for (int i = 0, n = active.length; i < n; i++) {\r
+                       if (!active[i]) {\r
+                               activateParticle(i);\r
+                               active[i] = true;\r
+                               this.activeCount = activeCount + 1;\r
+                               break;\r
+                       }\r
+               }\r
+       }\r
+\r
+       public void addParticles (int count) {\r
+               count = Math.min(count, maxParticleCount - activeCount);\r
+               if (count == 0) return;\r
+               boolean[] active = this.active;\r
+               int index = 0, n = active.length;\r
+               outer:\r
+               for (int i = 0; i < count; i++) {\r
+                       for (; index < n; index++) {\r
+                               if (!active[index]) {\r
+                                       activateParticle(index);\r
+                                       active[index++] = true;\r
+                                       continue outer;\r
+                               }\r
+                       }\r
+                       break;\r
+               }\r
+               this.activeCount += count;\r
+       }\r
+\r
+       public void update (float delta) {\r
+               accumulator += Math.min(delta * 1000, 250);\r
+               if (accumulator < 1) return;\r
+               int deltaMillis = (int)accumulator;\r
+               accumulator -= deltaMillis;\r
+\r
+               boolean[] active = this.active;\r
+               int activeCount = this.activeCount;\r
+               for (int i = 0, n = active.length; i < n; i++) {\r
+                       if (active[i] && !updateParticle(particles[i], delta, deltaMillis)) {\r
+                               active[i] = false;\r
+                               activeCount--;\r
+                       }\r
+               }\r
+               this.activeCount = activeCount;\r
+\r
+               if (delayTimer < delay) {\r
+                       delayTimer += deltaMillis;\r
+                       return;\r
+               }\r
+\r
+               if (firstUpdate) {\r
+                       firstUpdate = false;\r
+                       addParticle();\r
+               }\r
+\r
+               if (durationTimer < duration)\r
+                       durationTimer += deltaMillis;\r
+               else {\r
+                       if (!continuous || allowCompletion) return;\r
+                       restart();\r
+               }\r
+\r
+               emissionDelta += deltaMillis;\r
+               float emissionTime = emission + emissionDiff * emissionValue.getScale(durationTimer / (float)duration);\r
+               if (emissionTime > 0) {\r
+                       emissionTime = 1000 / emissionTime;\r
+                       if (emissionDelta >= emissionTime) {\r
+                               int emitCount = (int)(emissionDelta / emissionTime);\r
+                               emitCount = Math.min(emitCount, maxParticleCount - activeCount);\r
+                               emissionDelta -= emitCount * emissionTime;\r
+                               emissionDelta %= emissionTime;\r
+                               addParticles(emitCount);\r
+                       }\r
+               }\r
+               if (activeCount < minParticleCount) addParticles(minParticleCount - activeCount);\r
+       }\r
+\r
+       public void draw (SpriteBatch spriteBatch) {\r
+               if (additive) spriteBatch.setBlendFunction(GL10.GL_SRC_ALPHA, GL10.GL_ONE);\r
+\r
+               Particle[] particles = this.particles;\r
+               boolean[] active = this.active;\r
+               int activeCount = this.activeCount;\r
+\r
+               for (int i = 0, n = active.length; i < n; i++)\r
+                       if (active[i]) particles[i].draw(spriteBatch);\r
+               this.activeCount = activeCount;\r
+\r
+               if (additive) spriteBatch.setBlendFunction(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);\r
+       }\r
+\r
+       /** Updates and draws the particles. This is slightly more efficient than calling {@link #update(float)} and\r
+        * {@link #draw(SpriteBatch)} separately. */\r
+       public void draw (SpriteBatch spriteBatch, float delta) {\r
+               accumulator += Math.min(delta * 1000, 250);\r
+               if (accumulator < 1) {\r
+                       draw(spriteBatch);\r
+                       return;\r
+               }\r
+               int deltaMillis = (int)accumulator;\r
+               accumulator -= deltaMillis;\r
+\r
+               if (additive) spriteBatch.setBlendFunction(GL10.GL_SRC_ALPHA, GL10.GL_ONE);\r
+\r
+               Particle[] particles = this.particles;\r
+               boolean[] active = this.active;\r
+               int activeCount = this.activeCount;\r
+               for (int i = 0, n = active.length; i < n; i++) {\r
+                       if (active[i]) {\r
+                               Particle particle = particles[i];\r
+                               if (updateParticle(particle, delta, deltaMillis))\r
+                                       particle.draw(spriteBatch);\r
+                               else {\r
+                                       active[i] = false;\r
+                                       activeCount--;\r
+                               }\r
+                       }\r
+               }\r
+               this.activeCount = activeCount;\r
+\r
+               if (additive) spriteBatch.setBlendFunction(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);\r
+\r
+               if (delayTimer < delay) {\r
+                       delayTimer += deltaMillis;\r
+                       return;\r
+               }\r
+\r
+               if (firstUpdate) {\r
+                       firstUpdate = false;\r
+                       addParticle();\r
+               }\r
+\r
+               if (durationTimer < duration)\r
+                       durationTimer += deltaMillis;\r
+               else {\r
+                       if (!continuous || allowCompletion) return;\r
+                       restart();\r
+               }\r
+\r
+               emissionDelta += deltaMillis;\r
+               float emissionTime = emission + emissionDiff * emissionValue.getScale(durationTimer / (float)duration);\r
+               if (emissionTime > 0) {\r
+                       emissionTime = 1000 / emissionTime;\r
+                       if (emissionDelta >= emissionTime) {\r
+                               int emitCount = (int)(emissionDelta / emissionTime);\r
+                               emitCount = Math.min(emitCount, maxParticleCount - activeCount);\r
+                               emissionDelta -= emitCount * emissionTime;\r
+                               emissionDelta %= emissionTime;\r
+                               addParticles(emitCount);\r
+                       }\r
+               }\r
+               if (activeCount < minParticleCount) addParticles(minParticleCount - activeCount);\r
+       }\r
+\r
+       public void start () {\r
+               firstUpdate = true;\r
+               allowCompletion = false;\r
+               restart();\r
+       }\r
+\r
+       public void reset () {\r
+               emissionDelta = 0;\r
+               durationTimer = 0;\r
+               start();\r
+       }\r
+\r
+       private void restart () {\r
+               delay = delayValue.active ? delayValue.newLowValue() : 0;\r
+               delayTimer = 0;\r
+\r
+               durationTimer -= duration;\r
+               duration = durationValue.newLowValue();\r
+\r
+               emission = (int)emissionValue.newLowValue();\r
+               emissionDiff = (int)emissionValue.newHighValue();\r
+               if (!emissionValue.isRelative()) emissionDiff -= emission;\r
+\r
+               life = (int)lifeValue.newLowValue();\r
+               lifeDiff = (int)lifeValue.newHighValue();\r
+               if (!lifeValue.isRelative()) lifeDiff -= life;\r
+\r
+               lifeOffset = lifeOffsetValue.active ? (int)lifeOffsetValue.newLowValue() : 0;\r
+               lifeOffsetDiff = (int)lifeOffsetValue.newHighValue();\r
+               if (!lifeOffsetValue.isRelative()) lifeOffsetDiff -= lifeOffset;\r
+\r
+               spawnWidth = spawnWidthValue.newLowValue();\r
+               spawnWidthDiff = spawnWidthValue.newHighValue();\r
+               if (!spawnWidthValue.isRelative()) spawnWidthDiff -= spawnWidth;\r
+\r
+               spawnHeight = spawnHeightValue.newLowValue();\r
+               spawnHeightDiff = spawnHeightValue.newHighValue();\r
+               if (!spawnHeightValue.isRelative()) spawnHeightDiff -= spawnHeight;\r
+\r
+               updateFlags = 0;\r
+               if (angleValue.active && angleValue.timeline.length > 1) updateFlags |= UPDATE_ANGLE;\r
+               if (velocityValue.active && velocityValue.active) updateFlags |= UPDATE_VELOCITY;\r
+               if (scaleValue.timeline.length > 1) updateFlags |= UPDATE_SCALE;\r
+               if (rotationValue.active && rotationValue.timeline.length > 1) updateFlags |= UPDATE_ROTATION;\r
+               if (windValue.active) updateFlags |= UPDATE_WIND;\r
+               if (gravityValue.active) updateFlags |= UPDATE_GRAVITY;\r
+               if (tintValue.timeline.length > 1) updateFlags |= UPDATE_TINT;\r
+       }\r
+\r
+       protected Particle newParticle (Sprite sprite) {\r
+               return new Particle(sprite);\r
+       }\r
+\r
+       private void activateParticle (int index) {\r
+               Particle particle = particles[index];\r
+               if (particle == null) {\r
+                       particles[index] = particle = newParticle(sprite);\r
+                       particle.flip(flipX, flipY);\r
+               }\r
+\r
+               float percent = durationTimer / (float)duration;\r
+               int updateFlags = this.updateFlags;\r
+\r
+               particle.currentLife = particle.life = life + (int)(lifeDiff * lifeValue.getScale(percent));\r
+\r
+               if (velocityValue.active) {\r
+                       particle.velocity = velocityValue.newLowValue();\r
+                       particle.velocityDiff = velocityValue.newHighValue();\r
+                       if (!velocityValue.isRelative()) particle.velocityDiff -= particle.velocity;\r
+               }\r
+\r
+               particle.angle = angleValue.newLowValue();\r
+               particle.angleDiff = angleValue.newHighValue();\r
+               if (!angleValue.isRelative()) particle.angleDiff -= particle.angle;\r
+               float angle = 0;\r
+               if ((updateFlags & UPDATE_ANGLE) == 0) {\r
+                       angle = particle.angle + particle.angleDiff * angleValue.getScale(0);\r
+                       particle.angle = angle;\r
+                       particle.angleCos = MathUtils.cosDeg(angle);\r
+                       particle.angleSin = MathUtils.sinDeg(angle);\r
+               }\r
+\r
+               float spriteWidth = sprite.getWidth();\r
+               particle.scale = scaleValue.newLowValue() / spriteWidth;\r
+               particle.scaleDiff = scaleValue.newHighValue() / spriteWidth;\r
+               if (!scaleValue.isRelative()) particle.scaleDiff -= particle.scale;\r
+               particle.setScale(particle.scale + particle.scaleDiff * scaleValue.getScale(0));\r
+\r
+               if (rotationValue.active) {\r
+                       particle.rotation = rotationValue.newLowValue();\r
+                       particle.rotationDiff = rotationValue.newHighValue();\r
+                       if (!rotationValue.isRelative()) particle.rotationDiff -= particle.rotation;\r
+                       float rotation = particle.rotation + particle.rotationDiff * rotationValue.getScale(0);\r
+                       if (aligned) rotation += angle;\r
+                       particle.setRotation(rotation);\r
+               }\r
+\r
+               if (windValue.active) {\r
+                       particle.wind = windValue.newLowValue();\r
+                       particle.windDiff = windValue.newHighValue();\r
+                       if (!windValue.isRelative()) particle.windDiff -= particle.wind;\r
+               }\r
+\r
+               if (gravityValue.active) {\r
+                       particle.gravity = gravityValue.newLowValue();\r
+                       particle.gravityDiff = gravityValue.newHighValue();\r
+                       if (!gravityValue.isRelative()) particle.gravityDiff -= particle.gravity;\r
+               }\r
+\r
+               float[] color = particle.tint;\r
+               if (color == null) particle.tint = color = new float[3];\r
+               float[] temp = tintValue.getColor(0);\r
+               color[0] = temp[0];\r
+               color[1] = temp[1];\r
+               color[2] = temp[2];\r
+\r
+               particle.transparency = transparencyValue.newLowValue();\r
+               particle.transparencyDiff = transparencyValue.newHighValue() - particle.transparency;\r
+\r
+               // Spawn.\r
+               float x = this.x;\r
+               if (xOffsetValue.active) x += xOffsetValue.newLowValue();\r
+               float y = this.y;\r
+               if (yOffsetValue.active) y += yOffsetValue.newLowValue();\r
+               switch (spawnShapeValue.shape) {\r
+               case square: {\r
+                       float width = spawnWidth + (spawnWidthDiff * spawnWidthValue.getScale(percent));\r
+                       float height = spawnHeight + (spawnHeightDiff * spawnHeightValue.getScale(percent));\r
+                       x += MathUtils.random(width) - width / 2;\r
+                       y += MathUtils.random(height) - height / 2;\r
+                       break;\r
+               }\r
+               case ellipse: {\r
+                       float width = spawnWidth + (spawnWidthDiff * spawnWidthValue.getScale(percent));\r
+                       float height = spawnHeight + (spawnHeightDiff * spawnHeightValue.getScale(percent));\r
+                       float radiusX = width / 2;\r
+                       float radiusY = height / 2;\r
+                       if (radiusX == 0 || radiusY == 0) break;\r
+                       float scaleY = radiusX / (float)radiusY;\r
+                       if (spawnShapeValue.edges) {\r
+                               float spawnAngle;\r
+                               switch (spawnShapeValue.side) {\r
+                               case top:\r
+                                       spawnAngle = -MathUtils.random(179f);\r
+                                       break;\r
+                               case bottom:\r
+                                       spawnAngle = MathUtils.random(179f);\r
+                                       break;\r
+                               default:\r
+                                       spawnAngle = MathUtils.random(360f);\r
+                                       break;\r
+                               }\r
+                               float cosDeg = MathUtils.cosDeg(spawnAngle);\r
+                               float sinDeg = MathUtils.sinDeg(spawnAngle);\r
+                               x += cosDeg * radiusX;\r
+                               y += sinDeg * radiusX / scaleY;\r
+                               if ((updateFlags & UPDATE_ANGLE) == 0) {\r
+                                       particle.angle = spawnAngle;\r
+                                       particle.angleCos = cosDeg;\r
+                                       particle.angleSin = sinDeg;\r
+                               }\r
+                       } else {\r
+                               float radius2 = radiusX * radiusX;\r
+                               while (true) {\r
+                                       float px = MathUtils.random(width) - radiusX;\r
+                                       float py = MathUtils.random(width) - radiusX;\r
+                                       if (px * px + py * py <= radius2) {\r
+                                               x += px;\r
+                                               y += py / scaleY;\r
+                                               break;\r
+                                       }\r
+                               }\r
+                       }\r
+                       break;\r
+               }\r
+               case line: {\r
+                       float width = spawnWidth + (spawnWidthDiff * spawnWidthValue.getScale(percent));\r
+                       float height = spawnHeight + (spawnHeightDiff * spawnHeightValue.getScale(percent));\r
+                       if (width != 0) {\r
+                               float lineX = width * MathUtils.random();\r
+                               x += lineX;\r
+                               y += lineX * (height / (float)width);\r
+                       } else\r
+                               y += height * MathUtils.random();\r
+                       break;\r
+               }\r
+               }\r
+\r
+               float spriteHeight = sprite.getHeight();\r
+               particle.setBounds(x - spriteWidth / 2, y - spriteHeight / 2, spriteWidth, spriteHeight);\r
+\r
+               int offsetTime = (int)(lifeOffset + lifeOffsetDiff * lifeOffsetValue.getScale(percent));\r
+               if (offsetTime > 0) {\r
+                       if (offsetTime >= particle.currentLife) offsetTime = particle.currentLife - 1;\r
+                       updateParticle(particle, offsetTime / 1000f, offsetTime);\r
+               }\r
+       }\r
+\r
+       private boolean updateParticle (Particle particle, float delta, int deltaMillis) {\r
+               int life = particle.currentLife - deltaMillis;\r
+               if (life <= 0) return false;\r
+               particle.currentLife = life;\r
+\r
+               float percent = 1 - particle.currentLife / (float)particle.life;\r
+               int updateFlags = this.updateFlags;\r
+\r
+               if ((updateFlags & UPDATE_SCALE) != 0)\r
+                       particle.setScale(particle.scale + particle.scaleDiff * scaleValue.getScale(percent));\r
+\r
+               if ((updateFlags & UPDATE_VELOCITY) != 0) {\r
+                       float velocity = (particle.velocity + particle.velocityDiff * velocityValue.getScale(percent)) * delta;\r
+\r
+                       float velocityX, velocityY;\r
+                       if ((updateFlags & UPDATE_ANGLE) != 0) {\r
+                               float angle = particle.angle + particle.angleDiff * angleValue.getScale(percent);\r
+                               velocityX = velocity * MathUtils.cosDeg(angle);\r
+                               velocityY = velocity * MathUtils.sinDeg(angle);\r
+                               if ((updateFlags & UPDATE_ROTATION) != 0) {\r
+                                       float rotation = particle.rotation + particle.rotationDiff * rotationValue.getScale(percent);\r
+                                       if (aligned) rotation += angle;\r
+                                       particle.setRotation(rotation);\r
+                               }\r
+                       } else {\r
+                               velocityX = velocity * particle.angleCos;\r
+                               velocityY = velocity * particle.angleSin;\r
+                               if (aligned || (updateFlags & UPDATE_ROTATION) != 0) {\r
+                                       float rotation = particle.rotation + particle.rotationDiff * rotationValue.getScale(percent);\r
+                                       if (aligned) rotation += particle.angle;\r
+                                       particle.setRotation(rotation);\r
+                               }\r
+                       }\r
+\r
+                       if ((updateFlags & UPDATE_WIND) != 0)\r
+                               velocityX += (particle.wind + particle.windDiff * windValue.getScale(percent)) * delta;\r
+\r
+                       if ((updateFlags & UPDATE_GRAVITY) != 0)\r
+                               velocityY += (particle.gravity + particle.gravityDiff * gravityValue.getScale(percent)) * delta;\r
+\r
+                       particle.translate(velocityX, velocityY);\r
+               } else {\r
+                       if ((updateFlags & UPDATE_ROTATION) != 0)\r
+                               particle.setRotation(particle.rotation + particle.rotationDiff * rotationValue.getScale(percent));\r
+               }\r
+\r
+               float[] color;\r
+               if ((updateFlags & UPDATE_TINT) != 0)\r
+                       color = tintValue.getColor(percent);\r
+               else\r
+                       color = particle.tint;\r
+               particle.setColor(color[0], color[1], color[2],\r
+                       particle.transparency + particle.transparencyDiff * transparencyValue.getScale(percent));\r
+\r
+               return true;\r
+       }\r
+\r
+       public void setPosition (float x, float y) {\r
+               if (attached) {\r
+                       float xAmount = x - this.x;\r
+                       float yAmount = y - this.y;\r
+                       boolean[] active = this.active;\r
+                       for (int i = 0, n = active.length; i < n; i++)\r
+                               if (active[i]) particles[i].translate(xAmount, yAmount);\r
+               }\r
+               this.x = x;\r
+               this.y = y;\r
+       }\r
+\r
+       public void setSprite (Sprite sprite) {\r
+               this.sprite = sprite;\r
+               if (sprite == null) return;\r
+               float originX = sprite.getOriginX();\r
+               float originY = sprite.getOriginY();\r
+               Texture texture = sprite.getTexture();\r
+               for (int i = 0, n = particles.length; i < n; i++) {\r
+                       Particle particle = particles[i];\r
+                       if (particle == null) break;\r
+                       particle.setTexture(texture);\r
+                       particle.setOrigin(originX, originY);\r
+               }\r
+       }\r
+\r
+       /** Ignores the {@link #setContinuous(boolean) continuous} setting until the emitter is started again. This allows the emitter\r
+        * to stop smoothly. */\r
+       public void allowCompletion () {\r
+               allowCompletion = true;\r
+               durationTimer = duration;\r
+       }\r
+\r
+       public Sprite getSprite () {\r
+               return sprite;\r
+       }\r
+\r
+       public String getName () {\r
+               return name;\r
+       }\r
+\r
+       public void setName (String name) {\r
+               this.name = name;\r
+       }\r
+\r
+       public ScaledNumericValue getLife () {\r
+               return lifeValue;\r
+       }\r
+\r
+       public ScaledNumericValue getScale () {\r
+               return scaleValue;\r
+       }\r
+\r
+       public ScaledNumericValue getRotation () {\r
+               return rotationValue;\r
+       }\r
+\r
+       public GradientColorValue getTint () {\r
+               return tintValue;\r
+       }\r
+\r
+       public ScaledNumericValue getVelocity () {\r
+               return velocityValue;\r
+       }\r
+\r
+       public ScaledNumericValue getWind () {\r
+               return windValue;\r
+       }\r
+\r
+       public ScaledNumericValue getGravity () {\r
+               return gravityValue;\r
+       }\r
+\r
+       public ScaledNumericValue getAngle () {\r
+               return angleValue;\r
+       }\r
+\r
+       public ScaledNumericValue getEmission () {\r
+               return emissionValue;\r
+       }\r
+\r
+       public ScaledNumericValue getTransparency () {\r
+               return transparencyValue;\r
+       }\r
+\r
+       public RangedNumericValue getDuration () {\r
+               return durationValue;\r
+       }\r
+\r
+       public RangedNumericValue getDelay () {\r
+               return delayValue;\r
+       }\r
+\r
+       public ScaledNumericValue getLifeOffset () {\r
+               return lifeOffsetValue;\r
+       }\r
+\r
+       public RangedNumericValue getXOffsetValue () {\r
+               return xOffsetValue;\r
+       }\r
+\r
+       public RangedNumericValue getYOffsetValue () {\r
+               return yOffsetValue;\r
+       }\r
+\r
+       public ScaledNumericValue getSpawnWidth () {\r
+               return spawnWidthValue;\r
+       }\r
+\r
+       public ScaledNumericValue getSpawnHeight () {\r
+               return spawnHeightValue;\r
+       }\r
+\r
+       public SpawnShapeValue getSpawnShape () {\r
+               return spawnShapeValue;\r
+       }\r
+\r
+       public boolean isAttached () {\r
+               return attached;\r
+       }\r
+\r
+       public void setAttached (boolean attached) {\r
+               this.attached = attached;\r
+       }\r
+\r
+       public boolean isContinuous () {\r
+               return continuous;\r
+       }\r
+\r
+       public void setContinuous (boolean continuous) {\r
+               this.continuous = continuous;\r
+       }\r
+\r
+       public boolean isAligned () {\r
+               return aligned;\r
+       }\r
+\r
+       public void setAligned (boolean aligned) {\r
+               this.aligned = aligned;\r
+       }\r
+\r
+       public boolean isAdditive () {\r
+               return additive;\r
+       }\r
+\r
+       public void setAdditive (boolean additive) {\r
+               this.additive = additive;\r
+       }\r
+\r
+       public boolean isBehind () {\r
+               return behind;\r
+       }\r
+\r
+       public void setBehind (boolean behind) {\r
+               this.behind = behind;\r
+       }\r
+\r
+       public int getMinParticleCount () {\r
+               return minParticleCount;\r
+       }\r
+\r
+       public void setMinParticleCount (int minParticleCount) {\r
+               this.minParticleCount = minParticleCount;\r
+       }\r
+\r
+       public int getMaxParticleCount () {\r
+               return maxParticleCount;\r
+       }\r
+\r
+       public boolean isComplete () {\r
+               if (delayTimer < delay) return false;\r
+               return durationTimer >= duration && activeCount == 0;\r
+       }\r
+\r
+       public float getPercentComplete () {\r
+               if (delayTimer < delay) return 0;\r
+               return Math.min(1, durationTimer / (float)duration);\r
+       }\r
+\r
+       public float getX () {\r
+               return x;\r
+       }\r
+\r
+       public float getY () {\r
+               return y;\r
+       }\r
+\r
+       public int getActiveCount () {\r
+               return activeCount;\r
+       }\r
+\r
+       public String getImagePath () {\r
+               return imagePath;\r
+       }\r
+\r
+       public void setImagePath (String imagePath) {\r
+               this.imagePath = imagePath;\r
+       }\r
+\r
+       public void setFlip (boolean flipX, boolean flipY) {\r
+               this.flipX = flipX;\r
+               this.flipY = flipY;\r
+               if (particles == null) return;\r
+               for (int i = 0, n = particles.length; i < n; i++) {\r
+                       Particle particle = particles[i];\r
+                       if (particle != null) particle.flip(flipX, flipY);\r
+               }\r
+       }\r
+\r
+       public void save (Writer output) throws IOException {\r
+               output.write(name + "\n");\r
+               output.write("- Delay -\n");\r
+               delayValue.save(output);\r
+               output.write("- Duration - \n");\r
+               durationValue.save(output);\r
+               output.write("- Count - \n");\r
+               output.write("min: " + minParticleCount + "\n");\r
+               output.write("max: " + maxParticleCount + "\n");\r
+               output.write("- Emission - \n");\r
+               emissionValue.save(output);\r
+               output.write("- Life - \n");\r
+               lifeValue.save(output);\r
+               output.write("- Life Offset - \n");\r
+               lifeOffsetValue.save(output);\r
+               output.write("- X Offset - \n");\r
+               xOffsetValue.save(output);\r
+               output.write("- Y Offset - \n");\r
+               yOffsetValue.save(output);\r
+               output.write("- Spawn Shape - \n");\r
+               spawnShapeValue.save(output);\r
+               output.write("- Spawn Width - \n");\r
+               spawnWidthValue.save(output);\r
+               output.write("- Spawn Height - \n");\r
+               spawnHeightValue.save(output);\r
+               output.write("- Scale - \n");\r
+               scaleValue.save(output);\r
+               output.write("- Velocity - \n");\r
+               velocityValue.save(output);\r
+               output.write("- Angle - \n");\r
+               angleValue.save(output);\r
+               output.write("- Rotation - \n");\r
+               rotationValue.save(output);\r
+               output.write("- Wind - \n");\r
+               windValue.save(output);\r
+               output.write("- Gravity - \n");\r
+               gravityValue.save(output);\r
+               output.write("- Tint - \n");\r
+               tintValue.save(output);\r
+               output.write("- Transparency - \n");\r
+               transparencyValue.save(output);\r
+               output.write("- Options - \n");\r
+               output.write("attached: " + attached + "\n");\r
+               output.write("continuous: " + continuous + "\n");\r
+               output.write("aligned: " + aligned + "\n");\r
+               output.write("additive: " + additive + "\n");\r
+               output.write("behind: " + behind + "\n");\r
+       }\r
+\r
+       public void load (BufferedReader reader) throws IOException {\r
+               try {\r
+                       name = readString(reader, "name");\r
+                       reader.readLine();\r
+                       delayValue.load(reader);\r
+                       reader.readLine();\r
+                       durationValue.load(reader);\r
+                       reader.readLine();\r
+                       setMinParticleCount(readInt(reader, "minParticleCount"));\r
+                       setMaxParticleCount(readInt(reader, "maxParticleCount"));\r
+                       reader.readLine();\r
+                       emissionValue.load(reader);\r
+                       reader.readLine();\r
+                       lifeValue.load(reader);\r
+                       reader.readLine();\r
+                       lifeOffsetValue.load(reader);\r
+                       reader.readLine();\r
+                       xOffsetValue.load(reader);\r
+                       reader.readLine();\r
+                       yOffsetValue.load(reader);\r
+                       reader.readLine();\r
+                       spawnShapeValue.load(reader);\r
+                       reader.readLine();\r
+                       spawnWidthValue.load(reader);\r
+                       reader.readLine();\r
+                       spawnHeightValue.load(reader);\r
+                       reader.readLine();\r
+                       scaleValue.load(reader);\r
+                       reader.readLine();\r
+                       velocityValue.load(reader);\r
+                       reader.readLine();\r
+                       angleValue.load(reader);\r
+                       reader.readLine();\r
+                       rotationValue.load(reader);\r
+                       reader.readLine();\r
+                       windValue.load(reader);\r
+                       reader.readLine();\r
+                       gravityValue.load(reader);\r
+                       reader.readLine();\r
+                       tintValue.load(reader);\r
+                       reader.readLine();\r
+                       transparencyValue.load(reader);\r
+                       reader.readLine();\r
+                       attached = readBoolean(reader, "attached");\r
+                       continuous = readBoolean(reader, "continuous");\r
+                       aligned = readBoolean(reader, "aligned");\r
+                       additive = readBoolean(reader, "additive");\r
+                       behind = readBoolean(reader, "behind");\r
+               } catch (RuntimeException ex) {\r
+                       if (name == null) throw ex;\r
+                       throw new RuntimeException("Error parsing emitter: " + name, ex);\r
+               }\r
+       }\r
+\r
+       static String readString (BufferedReader reader, String name) throws IOException {\r
+               String line = reader.readLine();\r
+               if (line == null) throw new IOException("Missing value: " + name);\r
+               return line.substring(line.indexOf(":") + 1).trim();\r
+       }\r
+\r
+       static boolean readBoolean (BufferedReader reader, String name) throws IOException {\r
+               return Boolean.parseBoolean(readString(reader, name));\r
+       }\r
+\r
+       static int readInt (BufferedReader reader, String name) throws IOException {\r
+               return Integer.parseInt(readString(reader, name));\r
+       }\r
+\r
+       static float readFloat (BufferedReader reader, String name) throws IOException {\r
+               return Float.parseFloat(readString(reader, name));\r
+       }\r
+\r
+       static class Particle extends Sprite {\r
+               int life, currentLife;\r
+               float scale, scaleDiff;\r
+               float rotation, rotationDiff;\r
+               float velocity, velocityDiff;\r
+               float angle, angleDiff;\r
+               float angleCos, angleSin;\r
+               float transparency, transparencyDiff;\r
+               float wind, windDiff;\r
+               float gravity, gravityDiff;\r
+               float[] tint;\r
+\r
+               public Particle (Sprite sprite) {\r
+                       super(sprite);\r
+               }\r
+       }\r
+\r
+       static public class ParticleValue {\r
+               boolean active;\r
+               boolean alwaysActive;\r
+\r
+               public void setAlwaysActive (boolean alwaysActive) {\r
+                       this.alwaysActive = alwaysActive;\r
+               }\r
+\r
+               public boolean isAlwaysActive () {\r
+                       return alwaysActive;\r
+               }\r
+\r
+               public boolean isActive () {\r
+                       return alwaysActive || active;\r
+               }\r
+\r
+               public void setActive (boolean active) {\r
+                       this.active = active;\r
+               }\r
+\r
+               public void save (Writer output) throws IOException {\r
+                       if (!alwaysActive)\r
+                               output.write("active: " + active + "\n");\r
+                       else\r
+                               active = true;\r
+               }\r
+\r
+               public void load (BufferedReader reader) throws IOException {\r
+                       if (!alwaysActive)\r
+                               active = readBoolean(reader, "active");\r
+                       else\r
+                               active = true;\r
+               }\r
+\r
+               public void load (ParticleValue value) {\r
+                       active = value.active;\r
+                       alwaysActive = value.alwaysActive;\r
+               }\r
+       }\r
+\r
+       static public class NumericValue extends ParticleValue {\r
+               private float value;\r
+\r
+               public float getValue () {\r
+                       return value;\r
+               }\r
+\r
+               public void setValue (float value) {\r
+                       this.value = value;\r
+               }\r
+\r
+               public void save (Writer output) throws IOException {\r
+                       super.save(output);\r
+                       if (!active) return;\r
+                       output.write("value: " + value + "\n");\r
+               }\r
+\r
+               public void load (BufferedReader reader) throws IOException {\r
+                       super.load(reader);\r
+                       if (!active) return;\r
+                       value = readFloat(reader, "value");\r
+               }\r
+\r
+               public void load (NumericValue value) {\r
+                       super.load(value);\r
+                       this.value = value.value;\r
+               }\r
+       }\r
+\r
+       static public class RangedNumericValue extends ParticleValue {\r
+               private float lowMin, lowMax;\r
+\r
+               public float newLowValue () {\r
+                       return lowMin + (lowMax - lowMin) * MathUtils.random();\r
+               }\r
+\r
+               public void setLow (float value) {\r
+                       lowMin = value;\r
+                       lowMax = value;\r
+               }\r
+\r
+               public void setLow (float min, float max) {\r
+                       lowMin = min;\r
+                       lowMax = max;\r
+               }\r
+\r
+               public float getLowMin () {\r
+                       return lowMin;\r
+               }\r
+\r
+               public void setLowMin (float lowMin) {\r
+                       this.lowMin = lowMin;\r
+               }\r
+\r
+               public float getLowMax () {\r
+                       return lowMax;\r
+               }\r
+\r
+               public void setLowMax (float lowMax) {\r
+                       this.lowMax = lowMax;\r
+               }\r
+\r
+               public void save (Writer output) throws IOException {\r
+                       super.save(output);\r
+                       if (!active) return;\r
+                       output.write("lowMin: " + lowMin + "\n");\r
+                       output.write("lowMax: " + lowMax + "\n");\r
+               }\r
+\r
+               public void load (BufferedReader reader) throws IOException {\r
+                       super.load(reader);\r
+                       if (!active) return;\r
+                       lowMin = readFloat(reader, "lowMin");\r
+                       lowMax = readFloat(reader, "lowMax");\r
+               }\r
+\r
+               public void load (RangedNumericValue value) {\r
+                       super.load(value);\r
+                       lowMax = value.lowMax;\r
+                       lowMin = value.lowMin;\r
+               }\r
+       }\r
+\r
+       static public class ScaledNumericValue extends RangedNumericValue {\r
+               private float[] scaling = {1};\r
+               float[] timeline = {0};\r
+               private float highMin, highMax;\r
+               private boolean relative;\r
+\r
+               public float newHighValue () {\r
+                       return highMin + (highMax - highMin) * MathUtils.random();\r
+               }\r
+\r
+               public void setHigh (float value) {\r
+                       highMin = value;\r
+                       highMax = value;\r
+               }\r
+\r
+               public void setHigh (float min, float max) {\r
+                       highMin = min;\r
+                       highMax = max;\r
+               }\r
+\r
+               public float getHighMin () {\r
+                       return highMin;\r
+               }\r
+\r
+               public void setHighMin (float highMin) {\r
+                       this.highMin = highMin;\r
+               }\r
+\r
+               public float getHighMax () {\r
+                       return highMax;\r
+               }\r
+\r
+               public void setHighMax (float highMax) {\r
+                       this.highMax = highMax;\r
+               }\r
+\r
+               public float[] getScaling () {\r
+                       return scaling;\r
+               }\r
+\r
+               public void setScaling (float[] values) {\r
+                       this.scaling = values;\r
+               }\r
+\r
+               public float[] getTimeline () {\r
+                       return timeline;\r
+               }\r
+\r
+               public void setTimeline (float[] timeline) {\r
+                       this.timeline = timeline;\r
+               }\r
+\r
+               public boolean isRelative () {\r
+                       return relative;\r
+               }\r
+\r
+               public void setRelative (boolean relative) {\r
+                       this.relative = relative;\r
+               }\r
+\r
+               public float getScale (float percent) {\r
+                       int endIndex = -1;\r
+                       float[] timeline = this.timeline;\r
+                       int n = timeline.length;\r
+                       for (int i = 1; i < n; i++) {\r
+                               float t = timeline[i];\r
+                               if (t > percent) {\r
+                                       endIndex = i;\r
+                                       break;\r
+                               }\r
+                       }\r
+                       if (endIndex == -1) return scaling[n - 1];\r
+                       float[] scaling = this.scaling;\r
+                       int startIndex = endIndex - 1;\r
+                       float startValue = scaling[startIndex];\r
+                       float startTime = timeline[startIndex];\r
+                       return startValue + (scaling[endIndex] - startValue) * ((percent - startTime) / (timeline[endIndex] - startTime));\r
+               }\r
+\r
+               public void save (Writer output) throws IOException {\r
+                       super.save(output);\r
+                       if (!active) return;\r
+                       output.write("highMin: " + highMin + "\n");\r
+                       output.write("highMax: " + highMax + "\n");\r
+                       output.write("relative: " + relative + "\n");\r
+                       output.write("scalingCount: " + scaling.length + "\n");\r
+                       for (int i = 0; i < scaling.length; i++)\r
+                               output.write("scaling" + i + ": " + scaling[i] + "\n");\r
+                       output.write("timelineCount: " + timeline.length + "\n");\r
+                       for (int i = 0; i < timeline.length; i++)\r
+                               output.write("timeline" + i + ": " + timeline[i] + "\n");\r
+               }\r
+\r
+               public void load (BufferedReader reader) throws IOException {\r
+                       super.load(reader);\r
+                       if (!active) return;\r
+                       highMin = readFloat(reader, "highMin");\r
+                       highMax = readFloat(reader, "highMax");\r
+                       relative = readBoolean(reader, "relative");\r
+                       scaling = new float[readInt(reader, "scalingCount")];\r
+                       for (int i = 0; i < scaling.length; i++)\r
+                               scaling[i] = readFloat(reader, "scaling" + i);\r
+                       timeline = new float[readInt(reader, "timelineCount")];\r
+                       for (int i = 0; i < timeline.length; i++)\r
+                               timeline[i] = readFloat(reader, "timeline" + i);\r
+               }\r
+\r
+               public void load (ScaledNumericValue value) {\r
+                       super.load(value);\r
+                       highMax = value.highMax;\r
+                       highMin = value.highMin;\r
+                       scaling = new float[value.scaling.length];\r
+                       System.arraycopy(value.scaling, 0, scaling, 0, scaling.length);\r
+                       timeline = new float[value.timeline.length];\r
+                       System.arraycopy(value.timeline, 0, timeline, 0, timeline.length);\r
+                       relative = value.relative;\r
+               }\r
+       }\r
+\r
+       static public class GradientColorValue extends ParticleValue {\r
+               static private float[] temp = new float[4];\r
+\r
+               private float[] colors = {1, 1, 1};\r
+               float[] timeline = {0};\r
+\r
+               public GradientColorValue () {\r
+                       alwaysActive = true;\r
+               }\r
+\r
+               public float[] getTimeline () {\r
+                       return timeline;\r
+               }\r
+\r
+               public void setTimeline (float[] timeline) {\r
+                       this.timeline = timeline;\r
+               }\r
+\r
+               public float[] getColors () {\r
+                       return colors;\r
+               }\r
+\r
+               public void setColors (float[] colors) {\r
+                       this.colors = colors;\r
+               }\r
+\r
+               public float[] getColor (float percent) {\r
+                       int startIndex = 0, endIndex = -1;\r
+                       float[] timeline = this.timeline;\r
+                       int n = timeline.length;\r
+                       for (int i = 1; i < n; i++) {\r
+                               float t = timeline[i];\r
+                               if (t > percent) {\r
+                                       endIndex = i;\r
+                                       break;\r
+                               }\r
+                               startIndex = i;\r
+                       }\r
+                       float startTime = timeline[startIndex];\r
+                       startIndex *= 3;\r
+                       float r1 = colors[startIndex];\r
+                       float g1 = colors[startIndex + 1];\r
+                       float b1 = colors[startIndex + 2];\r
+                       if (endIndex == -1) {\r
+                               temp[0] = r1;\r
+                               temp[1] = g1;\r
+                               temp[2] = b1;\r
+                               return temp;\r
+                       }\r
+                       float factor = (percent - startTime) / (timeline[endIndex] - startTime);\r
+                       endIndex *= 3;\r
+                       temp[0] = r1 + (colors[endIndex] - r1) * factor;\r
+                       temp[1] = g1 + (colors[endIndex + 1] - g1) * factor;\r
+                       temp[2] = b1 + (colors[endIndex + 2] - b1) * factor;\r
+                       return temp;\r
+               }\r
+\r
+               public void save (Writer output) throws IOException {\r
+                       super.save(output);\r
+                       if (!active) return;\r
+                       output.write("colorsCount: " + colors.length + "\n");\r
+                       for (int i = 0; i < colors.length; i++)\r
+                               output.write("colors" + i + ": " + colors[i] + "\n");\r
+                       output.write("timelineCount: " + timeline.length + "\n");\r
+                       for (int i = 0; i < timeline.length; i++)\r
+                               output.write("timeline" + i + ": " + timeline[i] + "\n");\r
+               }\r
+\r
+               public void load (BufferedReader reader) throws IOException {\r
+                       super.load(reader);\r
+                       if (!active) return;\r
+                       colors = new float[readInt(reader, "colorsCount")];\r
+                       for (int i = 0; i < colors.length; i++)\r
+                               colors[i] = readFloat(reader, "colors" + i);\r
+                       timeline = new float[readInt(reader, "timelineCount")];\r
+                       for (int i = 0; i < timeline.length; i++)\r
+                               timeline[i] = readFloat(reader, "timeline" + i);\r
+               }\r
+\r
+               public void load (GradientColorValue value) {\r
+                       super.load(value);\r
+                       colors = new float[value.colors.length];\r
+                       System.arraycopy(value.colors, 0, colors, 0, colors.length);\r
+                       timeline = new float[value.timeline.length];\r
+                       System.arraycopy(value.timeline, 0, timeline, 0, timeline.length);\r
+               }\r
+       }\r
+\r
+       static public class SpawnShapeValue extends ParticleValue {\r
+               SpawnShape shape = SpawnShape.point;\r
+               boolean edges;\r
+               SpawnEllipseSide side = SpawnEllipseSide.both;\r
+\r
+               public SpawnShape getShape () {\r
+                       return shape;\r
+               }\r
+\r
+               public void setShape (SpawnShape shape) {\r
+                       this.shape = shape;\r
+               }\r
+\r
+               public boolean isEdges () {\r
+                       return edges;\r
+               }\r
+\r
+               public void setEdges (boolean edges) {\r
+                       this.edges = edges;\r
+               }\r
+\r
+               public SpawnEllipseSide getSide () {\r
+                       return side;\r
+               }\r
+\r
+               public void setSide (SpawnEllipseSide side) {\r
+                       this.side = side;\r
+               }\r
+\r
+               public void save (Writer output) throws IOException {\r
+                       super.save(output);\r
+                       if (!active) return;\r
+                       output.write("shape: " + shape + "\n");\r
+                       if (shape == SpawnShape.ellipse) {\r
+                               output.write("edges: " + edges + "\n");\r
+                               output.write("side: " + side + "\n");\r
+                       }\r
+               }\r
+\r
+               public void load (BufferedReader reader) throws IOException {\r
+                       super.load(reader);\r
+                       if (!active) return;\r
+                       shape = SpawnShape.valueOf(readString(reader, "shape"));\r
+                       if (shape == SpawnShape.ellipse) {\r
+                               edges = readBoolean(reader, "edges");\r
+                               side = SpawnEllipseSide.valueOf(readString(reader, "side"));\r
+                       }\r
+               }\r
+\r
+               public void load (SpawnShapeValue value) {\r
+                       super.load(value);\r
+                       shape = value.shape;\r
+                       edges = value.edges;\r
+                       side = value.side;\r
+               }\r
+       }\r
+\r
+       static public enum SpawnShape {\r
+               point, line, square, ellipse\r
+       }\r
+\r
+       static public enum SpawnEllipseSide {\r
+               both, top, bottom\r
+       }\r
+}\r
diff --git a/gdx/src/com/badlogic/gdx/graphics/g2d/ParticleEmitterBox2D.java b/gdx/src/com/badlogic/gdx/graphics/g2d/ParticleEmitterBox2D.java
new file mode 100644 (file)
index 0000000..2647798
--- /dev/null
@@ -0,0 +1,128 @@
+/*******************************************************************************\r
+ * Copyright 2011 See AUTHORS file.\r
+ * \r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ * \r
+ *   http://www.apache.org/licenses/LICENSE-2.0\r
+ * \r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ ******************************************************************************/\r
+\r
+package com.badlogic.gdx.graphics.g2d;\r
+\r
+import java.io.BufferedReader;\r
+import java.io.IOException;\r
+\r
+import com.badlogic.gdx.math.MathUtils;\r
+import com.badlogic.gdx.math.Vector2;\r
+import com.badlogic.gdx.physics.box2d.Fixture;\r
+import com.badlogic.gdx.physics.box2d.RayCastCallback;\r
+import com.badlogic.gdx.physics.box2d.World;\r
+\r
+/** @author kalle_h\r
+ * \r
+ *         ParticleEmitterBox2D use box2d rayCast:ing to achieve continuous collision detection against box2d fixtures. If\r
+ *         particle detect collision it change it's direction before actual collision would occur. Velocity is 100% reflected.\r
+ * \r
+ *         These particles does not have any other physical attributes or functionality. Particles can't collide to other\r
+ *         particles. */\r
+public class ParticleEmitterBox2D extends ParticleEmitter {\r
+       final World world;\r
+       final Vector2 startPoint = new Vector2();\r
+       final Vector2 endPoint = new Vector2();\r
+       /** collision flag */\r
+       boolean particleCollided;\r
+       float normalAngle;\r
+       /** If velocities squared is shorter than this it could lead 0 length rayCast that cause c++ assertion at box2d */\r
+       private final static float EPSILON = 0.001f;\r
+\r
+       /** default visibility to prevent synthetic accesor creation */\r
+       final RayCastCallback rayCallBack = new RayCastCallback() {\r
+               public float reportRayFixture (Fixture fixture, Vector2 point, Vector2 normal, float fraction) {\r
+                       ParticleEmitterBox2D.this.particleCollided = true;\r
+                       ParticleEmitterBox2D.this.normalAngle = MathUtils.atan2(normal.y, normal.x) * MathUtils.radiansToDegrees;\r
+                       return fraction;\r
+               }\r
+       };\r
+\r
+       /** Constructs default ParticleEmitterBox2D. Box2d World is used for rayCasting. Assumes that particles use same unit system\r
+        * that box2d world does.\r
+        * \r
+        * @param world */\r
+       public ParticleEmitterBox2D (World world) {\r
+               super();\r
+               this.world = world;\r
+       }\r
+\r
+       /** /**Constructs ParticleEmitterBox2D using bufferedReader. Box2d World is used for rayCasting. Assumes that particles use same\r
+        * unit system that box2d world does.\r
+        * \r
+        * @param world\r
+        * @param reader\r
+        * @throws IOException */\r
+       public ParticleEmitterBox2D (World world, BufferedReader reader) throws IOException {\r
+               super(reader);\r
+               this.world = world;\r
+       }\r
+\r
+       /** Constructs ParticleEmitterBox2D fully copying given emitter attributes. Box2d World is used for rayCasting. Assumes that\r
+        * particles use same unit system that box2d world does.\r
+        * \r
+        * @param world\r
+        * @param emitter */\r
+       public ParticleEmitterBox2D (World world, ParticleEmitter emitter) {\r
+               super(emitter);\r
+               this.world = world;\r
+       }\r
+\r
+       @Override\r
+       protected Particle newParticle (Sprite sprite) {\r
+               return new ParticleBox2D(sprite);\r
+       }\r
+\r
+       /** Particle that can collide to box2d fixtures */\r
+       private class ParticleBox2D extends Particle {\r
+               public ParticleBox2D (Sprite sprite) {\r
+                       super(sprite);\r
+               }\r
+\r
+               /** translate particle given amount. Continuous collision detection achieved by using RayCast from oldPos to newPos.\r
+                * \r
+                * @param velocityX\r
+                * @param velocityY */\r
+               @Override\r
+               public void translate (float velocityX, float velocityY) {\r
+                       /** If velocities squares summed is shorter than Epsilon it could lead ~0 length rayCast that cause nasty c++ assertion\r
+                        * inside box2d. This is so short distance that moving particle has no effect so this return early. */\r
+                       if ((velocityX * velocityX + velocityY * velocityY) < EPSILON) return;\r
+\r
+                       /** Position offset is half of sprite texture size. */\r
+                       final float x = getX() + getWidth() / 2f;\r
+                       final float y = getY() + getHeight() / 2f;\r
+\r
+                       /** collision flag to false */\r
+                       particleCollided = false;\r
+                       startPoint.set(x, y);\r
+                       endPoint.set(x + velocityX, y + velocityY);\r
+                       if (world != null) world.rayCast(rayCallBack, startPoint, endPoint);\r
+\r
+                       /** If ray collided boolean has set to true at rayCallBack */\r
+                       if (!particleCollided) {\r
+                               // perfect reflection\r
+                               angle = 2f * normalAngle - angle - 180f;\r
+                               angleCos = MathUtils.cosDeg(angle);\r
+                               angleSin = MathUtils.sinDeg(angle);\r
+                               velocityX = velocity * angleCos;\r
+                               velocityY = velocity * angleSin;\r
+                       }\r
+\r
+                       super.translate(velocityX, velocityY);\r
+               }\r
+       }\r
+}\r
index 21545e3..239aa93 100644 (file)
@@ -49,7 +49,7 @@ public class Box2DParticleFactory implements ParticleFactory {
      */\r
     private final static float EPSILON = 0.001f;\r
     /**\r
-     * default visibility to prevent synthetic accessor creation\r
+     * default visibility to prevent synthetic accesor creation\r
      */\r
     private final RayCastCallback rayCallBack = new RayCastCallback() {\r
         @Override\r
index 4693f95..9682ea6 100644 (file)
  */\r
 package com.badlogic.gdx.graphics.g2d.particle;\r
 \r
+import java.io.*;\r
 import java.util.BitSet;\r
 \r
 import com.badlogic.gdx.graphics.GL10;\r
 import com.badlogic.gdx.graphics.Texture;\r
 import com.badlogic.gdx.graphics.g2d.Sprite;\r
 import com.badlogic.gdx.graphics.g2d.SpriteBatch;\r
-import com.badlogic.gdx.graphics.g2d.particle.emitterattributs.*;\r
 import com.badlogic.gdx.math.MathUtils;\r
 //TODO - Javadoc.\r
 //TODO - Add a duplicate emitter button.\r
@@ -35,7 +35,6 @@ import com.badlogic.gdx.math.MathUtils;
 public class ParticleEmitter implements Cloneable {\r
 \r
     private static class StandardParticleFactory implements ParticleFactory {\r
-\r
         @Override\r
         public Particle createParticle(Sprite sprite) {\r
             return new Particle(sprite);\r
@@ -61,8 +60,8 @@ public class ParticleEmitter implements Cloneable {
     private ScaledNumericValue gravityValue = new ScaledNumericValue();\r
     private ScaledNumericValue transparencyValue = new ScaledNumericValue();\r
     private GradientColorValue tintValue = new GradientColorValue();\r
-    private RangedNumericValue xOffsetValue = new RangedNumericValue();\r
-    private RangedNumericValue yOffsetValue = new RangedNumericValue();\r
+    private RangedNumericValue xOffsetValue = new ScaledNumericValue();\r
+    private RangedNumericValue yOffsetValue = new ScaledNumericValue();\r
     private ScaledNumericValue spawnWidthValue = new ScaledNumericValue();\r
     private ScaledNumericValue spawnHeightValue = new ScaledNumericValue();\r
     private SpawnShapeValue spawnShapeValue = new SpawnShapeValue();\r
@@ -90,6 +89,7 @@ public class ParticleEmitter implements Cloneable {
     private boolean aligned;\r
     private boolean behind;\r
     private boolean additive = true;\r
+    \r
     private final ParticleFactory factory;\r
 \r
     public ParticleEmitter() {\r
@@ -108,19 +108,19 @@ public class ParticleEmitter implements Cloneable {
         n.name = name;\r
         n.setMaxParticleCount(maxParticleCount);\r
         n.minParticleCount = minParticleCount;\r
-        n.delayValue = delayValue.clone();\r
-        n.durationValue = durationValue.clone();\r
-        n.emissionValue = emissionValue.clone();\r
-        n.lifeValue = lifeValue.clone();\r
-        n.lifeOffsetValue = lifeOffsetValue.clone();\r
-        n.scaleValue = scaleValue.clone();\r
-        n.rotationValue = rotationValue.clone();\r
-        n.velocityValue = velocityValue.clone();\r
-        n.angleValue = angleValue.clone();\r
-        n.windValue = windValue.clone();\r
-        n.gravityValue = gravityValue.clone();\r
-        n.transparencyValue = transparencyValue.clone();\r
-        n.tintValue = tintValue.clone();\r
+        n.delayValue.load(delayValue);\r
+        n.durationValue.load(durationValue);\r
+        n.emissionValue.load(emissionValue);\r
+        n.lifeValue.load(lifeValue);\r
+        n.lifeOffsetValue.load(lifeOffsetValue);\r
+        n.scaleValue.load(scaleValue);\r
+        n.rotationValue.load(rotationValue);\r
+        n.velocityValue.load(velocityValue);\r
+        n.angleValue.load(angleValue);\r
+        n.windValue.load(windValue);\r
+        n.gravityValue.load(gravityValue);\r
+        n.transparencyValue.load(transparencyValue);\r
+        n.tintValue.load(tintValue);\r
         return n;\r
     }\r
 \r
@@ -160,16 +160,13 @@ public class ParticleEmitter implements Cloneable {
     }\r
 \r
     public void update(float delta) {\r
-        //this hole timeing code does not provide any benefit\r
-        accumulator += Math.min(delta * 1000, 250);//TODO bullshit to do this\r
+        accumulator += Math.min(delta * 1000, 250);\r
         if (accumulator < 1) {\r
             return;\r
         }\r
         int deltaMillis = (int) accumulator;\r
         accumulator -= deltaMillis;\r
 \r
-\r
-        //\r
         for (int i = 0; i < maxParticleCount; i++) {\r
             if (active.get(i) && !updateParticle(particles[i], delta, deltaMillis)) {\r
                 active.clear(i);\r
@@ -311,11 +308,11 @@ public class ParticleEmitter implements Cloneable {
     }\r
 \r
     private void restart() {\r
-        delay = delayValue.isActive() ? delayValue.newValue() : 0;\r
+        delay = delayValue.active ? delayValue.newLowValue() : 0;\r
         delayTimer = 0;\r
 \r
         durationTimer -= duration;\r
-        duration = durationValue.newValue();\r
+        duration = durationValue.newLowValue();\r
 \r
         emission = (int) emissionValue.newLowValue();\r
         emissionDiff = (int) emissionValue.newHighValue();\r
@@ -329,7 +326,7 @@ public class ParticleEmitter implements Cloneable {
             lifeDiff -= life;\r
         }\r
 \r
-        lifeOffset = lifeOffsetValue.isActive() ? (int) lifeOffsetValue.newLowValue() : 0;\r
+        lifeOffset = lifeOffsetValue.active ? (int) lifeOffsetValue.newLowValue() : 0;\r
         lifeOffsetDiff = (int) lifeOffsetValue.newHighValue();\r
         if (!lifeOffsetValue.isRelative()) {\r
             lifeOffsetDiff -= lifeOffset;\r
@@ -348,25 +345,25 @@ public class ParticleEmitter implements Cloneable {
         }\r
 \r
         updateFlags = 0;\r
-        if (angleValue.isActive() && angleValue.getTimeLineCount() > 1) {\r
+        if (angleValue.active && angleValue.timeline.length > 1) {\r
             updateFlags |= UPDATE_ANGLE;\r
         }\r
-        if (velocityValue.isActive() && velocityValue.isActive()) {\r
+        if (velocityValue.active && velocityValue.active) {\r
             updateFlags |= UPDATE_VELOCITY;\r
         }\r
-        if (scaleValue.getTimeLineCount() > 1) {\r
+        if (scaleValue.timeline.length > 1) {\r
             updateFlags |= UPDATE_SCALE;\r
         }\r
-        if (rotationValue.isActive() && rotationValue.getTimeLineCount() > 1) {\r
+        if (rotationValue.active && rotationValue.timeline.length > 1) {\r
             updateFlags |= UPDATE_ROTATION;\r
         }\r
-        if (windValue.isActive()) {\r
+        if (windValue.active) {\r
             updateFlags |= UPDATE_WIND;\r
         }\r
-        if (gravityValue.isActive()) {\r
+        if (gravityValue.active) {\r
             updateFlags |= UPDATE_GRAVITY;\r
         }\r
-        if (tintValue.getTimeLineCount() > 1) {\r
+        if (tintValue.timeline.length > 1) {\r
             updateFlags |= UPDATE_TINT;\r
         }\r
     }\r
@@ -383,7 +380,7 @@ public class ParticleEmitter implements Cloneable {
 \r
         particle.currentLife = particle.life = life + (int) (lifeDiff * lifeValue.getScale(percent));\r
 \r
-        if (velocityValue.isActive()) {\r
+        if (velocityValue.active) {\r
             particle.velocity = velocityValue.newLowValue();\r
             particle.velocityDiff = velocityValue.newHighValue();\r
             if (!velocityValue.isRelative()) {\r
@@ -412,7 +409,7 @@ public class ParticleEmitter implements Cloneable {
         }\r
         particle.setScale(particle.scale + particle.scaleDiff * scaleValue.getScale(0));\r
 \r
-        if (rotationValue.isActive()) {\r
+        if (rotationValue.active) {\r
             particle.rotation = rotationValue.newLowValue();\r
             particle.rotationDiff = rotationValue.newHighValue();\r
             if (!rotationValue.isRelative()) {\r
@@ -425,7 +422,7 @@ public class ParticleEmitter implements Cloneable {
             particle.setRotation(rotation);\r
         }\r
 \r
-        if (windValue.isActive()) {\r
+        if (windValue.active) {\r
             particle.wind = windValue.newLowValue();\r
             particle.windDiff = windValue.newHighValue();\r
             if (!windValue.isRelative()) {\r
@@ -433,7 +430,7 @@ public class ParticleEmitter implements Cloneable {
             }\r
         }\r
 \r
-        if (gravityValue.isActive()) {\r
+        if (gravityValue.active) {\r
             particle.gravity = gravityValue.newLowValue();\r
             particle.gravityDiff = gravityValue.newHighValue();\r
             if (!gravityValue.isRelative()) {\r
@@ -455,14 +452,14 @@ public class ParticleEmitter implements Cloneable {
 \r
         // Spawn.\r
         float x = this.x;\r
-        if (xOffsetValue.isActive()) {\r
-            x += xOffsetValue.newValue();\r
+        if (xOffsetValue.active) {\r
+            x += xOffsetValue.newLowValue();\r
         }\r
         float y = this.y;\r
-        if (yOffsetValue.isActive()) {\r
-            y += yOffsetValue.newValue();\r
+        if (yOffsetValue.active) {\r
+            y += yOffsetValue.newLowValue();\r
         }\r
-        switch (spawnShapeValue.getShape()) {\r
+        switch (spawnShapeValue.shape) {\r
             case square: {\r
                 float width = spawnWidth + (spawnWidthDiff * spawnWidthValue.getScale(percent));\r
                 float height = spawnHeight + (spawnHeightDiff * spawnHeightValue.getScale(percent));\r
@@ -479,9 +476,9 @@ public class ParticleEmitter implements Cloneable {
                     break;\r
                 }\r
                 float scaleY = radiusX / (float) radiusY;\r
-                if (spawnShapeValue.isEdges()) {\r
+                if (spawnShapeValue.edges) {\r
                     float spawnAngle;\r
-                    switch (spawnShapeValue.getSide()) {\r
+                    switch (spawnShapeValue.side) {\r
                         case top:\r
                             spawnAngle = -MathUtils.random(179f);\r
                             break;\r
@@ -543,31 +540,30 @@ public class ParticleEmitter implements Cloneable {
         active.set(index);\r
     }\r
 \r
-    /**\r
-     * @return false if the particles lifetime exceeded\r
-     */\r
     private boolean updateParticle(Particle particle, float delta, int deltaMillis) {\r
-        particle.currentLife -= deltaMillis;\r
-        if (particle.currentLife <= 0) {\r
+        int life = particle.currentLife - deltaMillis;\r
+        if (life <= 0) {\r
             return false;\r
         }\r
+        particle.currentLife = life;\r
 \r
-        float lostLifePercent = 1 - particle.currentLife / (float) particle.life;\r
+        float percent = 1 - particle.currentLife / (float) particle.life;\r
+        int updateFlags = this.updateFlags;\r
 \r
         if ((updateFlags & UPDATE_SCALE) != 0) {\r
-            particle.setScale(particle.scale + particle.scaleDiff * scaleValue.getScale(lostLifePercent));\r
+            particle.setScale(particle.scale + particle.scaleDiff * scaleValue.getScale(percent));\r
         }\r
 \r
         if ((updateFlags & UPDATE_VELOCITY) != 0) {\r
-            float velocity = (particle.velocity + particle.velocityDiff * velocityValue.getScale(lostLifePercent)) * delta;\r
+            float velocity = (particle.velocity + particle.velocityDiff * velocityValue.getScale(percent)) * delta;\r
 \r
             float velocityX, velocityY;\r
             if ((updateFlags & UPDATE_ANGLE) != 0) {\r
-                float angle = particle.angle + particle.angleDiff * angleValue.getScale(lostLifePercent);\r
+                float angle = particle.angle + particle.angleDiff * angleValue.getScale(percent);\r
                 velocityX = velocity * MathUtils.cosDeg(angle);\r
                 velocityY = velocity * MathUtils.sinDeg(angle);\r
                 if ((updateFlags & UPDATE_ROTATION) != 0) {\r
-                    float rotation = particle.rotation + particle.rotationDiff * rotationValue.getScale(lostLifePercent);\r
+                    float rotation = particle.rotation + particle.rotationDiff * rotationValue.getScale(percent);\r
                     if (aligned) {\r
                         rotation += angle;\r
                     }\r
@@ -577,7 +573,7 @@ public class ParticleEmitter implements Cloneable {
                 velocityX = velocity * particle.angleCos;\r
                 velocityY = velocity * particle.angleSin;\r
                 if (aligned || (updateFlags & UPDATE_ROTATION) != 0) {\r
-                    float rotation = particle.rotation + particle.rotationDiff * rotationValue.getScale(lostLifePercent);\r
+                    float rotation = particle.rotation + particle.rotationDiff * rotationValue.getScale(percent);\r
                     if (aligned) {\r
                         rotation += particle.angle;\r
                     }\r
@@ -586,28 +582,28 @@ public class ParticleEmitter implements Cloneable {
             }\r
 \r
             if ((updateFlags & UPDATE_WIND) != 0) {\r
-                velocityX += (particle.wind + particle.windDiff * windValue.getScale(lostLifePercent)) * delta;\r
+                velocityX += (particle.wind + particle.windDiff * windValue.getScale(percent)) * delta;\r
             }\r
 \r
             if ((updateFlags & UPDATE_GRAVITY) != 0) {\r
-                velocityY += (particle.gravity + particle.gravityDiff * gravityValue.getScale(lostLifePercent)) * delta;\r
+                velocityY += (particle.gravity + particle.gravityDiff * gravityValue.getScale(percent)) * delta;\r
             }\r
 \r
             particle.translate(velocityX, velocityY);\r
         } else {\r
             if ((updateFlags & UPDATE_ROTATION) != 0) {\r
-                particle.setRotation(particle.rotation + particle.rotationDiff * rotationValue.getScale(lostLifePercent));\r
+                particle.setRotation(particle.rotation + particle.rotationDiff * rotationValue.getScale(percent));\r
             }\r
         }\r
 \r
         float[] color;\r
         if ((updateFlags & UPDATE_TINT) != 0) {\r
-            color = tintValue.getColor(lostLifePercent);\r
+            color = tintValue.getColor(percent);\r
         } else {\r
             color = particle.tint;\r
         }\r
         particle.setColor(color[0], color[1], color[2],\r
-                          particle.transparency + particle.transparencyDiff * transparencyValue.getScale(lostLifePercent));\r
+                          particle.transparency + particle.transparencyDiff * transparencyValue.getScale(percent));\r
 \r
         return true;\r
     }\r
@@ -836,128 +832,563 @@ public class ParticleEmitter implements Cloneable {
             }\r
         }\r
     }\r
-//    public void save(Writer output) throws IOException {\r
-//        output.write(name + "\n");\r
-//        output.write("- Delay -\n");\r
-//        delayValue.save(output);\r
-//        output.write("- Duration - \n");\r
-//        durationValue.save(output);\r
-//        output.write("- Count - \n");\r
-//        output.write("min: " + minParticleCount + "\n");\r
-//        output.write("max: " + maxParticleCount + "\n");\r
-//        output.write("- Emission - \n");\r
-//        emissionValue.save(output);\r
-//        output.write("- Life - \n");\r
-//        lifeValue.save(output);\r
-//        output.write("- Life Offset - \n");\r
-//        lifeOffsetValue.save(output);\r
-//        output.write("- X Offset - \n");\r
-//        xOffsetValue.save(output);\r
-//        output.write("- Y Offset - \n");\r
-//        yOffsetValue.save(output);\r
-//        output.write("- Spawn Shape - \n");\r
-//        spawnShapeValue.save(output);\r
-//        output.write("- Spawn Width - \n");\r
-//        spawnWidthValue.save(output);\r
-//        output.write("- Spawn Height - \n");\r
-//        spawnHeightValue.save(output);\r
-//        output.write("- Scale - \n");\r
-//        scaleValue.save(output);\r
-//        output.write("- Velocity - \n");\r
-//        velocityValue.save(output);\r
-//        output.write("- Angle - \n");\r
-//        angleValue.save(output);\r
-//        output.write("- Rotation - \n");\r
-//        rotationValue.save(output);\r
-//        output.write("- Wind - \n");\r
-//        windValue.save(output);\r
-//        output.write("- Gravity - \n");\r
-//        gravityValue.save(output);\r
-//        output.write("- Tint - \n");\r
-//        tintValue.save(output);\r
-//        output.write("- Transparency - \n");\r
-//        transparencyValue.save(output);\r
-//        output.write("- Options - \n");\r
-//        output.write("attached: " + attached + "\n");\r
-//        output.write("continuous: " + continuous + "\n");\r
-//        output.write("aligned: " + aligned + "\n");\r
-//        output.write("additive: " + additive + "\n");\r
-//        output.write("behind: " + behind + "\n");\r
-//    }\r
-//\r
-//    public void load(BufferedReader reader) throws IOException {\r
-//        try {\r
-//            name = readString(reader, "name");\r
-//            reader.readLine();\r
-//            delayValue.load(reader);\r
-//            reader.readLine();\r
-//            durationValue.load(reader);\r
-//            reader.readLine();\r
-//            setMinParticleCount(readInt(reader, "minParticleCount"));\r
-//            setMaxParticleCount(readInt(reader, "maxParticleCount"));\r
-//            reader.readLine();\r
-//            emissionValue.load(reader);\r
-//            reader.readLine();\r
-//            lifeValue.load(reader);\r
-//            reader.readLine();\r
-//            lifeOffsetValue.load(reader);\r
-//            reader.readLine();\r
-//            xOffsetValue.load(reader);\r
-//            reader.readLine();\r
-//            yOffsetValue.load(reader);\r
-//            reader.readLine();\r
-//            spawnShapeValue.load(reader);\r
-//            reader.readLine();\r
-//            spawnWidthValue.load(reader);\r
-//            reader.readLine();\r
-//            spawnHeightValue.load(reader);\r
-//            reader.readLine();\r
-//            scaleValue.load(reader);\r
-//            reader.readLine();\r
-//            velocityValue.load(reader);\r
-//            reader.readLine();\r
-//            angleValue.load(reader);\r
-//            reader.readLine();\r
-//            rotationValue.load(reader);\r
-//            reader.readLine();\r
-//            windValue.load(reader);\r
-//            reader.readLine();\r
-//            gravityValue.load(reader);\r
-//            reader.readLine();\r
-//            tintValue.load(reader);\r
-//            reader.readLine();\r
-//            transparencyValue.load(reader);\r
-//            reader.readLine();\r
-//            attached = readBoolean(reader, "attached");\r
-//            continuous = readBoolean(reader, "continuous");\r
-//            aligned = readBoolean(reader, "aligned");\r
-//            additive = readBoolean(reader, "additive");\r
-//            behind = readBoolean(reader, "behind");\r
-//        } catch (RuntimeException ex) {\r
-//            if (name == null) {\r
-//                throw ex;\r
-//            }\r
-//            throw new RuntimeException("Error parsing emitter: " + name, ex);\r
-//        }\r
-//    }\r
-//\r
-//    static String readString(BufferedReader reader, String name) throws IOException {\r
-//        String line = reader.readLine();\r
-//        if (line == null) {\r
-//            throw new IOException("Missing value: " + name);\r
-//        }\r
-//        return line.substring(line.indexOf(":") + 1).trim();\r
-//    }\r
-//\r
-//    static boolean readBoolean(BufferedReader reader, String name) throws IOException {\r
-//        return Boolean.parseBoolean(readString(reader, name));\r
-//    }\r
-//\r
-//    static int readInt(BufferedReader reader, String name) throws IOException {\r
-//        return Integer.parseInt(readString(reader, name));\r
-//    }\r
-//\r
-//    static float readFloat(BufferedReader reader, String name) throws IOException {\r
-//        return Float.parseFloat(readString(reader, name));\r
-//    }\r
+\r
+    public void save(Writer output) throws IOException {\r
+        output.write(name + "\n");\r
+        output.write("- Delay -\n");\r
+        delayValue.save(output);\r
+        output.write("- Duration - \n");\r
+        durationValue.save(output);\r
+        output.write("- Count - \n");\r
+        output.write("min: " + minParticleCount + "\n");\r
+        output.write("max: " + maxParticleCount + "\n");\r
+        output.write("- Emission - \n");\r
+        emissionValue.save(output);\r
+        output.write("- Life - \n");\r
+        lifeValue.save(output);\r
+        output.write("- Life Offset - \n");\r
+        lifeOffsetValue.save(output);\r
+        output.write("- X Offset - \n");\r
+        xOffsetValue.save(output);\r
+        output.write("- Y Offset - \n");\r
+        yOffsetValue.save(output);\r
+        output.write("- Spawn Shape - \n");\r
+        spawnShapeValue.save(output);\r
+        output.write("- Spawn Width - \n");\r
+        spawnWidthValue.save(output);\r
+        output.write("- Spawn Height - \n");\r
+        spawnHeightValue.save(output);\r
+        output.write("- Scale - \n");\r
+        scaleValue.save(output);\r
+        output.write("- Velocity - \n");\r
+        velocityValue.save(output);\r
+        output.write("- Angle - \n");\r
+        angleValue.save(output);\r
+        output.write("- Rotation - \n");\r
+        rotationValue.save(output);\r
+        output.write("- Wind - \n");\r
+        windValue.save(output);\r
+        output.write("- Gravity - \n");\r
+        gravityValue.save(output);\r
+        output.write("- Tint - \n");\r
+        tintValue.save(output);\r
+        output.write("- Transparency - \n");\r
+        transparencyValue.save(output);\r
+        output.write("- Options - \n");\r
+        output.write("attached: " + attached + "\n");\r
+        output.write("continuous: " + continuous + "\n");\r
+        output.write("aligned: " + aligned + "\n");\r
+        output.write("additive: " + additive + "\n");\r
+        output.write("behind: " + behind + "\n");\r
+    }\r
+\r
+    public void load(BufferedReader reader) throws IOException {\r
+        try {\r
+            name = readString(reader, "name");\r
+            reader.readLine();\r
+            delayValue.load(reader);\r
+            reader.readLine();\r
+            durationValue.load(reader);\r
+            reader.readLine();\r
+            setMinParticleCount(readInt(reader, "minParticleCount"));\r
+            setMaxParticleCount(readInt(reader, "maxParticleCount"));\r
+            reader.readLine();\r
+            emissionValue.load(reader);\r
+            reader.readLine();\r
+            lifeValue.load(reader);\r
+            reader.readLine();\r
+            lifeOffsetValue.load(reader);\r
+            reader.readLine();\r
+            xOffsetValue.load(reader);\r
+            reader.readLine();\r
+            yOffsetValue.load(reader);\r
+            reader.readLine();\r
+            spawnShapeValue.load(reader);\r
+            reader.readLine();\r
+            spawnWidthValue.load(reader);\r
+            reader.readLine();\r
+            spawnHeightValue.load(reader);\r
+            reader.readLine();\r
+            scaleValue.load(reader);\r
+            reader.readLine();\r
+            velocityValue.load(reader);\r
+            reader.readLine();\r
+            angleValue.load(reader);\r
+            reader.readLine();\r
+            rotationValue.load(reader);\r
+            reader.readLine();\r
+            windValue.load(reader);\r
+            reader.readLine();\r
+            gravityValue.load(reader);\r
+            reader.readLine();\r
+            tintValue.load(reader);\r
+            reader.readLine();\r
+            transparencyValue.load(reader);\r
+            reader.readLine();\r
+            attached = readBoolean(reader, "attached");\r
+            continuous = readBoolean(reader, "continuous");\r
+            aligned = readBoolean(reader, "aligned");\r
+            additive = readBoolean(reader, "additive");\r
+            behind = readBoolean(reader, "behind");\r
+        } catch (RuntimeException ex) {\r
+            if (name == null) {\r
+                throw ex;\r
+            }\r
+            throw new RuntimeException("Error parsing emitter: " + name, ex);\r
+        }\r
+    }\r
+\r
+    static String readString(BufferedReader reader, String name) throws IOException {\r
+        String line = reader.readLine();\r
+        if (line == null) {\r
+            throw new IOException("Missing value: " + name);\r
+        }\r
+        return line.substring(line.indexOf(":") + 1).trim();\r
+    }\r
+\r
+    static boolean readBoolean(BufferedReader reader, String name) throws IOException {\r
+        return Boolean.parseBoolean(readString(reader, name));\r
+    }\r
+\r
+    static int readInt(BufferedReader reader, String name) throws IOException {\r
+        return Integer.parseInt(readString(reader, name));\r
+    }\r
+\r
+    static float readFloat(BufferedReader reader, String name) throws IOException {\r
+        return Float.parseFloat(readString(reader, name));\r
+    }\r
+\r
+    public static class ParticleValue {\r
+\r
+        boolean active;\r
+        boolean alwaysActive;\r
+\r
+        public void setAlwaysActive(boolean alwaysActive) {\r
+            this.alwaysActive = alwaysActive;\r
+        }\r
+\r
+        public boolean isAlwaysActive() {\r
+            return alwaysActive;\r
+        }\r
+\r
+        public boolean isActive() {\r
+            return alwaysActive || active;\r
+        }\r
+\r
+        public void setActive(boolean active) {\r
+            this.active = active;\r
+        }\r
+\r
+        public void save(Writer output) throws IOException {\r
+            if (!alwaysActive) {\r
+                output.write("active: " + active + "\n");\r
+            } else {\r
+                active = true;\r
+            }\r
+        }\r
+\r
+        public void load(BufferedReader reader) throws IOException {\r
+            if (!alwaysActive) {\r
+                active = readBoolean(reader, "active");\r
+            } else {\r
+                active = true;\r
+            }\r
+        }\r
+\r
+        public void load(ParticleValue value) {\r
+            active = value.active;\r
+            alwaysActive = value.alwaysActive;\r
+        }\r
+    }\r
+\r
+    static public class NumericValue extends ParticleValue {\r
+\r
+        private float value;\r
+\r
+        public float getValue() {\r
+            return value;\r
+        }\r
+\r
+        public void setValue(float value) {\r
+            this.value = value;\r
+        }\r
+\r
+        public void save(Writer output) throws IOException {\r
+            super.save(output);\r
+            if (!active) {\r
+                return;\r
+            }\r
+            output.write("value: " + value + "\n");\r
+        }\r
+\r
+        public void load(BufferedReader reader) throws IOException {\r
+            super.load(reader);\r
+            if (!active) {\r
+                return;\r
+            }\r
+            value = readFloat(reader, "value");\r
+        }\r
+\r
+        public void load(NumericValue value) {\r
+            super.load(value);\r
+            this.value = value.value;\r
+        }\r
+    }\r
+\r
+    static public class RangedNumericValue extends ParticleValue {\r
+\r
+        private float lowMin, lowMax;\r
+\r
+        public float newLowValue() {\r
+            return lowMin + (lowMax - lowMin) * MathUtils.random();\r
+        }\r
+\r
+        public void setLow(float value) {\r
+            lowMin = value;\r
+            lowMax = value;\r
+        }\r
+\r
+        public void setLow(float min, float max) {\r
+            lowMin = min;\r
+            lowMax = max;\r
+        }\r
+\r
+        public float getLowMin() {\r
+            return lowMin;\r
+        }\r
+\r
+        public void setLowMin(float lowMin) {\r
+            this.lowMin = lowMin;\r
+        }\r
+\r
+        public float getLowMax() {\r
+            return lowMax;\r
+        }\r
+\r
+        public void setLowMax(float lowMax) {\r
+            this.lowMax = lowMax;\r
+        }\r
+\r
+        public void save(Writer output) throws IOException {\r
+            super.save(output);\r
+            if (!active) {\r
+                return;\r
+            }\r
+            output.write("lowMin: " + lowMin + "\n");\r
+            output.write("lowMax: " + lowMax + "\n");\r
+        }\r
+\r
+        public void load(BufferedReader reader) throws IOException {\r
+            super.load(reader);\r
+            if (!active) {\r
+                return;\r
+            }\r
+            lowMin = readFloat(reader, "lowMin");\r
+            lowMax = readFloat(reader, "lowMax");\r
+        }\r
+\r
+        public void load(RangedNumericValue value) {\r
+            super.load(value);\r
+            lowMax = value.lowMax;\r
+            lowMin = value.lowMin;\r
+        }\r
+    }\r
+\r
+    static public class ScaledNumericValue extends RangedNumericValue {\r
+\r
+        private float[] scaling = {1};\r
+        float[] timeline = {0};\r
+        private float highMin, highMax;\r
+        private boolean relative;\r
+\r
+        public float newHighValue() {\r
+            return highMin + (highMax - highMin) * MathUtils.random();\r
+        }\r
+\r
+        public void setHigh(float value) {\r
+            highMin = value;\r
+            highMax = value;\r
+        }\r
+\r
+        public void setHigh(float min, float max) {\r
+            highMin = min;\r
+            highMax = max;\r
+        }\r
+\r
+        public float getHighMin() {\r
+            return highMin;\r
+        }\r
+\r
+        public void setHighMin(float highMin) {\r
+            this.highMin = highMin;\r
+        }\r
+\r
+        public float getHighMax() {\r
+            return highMax;\r
+        }\r
+\r
+        public void setHighMax(float highMax) {\r
+            this.highMax = highMax;\r
+        }\r
+\r
+        public float[] getScaling() {\r
+            return scaling;\r
+        }\r
+\r
+        public void setScaling(float[] values) {\r
+            this.scaling = values;\r
+        }\r
+\r
+        public float[] getTimeline() {\r
+            return timeline;\r
+        }\r
+\r
+        public void setTimeline(float[] timeline) {\r
+            this.timeline = timeline;\r
+        }\r
+\r
+        public boolean isRelative() {\r
+            return relative;\r
+        }\r
+\r
+        public void setRelative(boolean relative) {\r
+            this.relative = relative;\r
+        }\r
+\r
+        public float getScale(float percent) {\r
+            int endIndex = -1;\r
+            float[] timeline = this.timeline;\r
+            int n = timeline.length;\r
+            for (int i = 1; i < n; i++) {\r
+                float t = timeline[i];\r
+                if (t > percent) {\r
+                    endIndex = i;\r
+                    break;\r
+                }\r
+            }\r
+            if (endIndex == -1) {\r
+                return scaling[n - 1];\r
+            }\r
+            float[] scaling = this.scaling;\r
+            int startIndex = endIndex - 1;\r
+            float startValue = scaling[startIndex];\r
+            float startTime = timeline[startIndex];\r
+            return startValue + (scaling[endIndex] - startValue) * ((percent - startTime) / (timeline[endIndex] - startTime));\r
+        }\r
+\r
+        public void save(Writer output) throws IOException {\r
+            super.save(output);\r
+            if (!active) {\r
+                return;\r
+            }\r
+            output.write("highMin: " + highMin + "\n");\r
+            output.write("highMax: " + highMax + "\n");\r
+            output.write("relative: " + relative + "\n");\r
+            output.write("scalingCount: " + scaling.length + "\n");\r
+            for (int i = 0; i < scaling.length; i++) {\r
+                output.write("scaling" + i + ": " + scaling[i] + "\n");\r
+            }\r
+            output.write("timelineCount: " + timeline.length + "\n");\r
+            for (int i = 0; i < timeline.length; i++) {\r
+                output.write("timeline" + i + ": " + timeline[i] + "\n");\r
+            }\r
+        }\r
+\r
+        public void load(BufferedReader reader) throws IOException {\r
+            super.load(reader);\r
+            if (!active) {\r
+                return;\r
+            }\r
+            highMin = readFloat(reader, "highMin");\r
+            highMax = readFloat(reader, "highMax");\r
+            relative = readBoolean(reader, "relative");\r
+            scaling = new float[readInt(reader, "scalingCount")];\r
+            for (int i = 0; i < scaling.length; i++) {\r
+                scaling[i] = readFloat(reader, "scaling" + i);\r
+            }\r
+            timeline = new float[readInt(reader, "timelineCount")];\r
+            for (int i = 0; i < timeline.length; i++) {\r
+                timeline[i] = readFloat(reader, "timeline" + i);\r
+            }\r
+        }\r
+\r
+        public void load(ScaledNumericValue value) {\r
+            super.load(value);\r
+            highMax = value.highMax;\r
+            highMin = value.highMin;\r
+            scaling = new float[value.scaling.length];\r
+            System.arraycopy(value.scaling, 0, scaling, 0, scaling.length);\r
+            timeline = new float[value.timeline.length];\r
+            System.arraycopy(value.timeline, 0, timeline, 0, timeline.length);\r
+            relative = value.relative;\r
+        }\r
+    }\r
+\r
+    static public class GradientColorValue extends ParticleValue {\r
+\r
+        static private float[] temp = new float[4];\r
+        private float[] colors = {1, 1, 1};\r
+        float[] timeline = {0};\r
+\r
+        public GradientColorValue() {\r
+            alwaysActive = true;\r
+        }\r
+\r
+        public float[] getTimeline() {\r
+            return timeline;\r
+        }\r
+\r
+        public void setTimeline(float[] timeline) {\r
+            this.timeline = timeline;\r
+        }\r
+\r
+        public float[] getColors() {\r
+            return colors;\r
+        }\r
+\r
+        public void setColors(float[] colors) {\r
+            this.colors = colors;\r
+        }\r
+\r
+        public float[] getColor(float percent) {\r
+            int startIndex = 0, endIndex = -1;\r
+            float[] timeline = this.timeline;\r
+            int n = timeline.length;\r
+            for (int i = 1; i < n; i++) {\r
+                float t = timeline[i];\r
+                if (t > percent) {\r
+                    endIndex = i;\r
+                    break;\r
+                }\r
+                startIndex = i;\r
+            }\r
+            float startTime = timeline[startIndex];\r
+            startIndex *= 3;\r
+            float r1 = colors[startIndex];\r
+            float g1 = colors[startIndex + 1];\r
+            float b1 = colors[startIndex + 2];\r
+            if (endIndex == -1) {\r
+                temp[0] = r1;\r
+                temp[1] = g1;\r
+                temp[2] = b1;\r
+                return temp;\r
+            }\r
+            float factor = (percent - startTime) / (timeline[endIndex] - startTime);\r
+            endIndex *= 3;\r
+            temp[0] = r1 + (colors[endIndex] - r1) * factor;\r
+            temp[1] = g1 + (colors[endIndex + 1] - g1) * factor;\r
+            temp[2] = b1 + (colors[endIndex + 2] - b1) * factor;\r
+            return temp;\r
+        }\r
+\r
+        public void save(Writer output) throws IOException {\r
+            super.save(output);\r
+            if (!active) {\r
+                return;\r
+            }\r
+            output.write("colorsCount: " + colors.length + "\n");\r
+            for (int i = 0; i < colors.length; i++) {\r
+                output.write("colors" + i + ": " + colors[i] + "\n");\r
+            }\r
+            output.write("timelineCount: " + timeline.length + "\n");\r
+            for (int i = 0; i < timeline.length; i++) {\r
+                output.write("timeline" + i + ": " + timeline[i] + "\n");\r
+            }\r
+        }\r
+\r
+        public void load(BufferedReader reader) throws IOException {\r
+            super.load(reader);\r
+            if (!active) {\r
+                return;\r
+            }\r
+            colors = new float[readInt(reader, "colorsCount")];\r
+            for (int i = 0; i < colors.length; i++) {\r
+                colors[i] = readFloat(reader, "colors" + i);\r
+            }\r
+            timeline = new float[readInt(reader, "timelineCount")];\r
+            for (int i = 0; i < timeline.length; i++) {\r
+                timeline[i] = readFloat(reader, "timeline" + i);\r
+            }\r
+        }\r
+\r
+        public void load(GradientColorValue value) {\r
+            super.load(value);\r
+            colors = new float[value.colors.length];\r
+            System.arraycopy(value.colors, 0, colors, 0, colors.length);\r
+            timeline = new float[value.timeline.length];\r
+            System.arraycopy(value.timeline, 0, timeline, 0, timeline.length);\r
+        }\r
+    }\r
+\r
+    static public class SpawnShapeValue extends ParticleValue {\r
+\r
+        SpawnShape shape = SpawnShape.point;\r
+        boolean edges;\r
+        SpawnEllipseSide side = SpawnEllipseSide.both;\r
+\r
+        public SpawnShape getShape() {\r
+            return shape;\r
+        }\r
+\r
+        public void setShape(SpawnShape shape) {\r
+            this.shape = shape;\r
+        }\r
+\r
+        public boolean isEdges() {\r
+            return edges;\r
+        }\r
+\r
+        public void setEdges(boolean edges) {\r
+            this.edges = edges;\r
+        }\r
+\r
+        public SpawnEllipseSide getSide() {\r
+            return side;\r
+        }\r
+\r
+        public void setSide(SpawnEllipseSide side) {\r
+            this.side = side;\r
+        }\r
+\r
+        public void save(Writer output) throws IOException {\r
+            super.save(output);\r
+            if (!active) {\r
+                return;\r
+            }\r
+            output.write("shape: " + shape + "\n");\r
+            if (shape == SpawnShape.ellipse) {\r
+                output.write("edges: " + edges + "\n");\r
+                output.write("side: " + side + "\n");\r
+            }\r
+        }\r
+\r
+        public void load(BufferedReader reader) throws IOException {\r
+            super.load(reader);\r
+            if (!active) {\r
+                return;\r
+            }\r
+            shape = SpawnShape.valueOf(readString(reader, "shape"));\r
+            if (shape == SpawnShape.ellipse) {\r
+                edges = readBoolean(reader, "edges");\r
+                side = SpawnEllipseSide.valueOf(readString(reader, "side"));\r
+            }\r
+        }\r
+\r
+        public void load(SpawnShapeValue value) {\r
+            super.load(value);\r
+            shape = value.shape;\r
+            edges = value.edges;\r
+            side = value.side;\r
+        }\r
+    }\r
+\r
+    static public enum SpawnShape {\r
+\r
+        point, line, square, ellipse\r
+    }\r
+\r
+    static public enum SpawnEllipseSide {\r
+\r
+        both, top, bottom\r
+    }\r
 }\r
diff --git a/gdx/src/com/badlogic/gdx/graphics/g2d/particle/emitterattributs/GradientColorValue.java b/gdx/src/com/badlogic/gdx/graphics/g2d/particle/emitterattributs/GradientColorValue.java
deleted file mode 100644 (file)
index d96c4fd..0000000
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * To change this template, choose Tools | Templates
- * and open the template in the editor.
- */
-package com.badlogic.gdx.graphics.g2d.particle.emitterattributs;
-
-import java.util.Arrays;
-
-/**
- *
- * @author Daniel Heinrich <dannynullzwo@gmail.com>
- */
-public class GradientColorValue extends ParticleValue {
-
-    private float[] colors = {1, 1, 1};
-    float[] timeline = {0};
-
-    public GradientColorValue() {
-        setAlwaysActive(true);
-    }
-
-    public float[] getTimeline() {
-        return timeline;
-    }
-    
-    public int getTimeLineCount()
-    {
-        return timeline.length;
-    }
-
-    public void setTimeline(float[] timeline) {
-        this.timeline = timeline;
-    }
-
-    public float[] getColors() {
-        return colors;
-    }
-
-    public void setColors(float[] colors) {
-        this.colors = colors;
-    }
-
-    public float[] getColor(float percent) {
-        int startIndex = 0, endIndex = -1;
-        for (int i = 1; i < timeline.length; i++) {
-            float t = timeline[i];
-            if (t > percent) {
-                endIndex = i;
-                break;
-            }
-            startIndex = i;
-        }
-        float startTime = timeline[startIndex];
-        startIndex *= 3;
-        float r1 = colors[startIndex];
-        float g1 = colors[startIndex + 1];
-        float b1 = colors[startIndex + 2];
-        float[] temp = new float[3];
-        if (endIndex == -1) {
-            temp[0] = r1;
-            temp[1] = g1;
-            temp[2] = b1;
-            return temp;
-        }
-        float factor = (percent - startTime) / (timeline[endIndex] - startTime);
-        endIndex *= 3;
-        temp[0] = r1 + (colors[endIndex] - r1) * factor;
-        temp[1] = g1 + (colors[endIndex + 1] - g1) * factor;
-        temp[2] = b1 + (colors[endIndex + 2] - b1) * factor;
-        return temp;
-    }
-//        public void save(Writer output) throws IOException {
-//            super.save(output);
-//            if (!active) {
-//                return;
-//            }
-//            output.write("colorsCount: " + colors.length + "\n");
-//            for (int i = 0; i < colors.length; i++) {
-//                output.write("colors" + i + ": " + colors[i] + "\n");
-//            }
-//            output.write("timelineCount: " + timeline.length + "\n");
-//            for (int i = 0; i < timeline.length; i++) {
-//                output.write("timeline" + i + ": " + timeline[i] + "\n");
-//            }
-//        }
-//
-//        public void load(BufferedReader reader) throws IOException {
-//            super.load(reader);
-//            if (!active) {
-//                return;
-//            }
-//            colors = new float[readInt(reader, "colorsCount")];
-//            for (int i = 0; i < colors.length; i++) {
-//                colors[i] = readFloat(reader, "colors" + i);
-//            }
-//            timeline = new float[readInt(reader, "timelineCount")];
-//            for (int i = 0; i < timeline.length; i++) {
-//                timeline[i] = readFloat(reader, "timeline" + i);
-//            }
-//        }
-//
-//        public void load(GradientColorValue value) {
-//            super.load(value);
-//            colors = new float[value.colors.length];
-//            System.arraycopy(value.colors, 0, colors, 0, colors.length);
-//            timeline = new float[value.timeline.length];
-//            System.arraycopy(value.timeline, 0, timeline, 0, timeline.length);
-//        }
-
-    @Override
-    public GradientColorValue clone() {
-        GradientColorValue n = (GradientColorValue) super.clone();
-        n.colors = Arrays.copyOf(colors, colors.length);
-        n.timeline = Arrays.copyOf(timeline, timeline.length);
-        return n;
-    }
-}
diff --git a/gdx/src/com/badlogic/gdx/graphics/g2d/particle/emitterattributs/NumericValue.java b/gdx/src/com/badlogic/gdx/graphics/g2d/particle/emitterattributs/NumericValue.java
deleted file mode 100644 (file)
index 70de3d7..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * To change this template, choose Tools | Templates
- * and open the template in the editor.
- */
-package com.badlogic.gdx.graphics.g2d.particle.emitterattributs;
-
-/**
- *
- * @author Daniel Heinrich <dannynullzwo@gmail.com>
- */
-public class NumericValue extends ParticleValue {
-
-    private float value;
-
-    public float getValue() {
-        return value;
-    }
-
-    public void setValue(float value) {
-        this.value = value;
-    }
-//        public void save(Writer output) throws IOException {
-//            super.save(output);
-//            if (!active) {
-//                return;
-//            }
-//            output.write("value: " + value + "\n");
-//        }
-//
-//        public void load(BufferedReader reader) throws IOException {
-//            super.load(reader);
-//            if (!active) {
-//                return;
-//            }
-//            value = readFloat(reader, "value");
-//        }
-//    
-
-    @Override
-    public NumericValue clone() {
-        return (NumericValue) super.clone();
-    }
-}
\ No newline at end of file
diff --git a/gdx/src/com/badlogic/gdx/graphics/g2d/particle/emitterattributs/ParticleValue.java b/gdx/src/com/badlogic/gdx/graphics/g2d/particle/emitterattributs/ParticleValue.java
deleted file mode 100644 (file)
index 726408e..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * To change this template, choose Tools | Templates
- * and open the template in the editor.
- */
-package com.badlogic.gdx.graphics.g2d.particle.emitterattributs;
-
-/**
- *
- * @author Daniel Heinrich <dannynullzwo@gmail.com>
- */
-public class ParticleValue implements Cloneable{
-
-    private boolean active;
-    private boolean alwaysActive;
-
-    public void setAlwaysActive(boolean alwaysActive) {
-        this.alwaysActive = alwaysActive;
-    }
-
-    public boolean isAlwaysActive() {
-        return alwaysActive;
-    }
-
-    public boolean isActive() {
-        return alwaysActive || active;
-    }
-
-    public void setActive(boolean active) {
-        this.active = active;
-    }
-
-//    public void save(Writer output) throws IOException {
-//        if (!alwaysActive) {
-//            output.write("active: " + active + "\n");
-//        } else {
-//            active = true;
-//        }
-//    }
-//
-//    public void load(BufferedReader reader) throws IOException {
-//        if (!alwaysActive) {
-//            active = readBoolean(reader, "active");
-//        } else {
-//            active = true;
-//        }
-//    }
-//
-
-    @Override
-    public ParticleValue clone(){
-        try {
-            return (ParticleValue) super.clone();
-        } catch (CloneNotSupportedException ex) {
-            throw new UnsupportedOperationException();
-        }
-    }
-    
-}
diff --git a/gdx/src/com/badlogic/gdx/graphics/g2d/particle/emitterattributs/RangedNumericValue.java b/gdx/src/com/badlogic/gdx/graphics/g2d/particle/emitterattributs/RangedNumericValue.java
deleted file mode 100644 (file)
index 1cc8719..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * To change this template, choose Tools | Templates
- * and open the template in the editor.
- */
-package com.badlogic.gdx.graphics.g2d.particle.emitterattributs;
-
-import com.badlogic.gdx.math.MathUtils;
-
-/**
- *
- * @author Daniel Heinrich <dannynullzwo@gmail.com>
- */
-public class RangedNumericValue extends ParticleValue {
-
-    private float min, max;
-
-    public float newValue() {
-        return min + (max - min) * MathUtils.random();
-    }
-
-    public void set(float value) {
-        min = value;
-        max = value;
-    }
-
-    public void set(float min, float max) {
-        setMin(min);
-        setMax(max);
-    }
-
-    public float getMin() {
-        return min;
-    }
-
-    public void setMin(float min) {
-        this.min = min;
-    }
-
-    public float getMax() {
-        return max;
-    }
-
-    public void setMax(float max) {
-        this.max = max;
-    }
-//        public void save(Writer output) throws IOException {
-//            super.save(output);
-//            if (!active) {
-//                return;
-//            }
-//            output.write("lowMin: " + lowMin + "\n");
-//            output.write("lowMax: " + lowMax + "\n");
-//        }
-//
-//        public void load(BufferedReader reader) throws IOException {
-//            super.load(reader);
-//            if (!active) {
-//                return;
-//            }
-//            lowMin = readFloat(reader, "lowMin");
-//            lowMax = readFloat(reader, "lowMax");
-//        }
-
-    @Override
-    public RangedNumericValue clone() {
-        return (RangedNumericValue) super.clone();
-    }
-}
diff --git a/gdx/src/com/badlogic/gdx/graphics/g2d/particle/emitterattributs/ScaledNumericValue.java b/gdx/src/com/badlogic/gdx/graphics/g2d/particle/emitterattributs/ScaledNumericValue.java
deleted file mode 100644 (file)
index c868f36..0000000
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * To change this template, choose Tools | Templates
- * and open the template in the editor.
- */
-package com.badlogic.gdx.graphics.g2d.particle.emitterattributs;
-
-import java.util.Arrays;
-
-/**
- *
- * @author Daniel Heinrich <dannynullzwo@gmail.com>
- */
-public class ScaledNumericValue extends ParticleValue {
-
-    private float[] scaling = {1};
-    float[] timeline = {0};
-    private boolean relative;
-    private RangedNumericValue low, heigh;
-
-    public ScaledNumericValue() {
-        low = new RangedNumericValue();
-        heigh = new RangedNumericValue();
-    }
-
-    public float newHighValue() {
-        return heigh.newValue();
-    }
-
-    public float newLowValue() {
-        return low.newValue();
-    }
-
-    public RangedNumericValue getHeigh() {
-        return heigh;
-    }
-
-    public RangedNumericValue getLow() {
-        return low;
-    }
-
-    public float[] getScaling() {
-        return scaling;
-    }
-
-    public void setScaling(float[] values) {
-        this.scaling = values;
-    }
-
-    public float[] getTimeline() {
-        return timeline;
-    }
-    
-    public int getTimeLineCount()
-    {
-        return timeline.length;
-    }
-
-    public void setTimeline(float[] timeline) {
-        this.timeline = timeline;
-    }
-
-    public boolean isRelative() {
-        return relative;
-    }
-
-    public void setRelative(boolean relative) {
-        this.relative = relative;
-    }
-
-    public float getScale(float percent) {
-        int endIndex = -1;
-        for (int i = 1; i < timeline.length; i++) {
-            if (timeline[i] > percent) {
-                endIndex = i;
-                break;
-            }
-        }
-        if (endIndex == -1) {
-            return scaling[timeline.length - 1];
-        }
-        int startIndex = endIndex - 1;
-        float startValue = scaling[startIndex];
-        float startTime = timeline[startIndex];
-        return startValue + (scaling[endIndex] - startValue) * ((percent - startTime) / (timeline[endIndex] - startTime));
-    }
-//        public void save(Writer output) throws IOException {
-//            super.save(output);
-//            if (!active) {
-//                return;
-//            }
-//            output.write("highMin: " + highMin + "\n");
-//            output.write("highMax: " + highMax + "\n");
-//            output.write("relative: " + relative + "\n");
-//            output.write("scalingCount: " + scaling.length + "\n");
-//            for (int i = 0; i < scaling.length; i++) {
-//                output.write("scaling" + i + ": " + scaling[i] + "\n");
-//            }
-//            output.write("timelineCount: " + timeline.length + "\n");
-//            for (int i = 0; i < timeline.length; i++) {
-//                output.write("timeline" + i + ": " + timeline[i] + "\n");
-//            }
-//        }
-//
-//        public void load(BufferedReader reader) throws IOException {
-//            super.load(reader);
-//            if (!active) {
-//                return;
-//            }
-//            highMin = readFloat(reader, "highMin");
-//            highMax = readFloat(reader, "highMax");
-//            relative = readBoolean(reader, "relative");
-//            scaling = new float[readInt(reader, "scalingCount")];
-//            for (int i = 0; i < scaling.length; i++) {
-//                scaling[i] = readFloat(reader, "scaling" + i);
-//            }
-//            timeline = new float[readInt(reader, "timelineCount")];
-//            for (int i = 0; i < timeline.length; i++) {
-//                timeline[i] = readFloat(reader, "timeline" + i);
-//            }
-//        }
-
-    @Override
-    public ScaledNumericValue clone() {
-        ScaledNumericValue n= (ScaledNumericValue) super.clone();
-        n.low = low.clone();
-        n.heigh = heigh.clone();
-        n.scaling = Arrays.copyOf(scaling, scaling.length);
-        n.timeline = Arrays.copyOf(timeline, timeline.length);
-        return n;
-    }
-}
diff --git a/gdx/src/com/badlogic/gdx/graphics/g2d/particle/emitterattributs/SpawnShapeValue.java b/gdx/src/com/badlogic/gdx/graphics/g2d/particle/emitterattributs/SpawnShapeValue.java
deleted file mode 100644 (file)
index b91feb8..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * To change this template, choose Tools | Templates
- * and open the template in the editor.
- */
-package com.badlogic.gdx.graphics.g2d.particle.emitterattributs;
-
-/**
- *
- * @author Daniel Heinrich <dannynullzwo@gmail.com>
- */
-public class SpawnShapeValue extends ParticleValue {
-
-    public static enum SpawnShape {
-
-        point, line, square, ellipse
-    }
-
-    public static enum SpawnEllipseSide {
-
-        both, top, bottom
-    }
-    private SpawnShape shape = SpawnShape.point;
-    private boolean edges;
-    private SpawnEllipseSide side = SpawnEllipseSide.both;
-
-    public SpawnShape getShape() {
-        return shape;
-    }
-
-    public void setShape(SpawnShape shape) {
-        this.shape = shape;
-    }
-
-    public boolean isEdges() {
-        return edges;
-    }
-
-    public void setEdges(boolean edges) {
-        this.edges = edges;
-    }
-
-    public SpawnEllipseSide getSide() {
-        return side;
-    }
-
-    public void setSide(SpawnEllipseSide side) {
-        this.side = side;
-    }
-//        public void save(Writer output) throws IOException {
-//            super.save(output);
-//            if (!active) {
-//                return;
-//            }
-//            output.write("shape: " + shape + "\n");
-//            if (shape == SpawnShape.ellipse) {
-//                output.write("edges: " + edges + "\n");
-//                output.write("side: " + side + "\n");
-//            }
-//        }
-//
-//        public void load(BufferedReader reader) throws IOException {
-//            super.load(reader);
-//            if (!active) {
-//                return;
-//            }
-//            shape = SpawnShape.valueOf(readString(reader, "shape"));
-//            if (shape == SpawnShape.ellipse) {
-//                edges = readBoolean(reader, "edges");
-//                side = SpawnEllipseSide.valueOf(readString(reader, "side"));
-//            }
-//        }
-//
-
-    @Override
-    public SpawnShapeValue clone() {
-        return (SpawnShapeValue) super.clone();
-    }
-}