Generative Tangram

Generative Tangram – Illustrations for ›neues museum 16-4‹

I FAILED EXPERIMENTALLY*

Do you like Tangrams? Well, I certainly do! Maybe my attraction derives from the fact that it`s somehow generative in the way you can develop forms out of a limited set of units and rules.

That`s why I coded a virtual Tangram in Processing and made illustrations for my Layout of the recent issue of the austrian mag ›neues museum‹. I tweaked the algorithm of Tangram a bit though in order to match it the aethetics I had in mind.

I USED PANTONE 876C for the Bronze Effect.

* ›Experimentelles Scheitern erlaubt?‹
(Experimental failing Permitted?) is the title of the current issue of the magazine.

The concept was to visualize the title of the issue („Experimental Failing Permitted?“) by building the museum icon out of visual elements that can be reordered. By showing many different shapes that can be formed out of these elements, the failed attempts to create the „perfect“ form should be visualized. This points to the experimental process that doesn`t lead necessarily to a intended result but yields unsuspected and probably innovative and usable results.

icons-04

Finding a good configuration of blocks. Not too few, not too many, more small ones than big ones – and still the museum icon should have a good form.

tangram-icon-1-01

The tangramified version of the museum-icon.

Then I wrote a programm that takes the basic elements from the museum icon an uses rules and randomness to generate a potentially infinite amount of forms:

tangram_cover_2-02

Forms generated out of the basic elements by the code written in Processing.

Main code


import java.text.SimpleDateFormat;
import java.util.Date;               // to get Date-Stamp for PDF export
import processing.pdf.*;
import processing.opengl.*;

BlockShape b;
ArrayList Blocks;
ArrayList<float[][]> shapeTypes;
int[][] shapeTypeCounter;
int[] maxTypes = {3,4,8,1,2,2,3};

int blockTypes = 7;                                        // Set the amount of different Shapes. Tangram uses 7
int blocksTotal = 23;                                      // Set the amount of overall Blocks. Tangram uses 23
int randomBlockNumber;
int counter = 0;
int bezierCount = 0;
float[][] currentShape;

color blockColor, shapeColorOne, shapeColorTwo;
color[] shapeColors;

void setup() {
  size(1000, 800, P3D);
  background(255);

  Blocks = new ArrayList();
  shapeTypes = new ArrayList<float[][]>();

  float[][] shape1 = { {0,0},{20,0},{20,40},{0,40} };       // Coordinates for the shapes
  float[][] shape2 = { {0,0},{20,0},{20,20}, {0,20} };
  float[][] shape3 = { {0,0},{20,0},{0,20} };
  float[][] shape4 = { {0,0},{20,0},{20,80},{0,80} };
  float[][] shape5 = { {20,0},{70,0},{90,20},{0,20} }; 
  float[][] shape6 = { {0,0},{70,0},{90,20},{20,20} };
  float[][] shape7 = { {40.2,0},{80.39,40.18},{0,40.18} };

  shapeTypes.add(shape1);                                   // store the coordinates of the shapes in an array
  shapeTypes.add(shape2);
  shapeTypes.add(shape3);
  shapeTypes.add(shape4);
  shapeTypes.add(shape5);
  shapeTypes.add(shape6);
  shapeTypes.add(shape7);

  shapeColors = new color[blockTypes];                      // generate colors for all blocks and store them in an array
  shapeColors[0] = color(0,0, 0, 100);
  shapeColors[1] = color(0, 0, 0, 200);
  shapeColors[2] = color(0, 0, 0, 230);
  shapeColors[3] = color(0, 0, 0, 50);
  shapeColors[4] = color(0, 0, 0, 150);
  shapeColors[5] = color(0, 0, 0, 150);
  shapeColors[6] = color(0, 0, 0, 50);

  shapeTypeCounter = new int[blockTypes][1];                // initialize shape counter
  for(int i=0; i<shapeTypeCounter.length;i++){ // --> to check later, how often a shape has already been used 
    shapeTypeCounter[i][0]= 0;
  }

  int firstShape = int(random(6));                          // choose first block randomly
  Blocks.add(new BlockShape(shapeTypes.get(firstShape), width/2, height/2, shapeColors[0]));  // add the block to the block list
  BlockShape firstBLock = Blocks.get(0);                   // store pointer to first block in variable "firstblock"
  firstBLock.blockDraw();                                  // Draw first Block
  maxTypes[firstShape] ++;                                 // increment counter for the specific shape
}

void draw() {
  if (counter < 22){ // If there are less than 22 blocks BlockLoop(); // Find the next block and it`s position }else if (counter >= 22 && bezierCount < 20){
    stroke(0,250);
    strokeWeight(1.5);
    noFill();
    bezierCount ++;
  }else{
     //exit();
  }

}

