bookooBread – BlobFamily

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

My interpretation of the blob family prompt manifested itself into an exploration of the differential growth algorithm. After dong some research on the algorithm itself and code tracing a few different implementations, I landed on Alberto Giachino’s Differential line growth with Processing. I have mostly been working in processing, and it didn’t require any outside dependencies, so it made the most sense to use this one as the base. Jason Webb’s 2D differential growth experiments also really helped in understanding how the algorithm works and how it can be modified. After playing around with the algorithm in the hatching exercise from last week, I wanted to see how I could constrain the growth to one specific shape, more specifically, in a blob-like shape. Golan had showed me how to use an off-screen graphics buffer, so I thought it might work if I tried to use that as a layer mask of sorts. The off-screen graphics buffer would contain white filled blobs (made by modifying the radius of a circle with perlin noise) against a black background. Therefore, whenever a node was at the location of a white fill, the differential growth cohesion and separation forces would act. When a node on the circle was at location of a black fill, there were no forces acting on it to make it grow. The nodes that were growing then formed into the shape of the blobs, and to my surprise, sort of collapsed in on each other where the edges were. It made what looked like the outline of the blob. The two pictures below are the blobs rendered digitally.

I had a lot of fun with this one! I love texture and I love organic/bodily looking things.

Here is the code:


import processing.javafx.*;
import processing.svg.*;
boolean doRecord = false;
// This code is by Alberto Giachino with suggestions from Frederik Vanhoutte
// From: http://www.codeplastic.com/2017/07/22/differential-line-growth-with-processing/

// PARAMETERS
float _maxForce = 0.9; // Maximum steering force
float _maxSpeed = 35; // Maximum speed
float _desiredSeparation = 25;
float _separationCohesionRation = 0.6;
float _maxEdgeLen = 6;

PGraphics pg;
DifferentialLine _diff_line1;

void setup() {
  size(600, 600, FX2D);

  // Make our masks
  pg = createGraphics(600, 600);
  pg.beginDraw();
  pg.background(0);
  pg.noStroke();
  pg.fill(255);
  for (int i = 0; i<=5; i++) {
    randomSeed(1000*i + 13);
    noiseSeed(i+13);
    float radius = random(80, 130);
    float randX = random(10, width-10);
    float randY = random(10, height-10);
    createBounds(randX, randY, radius);
  }
  pg.endDraw();

  _diff_line1 = new DifferentialLine(_maxForce, _maxSpeed, _desiredSeparation, _separationCohesionRation, _maxEdgeLen);

  float nodesStart = 30;
  float angInc = TWO_PI/nodesStart;
  float rayStart = 150;
  for (float a=0; a<TWO_PI; a+=angInc) {
    float x = width/2 + cos(a) * rayStart;
    float y = height/2 + sin(a) * rayStart;
    _diff_line1.addNode(new Node(x, y, _diff_line1.maxForce, _diff_line1.maxSpeed));
  }
}

//make array of off screen graphics buffer blob class in future
void draw() {
  background(255);
  //image(pg, 0, 0);

  if (doRecord) {
    beginRecord(SVG, "differential_growth_10.svg");
  }
  stroke(0);//255, 0, 0);
  strokeWeight(1.0);
  noFill();

  _diff_line1.separationCohesionRation = 0.7;
  _diff_line1.maxForce = 0.6;

  _diff_line1.run();
  _diff_line1.renderLine();

  if (doRecord) {
    endRecord();
    doRecord = false;
  }
}

void createBounds(float randX, float randY, float radius) {

  float yoff = 0.0;

  pg.pushMatrix();
  pg.translate(randX, randY);

  pg.beginShape();
  float xoff = 0;
  for (float a = 0; a < TWO_PI; a += 0.1) {
    float offset = map(noise(xoff, yoff), 0, 1, -25, 25);
    float r = radius + offset;
    float x = r * cos(a);
    float y = r * sin(a);
    pg.vertex(x, y);
    xoff += 0.1;
  }
  pg.endShape();
  pg.popMatrix();

  yoff += 0.01;
}

void keyPressed() {
  if (key == ' ') {
    doRecord = true;
  }
}

class DifferentialLine {
  ArrayList nodes;
  float maxForce;
  float maxSpeed;
  float desiredSeparation;
  float sq_desiredSeparation;
  float separationCohesionRation;
  float maxEdgeLen;
  DifferentialLine(float mF, float mS, float dS, float sCr, float eL) {
    nodes = new ArrayList();
    maxSpeed = mF;
    maxForce = mS;
    desiredSeparation = dS;
    sq_desiredSeparation = sq(desiredSeparation);
    separationCohesionRation = sCr;
    maxEdgeLen = eL;
  }
  void addNode(Node n) {
    nodes.add(n);
  }
  void addNodeAt(Node n, int index) {
    nodes.add(index, n);
  }

  void run() {
    differentiate();
    growth();
  }

