Drawing array of curves from PVector

Hi friends! I am trying to draw an array of curves from PVector coordinates.
Basically it is similar to Particle system tutorial, just instead of points, I need to get curves.

I try to use begin shape (in class Particle), storing x,y coordinates in arraylists, but I am getting an error while iterating over the ArrayList (NullPointException).

Heeeeeeeeeeeeeeeeeeeelp :[

ArrayList<ParticleSystem> systems;

void setup() {
  size(640, 360);
  systems = new ArrayList<ParticleSystem>();
}

void draw() {
  background(0);
  for (ParticleSystem ps : systems) {
    ps.run();
    ps.addParticle();
  }
  if (systems.isEmpty()) {
    fill(255,255,0);
  }
}

void mousePressed() {
  systems.add(new ParticleSystem(1, new PVector(mouseX, mouseY)));
}



// A simple Particle class

class Particle {
  PVector position;
  //ArrayList<PVector> position;
  PVector velocity;
  float vector_scale;
  float lifespan;
  
  FloatList xx;
  FloatList yy;
  ArrayList<FloatList> old_x;
  ArrayList<FloatList> old_y;

  Particle(PVector l) {
    float vector_scale = 0.05;//ector scaling factor, we want small steps
    
    FloatList xx = new FloatList();
    FloatList yy = new FloatList();
    ArrayList<FloatList> old_x = new ArrayList<FloatList>();
    ArrayList<FloatList> old_y = new ArrayList<FloatList>();
    
    // v is vector from the field
    float n = 3 * map(noise(l.x/155,l.y/155),0,1,-1,1); // 100, 300 or 1000
    velocity = new PVector(cos(n),sin(n));
    
    l.x += vector_scale * velocity.x;
    l.y += vector_scale * velocity.y;
    
    //velocity = new PVector(random(-1, 1), random(-2, 0)); //moving vector
    position = l.copy();
    
    
    
    lifespan = 100.0;
  }

  void run() {
    update();
    display();
  }

  // Method to update position
  void update() {
    position.add(velocity);
    
    lifespan -= 1.0;
  }

  // Method to display
  void display() {
    strokeWeight(2);
    stroke(255, lifespan);

    beginShape();
    
    for (int i = xx.size()-1; i <= 0; i--) {
      curveVertex(xx.get(i), yy.get(i));
    }
    endShape();
    // populate the x and y lists
    xx.append(position.x);
    yy.append(position.y);
    
  
    
  }

  // Is the particle still useful?
  boolean isDead() {
    return (lifespan < 0.0);
  }
}


// An ArrayList is used to manage the list of Particles

class ParticleSystem {

  ArrayList<Particle> particles;    // An arraylist for all the particles
  PVector origin;                   // An origin point for where particles are birthed
  
  
  ParticleSystem(int num, PVector v) {
    particles = new ArrayList<Particle>();   // Initialize the arraylist
    origin = v.copy();  
    
    // Store the origin point
    for (int i = 0; i < num; i++) {
      particles.add(new Particle(origin));    // Add "num" amount of particles to the arraylist
      
  }
  }


  void run() {
    // Cycle through the ArrayList backwards, because we are deleting while iterating
    for (int i = particles.size()-1; i >= 0; i--) {
      Particle p = particles.get(i);
      p.run();
      
      
      //println(XXorigin.get(i));
      stroke(255,0,0);
      strokeWeight(8);
      strokeJoin(ROUND);
      //point(XXorigin.get(i), YYorigin.get(i));
      //line(XXorigin.get(0), YYorigin.get(0), XXorigin.get(i), YYorigin.get(i));
      if (p.isDead()) {
        particles.remove(i);
      }
    }
  }

  void addParticle() {
    Particle p;
    // Add either a Particle or CrazyParticle to the system
    p = new Particle(origin);

    particles.add(p);
  }

  void addParticle(Particle p) {
    particles.add(p);
  }

  // A method to test if the particle system still has particles
  boolean dead() {
    return particles.isEmpty();
  }
}

change this

  FloatList xx;
  FloatList yy;
  ArrayList<FloatList> old_x;
  ArrayList<FloatList> old_y;

  Particle(PVector l) {
    float vector_scale = 0.05;//ector scaling factor, we want small steps
    
    FloatList xx = new FloatList();
    FloatList yy = new FloatList();
    ArrayList<FloatList> old_x = new ArrayList<FloatList>();
    ArrayList<FloatList> old_y = new ArrayList<FloatList>();

to this

  FloatList xx;
  FloatList yy;
  ArrayList<FloatList> old_x;
  ArrayList<FloatList> old_y;

  Particle(PVector l) {
    float vector_scale = 0.05;//ector scaling factor, we want small steps

    xx = new FloatList();
    yy = new FloatList();
    old_x = new ArrayList<FloatList>();
    old_y = new ArrayList<FloatList>();

also wouldn’t it be easier just to maintain an arraylist of PVectors instead of two seperate lists for each ordinate… just a thought.

1 Like

Thank you for your reply @hotfooted !

I also changed

to this:

for (int i = 0; i < xx.size(); i++) {
      curveVertex(xx.get(i), yy.get(i));
    }

And it started to work!

Thus, adding every new curve slows down the process by a lot! :frowning:

Also it produces a new curve form the starting point to every new coordinate. What I want is to draw 1 curve from the beginning, following every new coordinate. And repeat that for every new origin I add.

Maybe I need to do that in ParticleSystem Class instead? Or something wrong with the for loop?

you call

ps.addParticle();

every draw

void draw() {
  background(0);
  for (ParticleSystem ps : systems) {
    ps.run();
    ps.addParticle();
  }
  if (systems.isEmpty()) {
    fill(255, 255, 0);
  }
}

but that function just adds another particle at the origin of that particle system

  void addParticle() {
    Particle p;
    // Add either a Particle or CrazyParticle to the system
    p = new Particle(origin);

    particles.add(p);
  }

so basically you end up drawing multiple curves from origin. remove that and you will have a single curve i believe.

edit: i think this is what you are aiming for yeh?

ArrayList<Particle> particles;

void setup() {
  size(640, 480);
  noFill();
  strokeWeight(2);
  particles = new ArrayList<Particle>();
}

void draw() {
  background(255);
  for(int i = particles.size() - 1; i >= 0; i--) {
    Particle p = particles.get(i);
    p.update();
    //if(p.pos.x < -100 || p.pos.x > width+100 || p.pos.y < -100 || p.pos.y > height+100)
      //particles.remove(i);
    p.present();
  }
}

void mousePressed() {
  Particle p = new Particle(mouseX, mouseY);
  p.addForce(random(-1, 1), random(-1, 1));
  particles.add(p);
}

class Particle {
  PVector pos;
  PVector vel;
  PVector acc;
  ArrayList<PVector> history;
  int maxHistory;
  float maxWander;
  float maxSpeed;
  
  Particle(float x, float y) {
      this.pos = new PVector(x, y);
      this.vel = new PVector();
      this.acc = new PVector();
      this.history = new ArrayList<PVector>();
      this.history.add(this.pos.copy());
      this.maxHistory = 50;
      this.maxWander = 45 * (PI / 180);
      this.maxSpeed = 2;
  }
  
  void addForce(float fx, float fy) {
    this.acc.x += fx;
    this.acc.y += fy;
  }
  
  void update() {
    //add some wander to the particle to make it more interesting
    float heading = this.vel.heading();
    heading += random(-this.maxWander, this.maxWander);
    this.addForce(cos(heading), sin(heading));
    
    this.vel.add(this.acc);
    this.vel.limit(this.maxSpeed);
    this.acc.mult(0);
    this.pos.add(this.vel);
    
    float dist = PVector.dist(this.pos, this.history.get(this.history.size() - 1));
    if(dist > 5) {
      this.history.add(this.pos.copy());
      if(this.history.size() > this.maxHistory) {
        this.history.remove(0);
      }
    }
  }
  
  void present() {    
    float k = (float)this.history.size();
    for(int i = 1; i < this.history.size(); i++) {
      stroke(0, (i / k) * 255);
      PVector p1 = this.history.get(i - 1);
      PVector p2 = this.history.get(i);
      line(p1.x, p1.y, p2.x, p2.y);
    }     
  }
}

it’s just you are repurposing code that has a lot of superfluous stuff for your goal making the situation a bit confusing

1 Like

Thank you so much for your help :]

Your code is wonderful, but has totally different structure than mine.
Could you please explain it a bit?
For instance, if I want to map a big noise field to draw it on, or an image to follow, how can I apply it with this code?
I tried to do that here, but it results in particles moving in circles.

    //add some wander to the particle to make it more interesting
    float heading = 2 * map(noise(this.acc.x/500,this.acc.y/500),0,1,-1,1);
    this.vel = new PVector(cos(heading),sin(heading));
    this.addForce(cos(heading), sin(heading));

Also, using this just made me so confused…

you basically have it correct. i was being lazy though and really the movement code should be removed from the particle update and instead the particles should be fed a force using the addForce method from outside. this whole thing follows Daniel’s work closely (for good reason :wink: ) so it might be worth checking out this video

and he might have actually made an updated version of that as well.
further you can read more on his book The Nature of Code website here

1 Like

Thank you again so much :slight_smile:
I am shy to ask more, you already helped a lot…

But I guess, if you say

Then my main question is how can I put data from particles.add into run() function, without adding new particles?
I guess the answer is somewhere in using “.this”?

class ParticleSystem {

  ArrayList<Particle> particles;    // An arraylist for all the particles
  PVector origin;    // An origin point for where particles are birthed
  FloatList xx;
  FloatList yy;

  ParticleSystem(int num, PVector v) {
    particles = new ArrayList<Particle>();   // Initialize the arraylist
    origin = v.copy();            // Store the origin point
    xx = new FloatList();
    yy = new FloatList();
    
    for (int i = 0; i < num; i++) {
      particles.add(new Particle(origin));    // Add "num" amount of particles to the arraylist
    }
  }


  void run() {
// Use origins of created particles here, witout creating new particles??
}
    
    

have a read of chapter 4 of Daniel’s book. Nearer to the end of the chapter you should find your answer. best of luck!

Hi @hotfooted ! :slight_smile:

I red and watched everything, and I finally understood your code!

However, when reviewing the created lines from

line(p1.x, p1.y, p2.x, p2.y);

It actually creates many many copies of the same line in the same place, so it does not create 1 constant vertex.
Here take a look for one of them


Is there a way to avoid it, to create one vertex from each origin, without making copies?

please show your code

This is your code, minimally modified to just record pdf.
It creates copies of lines in the same location with geometric progression, like so:


(the amount of lines visualized)

import processing.pdf.*;

ArrayList<Particle> particles;

void setup() {
  size(640, 480);
  noFill();
  strokeWeight(2);
  particles = new ArrayList<Particle>();
  beginRecord(PDF, "vector.pdf"); 
}

void draw() {
  background(255);
  for(int i = particles.size() - 1; i >= 0; i--) {
    Particle p = particles.get(i);
    p.update();
    p.present();
  }
  
  if (keyPressed) { 
   exit();
   saveFrame();  
   endRecord();
  }
  
}

void mousePressed() {
  Particle p = new Particle(mouseX, mouseY);
  p.addForce(random(-1, 1), random(-1, 1));
  particles.add(p);
}

class Particle {
  PVector pos;
  PVector vel;
  PVector acc;
  ArrayList<PVector> history;
  int maxHistory;
  float maxWander;
  float maxSpeed;
  
  Particle(float x, float y) {
      this.pos = new PVector(x, y);
      this.vel = new PVector();
      this.acc = new PVector();
      this.history = new ArrayList<PVector>();
      this.history.add(this.pos.copy());
      this.maxHistory = 50;
      this.maxWander = 45 * (PI / 180);
      this.maxSpeed = 2;
  }
  
  void addForce(float fx, float fy) {
    this.acc.x += fx;
    this.acc.y += fy;
  }
  
  void update() {
    float heading = this.vel.heading();
    heading += random(-this.maxWander, this.maxWander);
    this.addForce(cos(heading), sin(heading));
    
    this.vel.add(this.acc);
    this.vel.limit(this.maxSpeed);
    this.acc.mult(0);
    this.pos.add(this.vel);
    
    float dist = PVector.dist(this.pos, this.history.get(this.history.size() - 1));
    if(dist > 5) {
      this.history.add(this.pos.copy());
      if(this.history.size() > this.maxHistory) {
        this.history.remove(0);
      }
    }
  }
  
  void present() {    
    float k = (float)this.history.size();
    for(int i = 1; i < this.history.size(); i++) {
      stroke(0, (i / k) * 255);
      PVector p1 = this.history.get(i - 1);
      PVector p2 = this.history.get(i);
      line(p1.x, p1.y, p2.x, p2.y);
    }     
  }
}

Adding this at the end of for loop in void present(0

this.history.remove(i-1)

Helps, but it creates separated lines one by one.

I am trying to create a vertex insted, but I cannot create vertex with history.remove(i-1)…

I tried using lerp as well, but this did not help

    noFill();
    beginShape();
    for(int i = 2; i < this.history.size(); i++) {
      //PVector p1 = this.history.get(i - 1);
      PVector p1 = this.history.get(i-1);
      PVector p2 = this.history.get(i);
      float x = lerp(p1.x, p2.x, 1);
      float y = lerp(p1.y, p2.y, 1);
      vertex(x,y);
      //this.history.remove(i-1); //remove previous origin
    }
    endShape();

the curve is made up of many line segments. each of which is created using pairs of vertices.

here i’ve increased the line segment length by increasing the distance between vertices. i have also highlighted each vertex with an ellipse.

ArrayList<Particle> particles;

void setup() {
  size(640, 480);
  noFill();
  strokeWeight(2);
  particles = new ArrayList<Particle>();
}

void draw() {
  background(255);
  for(int i = particles.size() - 1; i >= 0; i--) {
    Particle p = particles.get(i);
    p.update();
    //if(p.pos.x < -100 || p.pos.x > width+100 || p.pos.y < -100 || p.pos.y > height+100)
      //particles.remove(i);
    p.present();
  }
}

void mousePressed() {
  Particle p = new Particle(mouseX, mouseY);
  p.addForce(random(-1, 1), random(-1, 1));
  particles.add(p);
}

class Particle {
  PVector pos;
  PVector vel;
  PVector acc;
  ArrayList<PVector> history;
  int maxHistory;
  float maxWander;
  float maxSpeed;
  
  Particle(float x, float y) {
      this.pos = new PVector(x, y);
      this.vel = new PVector();
      this.acc = new PVector();
      this.history = new ArrayList<PVector>();
      this.history.add(this.pos.copy());
      this.maxHistory = 50;
      this.maxWander = 45 * (PI / 180);
      this.maxSpeed = 2;
  }
  
  void addForce(float fx, float fy) {
    this.acc.x += fx;
    this.acc.y += fy;
  }
  
  void update() {
    //add some wander to the particle to make it more interesting
    float heading = this.vel.heading();
    heading += random(-this.maxWander, this.maxWander);
    this.addForce(cos(heading), sin(heading));
    
    this.vel.add(this.acc);
    this.vel.limit(this.maxSpeed);
    this.acc.mult(0);
    this.pos.add(this.vel);
    
    float dist = PVector.dist(this.pos, this.history.get(this.history.size() - 1));
    //this controls the length of the line segments or the resolution if you like of the curve which is made up of line segments
    if(dist > 25) {
      this.history.add(this.pos.copy());
      if(this.history.size() > this.maxHistory) {
        this.history.remove(0);
      }
    }
  }
  
  void present() {    
    float k = (float)this.history.size();
    for(int i = 1; i < this.history.size(); i++) {
      stroke(0, (i / k) * 255);
      PVector p1 = this.history.get(i - 1);
      PVector p2 = this.history.get(i);
      ellipse(p2.x, p2.y, 8, 8);
      line(p1.x, p1.y, p2.x, p2.y);
    }     
  }
}

you can see the resolution of the curve is controlled by the distance between each vertex and the smaller the distance the smoother the curve. i feel like there is a misunderstanding on one or both of our ends. i cannot see where you are calculating that there is multiple lines drawn. if you could demonstrate how you arrive at that conclusion i can look further into it but until then i am unsure if there is an issue.

perhaps someone else will weigh in with a solution?