Added an immobile particle type; updated UI, engines and tests; ticket #7 is done.
[desert.git] / src / org / sourceforge / desert / ParticleEngineCustomImplementation.java
blob175b2b757fa01e17d0ed29f7925482d4c34604d4
1 /*
2 * Copyright (c) 2010 The Desert team
4 * Permission is hereby granted, free of charge, to any person
5 * obtaining a copy of this software and associated documentation
6 * files (the "Software"), to deal in the Software without
7 * restriction, including without limitation the rights to use,
8 * copy, modify, merge, publish, distribute, sublicense, and/or sell
9 * copies of the Software, and to permit persons to whom the
10 * Software is furnished to do so, subject to the following
11 * conditions:
13 * The above copyright notice and this permission notice shall be
14 * included in all copies or substantial portions of the Software.
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23 * OTHER DEALINGS IN THE SOFTWARE.
26 package org.sourceforge.desert;
28 import java.awt.Dimension;
29 import java.util.ArrayList;
30 import java.util.Iterator;
31 import java.util.List;
33 import static java.lang.Math.*;
34 import org.sourceforge.desert.Particle.Type;
36 /**
38 * @author codistmonk (creation 2010-04-13)
40 public class ParticleEngineCustomImplementation implements ParticleEngine {
42 private final List<Particle> particles;
44 private float gravity;
46 private Dimension boardSize;
48 public ParticleEngineCustomImplementation() {
49 this.particles = new ArrayList<Particle>();
50 this.gravity = DEFAULT_GRAVITY;
53 /**
54 * Updates the positions and speeds of all the particles.
55 * @param deltaTime
56 * <br>Range: <code>[0 .. Float.POSITIVE_INFINITY[</code>
58 @Override
59 public final void update(final float deltaTime) {
60 for (final Particle particle : this.particles) {
61 final ParticleDefaultImplementation p = (ParticleDefaultImplementation) particle;
63 if (particle.getType().isMobile()) {
64 // Effect of gravity on speed
65 p.setSpeedY(particle.getSpeedY() + this.getGravity() * deltaTime);
67 this.moveUntilCollision(p, deltaTime);
70 // Make sure that the particle doesn't get out if bounds are set
71 this.constrain(p);
75 @Override
76 public final float getGravity() {
77 return this.gravity;
80 @Override
81 public final void setGravity(final float gravity) {
82 this.gravity = gravity;
85 /**
86 * Moves the particle according to its speed and deltaTime, but stop just before colliding
87 * with another particle.
88 * When a particle is stopped before a collision, is speed is set to 0F.
89 * @param particle
90 * <br>Should not be null
91 * @param deltaTime
92 * <br>Range: <code>[0 .. Float.POSITIVE_INFINITY[</code>
94 private final void moveUntilCollision(final ParticleDefaultImplementation particle, final float deltaTime) {
95 float deltaX = particle.getSpeedX() * deltaTime;
96 float deltaY = particle.getSpeedY() * deltaTime;
97 final int pixelCount = (int) ceil(max(abs(deltaX), abs(deltaY)));
99 if (pixelCount != 0) {
100 // Instead of moving the particle to its destination in one step,
101 // we are going to move it in as many steps as there are pixels
102 deltaX /= pixelCount;
103 deltaY /= pixelCount;
105 boolean collision = false;
107 for (int i = 0; i < pixelCount && !collision; ++i) {
108 move(particle, deltaX, deltaY);
110 // The goal is to go as far as possible without overlapping another particle
111 // Only the particles that have already been moved are tested for collision
112 // XXX this arbitrary and doesn't always prevent particles from overlapping
113 final Iterator<Particle> particleIterator = this.particles.iterator();
114 ParticleDefaultImplementation otherParticle = (ParticleDefaultImplementation) next(particleIterator);
116 while (particle != otherParticle && otherParticle != null && !(collision = collision(particle, otherParticle))) {
117 otherParticle = (ParticleDefaultImplementation) next(particleIterator);
121 if (collision) {
122 move(particle, -deltaX, -deltaY);
123 particle.setSpeedX(0F);
124 particle.setSpeedY(0F);
130 * Constrains the particle inside the bounding rectangle if it exists.
131 * @param particle
132 * <br>Should not be null
133 * <br>Input-output parameter
135 private final void constrain(final ParticleDefaultImplementation particle) {
136 if (this.getBoardSize() != null) {
137 // Left bound
138 if (particle.getX() < 0F) {
139 particle.setX(0F);
140 particle.setSpeedX(0F);
142 // Top bound
143 if (particle.getY() < 0F) {
144 particle.setY(0F);
145 particle.setSpeedY(0F);
147 // Right bound
148 if (particle.getX() >= this.getBoardSize().width) {
149 particle.setX(this.getBoardSize().width - 1F);
150 particle.setSpeedX(0F);
152 // Bottom bound
153 if (particle.getY() >= this.getBoardSize().height) {
154 particle.setY(this.getBoardSize().height - 1);
155 particle.setSpeedY(0F);
162 * @param iterator
163 * <br>Should no be null
164 * <br>Input-output parameter
165 * @return the next available element if there is one, or else null
166 * <br>A possibly null value
167 * <br>A reference
169 private static final <T> T next(final Iterator<T> iterator) {
170 return iterator.hasNext() ? iterator.next() : null;
174 * TODO move this function into a ParticleUtilities class, or ParticleDefaultImplementation itself
175 * @param particle
176 * <br>Should not be null
177 * @param deltaX
178 * <br>Range: <code>]Float.NEGATIVE_INFINITY .. Float.POSITIVE_INFINITY[</code>
179 * @param deltaY
180 * <br>Range: <code>]Float.NEGATIVE_INFINITY .. Float.POSITIVE_INFINITY[</code>
182 private static final void move(final ParticleDefaultImplementation particle, final float deltaX, final float deltaY) {
183 particle.setX(particle.getX() + deltaX);
184 particle.setY(particle.getY() + deltaY);
188 * TODO move this function into a ParticleUtilities class
189 * @param particle1
190 * <br>Should not be null
191 * @param particle2
192 * <br>Should not be null
193 * @return <code>distance(particle1, particle2) <= particle1.getRadius() + particle2.getRadius()</code>
194 * <br>A non-null value
196 private static final Boolean collision(final Particle particle1, final Particle particle2) {
197 return distance(particle1, particle2) <= particle1.getType().getRadius() + particle2.getType().getRadius();
201 * TODO move this function into a ParticleUtilities class
202 * @param particle1
203 * <br>Should not be null
204 * @param particle2
205 * <br>Should not be null
206 * @return <code>max(abs(particle1.getX() - particle2.getX()), abs(particle1.getY() - particle2.getY()))</code>
207 * <br>A non-null value
208 * <br>Range: <code>[0 .. Float.POSITIVE_INFINITY[</code>
210 private static final float distance(final Particle particle1, final Particle particle2) {
211 return max(abs(particle1.getX() - particle2.getX()), abs(particle1.getY() - particle2.getY()));
214 @Override
215 public final Dimension getBoardSize() {
216 return this.boardSize;
219 @Override
220 public final void setBoardSize(final Dimension size) {
221 this.boardSize = size;
224 @Override
225 public final void addParticle(final Type type, final float x, final float y, final float speedX, final float speedY) {
226 this.particles.add(new ParticleDefaultImplementation(type, x, y, speedX, speedY));
229 @Override
230 public int getParticleCount() {
231 return this.particles.size();
234 @Override
235 public Iterator<Particle> iterator() {
236 return ((Iterable<Particle>) this.particles).iterator();