Vector field to an array (Aborigines Art)

Hello (please excuse my poor English I am French speaking Belgian) I would like to know how I could put all the points of a vector field in an array?My goal is to replace the lines of my vector field with circles to simulate the aboriginal paintings I can’t find the answer. Thank you
What i m expect

2 Likes

Salut Hiram!

Would you have some vector field code to share with us?

I’m not sure if you will need a bi-dimensional array (like a grid of points) or perhaps another bi-dimensional structure, so that each “path” is an array (and you have an array of paths).
When you have an array that has to grow, sometimes we use an ArrayList.

So share some vector field ideas you have so that we can think together :slight_smile:

1 Like

I don’t fully understand but try something like this: (make the points yourself)

Code
ArrayList<PVector> pos = new ArrayList<PVector>(); // PVector info -> new PVector( x, y, radius)
float r = 10;
void setup() {
  size(600, 600);
}
void draw() {
  background(0);
  fill(255);
  for(int i = 0; i < pos.size(); i++) circle(pos.get(i).x,pos.get(i).y,pos.get(i).z*2);
  fill(255,50);
  circle(mouseX,mouseY,r*2);
}
void mousePressed() {
  pos.add(new PVector(mouseX,mouseY,r));
}
void mouseWheel(MouseEvent e) {
  r -= e.getCount();
}

The program doesn’t support saving the circles, in other words, you always start from zero.
Try implementing an eraser (clicking on a circle erases it).

2 Likes

Hello and thank you for your answers ! In fact, I am an artist and unfortunately a poor programmer :slight_smile:
I am attaching the image of what I would like to implement. I don’t want to copy, I would like to implement. But I’m not good enough to figure out how to get started.
Here’s what the creator says
"make a custom field of vectors (because I was fed up of using perlin) represented by a 2D array. Then I place a particle in it and make it step forward by a length of its diameter in the direction of the field ‘cell’ it is in. This makes a strip of particles that just touch each other.

Repeat this process, adding a function that checks for collisions before adding a new particle to the list.

Repeat until canvas is filled.

Colors are controlled with a hard coded color scheme that I generated. I use a process that decides where the main blocks of color should be with a% chance to pick a random color from the scheme. "

My goal is obviously to get closer to the art of the Australian aborigines. I do it well in hand painting with my brushes but I would like to make a digital canvas of 20 meters by 2.5 meters :slight_smile:

4 Likes

I tried to make it, but no luck : P, i can’t get the elaborate patterns, I just get a few random lines


(version 1 (left), version 2 (right) )

v2 is a lot better. Feel free to use it in any way!

Version 1
ArrayList<PVector> c = new ArrayList<PVector>();
ArrayList<path> p = new ArrayList<path>();
float v = 6, buffer = 1, r = 2;
float m = 0.01, maxTurn = PI;
void setup() { 
  size(600, 600);
  for(int i = 0; i < 20; i++) p.add(new path(random(width),random(height),random(TWO_PI),v,m));
}
void draw() {
  noStroke();
  background(0);
  loadPixels();
  for (int i = 0; i < width; i++) for (int j = 0; j < height; j++) { 
    color clr = color(255*noise(i*m, j*m));
    pixels[i+j*width] = clr;
  }
  updatePixels();
  for(int i = 0; i < p.size(); i++) p.get(i).move();
  for(int i = 0; i < p.size(); i++) p.get(i).display();
  for (int i = 0; i < c.size(); i++) circle(c.get(i).x, c.get(i).y, c.get(i).z*2);
}
class path {
  float x, y, dir, speed, m;
  boolean active = true;
  path(float x_, float y_, float dir_, float speed_, float m_) {
    x = x_;
    y = y_;
    dir = dir_;
    speed = speed_;
    m = m_;
  }
  void move() {
    if (active) {
      dir += noise(x*m, y*m)-0.5;
      x += cos(dir)*speed;
      y += sin(dir)*speed;
      boolean canPlace = true;
      for (int i = 0; i < c.size(); i++) {
        if (dist(x, y, c.get(i).x, c.get(i).y) < 2*r+buffer) canPlace = false;
      }
      if (canPlace) {
        c.add(new PVector(x, y, r));
      } else active = false;
    }
  }
  void display() {
    fill(255);
    circle(x,y,r*2);
  }
}