void BlockLoop(){
  // chose block and edge to attach to
  // -------------------------------------------------
  BlockShape lastBlock = Blocks.get(Blocks.size()-1);          // remember last Block for positioning  
                                                               // (later use any block for positioning)

  randomBlockNumber =  int(random(0,Blocks.size()-1));         // chose random Block to attach current Block 
  BlockShape randomBlock = Blocks.get(randomBlockNumber);      // name it "randomBlock" for further reference

  int dockingPoint = int(random(randomBlock.blockPoints.size()-1));    // chose random point to attach current Block
  float lastX = randomBlock.getPointX(dockingPoint);           // use random edge of random block for positioning 
  float lastY = randomBlock.getPointY(dockingPoint);           // 

  // chose next shape and test if it`s available
  // -------------------------------------------------
  int randomShape = int(random(0,7));                          // which shape will the next block have?
  boolean notUsed = false;
  while (notUsed == false){                            
      if(shapeTypeCounter[randomShape][0] < maxTypes[randomShape]) { notUsed = true; } else { notUsed = false; randomShape = int(random(0,7)); } } // set color and exact position for new block and add it to the block list // ------------------------------------------------- blockColor = shapeColors[randomShape]; currentShape = shapeTypes.get(randomShape); Blocks.add(new BlockShape(currentShape, lastX, lastY, blockColor)); // add new Block to List BlockShape currentBlock = Blocks.get(Blocks.size()-1); // Pointer to current Block float transX = 0; float transY = 0; if (currentBlock.x > randomBlock.x){                          // test, if new Block is top, bottom, left or right of old one
      transX = 0.01;
  }else{
      transX = -0.01;
  }
  if (currentBlock.y > randomBlock.y){           
      transY = 0.01;
  }else{
      transY = -0.01;
  } 

  currentBlock.blockVectorsShift(currentBlock.getAllBlockPoints(), transX, transY);  // translate it accordingly by 0.01 pixel to avoid initial intersection

  // test if new block overlaps any of the existing blocks
  // -------------------------------------------------
  boolean hitTest = false;                                      // test for intersection with all existing blocks
  for (int i = 0; i < Blocks.size()-1; i++){ hitTest = currentBlock.testIntesection(Blocks.get(i).getAllBlockPoints()); if (hitTest == true){ break; } } // if everything is alright, draw block to screen // ------------------------------------------------- if (hitTest == false){ currentBlock.blockDraw(); // no intersection --> draw Block to screen
    shapeTypeCounter[randomShape][0] ++;                       // increment counter for shape type now
    counter ++;                                                // increment overall counter for blocks
    //println(shapeTypeCounter[randomShape][0]+","+maxTypes[randomShape]);
  }else{
    Blocks.remove(currentBlock);                               // intersection --> remove Block from list and start over
  }

}

Class BlockShape


import processing.opengl.*;
import geomerative.*;

class BlockShape {
  float x,y, rotation;
  //float px,py,ox,oy;
  PShape blockForm;                   // the visible Form of the Block
  ArrayList blockPoints;     // the VectorPoints of the Block
  //int[] rotationList = {-315, -270,-225, -180, -135, -90, -45, 0, 45, 90, 135, 180, 225, 270, 315};
  //int[] rotationList = {-135, -45, 0, 45, 135};
  int[] rotationList = {int(random(360)), int(random(360)),int(random(360)), int(random(360))};
  int vertexCount;
  ArrayList Blocks;
  PVector center;                    // Middle of the Shape, used for rotation

  //int blockCount = 0;

  BlockShape(float[][] pointCoordinates, float x_, float y_, color blockColor) {
     this.x = x_;
     this.y = y_;
     blockPoints = new ArrayList();
     center = new PVector();
     rotation = rotationList[int(random(4))];

     for(int i = 0; i<pointCoordinates.length; i++){ blockPoints.add(new PVector(pointCoordinates[i][0],pointCoordinates[i][1],0)); } if (blockPoints.size()>3){
       center = getBlockCenterRect(blockPoints);                      // calculate center of object if it`s a rectangle
     } else {
       center = getBlockCenterTri(blockPoints);                       // do the same if it`s a triangle
     }
     blockPoints = blockVectorsRotate(blockPoints, center, rotation); // rotate points (vectors) around center
     float amountX = x - blockPoints.get(0).x;                        // translate the vectors to their final (correct) position
     float amountY = y - blockPoints.get(0).y;                        // (because otherwise the rotated object won`t fit to the other blocks
     blockPoints = blockVectorsShift(blockPoints, amountX, amountY);              

     int alignPoint = int(random(blockPoints.size()));
     blockPoints = blockVectorsShift(blockPoints, blockPoints.get(0).x-blockPoints.get(alignPoint).x, blockPoints.get(0).y-blockPoints.get(alignPoint).y);                // chose random corner of the currentblock

  }

