Particle Life

Particle Life ist eine Simulation, in der sich Partikel abhängig von ihrer Farbe und Distanz gegenseitig anziehen oder abstoßen. Wie stark sich die verschiedenen Farben sich anziehen oder abstoßen und wo die Partikel am Anfang der Simulation starten, wird durch Zufall mit Hilfe eines Seeds bestimmt. Der Seed ist dafür da, dass man immer das gleiche Ergebnis erhält, wenn man den gleichen Seed benutzt und die anderen Simulationsparameter nicht verändert.

Die Parameter der Simulation sind:

  • Die Größe des Fensters (in Pixel)
  • Das Mindestzeitintervall zwischen zwei Frames (in Millisekunden)
  • Die Anzahl der Partikel
  • Die Anzahl von Farben
  • Der Reibungsfaktor der auf die Geschwindigkeit der Partikel pro Frame angewendet wird
  • Die Maximaldistanz, in der Kräfte zwischen zwei Partikel wirken (in Pixel)
  • Die Mindestdistanz, die die Partikel von einander entfernt sein sollen. (als Faktor der Maximaldistanz) Wenn die Distanz zwischen zwei Partikel kleiner als der Mindestdistanz ist, stoßen sich die Partikel immer von einander ab.
  • Das Zeitintervall, das pro Frame simuliert wird (in Sekunden)

Hier sind die drei Java-Klassen aus dem meine Implementation von Particle Life besteht (Der verwendete Seed und die anderen Parameter im Programm führen zu dem oben sichtbaren Ergebnis):

Klasse ParticleLife

import java.awt.Dimension;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.Toolkit;
import javax.swing.JFrame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.Color;
import javax.swing.Timer;

@SuppressWarnings("serial")
public class ParticleLive extends JFrame implements ActionListener, KeyListener{

  Bild image;
  Timer timer;

  static int[] size=new int[] {1920,1000};
  static Color[] color_palete = new Color[] {
      new Color(0,0,0),      //background
      new Color(255,0,0),    //color 1
      new Color(255,255,0),  //color 2
      new Color(0,255,0),    //color 3
      new Color(0,255,255),  //color 4
      new Color(0,0,255),    //color 5
      new Color(255,0,255),
      new Color(255,255,255),
      new Color(255,127,0),
      new Color(127,0,255),
      new Color(0,255,127),  //color 10
      new Color(127,255,0),
      new Color(255,0,127),
      new Color(0,127,255),
      new Color(127,0,0),
      new Color(0,127,0),    //color 15
      new Color(0,0,127),
      new Color(255,127,127),
      new Color(127,255,127),
      new Color(127,127,255),
      new Color(127,127,127) //color 20
  };

  static int frameSizeX = size[0] + 16;
  static int frameSizeY = size[1] + 39;
  static int time = 100;
  
  //Particle parameters
  static int amount = 2000; //default: 2000
  static byte colors = 7; //default: 7
  static double friction = 0.9; //default: 0.9
  static double minDist = 0.3; //default: 0.3
  static double maxDist = 200; //default: 200
  static double dt = 0.02; //default: 0.02
  
  static long seed = -4808773395987877888L; //set Seed

  @Override
  public void keyPressed(KeyEvent e) {
    switch (e.getKeyCode()) {
      case  80: //pressed p
        if (timer.isRunning()) {
          timer.stop();
        } else {
          timer.start();
        }
        break;
      case 88: //pressed x
        image.nextFrame();
        break;
      default: 
        
    }
  }
  
  @Override
  public void keyTyped(KeyEvent e) {}

  @Override
  public void keyReleased(KeyEvent e) {}
  
  @Override
  public void actionPerformed(ActionEvent e) {
    image.nextFrame();
  }

  public ParticleLive(){
    this.image = new Bild(size, color_palete, amount, colors, friction, maxDist, minDist, dt, seed);
    addKeyListener(this);
    timer = new Timer(time, this);
    timer.setRepeats(true);
    setFocusable(true);
    setFocusTraversalKeysEnabled(false);
  }

  public static void main(String[] args) {
    javax.swing.SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        ParticleLive frame = new ParticleLive();
        frame.setTitle("ParticleLife");
        frame.setResizable(false);
        frame.setSize(frameSizeX, frameSizeY);
        Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
        frame.setLocation((d.width - frame.getSize().width) / 2, ((d.height - 30) - frame.getSize().height) / 2);
        frame.setMinimumSize(new Dimension(frameSizeX, frameSizeY));
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(frame.image);
        frame.pack();
        frame.setVisible(true);
      }
    });
  }
}Code-Sprache: Java (java)

Klasse Bild

import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JComponent;
import java.util.Random;

public class Bild extends JComponent {
  
  int[] size;
  Color background;
  Color[] particleColors;
  double[][] forces;
  Particle[] particles;
  double friction;
  double minDist;
  double maxDist;
  double dt;
  Random rng = new Random();
  
