Circles, Nomads, and A Running Course

Nomads starting on circles, then finding their own way in life

My 2D graphics course went live today! It was hot here last night so I slept on the couch downstairs, and by coincidence my dog woke me up around 12:30. Since the course materials were supposed to appear on the site at midnight, I checked – and there they were! So everything seems smooth so far, and I’ve had no complaints from students.

More Nomads, searching for fulfillment

To celebrate, I decided to make something pretty. I wrote this program for making little splindly-yarn things. Here are a few images I just made. It’s fun to play with this – I could sit here making little things like this all day!

Here’s the code. If you’re in my class, you’ll find all of this easy to understand by the time we’re done.

// Circle Nomads 
// Andrew Glassner, 5 May 2013
// We start with a single circle of Nomads. Click the
// mouse to start a new one, centered at the mouse.
Nomad[] NomadList;
int StepCount;
int MaxSteps;
boolean Frozen = false;
float NoiseScale;

void setup() {
  size(800, 600);
  smooth();
  // Start with one burst in a random place
  initNomads(random(0,width), random(0,height));
  background(0);
}

// Create a circle of Nomads. Each one is randomly on the
// circle, and has some variation of the shared color.
void initNomads(float diskX, float diskY) {
  NomadList = new Nomad[300];
  float diskR = random(25, 100);
  colorMode(HSB);
    color baseClr = color(random(0,255), random(100,255), random(170,255));
  colorMode(RGB);
  for (int i=0; i<300; i++) {
    float angle = random(0, TWO_PI);
    color thisClr = wiggleColorHSB(baseClr, .1, .1, .1);
    NomadList[i] = new Nomad(diskX+(diskR*cos(angle)),
                             diskY+(diskR*sin(angle)), thisClr, 1);
  }
  StepCount = 0;
  MaxSteps = int(random(200, 800)); // how many times to draw
  // low values of noise give long, thin tendrils. High values are twistier.
  NoiseScale = random(.0001, .03);
} 

// given an HSB color, wiggle each of the values around a bit
color wiggleColorHSB(color c, float wh, float ws, float wb) {
  colorMode(HSB);
    float newHue = (hue(c)+(wh*255*random(-1, 1))) % 255;
    float newSat = constrain(saturation(c)+(ws*255*random(-1,1)), 0, 255);
    float newBrt = constrain(brightness(c)+(wb*255*random(-1,1)), 0, 255);
    color newClr = color(newHue, newSat, newBrt);
  colorMode(RGB);
  return(newClr);
}

void draw() {
  if (Frozen) return;
  if (++StepCount > MaxSteps) {
    return;
  }
  for (int i=0; i<NomadList.length; i++) {
    NomadList[i].render();
    NomadList[i].update();
  }
}

void keyTyped() {
  if (key == ' ') Frozen = !Frozen;
} 

void mousePressed() {
  initNomads(mouseX, mouseY);
} 

// A class for a little wanderer
class Nomad {
  PVector velocity, pos, oldPos;
  color clr;
  float r, opacity;

  Nomad(float startX, float startY, color startClr, float startR) {
    pos = new PVector(startX, startY);
    oldPos = pos.get();
    clr = startClr;
    r = startR;
    opacity = random(.25, .5);
    velocity = new PVector(random(.5, 1), random(.5, 1));
    if (random(100)>50) velocity.x *= -1;
    if (random(100)>50) velocity.y *= -1;
  } 

  /* Each Nomad has a velocity and a position. On each frame, the
  position is updated by the velocity. The velocity changes on every
  frame, inheriting some change from the underlying noise. */

  void update() {
    // Get a new value for the velocity from the noise
    float vx = noise(pos.x*NoiseScale, pos.y*NoiseScale);
    float vy = noise((2*width)+(pos.x*NoiseScale), (2*height)+(pos.y*NoiseScale));
    // Noise values are from (0,1) but we want them from (-1,1)
    vx = lerp(-1, 1, vx);
    vy = lerp(-1, 1, vy);
    // Get some "blending factors" to control how the velocity updates
    float bx = lerp(.25, .75, noise(pos.y * NoiseScale));
    float by = lerp(.25, .75, noise(pos.x * NoiseScale));
    // Add some of the new values to the velocity
    velocity.x += bx*vx;
    velocity.y += by*vy;
    // Normalize the velocity, save the old point, update the position
    velocity.normalize();
    oldPos = pos.get();
    pos.x += velocity.x;
    pos.y += velocity.y; 
    // Finally, make the particle just ever so slightly less opaque
    opacity *= .995;
  }

  void render() {
    stroke(clr, opacity*255);
    strokeWeight(r);
    noFill();
    line(oldPos.x, oldPos.y, pos.x, pos.y);
  }
}

Leave a Reply

Your email address will not be published. Required fields are marked *