How to make an object move towards another object (its food) in processing?

Viewed 63

I am trying to simulate a herd of rabbits in a grass field using processing. As I am a beginner, I decided to skip acceleration while moving, that is the rabbits have a constant magnitude of velocity. Each rabbit has a separation radius in order to keep them separated. For food, I decided to greedily search all the grass objects near me and move towards the closest, however whenever I try to run the program, all the grass objects teleport towards the origin and I can't find the reason for it.

This is the main file.

ArrayList<Rabbit> R = new ArrayList<Rabbit>();
ArrayList<Grass> G = new ArrayList<Grass>();
ArrayList<Grass> K = G;
int grass = 25;
int r_Size = 10;
int day = 0;
int i = 0;

void setup(){
 size(1000,8000);
 for(int i = 0;i<r_Size;i++){
   R.add(new Rabbit());
 }
  for(int i = 0;i<grass;i++){
   G.add(new Grass());
 }
}

void draw(){
  background(120);
  i++;
  

  
  for(Grass r : G){
    r.show();
    r.find();
  }
  for(Rabbit r : R){
    r.show();
    r.move();
  }
  
  fill(255);
  text(day,10,100);
  if(i >= 10){
    day++;
    i = 0;
   ArrayList<Grass> d = new ArrayList<Grass>();
   for(Grass g : G){
    if(g.isEaten){
      d.add(g);
    }
   }
   for(Grass g : d){
    G.remove(g); 
   }
  }
  
  if(day >= 30){
   System.out.println(R.size());
   for(Grass r : G){
    r.update();
   }
   for(int i = 0;i<10;i++){
    G.add(new Grass()); 
   }

   ArrayList<Rabbit> dead = new ArrayList<Rabbit>();
   for (Rabbit r : R){
    r.update();
    if(r.isDead == true) 
      dead.add(r);
   }
   for(Rabbit r : dead){
    R.remove(r); 
   }
   day = 0;
  }
}

This is the Grass Class.

class Grass{
 PVector pos;
 int age = 0;
 int side = 20;
 boolean isEaten = false;
 int perTurn = 10;
 Grass(){
  pos = new PVector(random(0,width-side),random(0,height-side)); 
 }
 
 void update(){
  age++;
 }
 
 boolean find(){
  if(isEaten)
    return true;
  for(Rabbit r : R){
   if(pos.dist(r.pos) < side){
    isEaten = true;
    r.hasBeenHungry = 0;
    return true;
   }
  }
  return false;
 }
 
 
 void show(){
  float c = map(age,0,10,100,255);
  fill(0,0+c,0);
  noStroke();
  square(pos.x,pos.y,side);
 }
}

Second is the animal class which I wanted to extend to rabbits and foxes (I will add them later).

class Animal{
  PVector pos;
  PVector vel;
  boolean isDead;
  int age;
  int gender;
  int max_age;
  float max_speed;
  int hasBeenHungry = 0;
  
  //gender 0 means female and 1 means male;
  
  // update means age in months;
  void update(){
   this.age++;
   hasBeenHungry++;
   if(this.age >= max_age){
    isDead = true; 
   }
   float k = (int)random(10);
   if(k == 0){
    isDead = true; 
   }
   if(hasBeenHungry >= 5){
    isDead = true; 
   }
  }
  
  void move(){
   pos.add(this.vel);
   checkBound();
   //this.vel.mult(0);
  }
  
  void checkBound(){
   if(pos.x < 0 || pos.x > width){
       vel.x *= -1;
   }
   if(pos.y < 0|| pos.y > height){
    vel.y *= -1;
   }
  }
  
}

Finally, the Rabbit class (I think the issue is with the target function).

class Rabbit extends Animal{
  
  
  Rabbit(){
   pos = new PVector(random(0,width),random(0,height));
   vel = new PVector(2,1);
   int k = (int)(random(0,2));
   this.isDead = false;
   this.gender = k;
   this.max_age = 10;
   this.max_speed = 3;
  }
 
  
  void show(){
   if(this.gender == 0){
    fill(120,0,0 );
   }
   else{
    fill(0,0,120); 
   }
   circle(this.pos.x,this.pos.y,15);
  }
  
  void seperate(){
   float desiredRadius = 30;
   int count = 0;
   PVector c = new PVector(0,0);
   for(Rabbit r : R){
     if(r != this){
      float a = pos.dist(r.pos);
      if(a > 0 && a < desiredRadius){
        PVector diff = PVector.sub(pos, r.pos);
        //diff.normalize();
        diff.div(a);        // Weight by distance
        c.add(diff);
        count++;  
      }
     }
   }
    if (count > 0){
      c.div((float)count);
      vel.add(c);
      vel.normalize();
      vel.mult(max_speed);
    }
  }
  
  PVector target(){
   //Each rabbit eats grass so we could make a greedy approach where the rabbit focuses on the grass closest to it
   PVector mi = new PVector(0,0);
   //int distance =  1000;
   for(Grass g : G){
    float a = this.pos.dist(g.pos);
     if(a < pos.dist(mi)){
      mi = g.pos;
     }
    }
    return mi;
  }
  
  void move(){
   super.move();
   vel = target();
   seperate();
   vel.normalize();
   vel.mult(max_speed);
  }
}

P.S I am a beginner so some implementations may not be up to the mark, if you find any such issue please tell me so that I can improve.

1 Answers

The issue is in target() which defaults the return value to origin (0,0).

Therefore, if a Rabbit is closer to origin than any Grass (initially or at any time) it will always head to origin.

So this code below corrects for that by targeting the closest Grass unless there is no Grass left in which case it targets origin. I'll make some assumptions:

  1. If there is any Grass remaining a Rabbit should target the closest Grass.
  2. If there is no Grass remaining a Rabbit should head to origin (or home).

Then alter your target class as such:

PVector target(){
    //Each rabbit eats grass so we could make a greedy approach where the rabbit focuses on the grass closest to it
    if (G.isEmpty()) {
        // no more grass - head to origin
        return new PVector(0,0);
    }
    PVector mi = G.get(0).pos;
    for(Grass g : G){
       float a = this.pos.dist(g.pos);

       // this can be optimized by retaining the distance to the current `mi` rather
       // than recalculating it (this was not done).
       if(a < pos.dist(mi)) {
           mi = g.pos;
       }
    }
    // return a new PVector with the selected grass position.
    return new PVector(mi.x,mi.y);
}

Also your velocity calculation (which uses target) needs some work. It uses the result of target which is a position as a velocity - this likely results in instantaneous travel.

To avoid creating lots of garbage PVectors you should alter target() to pass in a PVector reference as in :

PVector target(PVector p) {
    // and the return becomes
    p.x = mi.x;
    p.y = mi.y;
    return p;
}

And the invocation comes:

vel = target(vel);
Related