From b231358de19d902155a8700d315007e9efe052b4 Mon Sep 17 00:00:00 2001 From: codistmonk Date: Tue, 20 Apr 2010 15:14:22 +0000 Subject: [PATCH] Added an immobile particle type; updated UI, engines and tests; ticket #7 is done. git-svn-id: https://desert.svn.sourceforge.net/svnroot/desert@71 cbff3641-ea5c-438d-a1bd-c9f42921e016 --- src/org/sourceforge/desert/DesertPanel.java | 10 +- src/org/sourceforge/desert/DrawingBoard.java | 9 +- src/org/sourceforge/desert/Particle.java | 24 ++++- .../desert/ParticleEngineCustomImplementation.java | 9 +- .../desert/ParticleEngineOde4JImplementation.java | 101 ++++++++++++--------- .../desert/resources/messages.properties | 3 +- .../desert/AbstractParticleEngineTestBase.java | 101 ++++++++++++++++----- 7 files changed, 174 insertions(+), 83 deletions(-) diff --git a/src/org/sourceforge/desert/DesertPanel.java b/src/org/sourceforge/desert/DesertPanel.java index 897c2f8..c3f5870 100644 --- a/src/org/sourceforge/desert/DesertPanel.java +++ b/src/org/sourceforge/desert/DesertPanel.java @@ -167,9 +167,16 @@ public class DesertPanel extends Panel { this.add(resourceBundle.getString(type.toString())); } + this.updateDrawingBoardParticleType(); + this.addItemListener(this.new ItemHandler()); } + final void updateDrawingBoardParticleType() { + DesertPanel.this.getDrawingBoard().setParticleType( + Particle.Type.values()[ParticleTypeChoice.this.getSelectedIndex()]); + } + /** * * @author andrew (creation 2010-04-17) @@ -179,8 +186,7 @@ public class DesertPanel extends Panel { @Override public final void itemStateChanged(final ItemEvent event) { if (event.getStateChange() == ItemEvent.SELECTED) { - DesertPanel.this.getDrawingBoard().setParticleType( - Particle.Type.values()[ParticleTypeChoice.this.getSelectedIndex()]); + ParticleTypeChoice.this.updateDrawingBoardParticleType(); } } diff --git a/src/org/sourceforge/desert/DrawingBoard.java b/src/org/sourceforge/desert/DrawingBoard.java index c1c6059..1fe39fa 100644 --- a/src/org/sourceforge/desert/DrawingBoard.java +++ b/src/org/sourceforge/desert/DrawingBoard.java @@ -226,19 +226,16 @@ public class DrawingBoard extends Canvas implements ActionListener { /** * * @param x - *
Range: [Float.NEGATIVE_INFINITY .. Float.POSITIVE_INFINITY] + *
Range: ]Float.NEGATIVE_INFINITY .. Float.POSITIVE_INFINITY[ * @param y - *
Range: [Float.NEGATIVE_INFINITY .. Float.POSITIVE_INFINITY] + *
Range: ]Float.NEGATIVE_INFINITY .. Float.POSITIVE_INFINITY[ */ private final void addRandomParticle(final float x, final float y) { - final float orientation = (float) (Math.random() * 2F * Math.PI); - DrawingBoard.this.addParticle(x, y, (float) (Math.cos(orientation) * INITIAL_SPEED), (float) (Math.sin(orientation) * INITIAL_SPEED)); + DrawingBoard.this.addParticle(x, y, 0F, 0F); } } - private static final float INITIAL_SPEED = 4F; - private static final int NUMBER_OF_PARTICLES_CREATED_DURING_A_MOUSE_EVENT = 1; } diff --git a/src/org/sourceforge/desert/Particle.java b/src/org/sourceforge/desert/Particle.java index 9d2a89d..db9e26f 100644 --- a/src/org/sourceforge/desert/Particle.java +++ b/src/org/sourceforge/desert/Particle.java @@ -74,11 +74,13 @@ public interface Particle { */ public static enum Type { - SAND(2F, 1F, new Color(255, 255, 0)), + IMMOBILE(IMMOBILE_MASS, DEFAULT_RADIUS, Color.WHITE), - WATER(1F, 1F, new Color(0, 0, 255)), - - ROCK(5F, 1F, Color.LIGHT_GRAY); + ROCK(5F, DEFAULT_RADIUS, Color.LIGHT_GRAY), + + SAND(2F, DEFAULT_RADIUS, Color.YELLOW), + + WATER(1F, DEFAULT_RADIUS, Color.BLUE); private final float mass; @@ -96,7 +98,7 @@ public interface Particle { *
Can be null *
Reference parameter */ - Type(final float mass, final float radius, final Color color) { + private Type(final float mass, final float radius, final Color color) { this.mass = mass; this.radius = radius; this.color = color; @@ -104,6 +106,14 @@ public interface Particle { /** * + * @return true iff the mass is non-null and finite + */ + public final boolean isMobile() { + return this.getMass() != 0F && !Float.isInfinite(this.getMass()); + } + + /** + * * @return *
A possibly null value *
A reference @@ -132,4 +142,8 @@ public interface Particle { } + public static final float DEFAULT_RADIUS = 0.5F; + + public static final float IMMOBILE_MASS = Float.POSITIVE_INFINITY; + } diff --git a/src/org/sourceforge/desert/ParticleEngineCustomImplementation.java b/src/org/sourceforge/desert/ParticleEngineCustomImplementation.java index 01a4c75..175b2b7 100644 --- a/src/org/sourceforge/desert/ParticleEngineCustomImplementation.java +++ b/src/org/sourceforge/desert/ParticleEngineCustomImplementation.java @@ -59,10 +59,13 @@ public class ParticleEngineCustomImplementation implements ParticleEngine { public final void update(final float deltaTime) { for (final Particle particle : this.particles) { final ParticleDefaultImplementation p = (ParticleDefaultImplementation) particle; - // Effect of gravity on speed - p.setSpeedY(particle.getSpeedY() + this.getGravity() * deltaTime); + + if (particle.getType().isMobile()) { + // Effect of gravity on speed + p.setSpeedY(particle.getSpeedY() + this.getGravity() * deltaTime); - this.moveUntilCollision(p, deltaTime); + this.moveUntilCollision(p, deltaTime); + } // Make sure that the particle doesn't get out if bounds are set this.constrain(p); diff --git a/src/org/sourceforge/desert/ParticleEngineOde4JImplementation.java b/src/org/sourceforge/desert/ParticleEngineOde4JImplementation.java index fb41185..a59edcc 100644 --- a/src/org/sourceforge/desert/ParticleEngineOde4JImplementation.java +++ b/src/org/sourceforge/desert/ParticleEngineOde4JImplementation.java @@ -53,8 +53,6 @@ public class ParticleEngineOde4JImplementation implements ParticleEngine { private final CollisionHandler collisionHandler; - private final List bodies; - private final ParticleIterator particleIterator; private DPlane bottomBorder; @@ -70,7 +68,6 @@ public class ParticleEngineOde4JImplementation implements ParticleEngine { this.space = OdeHelper.createHashSpace(); this.jointGroup = OdeHelper.createJointGroup(); this.collisionHandler = new CollisionHandler(); - this.bodies = new ArrayList(); this.particleIterator = new ParticleIterator(); this.setGravity(DEFAULT_GRAVITY); @@ -109,25 +106,27 @@ public class ParticleEngineOde4JImplementation implements ParticleEngine { @Override public final void addParticle(final Type type, final float x, final float y, final float speedX, final float speedY) { - final DBody body = OdeHelper.createBody(this.getWorld()); - final DMass mass = OdeHelper.createMass(); final DSphere sphere = OdeHelper.createSphere(this.space, type.getRadius()); - mass.setSphereTotal(type.getMass(), type.getRadius()); - - sphere.setBody(body); + if (type.isMobile()) { + final DBody body = OdeHelper.createBody(this.getWorld()); + final DMass mass = OdeHelper.createMass(); - body.setMass(mass); - body.setData(type); - body.setPosition(x, y, 0.0); - body.setLinearVel(speedX, speedY, 0.0); - - this.bodies.add(body); + mass.setSphereTotal(type.getMass(), type.getRadius()); + + sphere.setBody(body); + + body.setMass(mass); + body.setLinearVel(speedX, speedY, 0.0); + } + + sphere.setData(type); + sphere.setPosition(x, y, 0.0); } @Override public final int getParticleCount() { - return this.bodies.size(); + return this.getSpace().getNumGeoms() - (this.areBordersDefined() ? 4 : 0); } @Override @@ -136,7 +135,7 @@ public class ParticleEngineOde4JImplementation implements ParticleEngine { // In the Ode examples, the structures in CollisionHandler // are recreated at each step. I decided to reuse them instead, // but I don't know if it is correct to do so. - this.space.collide(null, this.collisionHandler); + this.getSpace().collide(null, this.collisionHandler); this.getWorld().quickStep(SIMULATION_STEP); @@ -154,6 +153,16 @@ public class ParticleEngineOde4JImplementation implements ParticleEngine { return this.world; } + /** + * + * @return + *
A non-null value + *
A reference + */ + final DSpace getSpace() { + return this.space; + } + private final void removeBorders() { this.bottomBorder.destroy(); this.leftBorder.destroy(); @@ -172,10 +181,10 @@ public class ParticleEngineOde4JImplementation implements ParticleEngine { *
Should not be null */ private final void createBorders(final Dimension size) { - this.bottomBorder = OdeHelper.createPlane(this.space, 0.0, 1.0, 0.0, 0.0); - this.leftBorder = OdeHelper.createPlane(this.space, 1.0, 0.0, 0.0, 0.0); - this.topBorder = OdeHelper.createPlane(this.space, 0.0, -1.0, 0.0, -size.height); - this.rightBorder = OdeHelper.createPlane(this.space, -1.0, 0.0, 0.0, -size.width); + this.bottomBorder = OdeHelper.createPlane(this.getSpace(), 0.0, 1.0, 0.0, 0.0); + this.leftBorder = OdeHelper.createPlane(this.getSpace(), 1.0, 0.0, 0.0, 0.0); + this.topBorder = OdeHelper.createPlane(this.getSpace(), 0.0, -1.0, 0.0, -size.height); + this.rightBorder = OdeHelper.createPlane(this.getSpace(), -1.0, 0.0, 0.0, -size.width); } @Override @@ -187,16 +196,6 @@ public class ParticleEngineOde4JImplementation implements ParticleEngine { /** * - * @return - *
A new value - *
A non-null value - */ - final ListIterator createBodyIterator() { - return this.bodies.listIterator(); - } - - /** - * * @return true iff setBoardSize() has last been called with a non-null parameter */ private final boolean areBordersDefined() { @@ -251,24 +250,29 @@ public class ParticleEngineOde4JImplementation implements ParticleEngine { private final ParticleDefaultImplementation particle; - private ListIterator bodyIterator; + private int geomIndex; public ParticleIterator() { this.particle = new ParticleDefaultImplementation(); } public final void reset() { - this.bodyIterator = ParticleEngineOde4JImplementation.this.createBodyIterator(); + this.geomIndex = 0; } @Override public final boolean hasNext() { - return this.bodyIterator.hasNext(); + this.ignoreNonparticles(); + + return this.geomIndex < ParticleEngineOde4JImplementation.this.getSpace().getNumGeoms(); } @Override public final Particle next() { - return initializeParticleWithBody(this.particle, this.bodyIterator.next()); + this.ignoreNonparticles(); + + return initializeParticleWithGeom(this.particle, + ParticleEngineOde4JImplementation.this.getSpace().getGeom(this.geomIndex++)); } @Override @@ -276,6 +280,13 @@ public class ParticleEngineOde4JImplementation implements ParticleEngine { throw new UnsupportedOperationException("Not supported."); } + private final void ignoreNonparticles() { + while (this.geomIndex < ParticleEngineOde4JImplementation.this.getSpace().getNumGeoms() && + !(ParticleEngineOde4JImplementation.this.getSpace().getGeom(this.geomIndex).getData() instanceof Particle.Type)) { + ++this.geomIndex; + } + } + } private static final int MAX_CONTACTS = 1; @@ -292,17 +303,25 @@ public class ParticleEngineOde4JImplementation implements ParticleEngine { * @param particle *
Should not be null *
Input-output parameter - * @param body + * @param geom *
Should not be null * @return particle *
A non-null value */ - static final ParticleDefaultImplementation initializeParticleWithBody(final ParticleDefaultImplementation particle, final DBody body) { - particle.setType((Type) body.getData()); - particle.setX((float) body.getPosition().get0()); - particle.setY((float) body.getPosition().get1()); - particle.setSpeedX((float) body.getLinearVel().get0()); - particle.setSpeedY((float) body.getLinearVel().get1()); + static final ParticleDefaultImplementation initializeParticleWithGeom(final ParticleDefaultImplementation particle, final DGeom geom) { + particle.setType((Type) geom.getData()); + + particle.setX((float) geom.getPosition().get0()); + particle.setY((float) geom.getPosition().get1()); + + if (particle.getType().isMobile()) { + particle.setSpeedX((float) geom.getBody().getLinearVel().get0()); + particle.setSpeedY((float) geom.getBody().getLinearVel().get1()); + } + else { + particle.setSpeedX(0F); + particle.setSpeedY(0F); + } return particle; } diff --git a/src/org/sourceforge/desert/resources/messages.properties b/src/org/sourceforge/desert/resources/messages.properties index 7e06d37..045c95e 100644 --- a/src/org/sourceforge/desert/resources/messages.properties +++ b/src/org/sourceforge/desert/resources/messages.properties @@ -1,3 +1,4 @@ +IMMOBILE = Immobile +ROCK = Rock SAND = Sand WATER = Water -ROCK = Rock \ No newline at end of file diff --git a/test/org/sourceforge/desert/AbstractParticleEngineTestBase.java b/test/org/sourceforge/desert/AbstractParticleEngineTestBase.java index 5ed7976..d578db9 100644 --- a/test/org/sourceforge/desert/AbstractParticleEngineTestBase.java +++ b/test/org/sourceforge/desert/AbstractParticleEngineTestBase.java @@ -26,7 +26,9 @@ package org.sourceforge.desert; import java.awt.Dimension; +import java.util.HashSet; import java.util.Iterator; +import java.util.Set; import org.junit.Test; import static org.junit.Assert.*; @@ -38,6 +40,27 @@ import static org.junit.Assert.*; public abstract class AbstractParticleEngineTestBase { @Test + public final void testImmobileParticleNotMovedByGravity() { + final float x = 2F; + final float y = 3F; + final float deltaTime = 5F; + final ParticleEngine particleEngine = this.createParticleEngine(); + + particleEngine.setGravity(ParticleEngine.DEFAULT_GRAVITY); + particleEngine.setBoardSize(null); + particleEngine.addParticle(IMMOBILE_TYPE, x, y, 0F, 0F); + update(particleEngine, deltaTime, SIMULATION_STEP); + + final Particle particle = particleEngine.iterator().next(); + + assertEquals(IMMOBILE_TYPE, particle.getType()); + assertEquals(x, particle.getX(), DELTA); + assertEquals(y, particle.getY(), DELTA); + assertEquals(0F, particle.getSpeedX(), DELTA); + assertEquals(0F, particle.getSpeedY(), DELTA); + } + + @Test public final void testGravityInitializedToDefault() { final ParticleEngine particleEngine = this.createParticleEngine(); @@ -73,8 +96,8 @@ public abstract class AbstractParticleEngineTestBase { assertNull("board size should have been null but was " + particleEngine.getBoardSize(), particleEngine.getBoardSize()); particleEngine.setGravity(-0.1F); - particleEngine.addParticle(Particle.Type.values()[0], x, y, speedX, speedY); - update(particleEngine, deltaTime, 1F / 60F); + particleEngine.addParticle(MOBILE_TYPE_1, x, y, speedX, speedY); + update(particleEngine, deltaTime, SIMULATION_STEP); final Particle particle = particleEngine.iterator().next(); @@ -97,8 +120,8 @@ public abstract class AbstractParticleEngineTestBase { particleEngine.setGravity(0F); particleEngine.setBoardSize(new Dimension(width, height)); - particleEngine.addParticle(Particle.Type.values()[0], x, y, speedX, speedY); - update(particleEngine, deltaTime, 1F / 60F); + particleEngine.addParticle(MOBILE_TYPE_1, x, y, speedX, speedY); + update(particleEngine, deltaTime, SIMULATION_STEP); final Particle particle = particleEngine.iterator().next(); @@ -118,8 +141,8 @@ public abstract class AbstractParticleEngineTestBase { particleEngine.setGravity(0F); particleEngine.setBoardSize(new Dimension(width, height)); - particleEngine.addParticle(Particle.Type.values()[0], x, y, speedX, speedY); - update(particleEngine, deltaTime, 1F / 60F); + particleEngine.addParticle(MOBILE_TYPE_1, x, y, speedX, speedY); + update(particleEngine, deltaTime, SIMULATION_STEP); final Particle particle = particleEngine.iterator().next(); @@ -139,8 +162,8 @@ public abstract class AbstractParticleEngineTestBase { particleEngine.setGravity(0F); particleEngine.setBoardSize(new Dimension(width, height)); - particleEngine.addParticle(Particle.Type.values()[0], x, y, speedX, speedY); - update(particleEngine, deltaTime, 1F / 60F); + particleEngine.addParticle(MOBILE_TYPE_1, x, y, speedX, speedY); + update(particleEngine, deltaTime, SIMULATION_STEP); final Particle particle = particleEngine.iterator().next(); @@ -160,8 +183,8 @@ public abstract class AbstractParticleEngineTestBase { particleEngine.setGravity(0F); particleEngine.setBoardSize(new Dimension(width, height)); - particleEngine.addParticle(Particle.Type.values()[0], x, y, speedX, speedY); - update(particleEngine, deltaTime, 1F / 60F); + particleEngine.addParticle(MOBILE_TYPE_1, x, y, speedX, speedY); + update(particleEngine, deltaTime, SIMULATION_STEP); final Particle particle = particleEngine.iterator().next(); @@ -236,16 +259,16 @@ public abstract class AbstractParticleEngineTestBase { public final void testUpdate() { final ParticleEngine particleEngine = this.createParticleEngine(); final ParticleDefaultImplementation particle1 = new ParticleDefaultImplementation( - Particle.Type.values()[0 % Particle.Type.values().length], 2F, 3F, 5F, 7F); + MOBILE_TYPE_1, 2F, 3F, 5F, 7F); final ParticleDefaultImplementation particle2 = new ParticleDefaultImplementation( - Particle.Type.values()[1 % Particle.Type.values().length], 11F, 13F, 17F, 19F); + MOBILE_TYPE_2, 11F, 13F, 17F, 19F); particleEngine.setGravity(0F); addParticle(particleEngine, particle1); addParticle(particleEngine, particle2); - update(particleEngine, 1F, 1F / 60F); + update(particleEngine, 1F, SIMULATION_STEP); final Iterator particleIterator = particleEngine.iterator(); @@ -257,20 +280,30 @@ public abstract class AbstractParticleEngineTestBase { public final void testIteratorWithoutBounds() { final ParticleEngine particleEngine = this.createParticleEngine(); final ParticleDefaultImplementation particle1 = new ParticleDefaultImplementation( - Particle.Type.values()[0 % Particle.Type.values().length], 2F, 3F, 5F, 7F); + MOBILE_TYPE_1, 2F, 3F, 5F, 7F); final ParticleDefaultImplementation particle2 = new ParticleDefaultImplementation( - Particle.Type.values()[1 % Particle.Type.values().length], 11F, 13F, 17F, 19F); + MOBILE_TYPE_2, 11F, 13F, 17F, 19F); particleEngine.setBoardSize(null); addParticle(particleEngine, particle1); addParticle(particleEngine, particle2); final Iterator particleIterator = particleEngine.iterator(); + final Set expectedParticles = new HashSet(); + + expectedParticles.add(particle1); + expectedParticles.add(particle2); + + for (int i = expectedParticles.size(); i > 0; --i) { + assertTrue("expected at least " + (i + 1) + " particles", particleIterator.hasNext()); + + final Particle particle = particleIterator.next(); - assertTrue(particleIterator.hasNext()); - assertEquals(particle1, particleIterator.next()); - assertTrue(particleIterator.hasNext()); - assertEquals(particle2, particleIterator.next()); + assertTrue("unexpected particle " + particle, expectedParticles.contains(particle)); + + expectedParticles.remove(particle); + } + assertTrue("missing particles " + expectedParticles, expectedParticles.isEmpty()); assertFalse(particleIterator.hasNext()); } @@ -278,20 +311,30 @@ public abstract class AbstractParticleEngineTestBase { public final void testIteratorWithBounds() { final ParticleEngine particleEngine = this.createParticleEngine(); final ParticleDefaultImplementation particle1 = new ParticleDefaultImplementation( - Particle.Type.values()[0 % Particle.Type.values().length], 2F, 3F, 5F, 7F); + MOBILE_TYPE_1, 2F, 3F, 5F, 7F); final ParticleDefaultImplementation particle2 = new ParticleDefaultImplementation( - Particle.Type.values()[1 % Particle.Type.values().length], 11F, 13F, 17F, 19F); + MOBILE_TYPE_2, 11F, 13F, 17F, 19F); particleEngine.setBoardSize(new Dimension()); addParticle(particleEngine, particle1); addParticle(particleEngine, particle2); final Iterator particleIterator = particleEngine.iterator(); + final Set expectedParticles = new HashSet(); + + expectedParticles.add(particle1); + expectedParticles.add(particle2); + + for (int i = expectedParticles.size(); i > 0; --i) { + assertTrue("expected at least " + (i + 1) + " particles", particleIterator.hasNext()); + + final Particle particle = particleIterator.next(); + + assertTrue("unexpected particle " + particle, expectedParticles.contains(particle)); - assertTrue(particleIterator.hasNext()); - assertEquals(particle1, particleIterator.next()); - assertTrue(particleIterator.hasNext()); - assertEquals(particle2, particleIterator.next()); + expectedParticles.remove(particle); + } + assertTrue("missing particles " + expectedParticles, expectedParticles.isEmpty()); assertFalse(particleIterator.hasNext()); } @@ -305,6 +348,14 @@ public abstract class AbstractParticleEngineTestBase { protected static final float DELTA = 1e-12F; + protected static final float SIMULATION_STEP = 1.0F / 60.0F; + + protected static final Particle.Type IMMOBILE_TYPE = Particle.Type.IMMOBILE; + + protected static final Particle.Type MOBILE_TYPE_1 = Particle.Type.SAND; + + protected static final Particle.Type MOBILE_TYPE_2 = Particle.Type.WATER; + /** * * @param particleEngine -- 2.11.4.GIT