Version 2
ArrayList<PVector> c = new ArrayList<PVector>();
ArrayList<path> p = new ArrayList<path>();
float v = 6, buffer = 1, r = 2;
float m = 0.01, maxTurn = PI;
void setup() { 
  size(600, 600);
  for(int i = 0; i < 20; i++) p.add(new path(random(width),random(height),random(TWO_PI),v,m));
}
void draw() {
  noStroke();
  background(0);
  loadPixels();
  for (int i = 0; i < width; i++) for (int j = 0; j < height; j++) { 
    color clr = color(255*noise(i*m, j*m));
    pixels[i+j*width] = clr;
  }
  updatePixels();
  for(int i = 0; i < p.size(); i++) p.get(i).move();
  for(int i = 0; i < p.size(); i++) p.get(i).display();
  for (int i = 0; i < c.size(); i++) circle(c.get(i).x, c.get(i).y, c.get(i).z*2);
}
class path {
  float x, y, dir, speed, m;
  boolean active = true;
  path(float x_, float y_, float dir_, float speed_, float m_) {
    x = x_;
    y = y_;
    dir = dir_;
    speed = speed_;
    m = m_;
  }
  void move() {
    if (active) {
      dir = map(noise(x*m, y*m),0,1,-maxTurn,maxTurn);
      x += cos(dir)*speed;
      y += sin(dir)*speed;
      boolean canPlace = true;
      for (int i = 0; i < c.size(); i++) {
        if (dist(x, y, c.get(i).x, c.get(i).y) < 2*r+buffer) canPlace = false;
      }
      if (canPlace) {
        c.add(new PVector(x, y, r));
      } else active = false;
    }
  }
  void display() {
    fill(255);
    circle(x,y,r*2);
  }
}

2 Likes

Hello, a big thank you for your efforts and your help which is invaluable to me! Version 2 is indeed a great lesson for me!
I will try to make offsets to fill the workspace. In general when we paint in the manner of the aborigines of Austalia, we make a first “line” of points then a second pass which is an offset of the first then an offset of the seconds, etc. One moment, we branch off and we start again … a point line and offsets :slight_smile: Thank you for your friendly interest!
I m find this on the web : https://tylerxhobbs.com/#/side-effects-inclued/

1 Like

Yeah, I see parallel curves. I think of a walker that
leaves a trail. When the walker collides with a line,
it follows it.

My Sketch

Here is a simple Walker program. You can make a new Walker with mouse click. You can steer with cursor.

You can save with ‘s’.

This translates for me as store type PVector into an ArrayList. I haven’t done this I just show the points on the screen.

Sketch




// Walker  : key input. Mouse Press starts new Walker 

Walker walker;

//easing for the angle 
float easing =   0.061288; // 0.23; //

// timer for delaying things a bit 
int timer; 

//-----------------------------------------------------------------------------------------------------

void setup() {
  size(990, 800);

  background(0);
  walker = new Walker(width / 2, height / 2);

  timer = millis();
}// func 

void draw() {
  // background(0);

  fill(0); 
  noStroke(); 
  rect(0, 0, width/2-100, 44);
  fill(255); 
  text("Cursor left/right steering, up/down speed. Mouse to place a new Walker.", 12, 15); 

  // key pressed throughout
  readKeys(); 

  walker.update();
  //
}//func

//-----------------------------------------------------------------------------------------------------

void mousePressed() {
  walker = new Walker(mouseX, mouseY);
}

void keyPressed() {
  // ses also readKeys() 
  if (key=='s') {
    save("Abor"
      +dateTimeStamp() 
      +"_"+int(random(10000))
      +".jpg");
  }//if
}

String dateTimeStamp() {
  // date
  int year1=year();
  int month1=month();
  int day1=day();  

  // time 
  int hour1=hour();
  int minute1=minute();
  int seconds1=second();

  String buffer = 
    nf(year1, 2) 
    + nf(month1, 2) 
    + nf(day1, 2) 
    + "_" 
    + nf(hour1, 2) 
    + nf(minute1, 2) 
    + nf(seconds1, 2);

  return buffer;
}

