From 3196914564c412a1f7516820dfad05eae611146c Mon Sep 17 00:00:00 2001 From: "J. Neugebauer" Date: Tue, 3 Dec 2024 10:20:23 +0100 Subject: [PATCH] First draft of basic particle system --- .../ngb/zm/particles/BasicParticle.java | 85 +++++++++ .../zm/particles/BasicParticleFactory.java | 33 ++++ .../schule/ngb/zm/particles/Particle.java | 64 +++++++ .../ngb/zm/particles/ParticleEmitter.java | 172 ++++++++++++++++++ .../ngb/zm/particles/ParticleFactory.java | 9 + .../ngb/zm/particles/PhysicsObject.java | 69 +++++++ .../ngb/zm/particles/ParticleExample.java | 36 ++++ 7 files changed, 468 insertions(+) create mode 100644 src/main/java/schule/ngb/zm/particles/BasicParticle.java create mode 100644 src/main/java/schule/ngb/zm/particles/BasicParticleFactory.java create mode 100644 src/main/java/schule/ngb/zm/particles/Particle.java create mode 100644 src/main/java/schule/ngb/zm/particles/ParticleEmitter.java create mode 100644 src/main/java/schule/ngb/zm/particles/ParticleFactory.java create mode 100644 src/main/java/schule/ngb/zm/particles/PhysicsObject.java create mode 100644 src/test/java/schule/ngb/zm/particles/ParticleExample.java diff --git a/src/main/java/schule/ngb/zm/particles/BasicParticle.java b/src/main/java/schule/ngb/zm/particles/BasicParticle.java new file mode 100644 index 0000000..e3135b3 --- /dev/null +++ b/src/main/java/schule/ngb/zm/particles/BasicParticle.java @@ -0,0 +1,85 @@ +package schule.ngb.zm.particles; + +import schule.ngb.zm.Color; +import schule.ngb.zm.Vector; + +import java.awt.Graphics2D; + +public class BasicParticle extends Particle { + + protected Color color, startColor, finalColor; + + + public BasicParticle() { + super(); + } + + public BasicParticle(Color startColor) { + this(0, 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); + + this.color = startColor; + this.startColor = startColor; + this.finalColor = finalColor; + } + + public Color getColor() { + return color; + } + + public void setColor( Color pColor ) { + this.color = pColor; + } + + public Color getStartColor() { + return startColor; + } + + public void setStartColor( Color pStartColor ) { + this.startColor = pStartColor; + } + + public Color getFinalColor() { + return finalColor; + } + + public void setFinalColor( Color pFinalColor ) { + this.finalColor = pFinalColor; + } + + @Override + public void spawn( Vector pPosition, Vector pVelocity ) { + super.spawn(pPosition, pVelocity); + this.color = this.startColor; + } + + @Override + public void update( double delta ) { + super.update(delta); + + if( startColor != null && finalColor != null ) { + double t = 1.0 - lifetime / maxLifetime; + this.color = Color.interpolate(startColor, finalColor, t); + } + } + + @Override + public void draw( Graphics2D graphics ) { + if( this.color != null ) { + graphics.setColor(this.color.getJavaColor()); + graphics.fillOval((int) position.x, (int) position.y, 6, 6); + } + } + +} diff --git a/src/main/java/schule/ngb/zm/particles/BasicParticleFactory.java b/src/main/java/schule/ngb/zm/particles/BasicParticleFactory.java new file mode 100644 index 0000000..af6a0a7 --- /dev/null +++ b/src/main/java/schule/ngb/zm/particles/BasicParticleFactory.java @@ -0,0 +1,33 @@ +package schule.ngb.zm.particles; + +import schule.ngb.zm.Color; + +public class BasicParticleFactory implements ParticleFactory { + + + private final Color startColor; + + private final Color finalColor; + + private final int maxLifetime = 50; + + public BasicParticleFactory() { + this.startColor = new Color(128, 128, 129); + this.finalColor = new Color(128, 128, 129, 0); + } + + public BasicParticleFactory( Color startColor, Color finalColor ) { + this.startColor = startColor; + this.finalColor = finalColor; + } + + public int getMaxLifetime() { + return maxLifetime; + } + + @Override + public Particle createParticle() { + return new BasicParticle(maxLifetime, startColor, finalColor); + } + +} diff --git a/src/main/java/schule/ngb/zm/particles/Particle.java b/src/main/java/schule/ngb/zm/particles/Particle.java new file mode 100644 index 0000000..c6e9e63 --- /dev/null +++ b/src/main/java/schule/ngb/zm/particles/Particle.java @@ -0,0 +1,64 @@ +package schule.ngb.zm.particles; + +import schule.ngb.zm.Drawable; +import schule.ngb.zm.Updatable; +import schule.ngb.zm.Vector; + + +public abstract class Particle extends PhysicsObject implements Updatable, Drawable { + + protected double maxLifetime = 0; + + protected double 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(); + } + + @Override + public boolean isActive() { + return lifetime > 0; + } + + @Override + public boolean isVisible() { + return isActive(); + } + + public double getLifetime() { + return lifetime; + } + + public void setLifetime( double pLifetime ) { + this.lifetime = pLifetime; + } + + public double getMaxLifetime() { + return maxLifetime; + } + + public void setMaxLifetime( double pMaxLifetime ) { + this.maxLifetime = pMaxLifetime; + } + + @Override + public void update( double delta ) { + super.update(delta); + // lifetime -= delta; + lifetime -= 1; + } + +} diff --git a/src/main/java/schule/ngb/zm/particles/ParticleEmitter.java b/src/main/java/schule/ngb/zm/particles/ParticleEmitter.java new file mode 100644 index 0000000..f24ff35 --- /dev/null +++ b/src/main/java/schule/ngb/zm/particles/ParticleEmitter.java @@ -0,0 +1,172 @@ +package schule.ngb.zm.particles; + + +import schule.ngb.zm.Drawable; +import schule.ngb.zm.Updatable; +import schule.ngb.zm.Vector; + +import java.awt.Graphics2D; + +public class ParticleEmitter implements Updatable, Drawable { + + protected ParticleFactory particleFactory; + + private int particlesPerFrame; + + private int particleLifetime = 180; + + private Particle[] particles; + + private boolean active = false; + + private Particle nextParticle; + + public Vector position; + + public Vector direction = new Vector(); + + public int angle = 0; + + public double randomness = 0.0; + + // private Vortex vortex = null; + + public ParticleEmitter( double pX, double pY, int pParticlesPerFrame, ParticleFactory pFactory ) { + this.position = new Vector(pX, pY); + this.particlesPerFrame = pParticlesPerFrame; + this.particleFactory = pFactory; + + // Create particle pool + this.particles = new Particle[particlesPerFrame * pFactory.getMaxLifetime()]; + this.direction = Vector.random(8, 16).setLength(100); + + // vortex = new Vortex(position.copy().add(-10, -10), -.2, 8); + } + + @Override + public boolean isActive() { + return active; + } + + @Override + public boolean isVisible() { + return active; + } + + public void start() { + // Partikel initialisieren + for( int i = 0; i < particles.length; i++ ) { + particles[i] = particleFactory.createParticle(); + } + + active = true; + } + + public void stop() { + for( int i = 0; i < particles.length; i++ ) { + particles[i].setLifetime(0); + particles[i] = null; + } + active = false; + } + + private Particle getNextParticle() { + // TODO: improve by caching next particle + for( Particle p : particles ) { + if( p != null && !p.isActive() ) { + return p; + } + } + return null; + } + + public void emitParticle() { + 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; + + double rotation = (angle / 2.0) - (int) (Math.random() * angle); + Vector velocity = direction.copy().rotate(rotation); + velocity.scale(random()); + + nextParticle.spawn(this.position, velocity); + nextParticle = getNextParticle(); + ppf -= 1; + } + } + + @Override + public void update( double delta ) { + emitParticle(); + + boolean _active = false; + for( Particle particle : particles ) { + if( particle != null ) { + if( particle.isActive() ) { +// if( vortex != null ) { +// vortex.attract(particle); +// } + particle.update(delta); + _active = true; + } + } + } + this.active = _active; + } + + private double random() { + return 1.0 - (Math.random() * randomness); + } + + private double random( double pZahl ) { + return pZahl * random(); + } + + @Override + public void draw( Graphics2D graphics ) { + java.awt.Color current = graphics.getColor(); + for( Particle particle : particles ) { + if( particle != null ) { + particle.draw(graphics); + } + } + +// if( vortex != null ) { +// graphics.setColor(java.awt.Color.BLACK); +// double vscale = (4 * vortex.scale); +// graphics.fillOval((int) (vortex.position.x - vscale * .5), (int) (vortex.position.y - vscale * .5), (int) vscale, (int) vscale); +// } + graphics.setColor(current); + } + + + class Vortex { + + Vector position; + + double speed = 1.0, scale = 1.0; + + public Vortex( Vector pPosition, double pSpeed, double pScale ) { + this.position = pPosition.copy(); + this.scale = pScale; + this.speed = pSpeed; + } + + public void attract( Particle pPartikel ) { + Vector diff = Vector.sub(pPartikel.position, this.position); + double dx = -diff.y * this.speed; + double dy = diff.x * this.speed; + double f = 1.0 / (1.0 + (dx * dx + dy * dy) / scale); + + pPartikel.position.x += (diff.x - pPartikel.velocity.x) * f; + pPartikel.position.y += (diff.y - pPartikel.velocity.y) * f; + + } + + } + +} diff --git a/src/main/java/schule/ngb/zm/particles/ParticleFactory.java b/src/main/java/schule/ngb/zm/particles/ParticleFactory.java new file mode 100644 index 0000000..14fcb0e --- /dev/null +++ b/src/main/java/schule/ngb/zm/particles/ParticleFactory.java @@ -0,0 +1,9 @@ +package schule.ngb.zm.particles; + +public interface ParticleFactory { + + Particle createParticle(); + + int getMaxLifetime(); + +} diff --git a/src/main/java/schule/ngb/zm/particles/PhysicsObject.java b/src/main/java/schule/ngb/zm/particles/PhysicsObject.java new file mode 100644 index 0000000..a3cc93a --- /dev/null +++ b/src/main/java/schule/ngb/zm/particles/PhysicsObject.java @@ -0,0 +1,69 @@ +package schule.ngb.zm.particles; + +import schule.ngb.zm.Updatable; +import schule.ngb.zm.Vector; + + +public abstract class PhysicsObject implements Updatable { + + protected Vector position, velocity, acceleration; + + protected double mass = 1.0; + + + public PhysicsObject() { + position = new Vector(); + velocity = new Vector(); + acceleration = new Vector(); + } + + public PhysicsObject( Vector pPosition ) { + position = pPosition.copy(); + velocity = new Vector(); + acceleration = new Vector(); + } + + public Vector getAcceleration() { + return acceleration; + } + + public void setAcceleration( Vector pAcceleration ) { + this.acceleration = pAcceleration; + } + + public double getMass() { + return mass; + } + + public void setMass( double pMass ) { + this.mass = pMass; + } + + public Vector getPosition() { + return position; + } + + public void setPosition( Vector pPosition ) { + this.position = pPosition; + } + + public Vector getVelocity() { + return velocity; + } + + public void setVelocity( Vector pVelocity ) { + this.velocity = pVelocity; + } + + public void accelerate( Vector pAcceleration ) { + acceleration.add(Vector.div(pAcceleration, mass)); + } + + @Override + public void update( double delta ) { + velocity.add(acceleration); + position.add(Vector.scale(velocity, delta)); + acceleration.scale(0.0); + } + +} diff --git a/src/test/java/schule/ngb/zm/particles/ParticleExample.java b/src/test/java/schule/ngb/zm/particles/ParticleExample.java new file mode 100644 index 0000000..a105414 --- /dev/null +++ b/src/test/java/schule/ngb/zm/particles/ParticleExample.java @@ -0,0 +1,36 @@ +package schule.ngb.zm.particles; + +import schule.ngb.zm.Zeichenmaschine; +import schule.ngb.zm.layers.DrawableLayer; + +public class ParticleExample extends Zeichenmaschine { + + public static void main( String[] args ) { + new ParticleExample(); + } + + public ParticleExample() { + super(400, 400, "ZM: Particles"); + } + + ParticleEmitter pgen; + + public void setup() { + pgen = new ParticleEmitter( + 200, 200, 1, + new BasicParticleFactory() + ); + pgen.randomness = .5; + pgen.angle = 45; + DrawableLayer drawables = new DrawableLayer(); + addLayer(drawables); + drawables.add(pgen); + pgen.start(); + } + + @Override + public void update( double delta ) { + pgen.update(delta); + } + +}