Project 09 – COMPOSITION

Inspired by Casey Reas, I set off to create my own generative environment governed by two independent factors. Here’s the idea: these little reactive agents (turtles) want food. The mouse cursor represents the food, so they try to get as close to it as possible. Here’s the catch: the environment in which they live is full of hills and valleys, and they don’t know how to traverse the landscape besides that which is in their immediate vicinity. Furthermore, the agents prefer to move around mountains or down inclines, but rarely straight up a steep incline.

Note: With the permission of Golan, I developed my project in OpenFrameworks (OF), a powerful open-source arts engineering toolkit. I also modified the turtle API to be compatible with C++ and added a “angleToward()” function, which angles the turtle a certain amount in a specific direction (specified in terms of an angle, not a coordinate pair). Since the sketch is written in C++ and not based in the browser, it’s a lot faster. I’ve attached video captures of it below. Here’s a link to the GitHub repository.

Process sketches:

IMG_5273 IMG_5274

Screenshots (Noise field, Noise-derived Topology, and following process screenshots):

Screen Shot 2015-11-05 at 9.27.24 PM Screen Shot 2015-11-05 at 9.27.47 PM Screen Shot 2015-11-05 at 10.25.34 PM Screen Shot 2015-11-05 at 10.32.10 PMScreen Shot 2015-11-05 at 10.48.51 PM  Screen Shot 2015-11-05 at 10.41.27 PM Screen Shot 2015-11-05 at 10.33.07 PM   Screen Shot 2015-11-05 at 10.51.33 PM  Screen Shot 2015-11-05 at 10.42.16 PM

Video Demos:

Crucial Code Snippets (in c++):