  Bild(int[] size,Color[] palete,int amount, byte colors, double friction,double maxDistance, double minDistance, double dTime, long seed){
    this.size = size;
    this.background = palete[0];
    this.rng.setSeed(seed);
    System.out.println(seed);
    if (colors <= 20) {
      this.particleColors = palete;
    } else {
      this.particleColors = new Color[colors+1];
      for (int i = 1; i < particleColors.length; i++) {
        particleColors[i] = new Color((int)(this.rng.nextDouble()*255), (int)(this.rng.nextDouble()*255), (int)(this.rng.nextDouble()*255));
      }
    }
    this.forces = new double[colors][colors];
    for (int i = 0; i < this.forces.length; i++) {
      for (int j = 0; j < this.forces[0].length; j++) {
        this.forces[i][j] = this.rng.nextDouble()*2 - 1;
      }
    }
    this.particles = new Particle[amount];
    for (int i = 0; i < amount; i++) {
      this.particles[i] = new Particle(size, (byte)(i % (int)colors), this.rng.nextDouble(), this.rng.nextDouble());
    }
    this.friction = friction;
    this.minDist = minDistance;
    this.maxDist = maxDistance;
    this.dt = dTime;
  }

  
  public void paintComponent(Graphics gr) {
    super.paintComponent(gr);
    gr.setColor(background);
    gr.fillRect(0, 0, size[0], size[1]);
    for (int i = 0; i < particles.length; i++) {
      gr.setColor(particleColors[particles[i].color + 1]);
      gr.fillOval((int)(particles[i].pos[0]) - 3, (int)(particles[i].pos[1]) - 3, 7, 7);
    }
  }
  
  public void calcNextFrame() {
    for (int i = 0; i < particles.length - 1; i++) {
      for (int j = i+1; j < particles.length; j++) {
        double dx = particles[j].pos[0] - particles[i].pos[0];
        double dy = particles[j].pos[1] - particles[i].pos[1];        
        double dx2 = particles[j].pos[0] - (particles[i].pos[0] + (double)size[0]);
        double dy2 = particles[j].pos[1] - (particles[i].pos[1] + (double)size[1]);
        if(Math.abs(dx2) < Math.abs(dx)){
          dx = dx2;
        }
        if(Math.abs(dy2) < Math.abs(dy)){
          dy = dy2;
        }
        dx2 = particles[j].pos[0] - (particles[i].pos[0] - (double)size[0]);
        dy2 = particles[j].pos[1] - (particles[i].pos[1] - (double)size[1]);
        if(Math.abs(dx2) < Math.abs(dx)){
          dx = dx2;
        }
        if(Math.abs(dy2) < Math.abs(dy)){
          dy = dy2;
        }        
        double distance = Particle.getDistance(dx,dy);
        particles[i].addAceleration(Particle.getNormelisedDirection(dx, dy, distance), Particle.getForcePower(minDist, maxDist, distance/maxDist, forces[ particles[i].color ][ particles[j].color ]));
        particles[j].addAceleration(Particle.getNormelisedDirection(-dx, -dy, distance), Particle.getForcePower(minDist, maxDist, distance/maxDist, forces[ particles[j].color ][ particles[i].color ]));
      }
      particles[i].applyAceleration(friction,dt,size[0],size[1]);      
    }
    particles[particles.length-1].applyAceleration(friction,dt,(double)size[0],(double)size[1]);
  }
  
  public void nextFrame() {
    calcNextFrame();
    repaint();
  }

}
Code-Sprache: Java (java)

Klasse Particle


public class Particle {
  
  double[] pos;
  double[] velocity;
  double[] aceleration;
  byte color;
  
  Particle(int[] screenSize,byte color, double rng1, double rng2) {
    this.pos = new double[] {Math.floor(rng1*screenSize[0]),Math.floor(rng2*screenSize[1])};
    this.color = color;
    this.velocity = new double[] {0.0,0.0};
    this.aceleration = new double[] {0.0,0.0};
  }
  
  public static double getDistance(double dx, double dy){
    return Math.sqrt(dx*dx + dy*dy);
  }
  
  public static double[] getNormelisedDirection(double dx, double dy, double distance) {
    if (distance != 0) {
      return new double[] {dx/distance, dy/distance};
    } else {
      return new double[] {1,0};
    }
  }
  
  public static double getForcePower(double minDistance, double maxDistance, double normDistance, double attraction) {
    if (normDistance < minDistance) {
      return (normDistance/minDistance - 1) * maxDistance;
    }
    if(normDistance < 1) {
      return (attraction * (1-(Math.abs(2*normDistance-1-minDistance)/(1-minDistance)))) * maxDistance;
    }
    return 0;
  }
  
  public void addAceleration(double[] direction, double forcePower) {
    this.aceleration[0] += direction[0] * forcePower;
    this.aceleration[1] += direction[1] * forcePower;
  }
  
  public void applyAceleration(double friction, double dt, double maxX, double maxY) {
    this.velocity[0] = this.velocity[0] * friction + aceleration[0] * dt;
    this.velocity[1] = this.velocity[1] * friction + aceleration[1] * dt;
    this.aceleration[0] = 0;
    this.aceleration[1] = 0;
    this.pos[0] += this.velocity[0] * dt;
    this.pos[1] += this.velocity[1] * dt;

    if (this.pos[0] < 0) {
      this.pos[0] += maxX;
    }
    if (this.pos[0] > maxX) {
      this.pos[0] -= maxX;
    }
    if (this.pos[1] < 0) {
      this.pos[1] += maxY;
    }
    if (this.pos[1] > maxY) {
      this.pos[1] -= maxY;
    }    
  }

}
Code-Sprache: Java (java)

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert