diff --git a/src/main/java/schule/ngb/zm/Vector.java b/src/main/java/schule/ngb/zm/Vector.java index 67f3aaa..c4cda9d 100644 --- a/src/main/java/schule/ngb/zm/Vector.java +++ b/src/main/java/schule/ngb/zm/Vector.java @@ -191,6 +191,22 @@ public class Vector extends Point2D.Double { return new Vector(x, y); } + public int getIntX() { + return (int)this.x; + } + + public int getRoundedX() { + return (int)Math.round(this.x); + } + + public int getIntY() { + return (int)this.y; + } + + public int getRoundedY() { + return (int)Math.round(this.y); + } + /** * Setzt die Komponenten dieses Vektors neu. * diff --git a/src/main/java/schule/ngb/zm/particles/BasicParticle.java b/src/main/java/schule/ngb/zm/particles/BasicParticle.java index e3135b3..e403587 100644 --- a/src/main/java/schule/ngb/zm/particles/BasicParticle.java +++ b/src/main/java/schule/ngb/zm/particles/BasicParticle.java @@ -14,20 +14,12 @@ public class BasicParticle extends Particle { super(); } - public BasicParticle(Color startColor) { - this(0, startColor, null); + public BasicParticle( Color startColor ) { + this(startColor, null); } - public BasicParticle(Color startColor, Color finalColor) { - this(0, startColor, finalColor); - } - - public BasicParticle( int pLifetime ) { - super(pLifetime); - } - - public BasicParticle( int pLifetime, Color startColor, Color finalColor ) { - super(pLifetime); + public BasicParticle( Color startColor, Color finalColor ) { + super(); this.color = startColor; this.startColor = startColor; @@ -59,8 +51,8 @@ public class BasicParticle extends Particle { } @Override - public void spawn( Vector pPosition, Vector pVelocity ) { - super.spawn(pPosition, pVelocity); + public void spawn( int pLifetime, Vector pPosition, Vector pVelocity ) { + super.spawn(pLifetime, pPosition, pVelocity); this.color = this.startColor; } @@ -68,7 +60,7 @@ public class BasicParticle extends Particle { public void update( double delta ) { super.update(delta); - if( startColor != null && finalColor != null ) { + if( isActive() && startColor != null && finalColor != null ) { double t = 1.0 - lifetime / maxLifetime; this.color = Color.interpolate(startColor, finalColor, t); } @@ -76,9 +68,9 @@ public class BasicParticle extends Particle { @Override public void draw( Graphics2D graphics ) { - if( this.color != null ) { + if( isActive() && this.color != null ) { graphics.setColor(this.color.getJavaColor()); - graphics.fillOval((int) position.x, (int) position.y, 6, 6); + graphics.fillOval(position.getIntX() - 3, position.getIntY() - 3, 6, 6); } } diff --git a/src/main/java/schule/ngb/zm/particles/BasicParticleFactory.java b/src/main/java/schule/ngb/zm/particles/BasicParticleFactory.java index af6a0a7..47bc8d9 100644 --- a/src/main/java/schule/ngb/zm/particles/BasicParticleFactory.java +++ b/src/main/java/schule/ngb/zm/particles/BasicParticleFactory.java @@ -4,16 +4,18 @@ import schule.ngb.zm.Color; public class BasicParticleFactory implements ParticleFactory { - private final Color startColor; private final Color finalColor; - private final int maxLifetime = 50; + private boolean fadeOut = true; public BasicParticleFactory() { - this.startColor = new Color(128, 128, 129); - this.finalColor = new Color(128, 128, 129, 0); + this(null, null); + } + + public BasicParticleFactory( Color startColor ) { + this(startColor, null); } public BasicParticleFactory( Color startColor, Color finalColor ) { @@ -21,13 +23,21 @@ public class BasicParticleFactory implements ParticleFactory { this.finalColor = finalColor; } - public int getMaxLifetime() { - return maxLifetime; + public void setFadeOut( boolean pFadeOut ) { + this.fadeOut = pFadeOut; } @Override public Particle createParticle() { - return new BasicParticle(maxLifetime, startColor, finalColor); + Color finalClr = finalColor; + if( fadeOut ) { + if( finalColor != null ) { + finalClr = new Color(finalColor, 0); + } else if( startColor != null ) { + finalClr = new Color(startColor, 0); + } + } + return new BasicParticle(startColor, finalClr); } } diff --git a/src/main/java/schule/ngb/zm/particles/GenericParticleFactory.java b/src/main/java/schule/ngb/zm/particles/GenericParticleFactory.java new file mode 100644 index 0000000..f79d565 --- /dev/null +++ b/src/main/java/schule/ngb/zm/particles/GenericParticleFactory.java @@ -0,0 +1,49 @@ +package schule.ngb.zm.particles; + +import schule.ngb.zm.util.Log; + +import java.util.Arrays; +import java.util.function.Supplier; + +public class GenericParticleFactory implements ParticleFactory { + + private final Class type; + + private final Supplier supplier; + + public GenericParticleFactory( Class type, Object... params ) { + this.type = type; + + // Create paramTypes array once + Class[] paramTypes = new Class[params.length]; + for( int i = 0; i < params.length; i++ ) { + paramTypes[i] = params[i].getClass(); + } + + this.supplier = () -> { + T p = null; + try { + p = GenericParticleFactory.this.type.getDeclaredConstructor(paramTypes).newInstance(params); + } catch( Exception ex ) { + LOG.error( ex, + "Unable to create new Particle of type %s", + GenericParticleFactory.this.type.getCanonicalName() + ); + } + return p; + }; + } + + public GenericParticleFactory( Supplier supplier ) { + this.supplier = supplier; + this.type = (Class)supplier.get().getClass(); + } + + @Override + public Particle createParticle() { + return this.supplier.get(); + } + + private static final Log LOG = Log.getLogger(GenericParticleFactory.class); + +} diff --git a/src/main/java/schule/ngb/zm/particles/Particle.java b/src/main/java/schule/ngb/zm/particles/Particle.java index c6e9e63..7c438c6 100644 --- a/src/main/java/schule/ngb/zm/particles/Particle.java +++ b/src/main/java/schule/ngb/zm/particles/Particle.java @@ -7,25 +7,19 @@ import schule.ngb.zm.Vector; public abstract class Particle extends PhysicsObject implements Updatable, Drawable { - protected double maxLifetime = 0; - - protected double lifetime = 0; + protected double maxLifetime = 0, lifetime = 0; public Particle() { super(); } - public Particle( int pLifetime ) { - super(); - maxLifetime = pLifetime; - } - - public void spawn( Vector pPosition, Vector pVelocity ) { - lifetime = maxLifetime; - position = pPosition.copy(); - velocity = pVelocity.copy(); - acceleration = new Vector(); + public void spawn( int pLifetime, Vector pPosition, Vector pVelocity ) { + this.maxLifetime = pLifetime; + this.lifetime = pLifetime; + this.position = pPosition.copy(); + this.velocity = pVelocity.copy(); + this.acceleration = new Vector(); } @Override @@ -59,6 +53,8 @@ public abstract class Particle extends PhysicsObject implements Updatable, Drawa super.update(delta); // lifetime -= delta; lifetime -= 1; + + // TODO: (ngb) calculate delta based on lifetime? } } diff --git a/src/main/java/schule/ngb/zm/particles/ParticleEmitter.java b/src/main/java/schule/ngb/zm/particles/ParticleEmitter.java index f24ff35..0e2a599 100644 --- a/src/main/java/schule/ngb/zm/particles/ParticleEmitter.java +++ b/src/main/java/schule/ngb/zm/particles/ParticleEmitter.java @@ -25,20 +25,23 @@ public class ParticleEmitter implements Updatable, Drawable { public Vector direction = new Vector(); + public double strength = 100.0; + public int angle = 0; public double randomness = 0.0; // private Vortex vortex = null; - public ParticleEmitter( double pX, double pY, int pParticlesPerFrame, ParticleFactory pFactory ) { + public ParticleEmitter( double pX, double pY, int pParticleLifetime, int pParticlesPerFrame, ParticleFactory pFactory ) { this.position = new Vector(pX, pY); this.particlesPerFrame = pParticlesPerFrame; + this.particleLifetime = pParticleLifetime; this.particleFactory = pFactory; // Create particle pool - this.particles = new Particle[particlesPerFrame * pFactory.getMaxLifetime()]; - this.direction = Vector.random(8, 16).setLength(100); + this.particles = new Particle[particlesPerFrame * pParticleLifetime]; + this.direction = Vector.random(8, 16).normalize(); // vortex = new Vortex(position.copy().add(-10, -10), -.2, 8); } @@ -54,6 +57,8 @@ public class ParticleEmitter implements Updatable, Drawable { } public void start() { + this.direction.normalize(); + // Partikel initialisieren for( int i = 0; i < particles.length; i++ ) { particles[i] = particleFactory.createParticle(); @@ -84,16 +89,13 @@ public class ParticleEmitter implements Updatable, Drawable { int ppf = particlesPerFrame; Particle nextParticle = getNextParticle(); while( ppf > 0 && nextParticle != null ) { - // TODO: (ngb) randomize lifetime of particles in Factory? -// int pLeben = (int) random(particleLifetime); -// p.lifetime = pLeben; -// p.maxLifetime = pLeben; + int lifetime = (int) random(particleLifetime); double rotation = (angle / 2.0) - (int) (Math.random() * angle); - Vector velocity = direction.copy().rotate(rotation); + Vector velocity = direction.copy().scale(strength).rotate(rotation); velocity.scale(random()); - nextParticle.spawn(this.position, velocity); + nextParticle.spawn(lifetime, this.position, velocity); nextParticle = getNextParticle(); ppf -= 1; } @@ -130,7 +132,7 @@ public class ParticleEmitter implements Updatable, Drawable { public void draw( Graphics2D graphics ) { java.awt.Color current = graphics.getColor(); for( Particle particle : particles ) { - if( particle != null ) { + if( particle != null && particle.isVisible() ) { particle.draw(graphics); } } diff --git a/src/main/java/schule/ngb/zm/particles/ParticleFactory.java b/src/main/java/schule/ngb/zm/particles/ParticleFactory.java index 14fcb0e..a030529 100644 --- a/src/main/java/schule/ngb/zm/particles/ParticleFactory.java +++ b/src/main/java/schule/ngb/zm/particles/ParticleFactory.java @@ -4,6 +4,4 @@ public interface ParticleFactory { Particle createParticle(); - int getMaxLifetime(); - } diff --git a/src/main/java/schule/ngb/zm/particles/StarParticle.java b/src/main/java/schule/ngb/zm/particles/StarParticle.java new file mode 100644 index 0000000..e6fd505 --- /dev/null +++ b/src/main/java/schule/ngb/zm/particles/StarParticle.java @@ -0,0 +1,35 @@ +package schule.ngb.zm.particles; + +import schule.ngb.zm.Color; + +import java.awt.Graphics2D; + +public class StarParticle extends BasicParticle { + + public StarParticle() { + super(); + this.startColor = Color.PURE_GREEN; + } + + public StarParticle( Color startColor ) { + this(startColor, null); + } + + public StarParticle( Color startColor, Color finalColor ) { + super(); + + this.color = startColor; + this.startColor = startColor; + this.finalColor = finalColor; + } + + @Override + public void draw( Graphics2D graphics ) { + if( isActive() && this.color != null ) { + graphics.setColor(this.color.getJavaColor()); + graphics.drawLine((int) position.x - 3, (int) position.y - 3, (int) position.x + 3, (int) position.y + 3); + graphics.drawLine((int) position.x + 3, (int) position.y - 3, (int) position.x - 3, (int) position.y + 3); + } + } + +} diff --git a/src/test/java/schule/ngb/zm/particles/ParticleExample.java b/src/test/java/schule/ngb/zm/particles/ParticleExample.java index a105414..4789d96 100644 --- a/src/test/java/schule/ngb/zm/particles/ParticleExample.java +++ b/src/test/java/schule/ngb/zm/particles/ParticleExample.java @@ -1,36 +1,158 @@ package schule.ngb.zm.particles; -import schule.ngb.zm.Zeichenmaschine; +import schule.ngb.zm.*; import schule.ngb.zm.layers.DrawableLayer; +import schule.ngb.zm.layers.DrawingLayer; -public class ParticleExample extends Zeichenmaschine { +import java.awt.Graphics2D; + +public class ParticleExample extends Testmaschine { public static void main( String[] args ) { new ParticleExample(); } public ParticleExample() { - super(400, 400, "ZM: Particles"); + super(); } - ParticleEmitter pgen; + ParticleEmitter pe1, pe2, pe3; + + Rocket r; public void setup() { - pgen = new ParticleEmitter( - 200, 200, 1, - new BasicParticleFactory() + getLayer(DrawingLayer.class).hide(); + background.setColor(0); + drawing.noStroke(); + drawing.setFillColor(WHITE); + for( int i = 0; i < 1000; i++ ) { + drawing.point(random(0, canvasWidth), random(0, canvasHeight)); + } + + pe1 = new ParticleEmitter( + 100, 100, 50, 5, + // new BasicParticleFactory(PINK, BLUE) + new GenericParticleFactory(() -> { + return new Particle() { + @Override + public void draw( Graphics2D graphics ) { + graphics.setColor(Color.MAGENTA.getJavaColor()); + graphics.rotate(Constants.radians(45), (int) position.x, (int) position.y); + graphics.drawRect((int) position.x - 3, (int) position.y - 3, 6, 6); + graphics.rotate(Constants.radians(-45), (int) position.x, (int) position.y); + } + }; + }) ); - pgen.randomness = .5; - pgen.angle = 45; + pe1.randomness = .2; + pe1.angle = 45; + pe1.strength = 200; + + + pe2 = new ParticleEmitter( + 300, 300, 50, 10, + new GenericParticleFactory(() -> new StarParticle(RED, new Color(BLUE, 55))) + //new GenericParticleFactory(StarParticle.class, RED, new Color(BLUE, 55)) + ); + pe2.direction = NORTH.asVector().scale(100); + pe2.randomness = .8; + pe2.angle = 90; + pe2.strength = 200; + + + pe3 = new ParticleEmitter( + 100, 400, 20, 8, + new BasicParticleFactory(YELLOW, RED) + ); + pe3.direction = SOUTH.asVector(); + pe3.randomness = .33; + pe3.angle = 30; + DrawableLayer drawables = new DrawableLayer(); addLayer(drawables); - drawables.add(pgen); - pgen.start(); + drawables.add(pe1, pe2, pe3); + + pe1.start(); + pe2.start(); + pe3.start(); + + r = new Rocket(200, 400); + drawables.add(r); + r.start(); } @Override public void update( double delta ) { - pgen.update(delta); + pe1.update(delta); + pe2.update(delta); + pe3.update(delta); + pe3.position.add(NORTH.asVector().scale(Constants.map(runtime, 0, 1000, 0, 10) * delta)); + + if( r.isActive() ) { + r.update(delta); + } + } + + class Rocket extends PhysicsObject implements Drawable { + + ParticleEmitter trail; + + private boolean starting = false; + + private double acc = 4; + + public Rocket( double x, double y ) { + super(new Vector(x, y)); + trail = new ParticleEmitter( + x, y, 30, 6, + new BasicParticleFactory(YELLOW, RED) + ); + trail.direction = SOUTH.asVector(); + trail.randomness = .33; + trail.angle = 30; + trail.position = this.position; + } + + public void start() { + starting = true; + trail.start(); + } + + @Override + public boolean isActive() { + return starting; + } + + @Override + public boolean isVisible() { + return true; + } + + @Override + public void update( double delta ) { + super.update(delta); + if( this.acceleration.lengthSq() < acc * acc ) { + this.accelerate(NORTHWEST.asVector().scale(acc)); + } + + trail.update(delta); + } + + @Override + public void draw( Graphics2D graphics ) { + graphics.rotate(-Constants.radians(velocity.angle() + 180), position.getIntX(), position.getIntY()); + trail.draw(graphics); + + graphics.setColor(WHITE.getJavaColor()); + graphics.fillRect(position.getIntX() - 6, position.getIntY() - 32, 12, 32); + graphics.fillPolygon( + new int[]{position.getIntX() - 6, position.getIntX(), position.getIntX() + 6}, + new int[]{position.getIntY() - 32, position.getIntY() - 40, position.getIntY() - 32}, + 3 + ); + graphics.rotate(Constants.radians(velocity.angle() + 180), position.getIntX(), position.getIntY()); + } + } }