void readKeys() {
  // key pressed throughout
  if (keyPressed) {
    if (keyCode == LEFT) {
      walker.turn(-1);
    } else if (keyCode == RIGHT) {
      walker.turn(1);
    } else if (keyCode == UP) {
      walker.changeSpeed(1);
    } else if (keyCode == DOWN) {
      walker.changeSpeed(-1);
    }
  }
}// func 

//==========================================================================

class Walker {

  float x, y;    // pos 
  float px, py;  // prev pos 
  float s, // speed 
    a, // angle 
    d;  // damping? 

  // constr 
  Walker(float _x, float  _y) {
    this.x = _x;
    this.y = _y;
    this.px = _x;
    this.py = _y;

    this.s = 1;
    this.a = TWO_PI-PI/2; // PI;
    this.d = 0.05;
  } // constr 

  void update() {
    this.move();
    this.checkEdges();
    this.show();
  }

  void move() {
    this.px = this.x;
    this.py = this.y;

    this.x = this.px + cos(this.a) * this.s;
    this.y = this.py + sin(this.a) * this.s;
  }

  void checkEdges() {
    if (this.x > width) {
      this.x = 0;
      this.px = this.x;
    } else if (this.x < 0) {
      this.x = width;
      this.px = this.x;
    }

    if (this.y > height) {
      this.y = 0;
      this.py = this.y;
    } else if (this.y < 0) {
      this.y = height;
      this.py = this.y;
    }
  }

  void turn(float _t) {
    this.a += _t * this.d;

    if (a>TWO_PI) 
      a=a-TWO_PI;
    if (a>TWO_PI) 
      a=a-TWO_PI;
    if (a>TWO_PI) 
      a=a-TWO_PI;
  }

  void changeSpeed(float _s) {
    this.s =
      constrain(this.s + (_s * this.d), 0, 3);
  }

  void show() {
    // stroke(255);
    stroke(0, 255, 0);
    strokeWeight(3);
    line(this.px, this.py, 
      this.x, this.y);
  }
}//class
//


New version with auto turning




// Walker  : key input. Mouse Press starts new Walker. Auto Turning of Walker  
Walker walker;

//easing for the angle 
float easing =   0.061288; // 0.23; //

//-----------------------------------------------------------------------------------------------------

void setup() {
  size(990, 800);

  background(0);
  walker = new Walker(width / 2, height / 2);

  //  timer = millis();
}// func 

void draw() {
  // background(0);

  fill(0); 
  noStroke(); 
  rect(0, 0, width/2-100, 44);
  fill(255); 
  text("Cursor left/right steering, up/down speed. Mouse to place a new Walker.", 12, 15); 

  // key pressed throughout
  readKeys(); 

  walker.update();


  //
}//func

//-----------------------------------------------------------------------------------------------------

void mousePressed() {
  walker = new Walker(mouseX, mouseY);
}

void keyPressed() {
  // ses also readKeys() 
  if (key=='s') {
    save("Abor"
      +dateTimeStamp() 
      +"_"+int(random(10000))
      +".jpg");
  }//if
}

String dateTimeStamp() {
  // date
  int year1=year();
  int month1=month();
  int day1=day();  

  // time 
  int hour1=hour();
  int minute1=minute();
  int seconds1=second();

  String buffer = 
    nf(year1, 2) 
    + nf(month1, 2) 
    + nf(day1, 2) 
    + "_" 
    + nf(hour1, 2) 
    + nf(minute1, 2) 
    + nf(seconds1, 2);

  return buffer;
}

void readKeys() {
  // key pressed throughout
  if (keyPressed) {
    if (keyCode == LEFT) {
      walker.turn(-1);
    } else if (keyCode == RIGHT) {
      walker.turn(1);
    } else if (keyCode == UP) {
      walker.changeSpeed(1);
    } else if (keyCode == DOWN) {
      walker.changeSpeed(-1);
    }
  }
}// func 

//==========================================================================

class Walker {

  float x, y;    // pos 
  float px, py;  // prev pos 
  float s, // speed 
    a, // angle 
    d;  // damping? 

  float angleThroughout = 0.021; 
  float angleThroughoutAdd = 0.001;
  float angleThroughoutAddAdd = 0.000001;

