3D Flocking simulation with two species and octree optimisation

Hi, I’m trying to set up a flocking simulation with two species of boids and octree optimization.
My problem here is that I want the two species to run on different octrees, and I don’t really understand how to do that starting from my code:

various functions:

void keyPressed() {

    if (key == 'o' || key == 'O'){
      useOcTree = !useOcTree;
      if (useOcTree == false) showOctree = false;
    }
    
    if (key == 's' || key == 'S'){
      showOctree = !showOctree;
    }
    
    if (key == 'a' || key == 'A'){
      show2 = !show2;
    }
}

void get_box(){
    noFill();
    strokeWeight(1);
    stroke(255);
    
    line(0, 0, z_min, 0, height, z_min);
    line(0, 0, z_max, 0, height, z_max);
    line(0, 0, z_min, width, 0, z_min);
    line(0, 0, z_max, width, 0, z_max);
    
    line(width, 0, z_min, width, height, z_min);
    line(width, 0, z_max, width, height, z_max);
    line(0, height, z_min, width, height, z_min);
    line(0, height, z_max, width, height, z_max);
    
    line(0, 0, z_min,  0, 0, z_max);
    line(0, height, z_min, 0, height, z_max);
    line(width, 0, z_min, width, 0, z_max);
    line(width, height, z_min,  width, height, z_max);
}

void calculateAxis( float length )
{
   // Store the screen positions for the X, Y, Z and origin
   axisXHud.set( screenX(length,0,0), screenY(length,0,0), 0 );
   axisYHud.set( screenX(0,length,0), screenY(0,length,0), 0 );     
   axisZHud.set( screenX(0,0,length), screenY(0,0,length), 0 );
   axisOrgHud.set( screenX(0,0,0), screenY(0,0,0), 0 );
}

// ------------------------------------------------------------------------ //
void drawAxis( float weight )
{
   pushStyle();   // Store the current style information

     strokeWeight( weight );      // Line width

     stroke( 255,   0,   0 );     // X axis color (Red)
     line( axisOrgHud.x, axisOrgHud.y, axisXHud.x, axisXHud.y );
 
     stroke(   0, 255,   0 );
     line( axisOrgHud.x, axisOrgHud.y, axisYHud.x, axisYHud.y );

     stroke(   0,   0, 255 );
     line( axisOrgHud.x, axisOrgHud.y, axisZHud.x, axisZHud.y );


      fill(255);                   // Text color
      textFont( axisLabelFont );   // Set the text font

      text( "X", axisXHud.x, axisXHud.y );
      text( "Y", axisYHud.x, axisYHud.y );
      text( "Z", axisZHud.x, axisZHud.y );

   popStyle();    // Recall the previously stored style information
}

TreeItem, Cube and Octree classes

class TreeItem{
    float x;
    float y;
    float z;
    Boid boid;

    public TreeItem(Boid boid_){
        this.x = boid_.position.x;
        this.y = boid_.position.y;
        this.z = boid_.position.z;
        this.boid = boid_;
    }
}


class Cube{
    float x, y, z;
    float w, h, d;

    
    public Cube(float x_, float y_, float z_, float w_, float h_, float d_){
        this.x = x_;
        this.y = y_;
        this.z = z_;
        this.w = w_;
        this.h = h_;
        this.d = d_;

    }
    
    Boolean contains(TreeItem treeItem) {
        return ( treeItem.x >= x - w && treeItem.x <= x + w &&
                 treeItem.y >= y - h && treeItem.y <= y + h &&
                 treeItem.z >= z - d && treeItem.z <= z + d    );
    }

    
    Boolean intersects(Cube range) {
    return !( range.x - range.w > x + w || range.x + range.w < x - w ||
              range.y - range.h > y + h || range.y + range.h < y - h ||
              range.z - range.d > z + d || range.z + range.d < z - d   );
    }
}

class OcTree {
    int capacity;
    boolean divided ;
    ArrayList <TreeItem> treeItems;
    
    Cube boundary;
    OcTree NWT, NET, SET, SWT;
    OcTree NWB, NEB, SEB, SWB;
    