  void growth() {
    for (int i=0; i<nodes.size()-1; i++) { Node n1 = nodes.get(i); Node n2 = nodes.get(i+1); float d = PVector.dist(n1.position, n2.position); if (d>maxEdgeLen) { // Can add more rules for inserting nodes
        int index = nodes.indexOf(n2);
        PVector middleNode = PVector.add(n1.position, n2.position).div(2);
        addNodeAt(new Node(middleNode.x, middleNode.y, maxForce*noise(i), maxSpeed), index);
      }
    }
  }
  void differentiate() {
    PVector[] separationForces = getSeparationForces();
    PVector[] cohesionForces = getEdgeCohesionForces();
    for (int i=0; i<nodes.size(); i++) { int nx = (int)round(nodes.get(i).position.x); int ny = (int)round(nodes.get(i).position.y); PVector separation = separationForces[i]; PVector cohesion = cohesionForces[i]; separation.mult(separationCohesionRation); color c = pg.get(nx, ny); int val = (int) red(c); if (val > 0) {

        nodes.get(i).applyForce(separation);
        nodes.get(i).applyForce(cohesion);
        nodes.get(i).update();
      }
    }
  }

  PVector[] getSeparationForces() {
    int n = nodes.size();
    PVector[] separateForces=new PVector[n];
    int[] nearNodes = new int[n];
    Node nodei;
    Node nodej;
    for (int i=0; i<n; i++) {
      separateForces[i]=new PVector();
    }
    for (int i=0; i<n; i++) {
      nodei=nodes.get(i);
      for (int j=i+1; j<n; j++) { nodej=nodes.get(j); PVector forceij = getSeparationForce(nodei, nodej); if (forceij.mag()>0) {
          separateForces[i].add(forceij);
          separateForces[j].sub(forceij);
          nearNodes[i]++;
          nearNodes[j]++;
        }
      }
      if (nearNodes[i]>0) {
        separateForces[i].div((float)nearNodes[i]);
      }
      if (separateForces[i].mag() >0) {
        separateForces[i].setMag(maxSpeed);
        separateForces[i].sub(nodes.get(i).velocity);
        separateForces[i].limit(maxForce);
      }
    }
    return separateForces;
  }
  PVector getSeparationForce(Node n1, Node n2) {
    PVector steer = new PVector(0, 0);
    float sq_d = sq(n2.position.x-n1.position.x)+sq(n2.position.y-n1.position.y);
    if (sq_d>0 && sq_d<sq_desiredSeparation) {
      PVector diff = PVector.sub(n1.position, n2.position);
      diff.normalize();
      diff.div(sqrt(sq_d)); //Weight by distacne
      steer.add(diff);
    }
    return steer;
  }
  PVector[] getEdgeCohesionForces() {
    int n = nodes.size();
    PVector[] cohesionForces=new PVector[n];
    for (int i=0; i<nodes.size(); i++) {
      PVector sum = new PVector(0, 0);
      if (i!=0 && i!=nodes.size()-1) {
        sum.add(nodes.get(i-1).position).add(nodes.get(i+1).position);
      } else if (i == 0) {
        sum.add(nodes.get(nodes.size()-1).position).add(nodes.get(i+1).position);
      } else if (i == nodes.size()-1) {
        sum.add(nodes.get(i-1).position).add(nodes.get(0).position);
      }
      sum.div(2);
      cohesionForces[i] = nodes.get(i).seek(sum);
    }
    return cohesionForces;
  }
  //void renderShape() {
  //  beginShape();
  //  for (int i=0; i<nodes.size(); i++) {
  //    vertex(nodes.get(i).position.x, nodes.get(i).position.y);
  //  }
  //  endShape(CLOSE);
  //}
  void renderLine() {
    beginShape();
    for (int i=0; i<nodes.size(); i++) {
      PVector p1 = nodes.get(i).position;
      curveVertex(p1.x, p1.y);
      //point(p1.x,p1.y);
    }
    endShape(CLOSE);

    //for (int i=0; i<nodes.size()-1; i++) {
    //  PVector p1 = nodes.get(i).position;
    //  PVector p2 = nodes.get(i+1).position;
    //  line(p1.x, p1.y, p2.x, p2.y);
    //  if (i==nodes.size()-2) {
    //    line(p2.x, p2.y, nodes.get(0).position.x, nodes.get(0).position.y);
    //  }
    //}
  }
}
class Node {
  PVector position;
  PVector velocity;
  PVector acceleration;
  float maxForce;
  float maxSpeed;
  Node(float x, float y, float mF, float mS) {
    acceleration = new PVector(0, 0);
    velocity =PVector.random2D();
    position = new PVector(x, y);
    maxSpeed = mF;
    maxForce = mS;
  }
  void applyForce(PVector force) {
    acceleration.add(force);
  }
  void update() {
    velocity.add(acceleration);
    velocity.limit(maxSpeed);
    velocity.limit(maxSpeed);
    position.add(velocity);
    acceleration.mult(0);
    if (position.x == width-200 || position.x == 200) {
      velocity.x = 0;
      maxForce = 0;
    }
    if (position.y == height-200 || position.x == 200) {
      velocity.y = 0;
      maxForce = 0;
    }
  }
  PVector seek(PVector target) {
    PVector desired = PVector.sub(target, position);
    desired.setMag(maxSpeed);
    PVector steer = PVector.sub(desired, velocity);
    steer.limit(maxForce);
    return steer;
  }
}