  // constr 
  Walker(float _x, float  _y) {
    this.x = _x;
    this.y = _y;
    this.px = _x;
    this.py = _y;

    this.s = 1;
    this.a = TWO_PI-PI/2; // PI;
    this.d = 0.05;
  } // constr 

  void update() {
    this.move();
    this.checkEdges();
    this.show();

    walker.turn(angleThroughout);
    angleThroughout+=angleThroughoutAdd;
    angleThroughoutAdd -= angleThroughoutAddAdd;

    if (angleThroughout<-0.1) {
      println("Here");
      angleThroughoutAddAdd = -1*abs(angleThroughoutAddAdd);
    }
  }

  void move() {
    this.px = this.x;
    this.py = this.y;

    this.x = this.px + cos(this.a) * this.s;
    this.y = this.py + sin(this.a) * this.s;
  }

  void checkEdges() {
    if (this.x > width) {
      this.x = 0;
      this.px = this.x;
    } else if (this.x < 0) {
      this.x = width;
      this.px = this.x;
    }

    if (this.y > height) {
      this.y = 0;
      this.py = this.y;
    } else if (this.y < 0) {
      this.y = height;
      this.py = this.y;
    }
  }

  void turn(float _t) {
    a += _t * d;

    if (a>TWO_PI) 
      a=a-TWO_PI;
    if (a>TWO_PI) 
      a=a-TWO_PI;
    if (a>TWO_PI) 
      a=a-TWO_PI;
  }

  void changeSpeed(float _s) {
    this.s =
      constrain(this.s + (_s * this.d), 0, 3);
  }

  void show() {
    // stroke(255);
    stroke(0, 255, 0);
    strokeWeight(3);
    line(this.px, this.py, 
      this.x, this.y);
  }
}//class
//

new version

In this new version you can start multiple Walkers that walk simultaneously.
Besides, when you google swarm you can also interpret the image a small swarm (flock) of birds that draw.

// Walker  : key input. Mouse Press starts new Walker. Auto Turning of Walker. With ArrayList  

ArrayList<Walker> walkers = new ArrayList(); 

//easing for the angle 
float easing =   0.061288; // 0.23; //

//-----------------------------------------------------------------------------------------------------

void setup() {
  size(990, 800);

  background(0);

  Walker nwalker = new Walker(width / 2, height / 2);
  walkers.add(nwalker); 

  // timer = millis();
}// func 

void draw() {
  // background(0);

  fill(0); 
  noStroke(); 
  rect(0, 0, width/2-100, 44);
  fill(255); 
  text("Cursor left/right steering, up/down speed. Mouse to place a new Walker.", 12, 15); 

  // key pressed throughout
  readKeys(); 

  for (Walker w : walkers)
    w.update();

  //
}//func

//-----------------------------------------------------------------------------------------------------

void mousePressed() {
  Walker  nwalker = new Walker(mouseX, mouseY);
  walkers.add(nwalker);
}

void keyPressed() {
  // ses also readKeys() 
  if (key=='s') {
    save("Abor"
      +dateTimeStamp() 
      +"_"+int(random(10000))
      +".jpg");
  }//if
}

String dateTimeStamp() {
  // date
  int year1=year();
  int month1=month();
  int day1=day();  

  // time 
  int hour1=hour();
  int minute1=minute();
  int seconds1=second();

  String buffer = 
    nf(year1, 2) 
    + nf(month1, 2) 
    + nf(day1, 2) 
    + "_" 
    + nf(hour1, 2) 
    + nf(minute1, 2) 
    + nf(seconds1, 2);

  return buffer;
}

void readKeys() {
  // key pressed throughout
  if (keyPressed) {
    if (keyCode == LEFT) {
      walkers.get(0).turn(-1);
    } else if (keyCode == RIGHT) {
      walkers.get(0).turn(1);
    } else if (keyCode == UP) {
      walkers.get(0).changeSpeed(1);
    } else if (keyCode == DOWN) {
      walkers.get(0).changeSpeed(-1);
    }
  }
}// func 

//==========================================================================

class Walker {

  float x, y;    // pos 
  float px, py;  // prev pos 
  float s, // speed 
    a, // angle 
    d;  // damping? 

  float angleThroughout = 0.021; 
  float angleThroughoutAdd = 0.001;
  float angleThroughoutAddAdd = 0.000001;