    OcTree(Cube boundary, int capacity) {
      this.boundary = boundary; 
      this.capacity = capacity; 
      this.treeItems = new ArrayList <TreeItem>(); 
      this.divided = false; 
    }

  
    boolean insert(TreeItem treeItem) {
        if (!this.boundary.contains(treeItem)) {
          return false;
        }
    
        if (this.treeItems.size() < this.capacity) {
          this.treeItems.add(treeItem);
          return true;
        } else {
          if (!this.divided) {
            this.subdivide();
          }
          
        // N = North, S = South, E = East, W = West, B = Bottom, T = Top
        if (this.NWT.insert(treeItem)) {
          return true;
        } else if (this.NET.insert(treeItem)) {
          return true;
        } else if (this.SET.insert(treeItem)) {
          return true;
        } else if (this.SWT.insert(treeItem)) {
          return true;
        } else if (this.NWB.insert(treeItem)) {
          return true;
        } else if (this.NEB.insert(treeItem)) {
          return true;
        } else if (this.SEB.insert(treeItem)) {
          return true;
        } else if (this.SWB.insert(treeItem)) {
          return true;
        }
      }
        return false;
    }
  
    void subdivide() {
        
    
        float x = this.boundary.x;
        float y = this.boundary.y;
        float z = this.boundary.z;
        float w = this.boundary.w / 2;
        float h = this.boundary.h / 2;
        float d = this.boundary.d / 2;
    
    
        Cube NWTBoundary = new Cube(x - w, y - h, z - d, w, h, d);
        Cube NETBoundary = new Cube(x + w, y - h, z - d, w, h, d);
        Cube SETBoundary = new Cube(x + w, y + h, z - d, w, h, d);
        Cube SWTBoundary = new Cube(x - w, y + h, z - d, w, h, d);
        Cube NWBBoundary = new Cube(x - w, y - h, z + d, w, h, d);
        Cube NEBBoundary = new Cube(x + w, y - h, z + d, w, h, d);
        Cube SEBBoundary = new Cube(x + w, y + h, z + d, w, h, d);
        Cube SWBBoundary = new Cube(x - w, y + h, z + d, w, h, d);
        
        this.NWT = new OcTree(NWTBoundary, this.capacity);
        this.NET = new OcTree(NETBoundary, this.capacity);
        this.SET = new OcTree(SETBoundary, this.capacity);
        this.SWT = new OcTree(SWTBoundary, this.capacity);   
        this.NWB = new OcTree(NWBBoundary, this.capacity);
        this.NEB = new OcTree(NEBBoundary, this.capacity);
        this.SEB = new OcTree(SEBBoundary, this.capacity);
        this.SWB = new OcTree(SWBBoundary, this.capacity);
        this.divided = true; 
    }
  
    ArrayList<TreeItem> query(Cube range, ArrayList<TreeItem> found) {
      if (found == null) found = new ArrayList<TreeItem>(); 
  
      if (!this.boundary.intersects(range)) {
          return found; 
      } else {
          for (TreeItem treeItem : this.treeItems) {
            if (range.contains(treeItem)) {
              found.add(treeItem); 
            }
          }
      
        if (this.divided) {
            this.NWT.query(range, found);
            this.NET.query(range, found);
            this.SET.query(range, found);
            this.SWT.query(range, found);
            this.NWB.query(range, found);
            this.NEB.query(range, found);
            this.SEB.query(range, found);
            this.SWB.query(range, found);
        }
      }
      return found;
    }
    
    void show(color col, int strokeW){
        pushMatrix();
        strokeWeight(strokeW);
        stroke(col, 10);
        noFill();
        translate(this.boundary.x, this.boundary.y, this.boundary.z);
        box(this.boundary.w * 2, this.boundary.h * 2, this.boundary.d * 2);
        popMatrix();
        
        if (this.divided){
            this.NWT.show(col, strokeW);
            this.NET.show(col, strokeW);
            this.SET.show(col, strokeW);
            this.SWT.show(col, strokeW);
            this.NWB.show(col, strokeW);
            this.NEB.show(col, strokeW);
            this.SEB.show(col, strokeW);
            this.SWB.show(col, strokeW);
        }
        
    }
}

Boid class

class Boid{
    PVector position;
    PVector velocity;
    PVector acceleration;
    float x, y, z;
    float maxForce = 0.5;
    float maxSpeed = 4;
    float flap = 0;
    float sc = 3;
    color col;
    int perceptionRadius;
    
    Boid(color col_, int perceptionRadius_){
        this.position = new PVector(random(width), 
                 random(height), random(z_min, z_max));
        this.x = this.position.x;
        this.y = this.position.y;
        this.z = this.position.z;
        this.velocity = PVector.random3D().setMag(random(2, 4));
        this.acceleration = new PVector(0, 0, 0);
        this.col = col_;
        this.perceptionRadius = perceptionRadius_;
    }
    