#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup(){
    ofSetBackgroundAuto(false);
    ofBackground(255);
    
    // ------------------------------
    // ------ REFERENCE NOISE -------
    // ------------------------------
    
    // create terrain (black = high, white = low)
    float noiseScale = .003; // 1 = one pixel is one unit
    float noiseOffset = 0.0;
    topoNoise.allocate(ofGetWidth(), ofGetHeight(), OF_IMAGE_GRAYSCALE);
    int nPixels = ofGetWidth() * ofGetHeight();

    // pointer to beginning of pixels array
    // this stores the address of the 0th element
    // (an array is really just a pointer to a contiguous block of memory)
    unsigned char* pixelPointer = topoNoise.getPixels();
    
    // go through all pixels
    for (int i = 0; i < nPixels; i++) {
        // set where the pointer is pointing to a pixel value (perlin noise)
        // note: data type pointed to must be same as pointer type (How is this possible if the pointer index can get larger than char?)
        float px = noiseScale * (i % ofGetWidth());
        float py = noiseScale * ((int)(i / ofGetWidth()));
        *pixelPointer = (unsigned char)(round(ofNoise(px + noiseOffset, py + noiseOffset) * 255.));
        
        // print the value just assigned (the value pointed to by the pointer)
        // note: convert to int since the stored value is char
//        cout << (int)*pixelPointer << endl;
        
        // increment the pointer (the "house" that stores the address)
        pixelPointer++;
    }
    topoNoise.update();
    
    
    // ------------------------------
    // ---------- TOPOLOGY ----------
    // ------------------------------
    
    // create a new pointer (is this necessary??) at beginning
    unsigned char* pointer = topoNoise.getPixels();
    
    // allocate space for directions array
    topoNormals.allocate(ofGetWidth(), ofGetHeight(), OF_IMAGE_GRAYSCALE);
    unsigned char* normals = topoNormals.getPixels();
    
    // based on topoNoise, create a new toplogy map of the direction of declines (i.e. normals projected down into 2d)
    // loop through pixels to find their directions (0 to 255 ~ 0 to 2PI radians)
    for (int i = 0; i < ofGetHeight(); i++) { // rows
        for (int j = 0; j < ofGetWidth(); j++) { // cols
            
            // --------- HORIZONTAL -----------
            
            // pixel to Left: find the index
            int xL = (j - 1) % ofGetWidth();        // x coordinate
            int yL = i;                             // y coordinate
            int indexL = yL * ofGetWidth() + xL;    // index
            // move pointer to this index
            pointer = pointer + indexL;
            // find pixel value at this location
            int pixelL = (int)*pointer;             // pixel value to left
            pointer = pointer - indexL;             // return pointer to start
            
            // pixel to Right
            int xR = (j + 1) % ofGetWidth();
            int yR = i;
            int indexR = yR * ofGetWidth() + xR;
            pointer = pointer + indexR;
            int pixelR = (int)*pointer;
            pointer = pointer - indexR;
            
            // find the x axis normal
            int xNormal = pixelL - pixelR; // facing to right is positive
            
            // ---------- VERTICAL -----------
            
            // pixel Up
            int xU = j;
            int yU = (i - 1) % ofGetHeight();
            int indexU = yU * ofGetWidth() + xU;
            pointer = pointer + indexU;
            // find pixel value at this location
            int pixelU = (int)*pointer;
            pointer = pointer - indexU;
            
            // pixel Down
            int xD = j;
            int yD = (i + 1) % ofGetHeight();
            int indexD = yD * ofGetWidth() + xD;
            pointer = pointer + indexD;
            int pixelD = (int)*pointer;
            pointer = pointer - indexD;
            
            // find the y axis normal
            int yNormal = pixelU - pixelD; // facing down is positive
            
            // ----------- NORMAL -----------
            
            // convert these into a direction
            // NOTE: this discards the amplitude (steepness of incline)
            int angle = round(atan2((double)yNormal, (double)xNormal) / (2 * M_PI) * 255.0);
            
            // set the pixel value to the angle
            *normals = (unsigned char)angle;

            // increment pointer
            normals++;
        }
    }
    topoNormals.update();
    
    
    // ------------------------------
    // -------- SETUP AGENTS --------
    // ------------------------------
    
    // initialize the agents
    for (int i = 0; i < nAgents; i++) {
        turtle tempAgent;
        tempAgent.setPosition(ofRandom(ofGetWidth()), ofRandom(ofGetHeight()));
        tempAgent.angle = ofRandom(2 * PI);
        tempAgent.setWeight(.01);
        tempAgent.setColor(round(ofRandom(1)) * 255);
        agents.push_back(tempAgent);
    }
}

//--------------------------------------------------------------
void ofApp::draw(){
    
//    topoNoise.draw(0, 0);
//    topoNormals.draw(0, 0);
    
    // ------------------------------
    // ---- UPDATE & DRAW AGENTS ----
    // ------------------------------
    
    // agents want to "eat" the mouse; however, their environment influences the path they must take
    
    if (bStart) {
        unsigned char* pointer = topoNormals.getPixels();
        for (int i = 0; i < nAgents; i++) {
            
            // find the slope downward at agent's location
            int px = round(agents[i].x - 1);
            int py = round(agents[i].y - 1);
            int index = px + py * ofGetWidth();
            pointer = pointer + index;
            int tempAngle = (int)*pointer;
            double topoAngle = (double)tempAngle / 255. * 2 * M_PI;
            pointer = pointer - index;
            
            // find direction to mouse
            double mouseAngle = agents[i].angleTo(mouseX, mouseY);
            
            // turn toward mouse by averaging terrain and food source
            double avgAngle = (0.7 * mouseAngle + 0.3 * topoAngle) / 2;
            agents[i].angleToward(avgAngle, 0.75); // low lerps are really interesting
            
            // move forward a bit
            agents[i].forward(10);
            
            // wrap the agents
            agents[i].x = fmod(agents[i].x + ofGetWidth(), ofGetWidth());
            agents[i].y = fmod(agents[i].y + ofGetHeight(), ofGetHeight());
        }
    }
    
//    ofDrawBitmapStringHighlight(ofToString(ofGetFrameRate()) + " fps", 10, 20);

}

Comments are closed.