  // constr 
  Walker(float _x, float  _y) {
    this.x = _x;
    this.y = _y;
    this.px = _x;
    this.py = _y;

    this.s = 1;
    this.a = TWO_PI-PI/2; // PI;
    this.d = 0.05;
  } // constr 

  void update() {
    this.move();
    this.checkEdges();
    this.show();

    turn(angleThroughout);
    angleThroughout+=angleThroughoutAdd;
    angleThroughoutAdd -= angleThroughoutAddAdd;

    if (angleThroughout<-0.1) {
      println("Here");
      angleThroughoutAddAdd = -1*abs(angleThroughoutAddAdd);
    }
  }

  void move() {
    this.px = this.x;
    this.py = this.y;

    this.x = this.px + cos(this.a) * this.s;
    this.y = this.py + sin(this.a) * this.s;
  }

  void checkEdges() {
    if (this.x > width) {
      this.x = 0;
      this.px = this.x;
    } else if (this.x < 0) {
      this.x = width;
      this.px = this.x;
    }

    if (this.y > height) {
      this.y = 0;
      this.py = this.y;
    } else if (this.y < 0) {
      this.y = height;
      this.py = this.y;
    }
  }

  void turn(float _t) {
    a += _t * d;

    if (a>TWO_PI) 
      a=a-TWO_PI;
    if (a>TWO_PI) 
      a=a-TWO_PI;
    if (a>TWO_PI) 
      a=a-TWO_PI;
  }

  void changeSpeed(float _s) {
    this.s =
      constrain(this.s + (_s * this.d), 0, 3);
  }

  void show() {
    // stroke(255);
    stroke(0, 255, 0);
    strokeWeight(3);
    line(this.px, this.py, 
      this.x, this.y);
  }
}//class
//

[EDITED: These images are the result of a bunch of different Sketches based on the above Sketches. They all feature different behavior of the Walkers. Mostly 7 Walkers at once are started where you click the mouse. I haven’t posted the Sketches, it was late, but I am willing to share.]

3 Likes

Expanding on the above, here is a rough recreation of your attached image if it helps


void setup(){
  size(800,800);
  circles = new ArrayList[int(width/circlesize)][int(height/circlesize)];
  for(int x=0;x<circles.length;x++){
    for(int y=0;y<circles[x].length;y++){
      circles[x][y] = new ArrayList();
    }
  }
  
  noiseDetail(1,1);
  background(0);
  colorMode(HSB);
  noStroke();
  for(int i = 0;i<1600;i++){// make 1600 circle droppers
    float x=0,y=0;
    for(int d=0;d<200;d++){ // give 200 tries to not spawn on a existing circle.
      x = random(width);y=random(height);
      if(!isCircleThere(x,y)){
        break;
      }
    }
    Walker w = new Walker(x,y);
    for(int z = 0;z<2000;z++){ //step them 2000 times
      w.move();
    }
  }
}

ArrayList<Walker> walkers = new ArrayList();

ArrayList<PVector> circles[][]; // grid structure to speed things up otherwise it gets way too slow
float circlesize =5;

boolean isCircleThere(float x,float y){
  int gx = int(x/circlesize);
  int gy = int(y/circlesize);
  for(int a = gx-1;a<=gx+1;a++){
    for(int b = gy-1;b<=gy+1;b++){
      if(a<0||b<0||a>=circles.length||b>=circles[0].length){continue;}
      ArrayList<PVector> gridcircle = circles[a][b];
      for(PVector p:gridcircle){
        if((p.x-x)*(p.x-x)+(p.y-y)*(p.y-y)<(circlesize*circlesize)){
          return true;
        }
      }
    }
  }
  return false;
}

void addCircle(PVector p){
  int gx = int(p.x/circlesize);
  int gy = int(p.y/circlesize);
  if(gx<0||gy<0||gx>=circles.length||gy>=circles[0].length){return;}
  circles[gx][gy].add(p);
}