    ArrayList<PVector> behaviour(ArrayList<Boid> boids){
        ArrayList<PVector> steering = new ArrayList<PVector>();
        
        
        
        float maxAngle = PI ;
        int tot = 0;

        PVector alignSteering = new PVector();
        PVector cohesionSteering = new PVector();
        PVector separationSteering = new PVector();
        
        
        
        if (useOcTree == true) {
            Cube range = new Cube(this.position.x, this.position.y, 
                          this.position.z, perceptionRadius, perceptionRadius, perceptionRadius);
            if (showBox){
              if (this == boids.get(0)){
                pushMatrix();
                strokeWeight(1);
                stroke(255, 255, 255);
                noFill();
                translate(range.x, range.y, range.z);
                box(range.w * 2, range.h * 2, range.d * 2);
                popMatrix();
              }
            }
            
            
            ArrayList<TreeItem> maybeNeighbors = octree.query(range, null);
            
            for (TreeItem otherr : maybeNeighbors){
                Boid other = otherr.boid;
                PVector diff = PVector.sub(this.position, other.position);
                float d = diff.mag();
                float theta = PVector.angleBetween(position, PVector.sub(this.position, other.position));
                
                if (other != this && d < perceptionRadius && theta < maxAngle){
                    alignSteering.add(other.velocity);
                    cohesionSteering.add(other.position);
                    
                    diff.setMag(1/d); // Magnitude inversely proportional to the distance
                    separationSteering.add(diff);
                    
                    tot++; 
                }
            } 
        }
        if (useOcTree == false){
             for (Boid other : boids){
                PVector diff = PVector.sub(this.position, other.position);
                float d = diff.mag();
                float theta = PVector.angleBetween(position, PVector.sub(this.position, other.position));
                
                if (other != this && d < perceptionRadius && theta < maxAngle){
                    alignSteering.add(other.velocity);
                    cohesionSteering.add(other.position);
                    
                    diff.setMag(1/d); // Magnitude inversely proportional to the distance
                    separationSteering.add(diff);
                    
                    tot++; 
                }
            } 
        }
        
        if (tot > 0){
            alignSteering.div(tot);
            alignSteering.setMag(this.maxSpeed);
            alignSteering.sub(this.velocity);
            alignSteering.limit(this.maxForce);
            
            cohesionSteering.div(tot);
            cohesionSteering.sub(this.position);
            cohesionSteering.setMag(this.maxSpeed);
            cohesionSteering.sub(this.velocity);
            cohesionSteering.limit(this.maxForce);
            
            separationSteering.div(tot);
            separationSteering.setMag(this.maxSpeed);
            separationSteering.sub(this.velocity);
            separationSteering.limit(this.maxForce);
            
        }
        separationSteering.mult(2);
        alignSteering.mult(2);
        cohesionSteering.mult(1.5);
        
        steering.add(alignSteering);
        steering.add(cohesionSteering);
        steering.add(separationSteering);
        
        return steering;
    }
    
    void flock(ArrayList<Boid> boids){
        ArrayList<PVector> steering = behaviour(boids);
        PVector alignment = steering.get(0);
        PVector cohesion = steering.get(1);
        PVector separation = steering.get(2);
        
        this.acceleration.add(alignment); // steering force !!
        this.acceleration.add(cohesion); // steering force !!
        this.acceleration.add(separation); // steering force !!
      }
      
    void update(){
        //this.sc = map(position.z, 0, z_max, 1, 3);
        velocity.add(acceleration);
        velocity.limit(maxSpeed);
        position.add(velocity);
        acceleration.mult(0);
    }

    void edges() {
        if (position.x > width) {
            position.x = 0;
        } else if (position.x < 0) {
            position.x = width;
        }
        if (position.y > height) {
            position.y = 0;
        } else if (position.y < 0) {
            position.y = height;
        }
        if(position.z > z_max) position.z = z_min;
        if(position.z < z_min) position.z = z_max;
    }
    
    void show(ArrayList<Boid> boids){
      pushMatrix();
      translate(position.x, position.y, position.z);
      rotateY(atan2(-velocity.z, velocity.x));
      rotateZ(asin(velocity.y/velocity.mag()));
      noFill();
      noStroke();
      strokeWeight(1);
      if (this == boids.get(0)){
        stroke(255);
        fill(255); 
      }
      else{
          stroke(col); 
          
          fill(col); 
      }
      //draw bird
      beginShape(TRIANGLES);
      vertex(3*sc, 0, 0);
      vertex(-3*sc, 2*sc, 0);
      vertex(-3*sc, -2*sc,0);
      
      vertex(3*sc,0,0);
      vertex(-3*sc,2*sc,0);
      vertex(-3*sc,0,2*sc);
      
      vertex(3*sc,0,0);
      vertex(-3*sc,0,2*sc);
      vertex(-3*sc,-2*sc,0);
      
      
      vertex(-3*sc,0,2*sc);
      vertex(-3*sc,2*sc,0);
      vertex(-3*sc,-2*sc,0);
      endShape();
      
      popMatrix();
      
    }
    