  boolean testIntesection(ArrayList testBlock){
    float x1,x2,y1,y2;
    boolean hit = false;

    PVector[] currentBlockPoints;                                      // function for testing intersections needs an array of Pvectors
    currentBlockPoints = new PVector[blockPoints.size()];              // so let`s give it that
    for (int i = 0; i < blockPoints.size(); i++){
       currentBlockPoints[i] = blockPoints.get(i);                     // transfer ArrayList to Array
    }

    for (int i = 0; i < testBlock.size()-1; i++){                      // Loop through all line segments of the current block
       x1 = testBlock.get(i).x;
       y1 = testBlock.get(i).y;
       x2 = testBlock.get(i+1).x;
       y2 = testBlock.get(i+1).y;
       hit = polyLine(currentBlockPoints, x1, y1, x2, y2);             // Check if current block (polygon) intersects a line of the tested Polygon 
       if (hit == true){
          break;                                                       // If there is an intersection break the loop, otherwise go on
       }
    }
    return hit;
  }

  float blockPosX(){
    return this.x;
  }

  float blockPosY(){
    return this.y;
  }

  ArrayList getAllBlockPoints(){
    return blockPoints;
  }

  float getPointX(int pointNumber){
    PVector v = blockPoints.get(pointNumber);
    return v.x;
  }

  float getPointY(int pointNumber){
    PVector v = blockPoints.get(pointNumber);  
    //return this.y+v.y+(blockForm.getHeight()/4);
    return v.y;
  }

  void blockDraw(){
     fill(blockColor);
     //noStroke();
     strokeWeight(3);
     stroke(0,0,0);
     blockForm = createShape();
     blockForm.beginShape();
     for (int i=0; i<blockPoints.size(); i++){                        
         blockForm.vertex(blockPoints.get(i).x, blockPoints.get(i).y);
     }
     blockForm.endShape(CLOSE);
     shape(blockForm);
     //stroke(0);
     //ellipse(x+center.x, y+center.y, 4,4);
  }

  ArrayList blockVectorsShift(ArrayList blockPoints, float amountHorizontal, float amountVertical) {   
      for (int i = 0; i < blockPoints.size(); i++){
          blockPoints.get(i).x += amountHorizontal;
          blockPoints.get(i).y += amountVertical;
      }
      center.x -= amountHorizontal;
      center.y -= amountVertical;
      return blockPoints;
  }

  ArrayList blockVectorsRotate(ArrayList blockPoints, PVector center, float angle){
    float px,py,ox,oy, radAngle;
    radAngle = radians(angle);
    for (int i=0; i<blockPoints.size(); i++){
        px = blockPoints.get(i).x;
        py = blockPoints.get(i).y;
        ox = center.x;
        oy = center.y;
        blockPoints.get(i).x = cos(radAngle) * (px-ox) - sin(radAngle) * (py-oy) + ox;
        blockPoints.get(i).y = sin(radAngle) * (px-ox) + cos(radAngle) * (py-oy) + oy;  
     }
     return blockPoints;
  }

  PVector getBlockCenterRect(ArrayList blockPoints){
    PVector calcCenter = new PVector();
    calcCenter.x = blockPoints.get(0).x + ((blockPoints.get(1).x - blockPoints.get(0).x)/2);
    calcCenter.y = blockPoints.get(0).y + ((blockPoints.get(3).y - blockPoints.get(0).y)/2);
    return calcCenter;
  }

  PVector getBlockCenterTri(ArrayList blockPoints){
    PVector calcCenter = new PVector();
    calcCenter.x = blockPoints.get(0).x + ((blockPoints.get(2).x + blockPoints.get(1).x)/2);
    calcCenter.y = blockPoints.get(0).y + ((blockPoints.get(2).y + blockPoints.get(1).y)/2);
    return calcCenter;
  }

  // Collision detection from Jeffrey Thompson
  // http://www.jeffreythompson.org/collision-detection/

  // POLYGON/LINE
  boolean polyLine(PVector[] vertices, float x1, float y1, float x2, float y2) {

    // go through each of the vertices, plus the next vertex in the list
    int next = 0;
    for (int current=0; current<vertices.length; current++) { // get next vertex in list // if we've hit the end, wrap around to 0 next = current+1; if (next == vertices.length) next = 0; // get the PVectors at our current position // extract X/Y coordinates from each float x3 = vertices[current].x; float y3 = vertices[current].y; float x4 = vertices[next].x; float y4 = vertices[next].y; // do a Line/Line comparison // if true, return 'true' immediately and stop testing (faster) boolean hit = lineLine(x1, y1, x2, y2, x3, y3, x4, y4); if (hit) { return true; } } // never got a hit return false; } // LINE/LINE boolean lineLine(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4) { // calculate the direction of the lines float uA = ((x4-x3)*(y1-y3) - (y4-y3)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1)); float uB = ((x2-x1)*(y1-y3) - (y2-y1)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1)); // if uA and uB are between 0-1, lines are colliding if (uA >= 0 && uA <= 1 && uB >= 0 && uB <= 1) {
      return true;
    }
    return false;
  }
}

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind markiert *