class Walker{
  PVector pos ; 
  color col;
  Walker(float  x,float y){
    pos = new PVector(x,y);
    col = color(noise(x*0.01,y*0.01)>0.25? random(20):random(120,140), noise(x*0.01,y*0.01+15)>0.25?random(50,80):random(200,250),random(200,250));
  }
  void move(){
    float mx = (noise(pos.x*0.005,pos.y*0.005)-0.25)*4;
    float my = (noise(pos.x*0.005+99,pos.y*0.005+99)-0.25)*4;
    pos.add(mx,my);
    if(!isCircleThere(pos.x,pos.y)){
      fill(col);
      ellipse(pos.x,pos.y,circlesize*0.8,circlesize*0.8);
      addCircle(pos.copy());
    }
    if(abs(mx)+abs(my)<0.1){
      //pos.set(random(width),random(height)); //- enable if you wish the point to teleport when it reaches a local minima
    }
  }
}
4 Likes

This looks amazing! Really interesting.

1 Like

Hello a big thank you to all for your kindness and your efforts which ultimately led to this wonderful result. It is a joy for a bad programmer artist to benefit from the experience of champions. Obviously I would never have found the solution myself. Awesome! Stay safe

2 Likes

Hello and thank you…. This is also an opportunity for me to learn by reading your code. Particularly concerning the detection of collisions … it has been a mystery to me for a long time (well it still is but as far as circles are concerned thanks to your brilliant demonstration I have advanced!! Stay safe

3 Likes

Hi @Hiram-Belgium,

I know it’s kind of late but I wanted to address the picture you were originally interested in (the first one you posted). The following doesn’t have much to do with vector fields but hopefully provides a different perspective on how to “simulate” the kind of aboriginal art you were initially going for.

One of the main challenges presented by this artwork is its structure. Unlike the other sketch from Tyler Hobbs, this drawing has a backbone or a network-like skeleton that divides the canvas into separated pieces.

Usually, creative coders or game designers like to rely on partitioning techniques like the Delaunay triangulation, the Voronoi decomposition or the quadtree subdivision, to name the most popular ones. But it appeared to me that in this case the overall shape of the partitioning was rather reminiscent of the reticulate pattern you can find on leaf venation.

From my past experiments with various growth simulations I learned that there is a particular type of graph that helps cells objects forming this kind of structure: the Relative Neighborhood Graph (RNG), also known as (albeit a bit different) the Urquhart Graph. And it turns out that computing the approximate relative neighborhood of the probable vertices of this artwork does indeed output a strikingly similar mesh:


What it means is that there seems to be an underlying logic ruling the arrangement of this artwork that a graph algorithm can approximate. In other words, it is most likely that a “generative” approach to this design can be implemented.

Unfortunately I’m pretty busy these days but if I had time to tackle this with Processing I would proceed like this:

  • randomly display points on a plane
  • compute the corresponding Urquhart graph (removing the longest edge from each triangle of a Delaunay triangulation)
  • apply noise to the edges
  • split the plane with the edges to get all the different pieces (“Hemesh” library)
  • populate each piece (surface) with the pattern of your choice:
    • point cloud with Poisson Disk Sampling
    • circles with Circle Packing algorithm
    • wavy lines with Perlin noise or “Handy” library
  • join some edges + offset them inward/outward
  • draw equidistant points in-between
  • create stripes (PShapes) from the offsets


(quick reenactment made with Rhino/Grasshopper, needs some fine-tuning obvioulsy)

4 Likes

Good morning all ! Thank you for this beautiful spirit of mutual aid. Thank you Solub for bringing additional ideas … Very interesting notions that I will have to analyze because I am an artist but not a programmer … I quickly measure my limits. It’s interesting that you are using Rhino / Grasshopper because it is a program that I like for making “3d illusions” objects with Boolean collisions … I didn’t know that it could as well be oriented in “aboriginal painting” type graphics is it possible to have the “gh” file to examine it please? guy.lemaire@art-today.eu

1 Like

@solub Hello, I cannot understand everything and I am sad about it … I made a little progress with your tutorial even if I do not understand all the elements … I used a plugin (super Delaunay) to try to understand the first steps … I am not able to go very far but without arriving at the quality of your demonstration, which is very very aboriginal, I am really in the attempts to understand your different levels … I don’t know if it is accessible to a beginner? Thank you

2 Likes

Hi @Hiram-Belgium, sorry for the late reply. I just sent you a private message, be sure to check it out !

1 Like