    PVector avoid(PVector target, boolean weight){
        PVector steer = new PVector(); 
        steer.set(PVector.sub(position, target)); 
        if(weight)
            steer.mult(1/sq(PVector.dist(position, target)));
        steer.limit(maxForce); 
        return steer;
    }
    
    void run(ArrayList<Boid> boids) {
        
        if(avoidWalls)
        {
          acceleration.add(PVector.mult(avoid(new PVector(position.x, height, position.z), true), 5));
          acceleration.add(PVector.mult(avoid(new PVector(position.x, 0, position.z), true), 5));
          acceleration.add(PVector.mult(avoid(new PVector(width, position.y, position.z), true), 5));
          acceleration.add(PVector.mult(avoid(new PVector(0, position.y, position.z), true), 5));
          acceleration.add(PVector.mult(avoid(new PVector(position.x, position.y, z_min), true), 5));
          acceleration.add(PVector.mult(avoid(new PVector(position.x, position.y, z_max), true), 5));
        }
      
        flock(boids);
        update();
        edges();
        show(boids);
    }
    
}

Flock class:

class Flock {
    ArrayList<Boid> boids; // An ArrayList for all the boids
    Flock() {
      this.boids = new ArrayList<Boid>(); // Initialize the ArrayList
    }
    
    void run() { 
        octree = new OcTree(bound, 2);
        for (Boid b : boids) {
            Boid tempBoid = b;
            octree.insert(new TreeItem(tempBoid));
            tempBoid.run(flock.boids);  
        }
    }
  
    void addBoid(Boid b) {
        boids.add(b);
    }
}

Main:

import peasy.*;
import peasy.org.apache.commons.math.geometry.*;
PeasyCam cam;
CameraState state;
PFont axisLabelFont;
PVector axisXHud, axisYHud, axisZHud, axisOrgHud;

import com.hamoid.*;
VideoExport videoExport;
int saveCount = 1;


// various verbs
boolean useOcTree = true, showBox = true, showOctree = true, show2 = true;
boolean avoidWalls = true, smoothEdges = true;

int z_min = 0, z_max = 800;
int perception_radius = 50, perception_radius2 = 50;
int boyz_num = 50;

Flock flock, flock2;
OcTree octree, octree2;
Cube bound;



void setup() {
  
   size(800, 800, P3D);
   
   // camera setup
   cam = new PeasyCam(this, 300);
   Rotation rot = new Rotation(RotationOrder.XYZ, PI, PI, 0);
   Vector3D center = new Vector3D(width/2, height/2, z_min);
   CameraState state = new CameraState(rot, center, 1500);
   cam.setState(state, 1500);
   state = cam.getState();
   
   // axis setup
   axisLabelFont = createFont( "Arial", 14 );
   axisXHud      = new PVector();
   axisYHud      = new PVector();
   axisZHud      = new PVector();
   axisOrgHud    = new PVector();
   
   
   // bounds of octrees 
   bound = new Cube(width/2, height/2, z_max/2, width/2, height/2, z_max/2);
   
   // flocks initialization
   flock = new Flock();
   flock2 = new Flock();
   
   for (int i = 0; i < boyz_num; i++){
       flock.addBoid( new Boid(#EA4C4C, perception_radius) );
       flock2.addBoid( new Boid(#BEAFD6, perception_radius2) );
   }
}

void draw() {
    
    background(27);
    directionalLight(150, 150, 150, 0, 1, 0);
    ambientLight(150, 150, 150);
    // BOX
    get_box();

    flock.run();
    flock2.run();
    
    if (showOctree){
        octree.show(#EA4C4C, 1);
        if (show2){
            // uncomment this to get the error that brokes the code, this is actually the problem,
            // i would like flock2 to run on octree2 but it runs on octree and thus the octree2
            // object never gets filled with the second kind (color) of boids
            // octree2.show(#BEAFD6, 1);
        }
    }
    
    // display axis
    calculateAxis(500);
    cam.beginHUD();
    drawAxis(3);
    cam.endHUD();
}

So, what I understand is that I should give octree as a “parameter” to the flock or (boid) class, so I could reference octree or octree2 respectively if I’m using the first flock or the second flock.

The problem here is that I’m not able to do it lol, I tried but if I add it in the flock class, also the boid class needs it, so I tried adding it to boid and I get a null pointer exception error…
I’m new to java/javascript so all this classes inheritance thing is something I still need to master.
Really don’t know, haaaaaalp me plz <3.

NB it’s the first time a write in a blog so if formatting is off let me know and I’ll fix it.
NB2 I’m Italian, so I’m sorry for the bad English.