ngdon-LookingOutwards09

For part of my last project I was doing generative text, so I looked into ways neural networks can be used to do so. Golan pointed me to a couple of resources, and one of them seems particularly interesting and effective: http://karpathy.github.io/2015/05/21/rnn-effectiveness/

This author applied recurrent neural networks to wikipedia articles, Shakespeare, code, and even research papers, and the result is very realistic.

The model learned how to start and end quotation marks and brackets, which I think is one of the more difficult problems in text generation.

Generative paper.

Generative code.

I’m particularly fascinated by the way neural networks magically learn. Unfortunately I never got them to work so well in my final project.

I was also planing to generate plants alongside my imaginary animals, so I also researched about L-system, which is a type of grammar that can be used to describe the shape of a plant.

I read this paper: http://algorithmicbotany.org/papers/abop/abop-ch1.pdf, in which usage and variations of l-systems are extensive explained.

I’m very interested in this system because it condenses a complicated graphic such a that of a tree into a plain line of text. And simply changing the order of a few symbols can result in a vast variety of different tree shapes.

For example, the above image can be simply described by the rule

(X → F−[[X]+X]+F[+FX]−X), (F → FF)

As I’m also doing text generation, I thought about the interesting possibilities of applying text generation methods onto the rules. So the shapes of the trees will not be limited by the number of rules that I can find, and exotic and alien-looking trees can be easily churned out.

Below are a couple of outputs I was able to get by applying Markov chain to existing l-system rules using python.

However more often the program generates something that resembles a messy ball of string. Therefore I’m still working on it.

Nngdon-Last Project

The PDF version is available here: FaunaOfSloogiaII.pdf

For my last project I decided to expand on my generative book, which is about imaginary creatures on an imaginary island. The last version had only generated illustrations of the creatures, so I felt that I could supplement the concept of “fauna of an island” by giving each creature a short description, some maps indicating the habitats of them, and some rendered pictures of the animals with a natural background (trees, rivers, mountains, etc.).

The Map

I first generated a height maps using 2D Perlin noise. This results in an even spread of lands and waters across the canvas. To make the terrain more island-ish, I used a vignette mask to darken (subtract value from) the corners before rendering the noise.

After this an edge finding algorithm was used to draw the isolines.

Labeling

The next task is to label the map: to find where the mountains and seas are and name them accordingly.

I wrote my own “blob detection” algorithm inspired by flood fill. First, given a point, the program will try to draw the largest possible circle, given the rule that all pixels in that circle have to be within a certain range of height. Then, around the circumference of the circle, the program tries to generate even more such circles. This is done recursively, until no more circles larger than a certain small radius can be drawn. The union of all the circles  is returned.

Using Mitchell’s best-candidate algorithm, I picked random points evenly spread across the map, and apply my blob detection. Blobs that are very close to each other or have a lot of overlapping are merged.

Then for each blob that indicates a water area, the program checks how surrounded by land it is, and decide whether it is a lake, strait, a gulf, or a sea. For the land areas, the program decides the terrain according to its height and whether it is connected to another piece of land.

A Markov chain is used to generate the names for the places. The text is rotated and scaled according to the general shape of the area.

Finally, the program exports a JSON file, saving the seed and the names, areas and locations of the places, to be used in the next step.

 

The Description

The description costed me the most time in this project. I spent a long time thinking about ways of generating high-quality generative text.

I noticed that there are usually three major ways of making generative text people are using:

  1. Markov chain/ machine learning method. The result has good variety, and is easy to implement, as the computer does the most part for you. However the programmer has the least control over what the program is generating, and nonsensical sentences often occur.
  2. Word substitution. The human writer writes the whole paragraph, and some words in the paragraph are substituted by words chosen randomly from a bank. This method is good for generating only one or two pieces of output, and soon gets very repetitive after a few iterations. A very boring algorithm.
  3. A set of pre-defined grammar + word substitution.

The third direction seems to be able to combine order and randomness well. However as I explored deeper I discovered that it’s like teaching the computer English from scratch, and massive amount of work is probably involved to make it generating something meaningful, instead of something like:

Nosier clarinet tweezes beacuse 77 carnauba sportily misteaches.

However I was in fact able to invent a versatile regex-like syntax that makes defining a large set of grammar rather easy. I believe it’s going to be a very promising algorithm, and I’m probably going to work on it later. As for this project, I tried to look into the other two algorithms.

Grab data, tokenize and scramble

Finally after some thought, I decided to combine the the first and the second method.

First I wrote a program to steal all the articles from the internet. The program pretends to be an innocent web browser and searches sites such as wikipedia using a long list of keywords. It retrieves the source code of the pages, and parses it to get the clean, plain text version of the articles.

Then I collected a database of animal names, place names, color names etc., and searched within the articles to substitute the keywords with special tokens (such as “$q$” for the name of the query animal, “$a$” for name of other animals, “$p$” for places, “$c$” for colors, etc.)

I developed various techniques, such as score-based word similarity comparison to avoid missing any keywords. For example, an article about the grey wolf may mention “gray wolf”, “grey wolves”,”the wolf”, “wolves” referring to the same thing.

After this, a scrambling algorithm such as Markov chain is used. Notice that since the keywords are tokenized before scrambling, the generator can slide easily from one phrase to another across different articles. This gives the result interesting variety.

LSTR and charRNN

Golan pointed me to the neural networks LSTR and charRNN as alternatives to Markov chain. It was very interesting to explore them and watch as the computer learns to speak English. However they still tend to generate gibberish after training overnight. There seems to be an asymptote to the loss function: the computer is becoming better and better, but then it reaches a bottleneck, and starts to confuse itself and slips back.

Another phenomenon I observed is that the computer seems to be falling in love with a certain word, and just keeps saying it whenever it’s possible. At the worst outburst of this symptom the computer falls into a madness like:

Calf where be will calf will calf that calf will calf different calf calf calf the and calf a calf only calf a other calf calf calf calf…

And oftentimes it does not know when to end its sentences, and keeps running on.

The problem with neural networks is that it’s like a magic black box. When it works it’s magical, but when it doesn’t you don’t know where to fix. As I’m not too familiar with the details of neural networks and was entirely using other people’s libraries, I have no idea how to improve the algorithm.

Generation

I wrote my own very portable version of Markov chain in 20 lines of python code, and it seems to be working better than the neural networks.(?)

My favorite lines are:

The $q$ can take a grave dislike towards their tail, which are the primary source of prey.

A female $q$ gives birth to one another through touch, movement and sound.

The infant $q$ remains with its mother until it was strong enough to overpower it and kill it.

And paradoxical ones such as:

…the tail which is twice as often as long as two million individuals.

Finally the tokens are substituted by relevant information about the animal described. These information are stored in JSON files when the illustrations and maps are generated.

The names of all the 50 animals and places are stored in a pool, so descriptions of different animals can refer to each other. For example, in the description of animal A, it says its predator is animal B. After flipping a few pages, the reader will be able to find a detailed account of animal B, and so on.

Eyes Improvement

Golan told me that my creatures eyes look dead and need to be fixed. I added in some highlights so they look more lively now (hopefully).

Code

The complete code will be available on Github once I finalize the project. Currently I’m working on rendering the animals against a natural background.

But here’s my 20-line Markov chain in python.

 

import random
class Markov20():
	def __init__(self,corp,delim=" ",punc=[".",",",";","?","!",'"']):
		self.corp = corp
		self.punc = punc
		self.delim = delim
		for p in self.punc: self.corp = self.corp.replace(p,delim+p)
		self.corp = self.corp.split(delim)
	def predict(self,wl):
		return random.choice([self.corp[i+len(wl)] for i in range(0,len(self.corp)-len(wl)) if self.corp[i:i+len(wl)] == wl ])
	def sentence(self,w,d,l=0):
		res = w + self.delim
		i = 0
		while (l != 0 and i < l) or (l==0 and w != self.punc[0]):
			w = self.predict(res.split(self.delim)[-1-d:-1])
			res += w + self.delim
			i+=1
		for p in self.punc: res = res.replace(self.delim+p,p)
		return res
	def randsentstart(self):
		return random.choice(self.delim.join(self.corp).split(self.punc[0]+self.delim)).split(self.delim)[0]


if __name__ == "__main__":
	f1 = open("nietzsche.txt") #s3.amazonaws.com/text-datasets/nietzsche.txt	
	corp = (f1.read()).replace("  ","").replace("\n"," ").replace("\r\n"," ").replace("\r"," ").replace("=","")
	m20 = Markov20(corp)
	for i in range(0,3):
		print m20.sentence(m20.randsentstart(),2)


ngdon-Proposal

For my final project I’m going to expand on my generative book. I’m going to fix the imperfections of the things I already have, and add a lot of new things.

First I’m going to improve the eyes of the creatures, and devise an algorithm for generating descriptions for them as Golan recommended. For the descriptions I’m thinking of grabbing descriptions about real animals from wikipedia, scramble them and substitute key information. I’m also thinking about extracting animal features from my generation algorithm to use them in the description.

For the new things, I’m also going to generate terrain and plants. The terrain, the plants and the animals are going to the three major components of my new book.

For the terrain, I’m going to render some images in 3D (or pseudo-3D) with the plants and animals I generated, so that it looks like a photograph of a real landscape. I’m also going to analyze the terrain, and assign the different animals and plants to different parts of the island based on the geography.

For the generative plants, I’m probably going to combine fractals with generative textures. I’ll look into L-systems, which I heard is very good at generating plants. I have already written an algorithm for generating realistic wood textures using perlin noise last week, and I will probably use similar algorithms to generate other plant-like textures.

ngdon-manifestoReading

“5. The Critical Engineer recognises that each work of engineering engineers its user, proportional to that user’s dependency upon it.”

I think the tenet means that a work of engineering modifies the way the user thinks about the problem, so the more the user uses it, the more the user thinks in its way. I found this particularly interesting because I’m gradually becoming aware of the fact that I’m being engineered by so many things around me. I’m also thinking about common things that could have been engineered in another way and what would happen to us if they were.

One example would be the Processing language. After using Processing a lot for a semester, I begin thinking in Processing’s way. I think of the screen as a canvas, and lines and shapes are drawn onto it to make a frame. Even when I’m not using Processing, or when I’m just thinking about random ideas, (or when I’m thinking about things not even related to programming,) this mode of thinking sticks. Similarly, if I have been using d3 for the whole semester, I might have think of programing as data entering and exiting.

Ngdon-Darca-Object

The Toilet Paper Printer

wechatimg5

The Toilet Paper Printer plots data in realtime on a roll of toilet paper. It can accept data such as a simple sine wave, amplitude of noises in its surroundings, or even any data curled from the internet. Its circuit mainly consists of three littlebit DC motors and a cloudbit, while the entire structure is built with cardboard boxes, straws, and toothpicks.

Video

plotterv

Inspiration

I like plotters. As a kid I enjoyed ripping off the top of a printer while its printing and peaking into the machine as it worked. So when I saw all the motors we’ve got, I suggested that we can make our own plotter/printer thing.

wechatimg3

The Print Head

We spent most of our time figuring out how to make a print head that actually works.

At first we tried to make a conveyer-belt-like mechanism to move the pen. That didn’t work because little bits motors couldn’t their change direction of rotation in real time. So I designed a logic circuit which splits the analog input to control the motors separately. This didn’t work either, because the axles are locked even when the motors are not activated.

I came up with the idea of a gear and a rack when I suddenly realized that the wavy layer in cardboards could serve perfectly as the teeth of the gears. We made both the gear and the rack out of peeled cardboard, and powered the gear with the only motor that can rotate both ways. It worked.

There is rail at the bottom of the print head, so it could slide smoothly left and right.

wechatimg1

ezgif-com-optimize

The Paper Feeder

We had a couple of ideas how this could look like. One is that the printer should have wheels like cars and drives back and fro on the paper. Later we decided to print onto a motor-driven toilet paper, because it’s both easy to implement, and interesting as a concept.

We made two gears, rotating in opposite directions, fixed very close to each other. Thus the paper which is placed in between them gets driven out.

wechatimg2

 

Littlebits Circuit

snip20161117_15

 

The Software

We used cloud bits to receive data from the computer over wifi. Basically a processing program gets input from the microphone, writes it real time into a text file, which is then read in real time by an AppleScript script, which uses shell commands to communicate with the cloud bit API.

snip20161118_18

Code

Processing:

import processing.sound.*;
Amplitude amp;
AudioIn in;

void setup() {
  size(640, 360);
  background(255);
    
  amp = new Amplitude(this);
  in = new AudioIn(this, 0);
  in.start();
  amp.input(in);
}      

void draw() {
  background(255);
  float aa = min(0.99,amp.analyze()*10);
  //println(aa);
  line(0,aa*height,width,aa*height);
  saveStrings("audioin.txt", new String[]{""+aa});
}

AppleScript:

set p to "/Users/lingdonghuang/Documents/Processing/audioin/audioin.txt"
set f to 0
set lf to 0
set i to 0
try
	repeat
		delay 0.2
		try
			set f to (read p) * 100
		end try
		set d to (f - lf) * 0.4 + 50
		if d > 80 then
			set d to 80
		else if d < 20 then
			set d to 20
		end if
		set d to d - 3
		set a to do shell script "curl \"https://api-http.littlebitscloud.cc/v2/devices/243c201dc4f7/output\" -X POST -H \"Authorization: b97ba2fe26cdb3de50b2ead1c2838e0c13e244f0d628a0c5a20a8ca3d6d358ab\" -H \"Content-type: application/json\" -d '{ \"percent\": " & d & ", \"duration_ms\": " & -1 & " }'"
		set lf to f
		log {d}
	end repeat
on error
	log ("STOP!")
	set a to do shell script "curl \"https://api-http.littlebitscloud.cc/v2/devices/243c201dc4f7/output\" -X POST -H \"Authorization: b97ba2fe26cdb3de50b2ead1c2838e0c13e244f0d628a0c5a20a8ca3d6d358ab\" -H \"Content-type: application/json\" -d '{ \"percent\": " & 50 & ", \"duration_ms\": " & -1 & " }'"
	
end try

The Box

Made of laser-cut white and frosted plastic.

img_4729

Reflections

Little bits sucks. They might sound like a nice idea, but when you actually want to make something with them, they only “kinda works”. They make you want to trample on them and fling them out of the window. But in general we’re quite satisfied with what we’re able to achieve.

I enjoyed the process of struggling with the littlebits and the cardboards-and-straws mechanisms we had. Instead of being able to make whatever I want like coding in Processing or Python, we had to constantly take into consideration the flakiness of little bits and the straws, and challenge ourselves into coming up with more and more robust and reliable solutions.

The thing that excites me most is being able to make a working printer entirely out of trash. In the future I can probably improve the hardware and software so that it will be able to write letters and even make drawings. I’m going to publish the recipe so even beggars can own printers.

 

img_4711

Ngdon-mocap

 

snip20161108_17

https://github.com/LingDong-/60-212/tree/master/decSkel

I am most interested in the dynamism of the bodily movements in motion captures, and the idea of abstract human forms composed of objects. I wanted to develop a way to present the abstract impression of humans in energetic motion.

The rotating tapes evolved from the ones in my animated loop assignment. I am fascinated by the way they can depict a form when and only when they’re in motion, and how they’re very flexible and sensitive to movement. So I decided to push on these concepts.

There are two layers of tapes around the bodies. The first, more densely populated, is related more closely to the human form, while the second respond mostly to the human motion. Therefore, when the actors are waving their hands crazily, or kicking and jumping, the second layer of tapes will fly around wildly exaggerating their movements, while the first layer still sticks closely to the bodies outlining their forms.

To achieve this, I first calculate a series of evenly spaced points on the skeleton from the motion capture data. These serves as the centers of rotation for the tapes. Then I figured out the directions of the bones at these points, which will be the normals of the planes of rotation for the tapes. I also had the last information stored, so I can know how much things moved since last frame.

After this, through trigonometry, translation and rotation, I can draw each of the segments which makes up a tape that rotates over time.

Since I received comments about how the tapes in my animated loop assignment had questionable colors, I decided to develop a better color scheme for this one.

At a single frame, for either of the two layers of tapes, the colors mainly consists of 3 different shades of the same hue, and one accented color. The colors for the second layer neighbor those of the first layer. When in motion, every colors shifts the same direction in hue. I then wrote a function to redistribute the hues on the color wheel, based on my discovery that some hues looks nicer than others on the tapes.

I used the Mocap data from Perfume, since I found that their data has the most decent quality when compared to others I can find on internet. But I really wonder what my program would look like when visualizing in real time.

ezgif-com-optimize-2  ezgif-com-optimize-4

 


 

BvhParser parserA = new BvhParser();
BvhParser parserA = new BvhParser();
PBvh[] bvhs = new PBvh[3];
Loop[] loops = new Loop[512];

int dmode = 0;

int[][] palette1 = new int[][]{{0,100,80},{5,100,60},{0,100,40},{30,100,80}};  
int[][] palette2= new int[][]{{45,17,95},{48,50,80},{60,50,80},{60,20,100}}; 


int bettercolor(int c0){
  if ( c0 < 120){
    return floor(lerp(0,50,c0/120.0));
  }else if (c0 < 170){
    return floor(lerp(50,170,(c0-120.0)/50.0));
  }else if (c0 < 230){
    return floor(lerp(170,200,(c0-170.0)/60.0));
  }else if (c0 < 260){
    return floor(lerp(200,260,(c0-230.0)/30.0));
  }
  return c0;
}


float[] lerpcoord(float[] p0, float[] p1, float r){
  return new float[]{
    lerp(p0[0],p1[0],r),
    lerp(p0[1],p1[1],r),
    lerp(p0[2],p1[2],r)
  };
}
float dist3d(float[] p0, float[] p1){
  return sqrt(
    sq(p0[0]-p1[0])+
    sq(p0[1]-p1[1])+
    sq(p0[2]-p1[2])
  );
  
}


class Loop{
  float x0;
  float y0;
  float z0;
  float[] lxyz = new float[3];
  float a;
  float w = 4;
  float[] dirv = new float[3];
  float[] dirv2 = new float[3];
  float r;
  float r1;
  float r2;
  float rp1=1;
  float rp2=1;
  float[][] cl = new float[32][4];
  int cll = 16;
  float spd = 0.1;
  int id;
  int[] col = new int[3];
  public Loop(float x,float y,float z){
    this.x0 = x;
    this.y0 = y;
    this.z0 = z;
    id = floor(random(100000));
    a = random(PI*2);
  } 
  public void update(){
    
    r1 = lerp(r1,dist3d(new float[]{x0,y0,z0},lxyz),0.25);
    r2 = noise(id,frameCount*0.1)*10;
    
    r = r1*rp1+r2*rp2;
    a+=PI*spd;
    
    dirv2 = new float[]{x0-lxyz[0],y0-lxyz[1],z0-lxyz[2]};

    cl[0][0] = r*cos(a);
    cl[0][1] = r*sin(a);

    for (int i = 1; i < cll; i++){
      pushMatrix();
      translate(x0,y0,z0);
      rotateX(atan2(dirv[2],dirv[1]));
      rotateZ(atan2(dirv[1],dirv[0]));

      //translate(10,0,0);
      //box(20,5,5);
      
      
      cl[i][0] = r*cos(a+i*0.05*PI);
      cl[i][1] = r*sin(a+i*0.05*PI);
      //cl[i] = lerpcoord(cl[i],cl[i-1],spd);
      
      rotateY(PI/2);
      noStroke();
      fill(col[0],col[1],col[2]);
      beginShape();
        vertex(cl[i][0],cl[i][1],-w/2);
        vertex(cl[i][0],cl[i][1],w/2);
        vertex(cl[i-1][0],cl[i-1][1],w/2);
        vertex(cl[i-1][0],cl[i-1][1],-w/2);      
      endShape();
      if (dmode == 0){
        stroke(0,0,10);
      }
      line(cl[i][0],cl[i][1],-w/2,cl[i-1][0],cl[i-1][1],-w/2);
      line(cl[i][0],cl[i][1],w/2,cl[i-1][0],cl[i-1][1],w/2);
      //line(cl[i][0],cl[i][1],cl[i][2],cl[i-1][0],cl[i-1][1],cl[i-1][2]);
      
      popMatrix();
    }
    
    a += PI*0.1;
    
  }
}


public void setup()
{
  size( 1200, 720, P3D );
  background( 0 );
  noStroke();
  frameRate( 30 );
  
  bvhs[0] = new PBvh( loadStrings( "aachan.bvh" ) );
  bvhs[1] = new PBvh( loadStrings( "nocchi.bvh" ) );
  bvhs[2] = new PBvh( loadStrings( "kashiyuka.bvh" ) );
  for (int i = 0; i < loops.length; i++){ loops[i] = new Loop(0.0,0.0,0.0); } if (dmode == 1){ palette1 = new int[][]{{255,255,255}}; palette2 = new int[][]{{100,255,255}}; }else{ colorMode(HSB,360,100,100); } //noLoop(); } public void draw() { background(0,0,10); //camera float rr = 600; float ra = PI/2.75; camera(width/2+rr*cos(ra),height/2,rr*sin(ra),width/2,height/2,0,0,1,0); pushMatrix(); translate( width/2+50, height/2+150, 0); scale(-2, -2, -2); if (dmode > 0){
    background(230);
    directionalLight(160,160,160, 0.5, -1, 0.5);
    //pointLight(255,255,255,0,-300,-200);
    //pointLight(255,255,255,0,-300,0);
    ambientLight(160,160,160);
    //shininess(5.0); 
    fill(250);
    pushMatrix();
    //rotateX(frameCount*0.1);
    box(500,10,500);
    popMatrix();
    
  }
  //model
  int j = 0;
  int e = 0;
  for (int i = 0; i < bvhs.length; i++){
    bvhs[i].update( 2000+frameCount*25 );
 
    for( BvhBone b : bvhs[i].parser.getBones())
    {
      
      
      if (b.getParent()!= null){
        float px = b.getParent().absPos.x;
        float py = b.getParent().absPos.y;
        float pz = b.getParent().absPos.z;
        
        float[] p1 =  new float[]{b.absPos.x,b.absPos.y,b.absPos.z};
        float[] p0 = new float[]{px,py,pz};
        float d =  dist3d(p0,p1);

        for (float k = 0; k < d; k+= 4){
          
          float[] c = lerpcoord(p0,p1,k/d);
          loops[j].lxyz = new float[]{loops[j].x0,loops[j].y0,loops[j].z0};
          loops[j].x0 = c[0];
          loops[j].y0 = c[1];
          loops[j].z0 = c[2];

          loops[j].rp1 = 0.5;
          loops[j].rp2 = 1.7;
          loops[j].dirv = new float[]{ px-b.absPos.x, py-b.absPos.y, pz-b.absPos.z};
          int[] col = palette1[j%palette1.length];
          loops[j].col[0] = bettercolor(floor(col[0]+320+frameCount*0.15)%360);
          loops[j].col[1] = col[1]; loops[j].col[2] = col[2];
          loops[j].cll = 24;
          j++;
        }
        for (float k = 0; k < d; k+= 100){
          
          float[] c = lerpcoord(p0,p1,k/d);
          loops[j].lxyz = new float[]{loops[j].x0,loops[j].y0,loops[j].z0};
          loops[j].x0 = c[0];
          loops[j].y0 = c[1];
          loops[j].z0 = c[2];
          loops[j].dirv = new float[]{ px-b.absPos.x, py-b.absPos.y, pz-b.absPos.z};
          loops[j].rp1 = 10;
          loops[j].rp2 = 2;
          int[] col = palette2[j%palette2.length];
          loops[j].col[0] = floor(col[0]+320+frameCount*0.15)%360;
          loops[j].col[1] = col[1]; loops[j].col[2] = col[2];
          loops[j].cll = 24;
          loops[j].cll = 16;
          loops[j].spd = 0.01;
          j++;
        }

        //line(b.absPos.x,b.absPos.y,b.absPos.z,px,py,pz);
      }

      pushMatrix();
      translate(b.absPos.x, b.absPos.y, b.absPos.z);
      fill(0,0,100);
      if (dmode <= 0){rotateY(PI/2-PI/2.75);ellipse(0, 0, 2, 2);}
      popMatrix();
      if (!b.hasChildren())
      {
        pushMatrix();
        translate( b.absEndPos.x, b.absEndPos.y, b.absEndPos.z);
        if (dmode <= 0){
          rotateY(PI/2-PI/2.75);
          ellipse(0,0,5,5);
        }
        popMatrix();
      }
    }
  }

  for (int i = 0; i < j; i++){
    loops[i].update();
  }

  popMatrix();
  //saveFrame("frames/"+nf(frameCount,6)+".png");

}

photo-on-11-10-16-at-3-27-pm

ngdon-LookingOutwards07

All streets limited by Ben Fry

http://3rdfloor.fathom.info/products/all-streets

This visualization simply draws all the streets in the U.S. without any other information such as terrain, boundaries, etc. However we can clearly see where the cities are and how the terrain probably look like from the density and shape of the streets.

I’m particularly interested in this type of visualization because it does not attempt to extract information for the reader but instead let the reader explore it themselves. It has a very simple idea (just drawing all the streets) but has a very complex effect. Different people with different interest can find out different things from the data, and the more you look at it, the more you see.

Also I find the visualization aesthetically pleasing. The way the delicate thin black lines divide the cells when you look up close and the texture of the image when look from afar are really beautiful.

 

Ngdon-Visualization

Number of Rents and Returns during Weekdays and Weekends

snip20161102_1

Interesting observations:

  • There are two peaks (8 am and 5 pm) during week days, probably people going to work and going back from work.
  • Bikes are used more to go home than to go to work.
  • People wake up late during weekends.
  • Nobody try to rent bikes when its 3-4 am. There are only returns.

Top Ten Most Ridden Bikes

snip20161102_3

The most popular bike of the quarter is Bike #70145, which has been ridden for almost 300 hours! The least popular bike is Bike #70008, which has been ridden only for about 10 minutes during the quarter.

 

Rents and Returns at Stations

snip20161104_10

Interesting observations:

  • There’s more traffic near city center.
  • People tend to rent their bikes at small stations and return them at larger ones.

 

I really enjoyed making this assignment. There seemed to be so much interesting information that I can extract from the data, and I kept thinking of possible visualizations I can do.

d3 felt strange at first, but soon I got used to it and started to admire its beauty. However for some of the features (for example one bar on top of another in the first chart) I couldn’t figure out how to make them using the idiomatic d3 way, so I used some hackish processing-like method to achieve them.

 

/*    HIDDEN INITIALIZATION BLOCK    */

// Select the DOM element
var parent = d3.select("#visualization");

// Set up the margins
var bbox   = parent.node().getBoundingClientRect();
var margin = {top: 50, right: 50, bottom: 50, left: 50};
var width  = +bbox.width - margin.left - margin.right;
var height = +bbox.height - margin.top - margin.bottom;

// Define svg as a group within the base SVG.
var svg = parent.select("svg").append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

/*  END HIDDEN INITIALIZATION BLOCK  */
var data1 = []
var data2 = []

for (var i = 0; i < 49; i++){
  data1.push(0)
  data2.push(0)
}

var datat = []
var mapdata = null;
var stations = null;
var rentals = null;

function isweekday(t){
  var dt = t.split(" ")[0].split("/")
  var date = new Date(dt[2],dt[0]-1,dt[1])
  var fmt = d3.timeFormat("%a")
  
  return ["Mon","Tue","Wed","Thur","Fri"].indexOf(fmt(date)) != -1
}


d3.json('http://d3.workergnome.com/examples/basic_map/data.geojson', function(loaded_data1) {
  d3.csv('db/HealthyRideStations2016.csv', function(loaded_data2) {
    d3.csv('db/HealthyRideRentals2016Q3.csv', function(loaded_data3) {
      mapdata = loaded_data1;
      stations = loaded_data2;
      rentals = loaded_data3;

      var s1 = 1
      var s2 = 1
      for (var i = 0; i < rentals.length; i++){
        var shr = +rentals[i]["Starttime"].split(" ")[1].split(":")[0]
        var ehr = +rentals[i]["Stoptime"].split(" ")[1].split(":")[0]
        if (isweekday(rentals[i]["Starttime"])){
          data1[shr]+=1
          data2[ehr]+=1
          //s1++
        }else{
          data1[shr+25]+=1
          data2[ehr+25]+=1
          //s2++
        }
      }
      console.log([s1,s2])
      for (var i = 0; i < 24; i++){
        datat.push((data1[i]+data2[i])/s1)
      }
      for (var i = 24; i < data1.length; i++){
        datat.push((data1[i]+data2[i])/s2)
      }
      var x = d3.scaleLinear().domain([0, d3.max(datat)]).range([0, height]);
      var x2 = d3.scaleLinear().domain([0, d3.max(datat)]).range([height, 0]);
      var d1s = []
      var d2s = []

      for (var i = 0; i < 24; i++){
        d1s.push(x(data1[i]/s1));
        d2s.push(x(data2[i]/s1));
      }
      for (var i = 24; i < data1.length; i++){
        d1s.push(x(data1[i]/s2));
        d2s.push(x(data2[i]/s2));
      }

      // define the bar width
      var barWidth = width/data1.length;

      // set up the x scale
      var col1 = d3.rgb(190,195,195)
      var col2 = d3.rgb(200,190,190)
      var col3 = d3.rgb(170,175,175)
      var col4 = d3.rgb(180,170,170)
      
      console.log(data1)
      console.log(data2)
      // Create each bar, select the enter selection, and append a svg group.

      svg.append("g")
        .attr("transform", "translate(-4,-2)")
        .call(d3.axisLeft(x2).ticks(10))
        .attr("font-family", "sans-serif")
        .attr("font-size", 8)
        .attr("opacity",.3)

      svg.selectAll("rect.i")
        .data(d1s).enter()
        .append("rect")
        .attr("class", "i")
        .attr("x",function(d,i){return i*barWidth})
        .attr("y",function(d,i){return height-d-d2s[i]})
        .attr("width",barWidth*0.9)
        .attr("height",function(d){return d})
        .attr("fill", function(d,i){if (i < 25){return col1}else{return col2}})

      svg.selectAll("rect.ii")
        .data(d2s).enter()
        .append("rect")
        .attr("class", "ii")
        .attr("x",function(d,i){return i*barWidth})
        .attr("y",function(d,i){return height-d-1})
        .attr("width",barWidth*0.9)
        .attr("height",function(d){return d})
        .attr("fill", function(d,i){if (i < 25){return col3}else{return col4}})

      var ts = 8
      svg.selectAll("text.i")
        .data(d2s).enter()
        .append("text")
        .attr("class", "i")
        .attr("x",function(d,i){return (i+0.45)*barWidth-ts/2})
        .attr("y",function(d,i){return height+8})
        .attr("fill", function(d,i){if (i < 25){return d3.rgb(170,175,175)}else{return d3.rgb(180,170,170)}})

        .attr("font-family", "sans-serif")
        .attr("font-size", ts)
        .text(function(d,i){if ((i%25)%2 == 1 && (i%25) != 24){return i%25}else{return ""}})

      var ww = ["Weekdays","Weekends"]
      svg.selectAll("text.ii")
        .data(ww).enter()
        .append("text")
        .attr("class", "ii")
        .attr("x",function(d,i){return (i*barWidth*25+10)})
        .attr("y",function(d,i){return 10})
        .attr("fill", function(d,i){if (i < 1){return d3.rgb(170,175,175)}else{return d3.rgb(180,170,170)}})

        .attr("font-family", "sans-serif")
        .attr("font-size", 10)
        .text(function(d,i){return d})


      //drawing the legend
      var t1 = svg.append("text").attr("x", barWidth*data1.length+8).attr("y", height+8)
        .attr("font-family", "sans-serif").attr("fill","silver").attr("font-size", 8).text("O'Clock");        

      var bx = svg.append("rect").attr("x", width-20).attr("y", 50)
        .attr("width", 50).attr("height",50).attr("fill","none").attr("stroke","Gainsboro") 

      var l1 = svg.append("rect").attr("x", width-15).attr("y", 60).attr("width", 10).attr("height",10).attr("fill",col1) 
      var l2 = svg.append("rect").attr("x", width-15).attr("y", 80).attr("width", 10).attr("height",10).attr("fill",col3) 

      var lt1 = svg.append("text").attr("x", width-2).attr("y", 67)
        .attr("font-family", "sans-serif").attr("fill","silver").attr("font-size", 7).text("Rents"); 

      var lt2 = svg.append("text").attr("x", width-2).attr("y", 87)
      .attr("font-family", "sans-serif").attr("fill","silver").attr("font-size", 7).text("Returns"); 

    });
  });


});


Ngdon-LookingOutwards06

LookingOutwards06 – The Art Assignment Bot

snip20161028_14

The art assignment bot generates random art assignments with requirements and due dates. When reading through them imagining what I would probably make for each of the assignments, I found that some of them are actually projects that could be potentially very meaningful.

Since the bot randomly mixes up very different subject, mediums and techniques so the results always sound weird and artsy. I find this kind of a satire of real world art assignments.

The concept is also particularly interesting. Usually, such as in this class, artists give assignments to computers to make art for them. However, the situation is reversed with art assignment bot: artists, who are usually considered the most free and creative people, are now getting assignments from a bot, which is not even capable of understanding art. It really makes me think about what art is and what a machine can do.

https://twitter.com/artassignbot

Ngdon-Book

Book of Illustrations of Imaginary Creatures

snip20161021_28

snip20161021_17

aasdfawelfhd

A PDF iteration of my book

View on Blurb

My book is a book of procedurally generated illustrations of imaginary creatures. Every generated book is different, detailing different creatures from different islands. Each of them is titled “Fauna of <Island Name>”, and has a map of that island and illustrations of 50 creatures living on that island, along with their Latin names.

Inspiration

My main inspiration is the book Classic of Mountains and Seas from 4th century BC China. The book contains detailed description of hundreds of weird animals that probably didn’t exist, such as snake headed bird, and tiger with nine heads, and headless humanoid with nipples as its eyes and belly button as its mouth, etc. This book also has geographic description of mountains and rivers, and together they rendered a fantastic world of prehistoric China.

 

 

 

 

 

 

 

 

Another inspiration is zoological illustrations. They are usually very carefully drawn, detailing every hair and every feather. However, creatures in these illustrations usually have a stiff, deadpan and lifeless look. This contrast gives them a very peculiar aesthetic that I found interesting.

Techniques

Skeleton
snip20161027_2

I started by making a general skeleton for my creatures. The skeleton consists of bones, and each bone object has only the following attributes:

  1. A pointer to its parent bone
  2. An array of pointer(s) to its child bone(s)
  3. Relative rotation to its parent bone
  4. Length of the bone

Thus the absolute position, rotation and other information of a bone can be calculated recursively. This proved to be a very versatile and robust data structure.

Flesh

snip20161027_4

For every bone I defined an additional attribute: The thickness of flesh around it. This way we can get a rough outline of the animal. The thickness is alway perpendicular to the bone and calculated from the front end of the bone.

To make the outline look more organic, I first tried to apply a simple quadratic bezier curve on it. But the outline got smoothed so much that it looked like the animal was made of rubber hoses. Then I discovered the “Rational Bezier Curve”, which assigns a weight on each point. With it I’m able to make the outline just smooth enough.

Rational Bezier Curve (From Wikipedia)

snip20161027_5

Fur

Now that we’ve got the shape of the creature, we can wrap it with its fur. I used the following algorithm to calculate the direction of each hair.

Each hair is enclosed by two curves: One is the outline of the creature, and the other is the skeleton of the creature. There is a point on each of the two curves that is the closest to the hair. Therefore, the direction of the hair is a value between the derivatives of the two curves at these two points. So we have:

Angle of Hair = Tangent of First Curve * Distance to Second Curve/ Distance between First and Second Curve + Tangent of Second Curve * Distance to First Curve/ Distance between First and Second Curve

Then it becomes a simple matter of drawing lots and lots of hair and tweaking the hue and darkness based on the hair’s relative position.

snip20161027_7

Fur Pattern

I Designed three types of patterns: stripes, dots, and rings using a very simple algorithm.

For the stripes, I use mod on the coordinate of each hair to determine which color region it is in. For the dots and rings, I pre-generate a bunch of random points at the beginning of the program, and before drawing each hair, calculate its distance to each point to see if it’s in the radius.

I used Mitchell’s Best Candidate algorithm Golan showed us a few classes ago to generate the random points, so there’s minimal overlapping.

snip20161027_10

snip20161027_9

snip20161027_8

Feathers & Wings

Wings consists of feathers. Here’s my algorithm for a single feather:

snip20161027_11

Notice that the angle of the lines on each side of the shaft changes from one end to the other: It’s lerped from PI/4 to 0. Notice also that the length of the lines also changes: It’s enveloped with an ellipsoid function.

Feathers are then pasted onto the wing just like how fur was drawn onto the skin, with their colors, sizes, directions and shapes calculated based on their relative location to the outline and the skeleton.

snip20161018_6

Misc

Horns – A function that takes a curve and draw a horn shape bent along that curve. This is handy because claws, teeth, tongue can all use this function to draw.

snip20161027_28

Horse tail – A function that takes a curve and draws a bunch of curves that are similar to this curve. It calculates the tangent at each point on the curve, and randomize that angle using Perlin noise to generate the other curves.

Scale – reptilian scales are generated the same way fur is, except they’re orderly arranged regular polygons rather than random short lines.

Randomization

snip20161021_29

I divided the types of creatures my program shall generate into three rough categories: mammals, fishes, birds, then randomized based on special features of these categories.

At first I used the simple random() function to create randomness. The result was chaotic. If the random range was large, I got a jumbled mess of fur and feathers across the screen; If I made the range smaller, I got identical creatures every time.

So I utilized Gaussian Randomness, which has normally distributed output values. I wrote a custom function that took a peak number and a minimum and maximum number and warps gaussian randomness to that range. Therefore for my generator, it is possible, for example, to have extremely long-necked and extremely short-necked animals, however most animals have not-too-long-and-not-too-short necks, which is realistic.

Pseudo-Latin Names

I generated a Latin name for each of my creature with a simple algorithm:

  • Download a list of latin names for real animals from the web.
  • Parse and split all the names into categorized lists of syllables, prefixes and suffixes.
  • Grab a random prefix, grab a couple of random syllables, grab a random suffix.
  • Boom! A pseudo-latin name.

At first I wanted to use a Markov chain word generator, but after playing with it I thought my own method would be more straightforward.

The Island

snip20161027_17

Now that I had my creatures complete, I wanted to make up a place for them to dwell in. First I made a place name generator similar to the latin name generator, then generated island maps using Perlin noises and filters.

At first I also wanted to have a brief description of each creature, along with information about where on the island the creature could be found. These eventually weren’t realized because I found that to write a really good implementation of these features would take days, and I didn’t want a crappy implementation to be next to my carefully generated creatures.

The part of organizing everything and putting them into the book form was rather easy; I used a Basil.js script to automate the process.

Result

I’m actually a bit disappointed with the quality of the printed book. The colors aren’t accurate: The cream background becomes a greenish grey, and everything is darker than it’s supposed to be. Looking at it closely I find that my delicate lines are blurry. The paper and the binding are average. Given that it costs so much, I was expecting something of a much higher quality.

However, nobody else seems to notice, so probably it’s just me nitpicking.

Thoughts

I think this book project can be one in a series of projects or as a part of a larger project. For example, I can generate some weird plants and put them into a book called “Flora of <Island Name>” as a companion to this “Fauna of <Island Name>” book. I can also make a big book about an island, with information about its geography, landscape, animals and plants, climate, history, etc. Or maybe a even bigger book about an imaginary planet, or a bigger bigger book about an imaginary universe.
snip20161027_18

A page from the book

Code

Github Link: https://github.com/LingDong-/60-212/tree/master/book

//Imaginary Creatures Generator

boolean debug = false;
boolean export = false;

void regpoly(float x, float y, float r, float n, float ro){
  beginShape();
  for (int i = 0; i < n; i ++){
    vertex(x+r*cos(ro+i/n*PI*2),y-r*sin(ro+i/n*PI*2));
  }
  endShape(CLOSE);
}

float distsum(float[][] P){ 
  float d = 0;
  for (int i = 0; i < P.length-1; i++){
    d += dist(P[i][0],P[i][1],P[i+1][0],P[i+1][1]);
  }
  return d;
}

float[] midpt(float[] p1,float[] p2){
  return new float[] {(p1[0]+p2[0])/2,(p1[1]+p2[1])/2};
}

float[][] revpl(float[][] pl){
  float[][] nl = new float[pl.length][pl[0].length];
  for (int i = 0; i < pl.length; i++){
    nl[pl.length-1-i]=pl[i];
  }
  return nl;
  
}

float[][] bezmh(float[][] P){
  int cpl = 0;
  float[][] plist = new float[10000][2];
  
  for (int j = 0; j < P.length-2; j++){
    
    float[] p0;float[] p1;float[] p2;
    if (j == 0){
      p0 = P[j];
    }else{
      p0 = midpt(P[j],P[j+1]);
    }
    p1 = P[j+1];
    if (j == P.length-3){
      p2 = P[j+2];
    }else{
      p2 = midpt(P[j+1],P[j+2]);
    }
    float pl = distsum(new float[][]{p0,p1,p2});
    for (int i = 0; i < pl; i+= 1){
       float t = i/pl;
       float w=2;
       plist[cpl][0] = (pow(1-t,2)*p0[0]+2*t*(1-t)*p1[0]*w+t*t*p2[0])/(pow(1-t,2)+2*t*(1-t)*w+t*t);
       plist[cpl][1] = (pow(1-t,2)*p0[1]+2*t*(1-t)*p1[1]*w+t*t*p2[1])/(pow(1-t,2)+2*t*(1-t)*w+t*t);
       cpl++;
    }
  }
  float[][] fplist = new float[cpl][2];
  for (int i = 0; i < cpl; i+= 1){
    fplist[i] = plist[i];
  }
  return fplist;
}



public class Limb{
 Limb[] subs = new Limb[32];
 int subslength = 0;
 Limb par = null;
 float l;
 float tl1;
 float tl2;
 float a;
 float[] alim = new float[2];

 public Limb(float l){
   this.l = l;
 }
 public Limb growlimb(float nl, float na){
   Limb newlimb = new Limb(nl);
   newlimb.par = this;
   newlimb.a = na;
   subs[subslength] = newlimb;
   subslength ++;
   return newlimb;
 }

 public float rot(){
   if (par == null){
     return a;
   }else{
     float la = par.rot();
     float nl = la + a;
     return nl;
   }      
 }
 public float[] loc(){
   float r = rot();
   if (par == null){
     return new float[] {l * cos(r),-l * sin(r)};
   }else{
     float[] ll = par.loc();
     return new float[] {ll[0] + l * cos(r), ll[1] - l*sin(r)};
   }   
 }
  public float[] tlloc(int n){
   float[] lo = this.loc();  
    if (par == null){
   if (n== 1){
     return new float[] {lo[0]+tl1*cos(rot()-PI/2),lo[1]-tl1*sin(rot()-PI/2)};     
   } else if (n== 2){
     return new float[] {lo[0]+tl2*cos(rot()+PI/2),lo[1]-tl2*sin(rot()+PI/2)};     
   }
    }
   lo = par.loc();  
   float ro = (rot()+par.rot()+PI)/2+PI;
   if (n== 1){
     return new float[] {lo[0]+par.tl1*cos(ro),lo[1]-par.tl1*sin(ro)};     
   } else if (n== 2){
     return new float[] {lo[0]+par.tl2*cos(ro+PI),lo[1]-par.tl2*sin(ro+PI)};     
   }
   return new float[2];
   
 }
 
 
 public float[] relcoord(float pr, float di){
   float[] parloc;
   float r = rot();
   if (par == null){
     parloc = new float[]{0,0};
   }else{
     parloc = par.loc();
   }
   return new float[]{parloc[0] + pr * l * cos(r) + di * cos(r+PI/2), parloc[1] - pr * l * sin(r) - di * sin(r+PI/2)};
  
 }
 
 public void bounds(float[] bd){
   if (loc()[0] < bd[0]){bd[0] = loc()[0];} if (loc()[0] > bd[1]){bd[1] = loc()[0];}
   if (loc()[1] < bd[2]){bd[2] = loc()[1];} if (loc()[1] > bd[3]){bd[3] = loc()[1];}
   for (int i = 0; i < subslength; i++){
     if (subs[i] != null){
       
       subs[i].bounds(bd);
     }
   } 
 }
 public void flip(){
   if (par != null){
     a = -a;
   }else{
     a = PI-a;
   }
   for (int i = 0; i < subslength; i++){
     if (subs[i] != null){ 
       subs[i].flip();
     }
   } 
 } 
 
 public void drawSkel(){
   if (par != null){
     line(par.loc()[0],par.loc()[1],loc()[0],loc()[1]);
   }
   for (int i = 0; i < subslength; i++){
     if (subs[i] != null){
       
       subs[i].drawSkel();
     }
   }
 }
  public void drawOline(){
   if (par != null){
     line(par.tlloc(1)[0],par.tlloc(1)[1],tlloc(1)[0],tlloc(1)[1]);
     line(par.tlloc(2)[0],par.tlloc(2)[1],tlloc(2)[0],tlloc(2)[1]);
   }
   for (int i = 0; i < subslength; i++){
     if (subs[i] != null){
       stroke(random(255),random(255),random(255));
       subs[i].drawOline();
     }
   }
 }

}

public class Creature{
 float x;
 float y;
 float[][] dotmap = new float [floor(random(200,800))][2];
 float stpwd = random(20,100);
 float[] stpcol = {random(100),random(30),random(10,40)};
 
 Limb head; Limb jaw_u; Limb jaw_l; Limb neck;
 Limb spine_a; Limb spine_b; Limb spine_c; Limb pelvis;
 Limb tail_a; Limb tail_b; Limb tail_c; Limb tail_d;
 
 Limb shoulder_l; Limb forethigh_l; Limb foreleg_l; Limb forepaw_l;
 Limb midshould_l; Limb midthigh_l; Limb midleg_l; Limb midpaw_l;
 Limb hip_l; Limb hindthigh_l; Limb hindleg_l; Limb hindpaw_l;
 Limb wing_a_l; Limb wing_b_l; Limb wing_c_l;
 
 Limb shoulder_r; Limb forethigh_r; Limb foreleg_r; Limb forepaw_r;
 Limb midshould_r; Limb midthigh_r; Limb midleg_r; Limb midpaw_r; 
 Limb hip_r; Limb hindthigh_r; Limb hindleg_r; Limb hindpaw_r;
 Limb wing_a_r; Limb wing_b_r; Limb wing_c_r;

 Limb[] limbs;


 public Creature(){

   pelvis = new Limb(0);
   spine_c = pelvis.growlimb(0,0);
   spine_b = spine_c.growlimb(0,0);
   spine_a = spine_b.growlimb(0,0);
   neck = spine_a.growlimb(0,0);
   head = neck.growlimb(0,0);
   jaw_u = head.growlimb(0,0);
   jaw_l = head.growlimb(0,0);
   shoulder_l = spine_a.growlimb(0,0);
   shoulder_r = spine_a.growlimb(0,0);
   forethigh_l = shoulder_l.growlimb(0,0);
   forethigh_r = shoulder_r.growlimb(0,0);
   foreleg_l = forethigh_l.growlimb(0,0);
   foreleg_r = forethigh_r.growlimb(0,0);
   forepaw_l = foreleg_l.growlimb(0,0);
   forepaw_r = foreleg_r.growlimb(0,0);
   midshould_l = spine_b.growlimb(0,0);
   midshould_r = spine_b.growlimb(0,0);
   midthigh_l = midshould_l.growlimb(0,0);
   midthigh_r = midshould_r.growlimb(0,0);
   midleg_l = midthigh_l.growlimb(0,0);
   midleg_r = midthigh_r.growlimb(0,0);
   midpaw_l = midleg_l.growlimb(0,0);
   midpaw_r = midleg_r.growlimb(0,0); 
   hip_l = pelvis.growlimb(0,0);
   hip_r = pelvis.growlimb(0,0);
   hindthigh_l = hip_l.growlimb(0,0);
   hindthigh_r = hip_r.growlimb(0,0);
   hindleg_l = hindthigh_l.growlimb(0,0);
   hindleg_r = hindthigh_r.growlimb(0,0);
   hindpaw_l = hindleg_l.growlimb(0,0);
   hindpaw_r = hindleg_r.growlimb(0,0); 
   wing_a_l = spine_a.growlimb(0,0);
   wing_a_r = spine_a.growlimb(0,0);
   wing_b_l = wing_a_l.growlimb(0,0);
   wing_b_r = wing_a_r.growlimb(0,0);
   wing_c_l = wing_b_l.growlimb(0,0);
   wing_c_r = wing_b_r.growlimb(0,0);
   tail_a = pelvis.growlimb(0,0);
   tail_b = tail_a.growlimb(0,0);
   tail_c = tail_b.growlimb(0,0);
   tail_d = tail_c.growlimb(0,0);
   
   limbs = new Limb[] {pelvis,spine_c,spine_b,spine_a,neck,head,jaw_u,jaw_l,
   shoulder_l,shoulder_r,forethigh_l,forethigh_r,foreleg_l,foreleg_r,forepaw_l,forepaw_r,
   midshould_l,midshould_r,midthigh_l,midthigh_r,midleg_l,midleg_r,midpaw_l,midpaw_r,
   hip_l,hip_r,hindthigh_l,hindthigh_r,hindleg_l,hindleg_r,hindpaw_l,hindpaw_r,
   wing_a_l,wing_a_r,wing_b_l,wing_b_r,wing_c_l,wing_c_r,tail_a,tail_b,tail_c};
   
  }
  void makedotmap(float s0, float s1){
    for (int i = 0; i < dotmap.length; i ++){
      float[][] cdd = new float[1000][2];
      for (int j = 0; j < cdd.length; j++){
        cdd[j] = new float[]{random(-width*0.2,width*1.2)-trans[0],random(-height*0.2,height*1.2)-trans[1],random(s0,s1)};
      }
      int maxind = 0;
      float maxdist = 0;
      for (int j = 0; j < cdd.length; j++){
        float shortdist = width*height;
        for (int k = 0; k < i; k++){
           float cd = dist(cdd[j][0],cdd[j][1],dotmap[k][0],dotmap[k][1])-cdd[j][2]-cdd[k][2];
           if (cd < shortdist){ shortdist = cd; } } if (shortdist > maxdist){
          maxdist = shortdist;
          maxind = j;
        }
      }
      dotmap[i] = cdd[maxind];
    } 

 }

 public void feather(float len, float bw, float[] col){

   for (int i = 0; i < len; i+=2){ float ang = PI/4*((len-i)/len);//+random(-0.5*PI/i,0.5*PI/i); float cl = bw*0.03*sqrt(pow(len/2,4)-pow(i-len/2,4)); stroke(col[0],col[1],lerp(min(col[2]+50,100),random(col[2],col[2]+50),i*1.0/len)); line(i,0,i+cl*cos(ang),0+cl*sin(ang)); stroke(col[0],col[1],lerp(min(col[2]+50,100),random(col[2],col[2]+50),i*1.0/len)); line(i,0,i+cl*cos(ang),0-cl*sin(ang)); } stroke(col[0],col[1],min(col[2],100)); line(0,0,len,0); } public void antler(float[] p0, float wd, float len, int depth){ if (depth > 0){
    float[][] cur = bezmh(new float[][]{{p0[0],p0[1]},{p0[0]+len/2,random(len/6,len/3)},{p0[0]+len,p0[1]}});
    hornnoil(cur,wd,new float[]{10,20,100});
    for (int i = 0; i < len/40; i++){
      pushMatrix();
      int ri = floor(random(0,cur.length));
      float[] rc = cur[ri];
      translate(rc[0],rc[1]);
      rotate(random(PI/4));
      float pp = 1.0*(cur.length-ri)/cur.length;
      antler(new float[]{0,0},wd*pp,len*random(pp/2,pp),depth-1);
      popMatrix();
    }
  }
 }
 
 
 public void horn(float[][] cur,float bw){
   
   beginShape();
   for (int i = 0; i < cur.length-1; i++){ float tang = PI/2+atan2(cur[i+1][1]-cur[i][1],cur[i+1][0]-cur[i][0]); float cw =bw * (cur.length-i)/cur.length; vertex(cur[i][0]+cw*cos(tang),cur[i][1]+cw*sin(tang)); } for (int i = cur.length-1; i >0; i--){
     float tang = PI/2+atan2(cur[i-1][1]-cur[i][1],cur[i-1][0]-cur[i][0]);
     float cw = bw * (cur.length-i)/cur.length;
     vertex(cur[i][0]+cw*cos(tang),cur[i][1]+cw*sin(tang));
   }   
   endShape(CLOSE);
 }
 
 public void hornil(float[][] cur, float bw, float[] col){

     noStroke();
    for (float i = bw; i > 0; i--){
      fill(col[0],col[1],col[2]*0.5+col[2]*0.5*(bw-i)/bw);
      horn(cur,i);  
    }  
   
 }
 
 public void hornnoil(float[][] cur, float bw, float[] col){

  fill(col[0],col[1],col[2]/3);
  noStroke();
  horn(cur,bw);
   for (int i = 0; i < cur.length-1; i++){

     float tang = PI/2+atan2(cur[i+1][1]-cur[i][1],cur[i+1][0]-cur[i][0]);

     float cw =(bw * (cur.length-i)/cur.length+1)*(0.4+1.1*noise(i*0.05));
     for (int j = 0; j < cw*10; j++){
       float rw = random(-1,1);
       float rc = col[2]*0.4+random(col[2]*0.3*(1-abs(rw)),col[2]*1.2*(1-abs(rw)));
       fill(col[0],col[1],rc);
       ellipse(cur[i][0]+rw*cw*cos(tang),cur[i][1]+rw*cw*sin(tang),1,1);
     }
   }

   
 }
 
 public void anyfill(String filltype, float[][] cur0, float[][] cur1, float[] coldat, float[] furdat, float[] patdat, int amount){
   if (filltype == "fur"){
     furfill(cur0,cur1,coldat,furdat,patdat,amount);
   }else if (filltype == "scale"){
     scalefill(cur0,cur1,coldat,furdat,patdat,amount);
   }else if (filltype == "feather"){
     featherfill(cur0,cur1,coldat,furdat,patdat,amount);
   }
   if (debug){
     strokeWeight(1);noFill();stroke(255,0,0);
     beginShape(); for (int i = 0; i < cur0.length; i++){
       vertex(cur0[i][0],cur0[i][1]);  
     }endShape(); stroke(255,0,255);
     beginShape(); for (int i = 0; i < cur1.length; i++){
       vertex(cur1[i][0],cur1[i][1]);    
     }endShape();
   }
 }
 
 public void furfill(float[][] cur0, float[][] cur1, float[] coldat, float[] furdat, float[] patdat, int amount){
   int ml = min(cur0.length,cur1.length);
   for (int i = 0; i < ml; i++){ float ca = atan2(cur1[i][1]-cur0[i][1],cur1[i][0]-cur0[i][0]); float cl = dist(cur0[i][0],cur0[i][1],cur1[i][0],cur1[i][1]); float d2e = min(i,ml-i)/ml; cl = cl*(1.0+d2e*(0.4-0.8*noise(0.005*i))); cur1[i][0] = cur0[i][0]+cl*cos(ca); cur1[i][1] = cur0[i][1]+cl*sin(ca); } strokeWeight(1); if (ml > 0){
   for (int i = 0; i < amount; i++){
     int ir = floor(random(0,min(cur0.length,cur1.length)-1));
     float r = random(1);
     float xr = floor(lerp(cur0[ir][0],cur1[ir][0],r));
     float yr = floor(lerp(cur0[ir][1],cur1[ir][1],r));
     float driv1 = atan2(cur1[ir+1][1]-cur1[ir][1],cur1[ir+1][0]-cur1[ir][0]);
     float driv2 = atan2(cur0[ir+1][1]-cur0[ir][1],cur0[ir+1][0]-cur0[ir][0]);
     float p = dist(xr,yr,cur1[ir][0],cur1[ir][1])/dist(cur0[ir][0],cur0[ir][1],cur1[ir+1][0],cur1[ir+1][1]);
     float d = driv1*p+driv2*(1-p);
     float dr = d+random(-PI*0.1,PI*0.1)+(1-p)*furdat[2];
     stroke(coldat[0]+p*coldat[1]+random(coldat[2]),coldat[3]+p*coldat[4]+random(coldat[5]),coldat[6]+p*coldat[7]+random(coldat[8]));
     if (patdat[0] == 1){
       if (floor(xr/stpwd)%2 == 0){
         stroke(stpcol[0],stpcol[1],random(stpcol[2]));
       }
     }else if (patdat[0] == 2){
       for (int j = 0; j < dotmap.length; j++){
         if (dist(xr,yr,dotmap[j][0],dotmap[j][1])<dotmap[j][2]){
           stroke(stpcol[0],stpcol[1],random(stpcol[2]));
         }
       }
     }else if (patdat[0] == 3){
       for (int j = 0; j < dotmap.length; j++){
         if (dist(xr,yr,dotmap[j][0],dotmap[j][1])<dotmap[j][2] && dist(xr,yr,dotmap[j][0],dotmap[j][1])>dotmap[j][2]/2){
           stroke(stpcol[0],stpcol[1],random(stpcol[2]));
         }
       }       
     }
     float fl = furdat[0]+furdat[1]*noise(0.01*i);
     line(xr,yr,xr+fl*cos(dr),yr+fl*sin(dr));  

   }  
   }

 }
 public void furball(float x, float y,float r,float[] coldat){
   for (int i = 0; i < 100; i++){
     stroke(coldat[0]+0*coldat[1]+random(coldat[2]),coldat[3]+0*coldat[4]+random(coldat[5]),coldat[6]+0*coldat[7]+random(coldat[8]));
     line(x+random(-r,r),y+random(-r,r),x+random(-r,r),y+random(-r,r)); 
   }
 }
 
 public void scalefill(float[][] cur0, float[][] cur1, float[] coldat, float[] furdat, float[] patdat, int amount){
   int ml = min(cur0.length,cur1.length);
   for (int i = 0; i < ml; i++){
     float ca = atan2(cur1[i][1]-cur0[i][1],cur1[i][0]-cur0[i][0]);
     float cl = dist(cur0[i][0],cur0[i][1],cur1[i][0],cur1[i][1]);
     float d2e = min(i,ml-i)/ml;
     cl = cl*(1.0+d2e*(0.4-0.8*noise(0.005*i)));
     cur1[i][0] = cur0[i][0]+cl*cos(ca);
     cur1[i][1] = cur0[i][1]+cl*sin(ca);
     
   }   
   strokeWeight(1);
   amount = amount/20;
   for (int i = 0; i < amount; i+=5){
     int ir = floor(random(0,min(cur0.length,cur1.length)-1));
     float cd =  dist(cur0[ir][0],cur0[ir][1],cur1[ir][0],cur1[ir][1]);
     for (int j = 0; j < cd; j+=5){
       float r = random(1);
       float xr = floor(lerp(cur0[ir][0],cur1[ir][0],r));
       float yr = floor(lerp(cur0[ir][1],cur1[ir][1],r));
       float driv1 = atan2(cur1[ir+1][1]-cur1[ir][1],cur1[ir+1][0]-cur1[ir][0]);
       float driv2 = atan2(cur0[ir+1][1]-cur0[ir][1],cur0[ir+1][0]-cur0[ir][0]);
       float p = dist(xr,yr,cur1[ir][0],cur1[ir][1])/dist(cur0[ir][0],cur0[ir][1],cur1[ir+1][0],cur1[ir+1][1]);
       float d = driv1*p+driv2*(1-p);
       float dr = d+random(-PI*0.1,PI*0.1)+(1-p)*furdat[2];
       //dr = random(PI*2);
       float[] col = new float[]{coldat[0]+p*coldat[1]+random(coldat[2]),coldat[3]+p*coldat[4]+random(coldat[5]),coldat[6]+p*coldat[7]+random(coldat[8])};
       fill(col[0],col[1],col[2]);
       stroke(col[0],col[1]*0.8,col[2]*0.6);
       regpoly(xr,yr,4,5,dr);

     }
   }     
   
   for (int i = 0; i < ml-1; i+=6){
     int ir = i;//floor((i*1.0)/amount*(ml-1));//floor(random(0,min(cur0.length,cur1.length)-1));
     float cd =  dist(cur0[ir][0],cur0[ir][1],cur1[ir][0],cur1[ir][1]);
     for (int j = 0; j < cd; j+=6){ float r = j/cd;//random(1); float xr = floor(lerp(cur0[ir][0],cur1[ir][0],r)); float yr = floor(lerp(cur0[ir][1],cur1[ir][1],r)); float driv1 = atan2(cur1[ir+1][1]-cur1[ir][1],cur1[ir+1][0]-cur1[ir][0]); float driv2 = atan2(cur0[ir+1][1]-cur0[ir][1],cur0[ir+1][0]-cur0[ir][0]); float p = dist(xr,yr,cur1[ir][0],cur1[ir][1])/dist(cur0[ir][0],cur0[ir][1],cur1[ir+1][0],cur1[ir+1][1]); float d = driv1*p+driv2*(1-p); float dr = d+random(-PI*0.1,PI*0.1)+(1-p)*furdat[2]; float[] col = new float[]{coldat[0]+p*coldat[1]+random(coldat[2]),coldat[3]+p*coldat[4]+random(coldat[5]),coldat[6]+p*coldat[7]+random(coldat[8])}; fill(col[0],col[1],col[2]); stroke(col[0],col[1]*0.8,col[2]*0.6); regpoly(xr,yr,6,5,dr); } } } public void featherfill(float[][] cur0, float[][] cur1, float[] coldat, float[] furdat, float[] patdat, int amount){ int ml = min(cur0.length,cur1.length); if (ml > 0){
   strokeWeight(1);
   float a0 = atan2(cur0[1][1]-cur0[0][1],cur0[1][0]-cur0[0][0])+PI/2;
   float a1 = atan2(cur0[cur0.length-1][1]-cur0[cur0.length-2][1],cur0[cur0.length-1][0]-cur0[cur0.length-2][0]);

   for (int k = 0; k <=4; k+=1){
     for (float i = 1; i < ml-1; i*=1.05){
       int ir = floor(i);
       float cd =  dist(cur0[ir][0],cur0[ir][1],cur1[ir][0],cur1[ir][1]);
       
       float j = (4-k)*cd/4;
       float r = j/cd;
       float xr = floor(lerp(cur0[ir][0],cur1[ir][0],r));
       float yr = floor(lerp(cur0[ir][1],cur1[ir][1],r));
       pushMatrix();
       translate(xr,yr);
       rotate(lerp(a0,a1,(i*1.0)/ml));
       feather(lerp(20,100,j/cd*(i*1.0)/ml+random(-0.1,0.1)),lerp(0.5,5,(cd-j)/cd*(ml-i)/ml),new float[]{coldat[0],coldat[3],pow(j/cd,0.9)*coldat[6]+random(20)*coldat[7]});
       popMatrix();
       
     }
   }    
 } }

 
 public void featherfin(float[][] cur,float[] coldat){
  for(int i = 0; i < cur.length-1; i++){
    float tng = atan2(cur[i+1][1]-cur[i][1],cur[i+1][0]-cur[i][0]);
     pushMatrix();
     translate(cur[i][0],cur[i][1]);
     rotate(tng+random(-PI*0.1,PI*0.1));
     feather(100,0.1,new float[]{coldat[0],coldat[3],pow(0.5,0.9)*coldat[6]+random(20)});
     popMatrix();
  }
 } 
 
 public void feathertail(float[][] cur, float[] coldat){
  for(float j = 1; j < cur.length-1; j=j*1.5){
    int i = floor(j);
    float tng = atan2(cur[i+1][1]-cur[i][1],cur[i+1][0]-cur[i][0]);
    for (float k = 0; k < 1; k++){
     pushMatrix();
     translate(cur[i][0],cur[i][1]);
     rotate(tng+random(-PI/10*(cur.length-i)/cur.length,PI/10*(cur.length-i)/cur.length));
     feather(lerp(50,200,j/cur.length),lerp(0.5,0.001,j/cur.length),new float[]{coldat[0],coldat[3],pow(0.5,0.9)*coldat[6]+random(20)});
     popMatrix();
    }
  }
 } 
 public void drawfeathertail(float[] coldat){
   feathertail(bezmh(new float[][]{pelvis.loc(),tail_a.loc(),tail_b.loc(),tail_c.loc()}),coldat);   
 }
 
 
 
 
 
 public void draweye(float pr,float di, float rad,float[] col1, float[] col2, float[] coldat, float[] furdat, float[]patdat){

  float[] p1;
  p1 = head.relcoord(pr,di);//0.75,-25
  stroke(col1[0],col1[1],col1[2]/10);
  strokeWeight(5);
  fill(col1[0],col1[1],col1[2]);
  ellipse(p1[0],p1[1],rad,rad);
  
  noStroke();
  
  for (float i = 0; i < PI*2; i+=PI*0.1){
    fill(col1[0],col1[1],col1[2]*random(0.8,1.0));
    float rr = random(rad*0.4);
    ellipse(p1[0]+rr*cos(i),p1[1]+rr*sin(i),2,2);
  }
  
  stroke(col2[0],col2[1],col2[2]*2);
  strokeWeight(2);
  fill(col2[0],col2[1],col2[2]);
  
  
  ellipse(p1[0],p1[1],rad/3,rad/3);
  strokeWeight(1);
  for (float i = 0; i < PI*2; i+=PI*0.2){
    stroke(col2[0],col2[1],col2[2]*1.5);
    line(p1[0]+rad/10*cos(i),p1[1]+rad/10*sin(i),p1[0]+rad/6*cos(i),p1[1]+rad/6*sin(i));
  }

  furfill(
  bezmh(new float[][]{head.relcoord(pr+0.45,di),head.relcoord(pr+0.05,di-rad+min(rad,13)),head.relcoord(pr-0.25,di+5)}),
  bezmh(new float[][]{head.relcoord(pr+0.45,di),head.relcoord(pr+0.04,di-rad-5),head.relcoord(pr-0.25,di+5)}),
  coldat,new float[]{furdat[0]/3,furdat[1]/3,furdat[2]},patdat,floor(furdat[3]/100));
  furfill(
  bezmh(new float[][]{head.relcoord(pr+0.45,di),head.relcoord(pr+0.25,di+rad-min(22,rad)),head.relcoord(pr-0.05,di+rad-min(10,rad)),head.relcoord(pr-0.25,di+5)}),
  bezmh(new float[][]{head.relcoord(pr+0.45,di),head.relcoord(pr-0.05,di+rad),head.relcoord(pr-0.25,di+5)}),
  coldat,new float[]{furdat[0]/3,furdat[1]/3,furdat[2]},patdat,floor(furdat[3]/100));  
   
 }
 public void drawclaw(Limb leg, Limb paw, float size, float bend, float[] col, float[] coldat, float[] furdat, float[] patdat){

  hornil(bezmh(new float[][]{paw.relcoord(1.0,-2),paw.relcoord((size+1)/2,-bend-4),paw.relcoord(size,-5)}),4,col);
  hornil(bezmh(new float[][]{paw.relcoord(1.0,0),paw.relcoord((size+1)/2,-bend),paw.relcoord(size,0)}),4,col);
  hornil(bezmh(new float[][]{paw.relcoord(1.0,2),paw.relcoord((size+1)/2,-bend+4),paw.relcoord(size,5)}),4,col);

  furfill(
  bezmh(new float[][]{paw.relcoord(0.8,0),paw.loc(),paw.relcoord(0.8,0)}),
  bezmh(new float[][]{paw.relcoord(0.8,0),paw.relcoord(1.0,5),paw.relcoord(1.0,-15),paw.relcoord(0.8,0)}),
  coldat,new float[]{furdat[0]/3,furdat[1]/3,furdat[2]},patdat,floor(furdat[3])); 
   
 }
 public void drawear(float wd, float ht, float[] incol, float[] coldat,float[] furdat, float[] patdat){
  float b = -neck.tl1/2;
  furfill(
  bezmh(new float[][]{neck.loc(),neck.relcoord(1.0,b-ht),neck.loc()}),
  bezmh(new float[][]{neck.relcoord(1+wd,0),neck.relcoord(1.0,b-ht-20),neck.relcoord(1-wd,0)}),
  coldat,furdat,patdat,floor(furdat[3]/100)); 
  furfill(
  bezmh(new float[][]{neck.relcoord(1,b-ht/6),neck.relcoord(1.0,b-ht/2),neck.relcoord(1,b-ht/6)}),
  bezmh(new float[][]{neck.relcoord(1+wd/3,b-ht/4),neck.relcoord(1.0,b-ht),neck.relcoord(1-wd/3,b-ht/4)}),
  incol,furdat,new float[]{0},floor(furdat[3]/100)) ;
 }
   
 public void drawteeth(float wd, float ht, float[] col){
    for (float i = jaw_u.l*0.2; i < jaw_u.l*0.8; i+=wd){

     hornil(bezmh(new float[][]{jaw_u.relcoord(i/jaw_u.l,0),jaw_u.relcoord(i/jaw_u.l,ht),jaw_u.relcoord(i/jaw_u.l,ht+ht*4*noise(i*0.1))}),
     wd,col);
       }
    for (float i = jaw_l.l*0.2; i < jaw_l.l*0.8; i+=wd){


     hornil(bezmh(new float[][]{jaw_l.relcoord(i/jaw_l.l,0),jaw_l.relcoord(i/jaw_l.l,-ht),jaw_l.relcoord(i/jaw_l.l,-ht-ht*10*noise(i*0.1))}),
     wd,col);
     }  
   
 }
 public void drawtusk(float wd, float ht, float un, float ln, float[] col){
    for (float ii = 0; ii < un; ii+=1){
     float i = random(jaw_u.l*0.5,jaw_u.l*0.9);
     hornnoil(bezmh(new float[][]{jaw_u.relcoord(i/jaw_u.l,0),jaw_u.relcoord(i/jaw_u.l,ht),jaw_u.relcoord(i/jaw_u.l,ht+ht*4*noise(i*0.1))}),
     wd,col);
       }
  for (float ii = 0; ii < ln; ii+=1){

     float i = random(jaw_l.l*0.5,jaw_l.l*0.9);
     hornnoil(bezmh(new float[][]{jaw_l.relcoord(i/jaw_l.l,0),jaw_l.relcoord(i/jaw_l.l,-ht),jaw_l.relcoord(i/jaw_l.l,-ht-ht*10*noise(i*0.1))}),
     wd,col);
     } 

 }

 public void drawhorsetail(float[] coldat){

  float[][] cur = bezmh(new float[][]{pelvis.loc(),tail_a.loc(),tail_b.loc(),tail_c.loc()});
  for (int i = 0; i < 500; i++){
    noFill();
    stroke(coldat[0],coldat[3],random(coldat[6]*3));
    
    beginShape();
    float[] lastp = new float[]{cur[0][0],cur[0][1]};
    float rl = random(1);
    for (int j = 1; j < cur.length*rl; j++){

      float dis = dist(cur[j][0],cur[j][1],cur[j-1][0],cur[j-1][1]);
      float ang = atan2(cur[j][1]-cur[j-1][1],cur[j][0]-cur[j-1][0]);
      float nz = noise(i,j*0.01)*2-1;
      lastp = new float[]{lastp[0]+dis*cos(ang+nz*PI/6),lastp[1]+dis*sin(ang+nz*PI/6)};
      vertex(lastp[0],lastp[1]);
    }
    endShape();
  }
 }
   
 public void fishtail(float[][]cur0, float[][]cur1,float[] coldat){
  float sp = cur1.length*1.0/cur0.length;
  
   for (int i = 1; i < cur0.length; i++){
    
    for (int j = 0; j <= sp; j++){
      stroke(coldat[0],coldat[1],coldat[2]/2);
      strokeWeight(3);
      int ji = floor(i*sp+j);
      if (ji < cur1.length && ji >= 0){
        line(cur0[i][0],cur0[i][1],cur1[ji][0]+random(-5,5),cur1[ji][1]+random(-5,5));

      }
    } 
  
   }
  for (int i = 0; i < cur0.length; i++){
    
    for (int j = 0; j <= sp; j++){
      stroke(coldat[0],coldat[1],random(coldat[2]/3,coldat[2]));
      strokeWeight(1);
      int ji = floor(i*sp+j);
      if (ji < cur1.length && ji >= 0){

        float[] nc = new float[]{cur1[ji][0]+random(-5,5),cur1[ji][1]+random(-5,5)};
        float di = dist(cur0[i][0],cur0[i][1],nc[0],nc[1]);
        
        pushMatrix();
        translate(cur0[i][0],cur0[i][1]);
        rotate(atan2(nc[1]-cur0[i][1],nc[0]-cur0[i][0]));
        float lp = random(-1,1);
        for (int k = 0; k <= di; k+=5){ float lp2 = random(-1,1); line(k,lp,k+5,lp+lp2); lp = lp + lp2; } popMatrix(); } } } } public void drawfeatherfin(float[] coldat){ featherfin(bezmh(new float[][]{pelvis.loc(),tail_a.loc(),tail_b.loc(),tail_c.loc()}),coldat); } public void drawfin_l(float[] coldat){ featherfin(bezmh(new float[][]{spine_a.loc(),forethigh_l.loc(),forethigh_l.loc()}),coldat); } public void drawfin_r(float[] coldat){ featherfin(bezmh(new float[][]{spine_a.loc(),forethigh_r.loc(),forethigh_r.loc()}),coldat); } public void drawmidfin_l(float[] coldat){ featherfin(bezmh(new float[][]{spine_b.loc(),midthigh_l.loc(),midthigh_l.loc()}),coldat); } public void drawmidfin_r(float[] coldat){ featherfin(bezmh(new float[][]{spine_b.loc(),midthigh_r.loc(),midthigh_r.loc()}),coldat); } public void drawwing(Limb wing_a, Limb wing_b, Limb wing_c, float[] coldat){ scalefill( bezmh(new float[][]{wing_a.relcoord(0,0),wing_a.loc(),wing_b.loc(),wing_c.relcoord(0.7,0)}), bezmh(new float[][]{wing_a.relcoord(0,-40),wing_b.tlloc(1),wing_c.tlloc(1),wing_c.relcoord(0.7,0)}), new float[]{10,5,0,10,10,0,30,10,10},new float[]{6,8,0},new float[]{2},10); featherfill( bezmh(new float[][]{wing_a.relcoord(0,0),wing_a.loc(),wing_b.loc(),wing_c.loc()}), bezmh(new float[][]{wing_a.relcoord(0,-40),wing_b.tlloc(1),wing_c.tlloc(1),wing_c.loc()}), coldat,new float[]{6,8,0},new float[]{2},10); } public void drawtongue(int len){ if (len > 0){
  float[][] cur = new float[len][2];
  for (int i = 0; i < len; i++){ cur[i] = head.relcoord(1+i*0.2,random(10)*i/len); } hornil(bezmh(cur),10,new float[]{0,100,50}); } } public void drawhorn(float wd, float ht, float[]col){ hornnoil(bezmh(new float[][]{head.relcoord(1.0,0),head.relcoord(1.0,-ht/2),head.relcoord(0.5,-ht)}), wd,col); } public void drawbeak(float[] col){ c.hornil(bezmh(new float[][]{c.head.loc(),c.jaw_u.relcoord(0.5,-random(30)),c.jaw_u.loc()}),c.head.tl1/3,col); c.hornil(bezmh(new float[][]{c.head.loc(),c.jaw_l.relcoord(0.5,-random(30)),c.jaw_l.loc()}),c.head.tl2/3,col); } public void drawback(String filltype, float[] coldat, float[] furdat, float[] patdat){ anyfill(filltype, bezmh(new float[][]{neck.loc(),spine_a.loc(),spine_b.loc(),spine_c.loc(),pelvis.loc(),tail_a.loc(),tail_b.loc(),tail_c.loc()}), bezmh(new float[][]{head.tlloc(1),neck.tlloc(1),spine_a.tlloc(1),spine_b.tlloc(1),pelvis.tlloc(2),tail_b.tlloc(2),tail_c.tlloc(2),tail_c.loc()}), coldat,furdat,patdat,floor(furdat[3])); } public void drawbacknotail(String filltype, float[] coldat, float[] furdat, float[] patdat){ anyfill(filltype, bezmh(new float[][]{neck.loc(),spine_a.loc(),spine_b.loc(),spine_c.loc(),pelvis.loc(),tail_a.loc()}), bezmh(new float[][]{head.tlloc(1),neck.tlloc(1),spine_a.tlloc(1),spine_b.tlloc(1),pelvis.tlloc(2),tail_a.loc()}), coldat,furdat,patdat,floor(furdat[3])); } public void drawbelly(String filltype, float[] coldat, float[] furdat, float[] patdat){ anyfill(filltype, bezmh(new float[][]{neck.loc(),spine_b.loc(),spine_c.loc(),pelvis.loc(),tail_a.loc()}), bezmh(new float[][]{neck.loc(),forethigh_l.relcoord(0.5,0),spine_a.tlloc(2),spine_b.tlloc(2),midpt(hip_l.loc(),hindthigh_l.loc())}), coldat,new float[]{furdat[0]*2,furdat[1],furdat[2]+PI/3},patdat,floor(furdat[3])); } public void drawforeleg_l(String filltype, float[] coldat, float[] furdat, float[] patdat){ anyfill(filltype, bezmh(new float[][]{forepaw_l.loc(),foreleg_l.loc(),forethigh_l.loc(),shoulder_l.loc(),spine_a.loc(),neck.loc()}), bezmh(new float[][]{forepaw_l.loc(),forepaw_l.tlloc(1),foreleg_l.tlloc(1),forethigh_l.tlloc(1),head.tlloc(2)}), coldat,furdat,patdat,floor(furdat[3])); anyfill(filltype, bezmh(new float[][]{forepaw_l.loc(),foreleg_l.loc(),forethigh_l.loc(),shoulder_l.loc(),neck.loc(),spine_a.loc()}), bezmh(new float[][]{forepaw_l.loc(),forepaw_l.tlloc(2),foreleg_l.tlloc(2),forethigh_l.tlloc(2),spine_a.loc()}), coldat,furdat,patdat,floor(furdat[3])); } public void drawforeleg_r(String filltype, float[] coldat, float[] furdat, float[] patdat){ float[] nc = new float[coldat.length]; arrayCopy(coldat,nc); nc[6] = nc[6]/3; nc[7] = nc[7]/3; nc[8] = nc[8]/3; anyfill(filltype, bezmh(new float[][]{forepaw_r.loc(),foreleg_r.loc(),forethigh_r.loc(),shoulder_r.loc(),spine_a.loc(),neck.loc()}), bezmh(new float[][]{forepaw_r.loc(),forepaw_r.tlloc(1),foreleg_r.tlloc(1),forethigh_r.tlloc(1),head.tlloc(2)}), nc,furdat,patdat,floor(furdat[3])); anyfill(filltype, bezmh(new float[][]{forepaw_r.loc(),foreleg_r.loc(),forethigh_r.loc(),shoulder_r.loc(),neck.loc(),spine_b.loc()}), bezmh(new float[][]{forepaw_r.loc(),forepaw_r.tlloc(2),foreleg_r.tlloc(2),forethigh_r.tlloc(2),spine_b.loc()}), nc,furdat,patdat,floor(furdat[3])); } public void drawmidleg_r(String filltype, float[] coldat, float[] furdat, float[] patdat){ float[] nc = new float[coldat.length]; arrayCopy(coldat,nc); nc[6] = nc[6]/3; nc[7] = nc[7]/3; nc[8] = nc[8]/3; anyfill(filltype, bezmh(new float[][]{midpaw_r.loc(),midleg_r.loc(),midthigh_r.loc(),midshould_r.loc(),spine_b.loc()}), bezmh(new float[][]{midpaw_r.loc(),midpaw_r.tlloc(1),midleg_r.tlloc(1),midthigh_r.tlloc(1),spine_b.loc()}), nc,furdat,patdat,floor(furdat[3])); anyfill(filltype, bezmh(new float[][]{midpaw_r.loc(),midleg_r.loc(),midthigh_r.loc(),midshould_r.loc(),spine_c.loc()}), bezmh(new float[][]{midpaw_r.loc(),midpaw_r.tlloc(2),midleg_r.tlloc(2),midthigh_r.tlloc(2),spine_c.loc()}), nc,furdat,patdat,floor(furdat[3])); } public void drawmidleg_l(String filltype, float[] coldat, float[] furdat, float[] patdat){ anyfill(filltype, bezmh(new float[][]{midpaw_l.loc(),midleg_l.loc(),midthigh_l.loc(),midshould_l.loc(),spine_b.loc()}), bezmh(new float[][]{midpaw_l.loc(),midpaw_l.tlloc(1),midleg_l.tlloc(1),midthigh_l.tlloc(1),spine_b.loc()}), coldat,furdat,patdat,floor(furdat[3])); anyfill(filltype, bezmh(new float[][]{midpaw_l.loc(),midleg_l.loc(),midthigh_l.loc(),midshould_l.loc(),spine_c.loc()}), bezmh(new float[][]{midpaw_l.loc(),midpaw_l.tlloc(2),midleg_l.tlloc(2),midthigh_l.tlloc(2),spine_c.loc()}), coldat,furdat,patdat,floor(furdat[3])); } public void drawhindleg_l(String filltype, float[] coldat, float[] furdat, float[] patdat){ anyfill(filltype, bezmh(new float[][]{hindpaw_l.loc(),hindleg_l.loc(),hindthigh_l.loc(),hip_l.loc(),pelvis.loc(),tail_a.loc(),tail_b.loc(),tail_c.loc()}), bezmh(new float[][]{hindpaw_l.loc(),hindpaw_l.tlloc(2),hindleg_l.tlloc(2),hindthigh_l.tlloc(2),pelvis.loc(),tail_b.tlloc(1),tail_c.tlloc(1),tail_c.loc()}), coldat,furdat,patdat,floor(furdat[3])); anyfill(filltype, bezmh(new float[][]{pelvis.loc(),hip_l.loc(),hindthigh_l.loc(),hindleg_l.loc(),hindpaw_l.loc()}), bezmh(new float[][]{spine_c.tlloc(2),hindthigh_l.tlloc(1),hindleg_l.tlloc(1),hindpaw_l.tlloc(1),hindpaw_l.loc()}), coldat,furdat,patdat,floor(furdat[3])); } public void drawhindlegnotail_l(String filltype, float[] coldat, float[] furdat, float[] patdat){ anyfill(filltype, bezmh(new float[][]{hindpaw_l.loc(),hindleg_l.loc(),hindthigh_l.loc(),hip_l.loc(),pelvis.loc()}), bezmh(new float[][]{hindpaw_l.loc(),hindpaw_l.tlloc(2),hindleg_l.tlloc(2),hindthigh_l.tlloc(2),pelvis.loc()}), coldat,furdat,patdat,floor(furdat[3])); anyfill(filltype, bezmh(new float[][]{pelvis.loc(),hip_l.loc(),hindthigh_l.loc(),hindleg_l.loc(),hindpaw_l.loc()}), bezmh(new float[][]{spine_c.tlloc(2),hindthigh_l.tlloc(1),hindleg_l.tlloc(1),hindpaw_l.tlloc(1),hindpaw_l.loc()}), coldat,furdat,patdat,floor(furdat[3])); } public void drawhindleg_r(String filltype, float[] coldat, float[] furdat, float[] patdat){ float[] nc = new float[coldat.length]; arrayCopy(coldat,nc); nc[6] = nc[6]/3; nc[7] = nc[7]/3; nc[8] = nc[8]/3; anyfill(filltype, bezmh(new float[][]{hindpaw_r.loc(),hindleg_r.loc(),hindthigh_r.loc(),hip_r.loc(),pelvis.loc()}), bezmh(new float[][]{hindpaw_r.loc(),hindpaw_r.tlloc(2),hindleg_r.tlloc(2),hindthigh_r.tlloc(2),pelvis.loc()}), nc,furdat,patdat,floor(furdat[3])); anyfill(filltype, bezmh(new float[][]{pelvis.loc(),hip_r.loc(),hindthigh_r.loc(),hindleg_r.loc(),hindpaw_r.loc()}), bezmh(new float[][]{spine_c.tlloc(2),hindthigh_r.tlloc(1),hindleg_r.tlloc(1),hindpaw_r.tlloc(1),hindpaw_r.loc()}), nc,furdat,patdat,floor(furdat[3])); } public void drawhead(String filltype, float[] coldat, float[] furdat, float[] patdat){ anyfill(filltype, bezmh(new float[][]{spine_a.loc(),neck.loc(),head.loc(),jaw_l.loc(),head.loc(),jaw_u.loc(),head.loc(),neck.loc(),spine_a.loc()}), bezmh(new float[][]{forethigh_l.tlloc(2),head.tlloc(2),jaw_l.tlloc(2),jaw_l.loc(),head.loc(),jaw_u.loc(),jaw_u.tlloc(1),head.tlloc(1),neck.tlloc(1)}), coldat,furdat,patdat,floor(furdat[3])); } public void drawbirdhead(String filltype, float[] coldat, float[] furdat, float[] patdat){ anyfill(filltype, bezmh(new float[][]{spine_a.loc(),neck.loc(),head.loc(),jaw_l.relcoord(0.5,0),head.loc(),jaw_u.relcoord(0.5,0),head.loc(),neck.loc(),spine_a.loc()}), bezmh(new float[][]{forethigh_l.tlloc(2),head.tlloc(2),jaw_l.tlloc(2),jaw_l.relcoord(0.5,0),head.loc(),jaw_u.relcoord(0.5,0),jaw_u.tlloc(1),head.tlloc(1),neck.tlloc(1)}), coldat,furdat,patdat,floor(furdat[3])); } public void drawantler(float[] coldat){ pushMatrix(); translate(neck.loc()[0]-10,neck.loc()[1]-30); rotate(-PI/4); antler(new float[] {0,0},5,200,3); popMatrix(); furball(neck.loc()[0]-10,neck.loc()[1]-30,10,coldat); } } float choice(float a,float b){ if (random(1) > 0.5){
    return a;
  }
  return b;
  
}

float rg(float x, float xmin, float xmax){
  if (random(xmin,xmax) < x){
    return x-abs(randomGaussian()*(x-xmin)/2);
  }
  return x+abs(randomGaussian()*(xmax-x)/2);
}

float rtg(float x, float xmin, float xmax){
  if (random(xmin,xmax) < x){
    return x-abs(randomGaussian()*(x-xmin)/5);
  }
  return x+abs(randomGaussian()*(xmax-x)/5);
}

float[] dark(float[] col){
  return new float[] {col[0],col[1],col[2],col[3],col[4],col[5],col[6]/2.2,col[7]/2.2,col[8]/2.2};
  
}

public int maind(float[] arr){
  int ind = 0;
  float ma = 0;
  for (int i = 0; i < arr.length; i++){ if (arr[i]>ma){
      ind = i;
      ma = arr[i];
    }    
  }
  return ind;
}

public class Generator{
  Creature c;
  float[] col;
  float[] fur;
  float[] pat;
  
  float[] bonecol;
  float[] feathercol;
  float[] fincol;
  float[] scalecol;
  String filltype;
  
  float sixleg = floor(random(4));
  float[] ctype;
  
  float patstyle = floor(random(4));
  float tailstyle = floor(random(3));
  float hoofy = (random(1));
  float clawsize = rg(2,1,2.5);
  float clawbend = rg(5,0,20);
  float eyehue = random(70);
  float winged = random(1);
    
  public Generator(){
    float a = random(7);
    float b = random(4);
    float c = random(4);
    ctype = new float[]{a/(a+b+c),b/(a+b+c),c/(a+b+c)};    
  }

  public void gen(){
    c = new Creature();
    if (patstyle == 0){
      c.makedotmap(10,100);
    }else{
      c.makedotmap(20,20);
    }
    if (random(1) > 0.2){
      filltype = "fur";
    }else{
      filltype = "scale";
    }    
    c.pelvis.tl1 = rg(50,30,100);
    c.pelvis.tl2 = rg(50,30,100);
    
    c.spine_c.l = rg(100,10,200);
    c.spine_c.a = rtg(0.9*PI,0.6*PI,1.2*PI);
    c.spine_c.tl1 = rg(20,5,50);
    c.spine_c.tl2 = rg(100,10,200);
    
    c.spine_b.l = rg(100,10,300);
    c.spine_b.a = rtg(0.1*PI,-0.5*PI,0.5*PI);
    c.spine_b.tl1 = rg(50,10,100);
    c.spine_b.tl2 = rg(100,10,200);
        
    c.spine_a.l = rg(100,10,300);
    c.spine_a.a = rtg(0.1*PI,-0.5*PI,0.5*PI);
    c.spine_a.tl1 = rg(50,10,100);
    c.spine_a.tl2 = rg(50,10,100);
    
    
    c.neck.l = rg(50,0,200);
    c.neck.a = rtg(-0.3*PI,-0.5*PI,-0.0*PI);
    c.neck.tl1 = rg(40,0,60);
    c.neck.tl2 = rg(40,0,60);
    
    
    c.head.l = rg(80,0,200);
    c.head.a = rtg(0.1*PI,-0.5*PI,0.5*PI);
    c.head.tl1 = rg(60,10,100);
    c.head.tl2 = rg(30,10,100);    
    
    
    c.jaw_u.l = rg(80,10,250);
    c.jaw_u.a = rtg(-0.1*PI,-0.4*PI,0.0*PI);
    c.jaw_l.l = rg(80,10,250);
    c.jaw_l.a = rtg(0.1*PI,0.0*PI,0.4*PI);
   

    c.shoulder_l.l = rg(20,10,100);
    c.shoulder_r.l = c.shoulder_l.l;
    c.shoulder_l.a = rtg(0.2*PI,-0.1*PI,0.6*PI);
    c.shoulder_r.a = c.shoulder_l.a + rtg(0,-PI*0.5,PI*0.5);
    c.shoulder_l.tl1 = rg(10,0,80);
    c.shoulder_l.tl2 = rg(50,10,150); 
    c.shoulder_r.tl1 = c.shoulder_l.tl1;
    c.shoulder_r.tl2 = c.shoulder_l.tl2;
    
    c.forethigh_l.l = rg(100,20,200);
    c.forethigh_r.l = c.forethigh_l.l;
    c.forethigh_l.a = rtg(0.4*PI,0.2*PI,0.6*PI);
    c.forethigh_r.a = c.forethigh_l.a + rtg(0,-PI*0.5,PI*0.5);    
    c.forethigh_l.tl1 = c.shoulder_l.tl1 * rg(0.8,0,1);
    c.forethigh_l.tl2 = c.shoulder_l.tl2 * rg(0.8,0,1);; 
    c.forethigh_r.tl1 = c.forethigh_l.tl1;
    c.forethigh_r.tl2 = c.forethigh_l.tl2;    
    
    c.foreleg_l.l = c.forethigh_l.l*rtg(1,0,2);
    c.foreleg_r.l = c.foreleg_l.l;
    c.foreleg_l.a = rtg(-0.5*PI,-0.7*PI,-0.3*PI);
    c.foreleg_r.a = c.foreleg_l.a +  rtg(0,-PI*0.5,PI*0.5);       
    c.foreleg_l.tl1 = c.forethigh_l.tl1  * rg(0.8,0,1);
    c.foreleg_l.tl2 = c.forethigh_l.tl2  * rg(0.8,0,1); 
    c.foreleg_r.tl1 = c.foreleg_l.tl1;
    c.foreleg_r.tl2 = c.foreleg_l.tl2;   
  
 
    if (hoofy < 0.3){ 
      c.forepaw_l.l = c.foreleg_l.l*rtg(0.5,0,1);
      c.forepaw_r.l = c.forepaw_l.l;
      c.forepaw_l.a = rtg(-0.2*PI,-0.7*PI,0.3*PI);
      c.forepaw_r.a = c.forepaw_l.a +  rtg(0,-PI*0.5,PI*0.5);         
    }else{
      c.forepaw_l.l = c.foreleg_l.l*rtg(1,0,2);
      c.forepaw_r.l = c.forepaw_l.l;
      c.forepaw_l.a = rtg(0.3*PI,0.0*PI,0.8*PI);
      c.forepaw_r.a = c.forepaw_l.a +  rtg(0,-PI*0.5,PI*0.5);        
    }
    if (sixleg == 1){
      c.midshould_l.l = c.shoulder_l.l * rtg(1,0.8,1.1);
      c.midshould_r.l = c.midshould_l.l;
      c.midshould_l.a = rtg(0.5*PI,-0.1*PI,0.6*PI);
      c.midshould_r.a = c.midshould_l.a + rtg(0,-PI*0.5,PI*0.5);
      c.midshould_l.tl1 = c.shoulder_l.tl1 * rtg(1,0.8,1.1);
      c.midshould_l.tl2 = c.shoulder_l.tl2 * rtg(1,0.8,1.1); 
      c.midshould_r.tl1 = c.midshould_l.tl1;
      c.midshould_r.tl2 = c.midshould_l.tl2;
      
      c.midthigh_l.l = c.forethigh_l.l * rtg(1,0.8,1.1);
      c.midthigh_r.l = c.midthigh_l.l;
      c.midthigh_l.a = rtg(0.4*PI,0.2*PI,0.6*PI);
      c.midthigh_r.a = c.midthigh_l.a + rtg(0,-PI*0.5,PI*0.5);    
      c.midthigh_l.tl1 = c.midshould_l.tl1 * rg(0.8,0,1);
      c.midthigh_l.tl2 = c.midshould_l.tl2 * rg(0.8,0,1);; 
      c.midthigh_r.tl1 = c.midthigh_l.tl1;
      c.midthigh_r.tl2 = c.midthigh_l.tl2;    
      
      c.midleg_l.l =  c.foreleg_l.l * rtg(1,0.8,1.1);
      c.midleg_r.l = c.midleg_l.l;
      c.midleg_l.a = rtg(-0.5*PI,-0.7*PI,-0.3*PI);
      c.midleg_r.a = c.midleg_l.a +  rtg(0,-PI*0.5,PI*0.5);       
      c.midleg_l.tl1 = c.midthigh_l.tl1  * rg(0.8,0,1);
      c.midleg_l.tl2 = c.midthigh_l.tl2  * rg(0.8,0,1); 
      c.midleg_r.tl1 = c.midleg_l.tl1;
      c.midleg_r.tl2 = c.midleg_l.tl2;   
           
      if (hoofy < 0.3){ c.midpaw_l.l = c.midleg_l.l*rtg(0.5,0,1); c.midpaw_r.l = c.midpaw_l.l; c.midpaw_l.a = rtg(-0.2*PI,-0.7*PI,0.3*PI); c.midpaw_r.a = c.midpaw_l.a + rtg(0,-PI*0.5,PI*0.5); }else{ c.midpaw_l.l = c.midleg_l.l*rtg(1,0,2); c.midpaw_r.l = c.midpaw_l.l; c.midpaw_l.a = rtg(0.3*PI,0.0*PI,0.8*PI); c.midpaw_r.a = c.midpaw_l.a + rtg(0,-PI*0.5,PI*0.5); } } c.hip_l.l = rg(20,10,100); c.hip_r.l = c.hip_l.l; c.hip_l.a = rtg(-0.3*PI,-0.5*PI,0.1*PI); c.hip_r.a = c.hip_l.a + rtg(0,-PI*0.4,PI*0.4); c.hip_l.tl1 = rg(80,10,200); c.hip_l.tl2 = rg(20,0,30); c.hip_r.tl1 = c.hip_l.tl1; c.hip_r.tl2 = c.hip_l.tl2; c.hindthigh_l.l = rg(100,20,200); c.hindthigh_r.l = c.hindthigh_l.l; c.hindthigh_l.a = rtg(-0.5*PI,-0.7*PI,-0.3*PI); c.hindthigh_r.a = c.hindthigh_l.a + rtg(0,-PI*0.4,PI*0.4); c.hindthigh_l.tl1 = rg(20,0,30); c.hindthigh_l.tl2 = rg(20,0,30); c.hindthigh_r.tl1 = c.hindthigh_l.tl1; c.hindthigh_r.tl2 = c.hindthigh_l.tl2; c.hindleg_l.l = rg(100,20,200); c.hindleg_r.l = c.hindleg_l.l; c.hindleg_l.a = rtg(0.5*PI,0.3*PI,0.7*PI); c.hindleg_r.a = c.hindleg_l.a + rtg(0,-PI*0.4,PI*0.4); c.hindleg_l.tl1 = rg(5,0,30); c.hindleg_l.tl2 = rg(20,0,30); c.hindleg_r.tl1 = c.hindleg_l.tl1; c.hindleg_r.tl2 = c.hindleg_l.tl2; c.hindpaw_l.l = rg(80,20,150); c.hindpaw_r.l = c.hindpaw_l.l; c.hindpaw_l.a = rtg(-0.3*PI,-0.6*PI,-0.0*PI); c.hindpaw_r.a = c.hindpaw_l.a + rtg(0,-PI*0.4,PI*0.4); c.wing_a_l.l = rg(100,10,200); c.wing_a_r.l = c.wing_a_l.l; c.wing_a_l.a = rtg(-0.8*PI,-1.0*PI,-0.5*PI); c.wing_a_r.a = c.wing_a_l.a + rtg(0,-PI*0.4,PI*0.4); c.wing_a_l.tl1 = rg(50,0,100); c.wing_a_l.tl2 = rg(0,0,10); c.wing_a_r.tl1 = c.wing_a_l.tl1; c.wing_a_r.tl2 = c.wing_a_l.tl2; c.wing_b_l.l = rg(100,10,200); c.wing_b_r.l = c.wing_b_l.l; c.wing_b_l.a = rtg(0.2*PI,0.0*PI,0.7*PI); c.wing_b_r.a = c.wing_b_l.a + rtg(0,-PI*0.4,PI*0.4); c.wing_b_l.tl1 = rg(80,0,160); c.wing_b_l.tl2 = rg(0,0,10); c.wing_b_r.tl1 = c.wing_b_l.tl1; c.wing_b_r.tl2 = c.wing_b_l.tl2; c.wing_c_l.l = rg(100,10,200); c.wing_c_r.l = c.wing_c_l.l; c.wing_c_l.a = rtg(-0.3*PI,-0.6*PI,0.0*PI); c.wing_c_r.a = c.wing_c_l.a + rtg(0,-PI*0.4,PI*0.4); c.wing_c_l.tl1 = rg(80,0,160); c.wing_c_l.tl2 = rg(0,0,10); c.wing_c_r.tl1 = c.wing_c_l.tl1; c.wing_c_r.tl2 = c.wing_c_l.tl2; c.tail_a.l = rg(100,0,200); c.tail_a.a = rg(0,-PI/3,PI/3); c.tail_a.tl1 = rg(5,0,50); c.tail_a.tl2 = rg(10,0,50); c.tail_b.l = rg(100,0,200); c.tail_b.a = rg(0,-PI/3,PI/3); c.tail_b.tl1 = rg(5,0,50); c.tail_b.tl2 = rg(10,0,50); c.tail_c.l = rg(100,0,200); c.tail_c.a = rg(0,-PI/3,PI/3); c.pelvis.a = -PI+ atan2(c.spine_a.loc()[1]-c.pelvis.loc()[1],c.spine_a.loc()[0]-c.pelvis.loc()[0]); bonecol = new float[]{rtg(5,0,50),random(5,15),90}; fincol = new float[]{random(100),0,0,rg(5,0,50),0,0,random(30),random(0.2,1),0}; feathercol = new float[]{rtg(5,0,80),0,0,rg(5,0,50),0,0,random(60),random(0.5,1),0}; if (filltype == "fur"){ col = new float[]{rtg(5,0,30),2,10,random(10,50),-10,10,random(30),20,50}; }else if (filltype == "scale"){ col = new float[]{random(100),2,10,random(10,50),-10,10,random(20),10,10}; } fur = new float[]{rg(6,0,20),8,0,50000}; pat = new float[]{floor(random(4))}; } public void render(){ //c.drawbody(); if (maind(ctype) == 2){ winged = 1; } switch (maind(ctype)){ case 0: c.drawforeleg_r(filltype,col,fur,pat); c.drawhindleg_r(filltype,col,fur,pat); c.drawclaw(c.foreleg_r,c.forepaw_r,(clawsize+1)/2,clawbend,bonecol,dark(col),fur,pat); c.drawclaw(c.hindleg_r,c.hindpaw_r,(clawsize+1)/2,clawbend,bonecol,dark(col),fur,pat); if (sixleg == 1){ c.drawmidleg_r(filltype,col,fur,pat); c.drawclaw(c.midleg_r,c.midpaw_r,(clawsize+1)/2,clawbend,bonecol,dark(col),fur,pat); } break;case 1: c.drawfin_r(dark(fincol)); if (sixleg == 1){ c.drawmidfin_r(dark(fincol)); } break;case 2: c.drawhindleg_r("scale",col,fur,pat); c.drawhindlegnotail_l("scale",col,fur,pat); c.drawclaw(c.hindleg_r,c.hindpaw_r,clawsize,clawbend,bonecol,dark(col),fur,pat); c.drawclaw(c.hindleg_l,c.hindpaw_l,clawsize,clawbend,bonecol,col,fur,pat); break; } if (winged > 0.8){
      c.drawwing(c.wing_a_r,c.wing_b_r,c.wing_c_r,dark(feathercol));
    }

    switch (maind(ctype)){
      case 0:
        if (tailstyle == 1){
          c.drawhorsetail(dark(col));
          c.drawbacknotail(filltype,col,fur,pat);
          
        }else if (tailstyle == 2){
          c.drawfeatherfin(feathercol);
          c.drawbacknotail(filltype,col,fur,pat);
          
        }else{
          c.drawback(filltype,col, fur, pat);
          
        }break;
      case 1:
        c.drawfeatherfin(fincol);
        c.drawbacknotail(filltype,col,fur,pat);
        break; 
      case 2:
      
        if (random(1) > 0.5){
          c.drawfeathertail(feathercol);
        }else{
          c.drawfeatherfin(feathercol);
        }
        c.drawbacknotail(filltype,col,fur,pat);
        break;
       
    }
    c.drawbelly(filltype,col,fur,pat);

    if (random(1) <0.2){
      c.drawhorn(random(5,30),random(100,300),bonecol);
    }
    c.drawtongue(floor(random(2,12)));
    
    if (maind(ctype) == 2){
      c.drawbeak(bonecol);
      c.drawbirdhead(filltype,col,fur,pat);
    }else{
      
      c.drawhead(filltype,col,fur,pat);
      c.drawteeth(random(3,8),random(3,4),bonecol);
      if (random(1) <0.2){ c.drawantler(col); } if (filltype == "fur"){ c.drawear(random(0.1,0.3),random(20,120),new float[]{random(10),2,10,random(20),-10,10,random(50,80),20,50},col,fur,pat); } if (random(1)>0.5){
        c.drawtusk(random(3,10),random(3,20),random(3),random(3),bonecol);
      }
    }

    c.draweye(0.75,-c.head.tl1*random(0.1,0.8),random(15,40),new float[]{eyehue,random(30),random(80,100)},new float[]{eyehue,random(60),random(0,50)},col,fur,pat);
    if (winged > 0.8){
      c.drawwing(c.wing_a_l,c.wing_b_l,c.wing_c_l,feathercol);
    }


    switch (maind(ctype)){
      case 0: 
        c.drawforeleg_l(filltype,col,fur,pat);
        
        if (tailstyle == 0){
          c.drawhindleg_l(filltype,col,fur,pat);
        }else{
          c.drawhindlegnotail_l(filltype,col,fur,pat);
        }
        c.drawclaw(c.foreleg_l,c.forepaw_l,(clawsize+1)/2,clawbend,bonecol,col,fur,pat);
        c.drawclaw(c.hindleg_l,c.hindpaw_l,(clawsize+1)/2,clawbend,bonecol,col,fur,pat);
        
        if (sixleg == 1){
          c.drawmidleg_l(filltype,col,fur,pat);
          c.drawclaw(c.midleg_l,c.midpaw_l,(clawsize+1)/2,clawbend,bonecol,col,fur,pat);
        }
      break;case 1:
        c.drawfin_l(fincol);
        if (sixleg == 1){
          c.drawmidfin_l(fincol);
        }
      break;case 2:
        
      break;
    }

  }

}





float[] trans = new float[] {500,400};
Creature c = new Creature();
Generator g;
float[] bd;

void settings(){
  if(export){size(2000,2000);}
  else{size(800,800);}
}
void setup(){
  if(!export){noLoop();}
  colorMode(HSB,100,100,100);  
}
void draw(){
  //c.wing_c_l.a += PI/4;
  float flippy = choice(0,1);
  pushMatrix();
  g = new Generator();
  bd = new float[]{0,0,0,0};
  g.gen();
  if (flippy == 1){
    g.c.pelvis.flip();
  }
  g.c.pelvis.bounds(bd);
  if (flippy == 1){
    g.c.pelvis.flip();
  }
  background(18,4,100);
  
  float sc = min(width*0.7/(bd[1]-bd[0]),height*0.7/(bd[3]-bd[2]));
  translate(width/2-sc*(bd[1]-(bd[1]-bd[0])/2),height/2.2-sc*(bd[3]-(bd[3]-bd[2])/2));//width/2-bd[0]-(bd[1]-bd[0])/2,height/2+bd[2]+(bd[3]-bd[2])/2);
  scale(sc);
  scale(1-flippy*2,1);
  
  g.render();
  popMatrix();

  if(debug){stroke(0,100,100);g.c.pelvis.drawSkel();}
  if(export){saveFrame("render"+nf(day(),2)+nf(hour(),2)+nf(minute(),2)+nf(second(),2)+nf(millis(),3)+".png");}

}

Sketchbook

snip20161027_23 snip20161027_24

ngdon-FaceOSC

imageedit_1_4727480009
afejalwfejalewfjwalj

 

I observed an interesting fact that many people (including me) brandish their heads while playing games. For example, when they want to go left, they tilt their heads toward that direction in addition to pressing the left key on the keyboard. They also exhibit different facial expressions when they’re doing different actions in the game.

Therefore I thought that in order to know what the player wants to do in the game, we only need to look at his/her face, and the mouse and keypress input are in fact redundant.

I decided to implement this idea in a first person shooter game. In this world, everyone has no body, but only a face mounted on a machine controlled by it.

I used in P3D mode lots of rotates and translates to render the 3D effect. The map is generated using prim algorithm. The enemies are controlled by a simple AI, and wander around if the player is not near, and slowly try to turn and move toward the player and attack him if he comes into a certain range.

The gameplay is really an interesting experience. It is so intuitive that I almost feel effortless controlling my character. When using keyboard/mouse to play a game, no matter how proficient I am with the binding, I always have to go through the process of: “Enemy’s shooting at me->I need to dodge left->tell finger to press left key->finger press left key->character dodge left”. But controlling with face is very different:”Enemy’s shooting at me->my head automatically tilt left->character dodge left”. So happy.

I plan on making it into a multiplayer game, so that people can compete with each other over the internet and see who’s got the best facial muscles.

snip20161014_3

 

 

import oscP5.*;
OscP5 oscP5;
int found;
float[] rawarr;
float mouthWidth;
float mouthHeight;
float poseScale;
PVector orientation = new PVector();
PVector posePos = new PVector();
public void found(int i) {
    found = i;
}
public void rawData(float[] raw) {
    rawarr = raw;
}
public void mouthWidth(float i) {
    mouthWidth = i;
}
public void mouthHeight(float i) {
    mouthHeight = i;
}
public void orientation(float x, float y, float z) {
    orientation.set(x, y, z);
}
public void poseScale(float x) {
    poseScale = x;
}
public void posePos(float x, float y) {
    posePos.set(x, y);
}

int[][] mat = new int[64][64];
int dw = 20;

String multStr(String s, int n) {
    String ms = "";
    for (int i = 0; i < n; i++) {
        ms += s;
    }
    return ms;
}


public class Prim {
    int[][] walls;
    int[][] visited;
    int[][] wallist;
    int wlen = 0;
    int w;
    int h;
    public Prim(int w, int h) {
        this.w = w;
        this.h = h;
        walls = new int[w * h * 2][3];
        visited = new int[h][w];
        wallist = new int[w * h * 2 + 1][3];
    }
    public void addcellwalls(int j, int i) {
        addwall(j, i, 1);
        addwall(j, i, 2);
        addwall(j, (i + 1), 1);
        addwall((j + 1), i, 2);
    }
    public void addwall(int j, int i, int t) {
        wallist[wlen] = new int[] {
            j, i, t
        };
        wlen++;
    }

    public void delwall1(int j, int i, int t) {
        for (int k = 0; k < walls.length; k++) {
            if (walls[k][0] == j && walls[k][1] == i && walls[k][2] == t) {
                walls[k] = new int[] {
                    -1, -1, -1
                };
            }
        }
    }
    public void delwall2(int j, int i, int t) {
        for (int k = 0; k < wlen; k++) {
            if (wallist[k][0] == j && wallist[k][1] == i && wallist[k][2] == t) {
                for (int l = k; l < wlen; l++) {
                    wallist[l] = wallist[l + 1];
                }
                wlen -= 1;
            }
        }
    }

    public int[][] getadjcells(int j, int i, int t) {
        if (t == 1) {
            return new int[][] {
                {
                    j, i
                }, {
                    j, i - 1
                }
            };
        } else {
            return new int[][] {
                {
                    j, i
                }, {
                    j - 1, i
                }
            };
        }

    }
    public boolean isvisited(int j, int i) {
        if (i < 0 || j < 0 || i >= h || j >= w) {
            return true;
        }
        return visited[i][j] == 1;
    }

    public void gen() {
        for (int i = 0; i < h; i++) {
            for (int j = 0; j < w; j++) {
                walls[(i * w + j) * 2] = new int[] {
                    j, i, 1
                };
                walls[(i * w + j) * 2 + 1] = new int[] {
                    j, i, 2
                };
                visited[i][j] = 0;
            }
        }
        int[] o = new int[] {
            floor(random(w)), floor(random(h))
        };
        addcellwalls(o[0], o[1]);
        visited[o[1]][o[0]] = 1;
        int count = 0;
        while (wlen > 0 && count < 1000000) {
            count++;
            int i = floor(random(wlen));
            int[][] adjs = getadjcells(wallist[i][0], wallist[i][1], wallist[i][2]);

            if (isvisited(adjs[0][0], adjs[0][1]) != isvisited(adjs[1][0], adjs[1][1])) {
                int uv = isvisited(adjs[0][0], adjs[0][1]) ? 1 : 0;
                visited[adjs[uv][1]][adjs[uv][0]] = 1;
                addcellwalls(adjs[uv][0], adjs[uv][1]);
                delwall1(wallist[i][0], wallist[i][1], wallist[i][2]);
                delwall2(wallist[i][0], wallist[i][1], wallist[i][2]);
            } else {
                delwall2(wallist[i][0], wallist[i][1], wallist[i][2]);
            }

        }

    }

}


public class Bullet {
    float x;
    float y;
    float z;
    PVector forw;
    float spd = 1;
    float size = 1;
    float g = 0;
    float dec = 0.5;
    int typ = 1;
    int mast = 1;
    public Bullet(float x, float y, float z, PVector forw) {
        this.x = x;
        this.y = y;
        this.z = z;
        this.forw = forw;
    }
    public void update() {
        x += forw.x * spd;
        y += forw.y * spd + g;
        z += forw.z * spd;
        g += 0.01;
        if (typ == 2) {
            size = size * dec;
            if (size <= 0.001) {
                typ = 0;
            }
        }

    }
}

void drawelec(float r) {
    pg.beginShape();
    for (int i = 0; i < 22; i++) {
        pg.vertex(i * 0.1, noise(r, 0.1 * i, 0.5 * frameCount));
    }
    pg.endShape();

}

public class Enemy {
    float x;
    float y;
    PVector forw = new PVector(0, 0, 1);
    PVector fdir = new PVector(0, 0, 1);
    float spd = 0.2;
    int state = 1;
    int hp = 12;
    float fall = 0;
    String[] names = new String[] {
        "James", "John", "Robert", "Michael", "Mary",
        "William", "David", "Richard", "Charles", "Joseph", "Thomas", "Patricia",
        "Christopher", "Linda", "Barbara", "Daniel", "Paul", "Mark", "Elizabeth", "Donald"
    };
    String name = names[floor(random(names.length))];

    float[][] mockface = new float[][] {
        {
            0, 0
        }, {
            2, 1
        }, {
            5, 3
        }, {
            6, 10
        }, {
            4, 12.5
        }, {
            2, 12.5
        }, {
            0, 12
        }

    };
    int[][] mockmouth = new int[][] {
        {
            0, 1
        }, {
            1, 1
        }, {
            3, 4
        }, {
            1, 7
        }, {
            0, 7
        }

    };

    float[][] mockeye = new float[][] {
        {
            0.5, 10
        }, {
            2.5, 9
        }, {
            4.5, 10
        }, {
            2.5, 11
        }, {
            0.5, 10
        }

    };


    public Enemy(float x, float y) {
        this.x = x;
        this.y = y;


    }
    public void nav(Prim p) {
        if (state == 1 || state == 2) {
            fdir.lerp(forw, 0.1);
        }
        if (dist(x, y, px, py) < dw * 2 && state != 0) {
            state = 3;

        }


        if (state == 1) {
            x += forw.x * spd;
            y -= forw.z * spd;
            fdir.lerp(forw, 0.1);
            for (int k = 0; k < p.walls.length; k++) {

                if (p.walls[k][2] != -1) {
                    float wallx = p.walls[k][0] * dw;
                    float wally = p.walls[k][1] * dw;

                    if ((p.walls[k][2] == 1 && x >= wallx && x <= wallx + dw && y >= wally - 3 && y <= wally + 3) || (p.walls[k][2] == 2 && y >= wally && y <= wally + dw && x >= wallx - 3 && x <= wallx + 3)) {
                        x -= forw.x * spd * 2;
                        y += forw.z * spd * 2;
                        state = 2;

                    }
                }
            }
            if (random(1.0) < 0.005) {
                state = 2;
            }

        } else if (state == 2) {
            PVector v = new PVector(forw.x, forw.z);
            v.rotate(0.1);
            forw.x = v.x;
            forw.z = v.y;
            if (random(1.0) < 0.1) {
                state = 1;
            }
        } else if (state == 3) {
            x += forw.x * spd * 0.5;
            y -= forw.z * spd * 0.5;
            for (int k = 0; k < p.walls.length; k++) {

                if (p.walls[k][2] != -1) {
                    float wallx = p.walls[k][0] * dw;
                    float wally = p.walls[k][1] * dw;

                    if ((p.walls[k][2] == 1 && x >= wallx && x <= wallx + dw && y >= wally - 3 && y <= wally + 3) || (p.walls[k][2] == 2 && y >= wally && y <= wally + dw && x >= wallx - 3 && x <= wallx + 3)) {
                        x -= forw.x * spd * 2;
                        y += forw.z * spd * 2;

                    }
                }
            }
            PVector v = new PVector(-px + x, py - y);
            v.rotate(PI);
            fdir.lerp(new PVector(v.x, 0, v.y), 0.005);
            fdir.limit(1);
            forw.lerp(fdir, 0.1);

            PVector v2 = new PVector(-fdir.x, fdir.z);
            v2.rotate(PI);
            if (noise(0.5 * frameCount) > 0.65) {
                bullets[bl] = new Bullet(x, -1.5, y, new PVector(v2.x, 0, v2.y));
                bullets[bl].size = 0.6;
                bullets[bl].spd = 0.9;
                bullets[bl].mast = 2;
                bl++;
            }

            if (dist(x, y, px, py) > dw * 2) {
                state = 2;
            }
        }
        for (int i = 0; i < bl; i++) {
            if (bullets[i].mast == 1 && bullets[i].typ == 1 && state > 0) {

                if (dist(bullets[i].x, bullets[i].z, x, y) < 2) {

                    bullets[i].typ = 0;
                    hp -= 1;
                    for (int j = 0; j < 3; j++) {
                        bullets[bl] = new Bullet(bullets[i].x, bullets[i].y, bullets[i].z - 0.01, PVector.random3D());
                        bullets[bl].size = 0.8;
                        bullets[bl].spd = 0.4;
                        bullets[bl].typ = 2;
                        bullets[bl].dec = 0.8;
                        bl++;
                    }
                    if (hp <= 0) {
                        score += 100;
                        for (int j = 0; j < 10; j++) {
                            bullets[bl] = new Bullet(x, -3, y, PVector.random3D());
                            bullets[bl].size = 3;
                            bullets[bl].spd = 0.4;
                            bullets[bl].typ = 2;
                            bullets[bl].dec = 0.8;
                            bl++;
                        }
                    }
                }
            }
        }
        if (hp <= 0) {
            this.state = 0;
        }
    }


    public void drawenem() {
        pg.pushMatrix();
        pg.translate(x, 0, y);
        pg.rotateY(-PI / 2 + atan2(forw.z, forw.x));
        if (this.state == 0) {
            pg.translate(0, 7, 0);
            //rotateY(random(PI*2));
            pg.rotateX(-fall);
            if (fall < PI / 2) {
                fall += 0.1;
            }
            pg.translate(0, -7, 0);
            pg.stroke(100);
            pg.strokeWeight(2);
            pg.fill(100);
            pg.pushMatrix();
            pg.translate(0, 7, 0);
            pg.box(2.5, 2, 2.5);
            pg.translate(0, -3, 0);
            //pg.box(1.5,6,0.4);
            pg.translate(-1.1, -2, 0);
            pg.box(0.1, 9, 0.1);
            pg.translate(2.2, 0, 0);
            pg.box(0.1, 9, 0.1);
            pg.popMatrix();
            pg.fill(255);


        } else {
            pg.stroke(100);
            pg.strokeWeight(2);
            pg.fill(100);
            pg.pushMatrix();
            pg.translate(0, 7, 0);
            pg.box(2.5, 2, 2.5);
            pg.translate(0, -3, 0);
            //pg.box(1.5,6,0.4);
            pg.translate(-1.1, -2, 0);
            pg.box(0.1, 9, 0.1);
            pg.translate(2.2, 0, 0);
            pg.box(0.1, 9, 0.1);
            pg.popMatrix();
            pg.fill(255);

            pg.pushMatrix();
            pg.translate(0, -1.2, -0.5);
            //scale(0.2);
            pg.fill(100);
            pg.textSize(12);
            if (dist(x, y, px, py) < dw * 3) {
                pg.pushMatrix();
                pg.translate(0, -3, 0);
                pg.scale(0.05);
                pg.textAlign(CENTER);
                pg.textMode(SHAPE);
                pg.rotateY(PI);
                pg.text(name, 0, 0);
                pg.popMatrix();
            }
            pg.fill(255);
            pg.rotateY(PI / 2 - atan2(forw.z, forw.x));
            pg.rotateY(-PI / 2 + atan2(fdir.z, fdir.x));
            pg.rotateY(-PI / 8);
            pg.beginShape();

            for (int i = 0; i < mockface.length; i++) {

                pg.vertex(mockface[i][0] * 0.15, -mockface[i][1] * 0.15);
            }
            pg.endShape();
            pg.beginShape();
            for (int i = 0; i < mockmouth.length; i++) {

                pg.vertex(mockmouth[i][0] * 0.15, 0.15 * (-4 + (mockmouth[i][1] - 4) * noise(0.5 * frameCount)));
            }
            pg.endShape();
            pg.beginShape();
            for (int i = 0; i < mockeye.length; i++) {

                pg.vertex(mockeye[i][0] * 0.15, -mockeye[i][1] * 0.15);
            }
            pg.endShape();
            pg.rotateY(PI / 4);
            pg.beginShape();

            for (int i = mockface.length - 1; i >= 0; i--) {
                pg.vertex(-mockface[i][0] * 0.15, -mockface[i][1] * 0.15);
            }
            pg.endShape();
            pg.beginShape();
            for (int i = mockmouth.length - 1; i >= 0; i--) {
                pg.vertex(-mockmouth[i][0] * 0.15, 0.15 * (-4 + (mockmouth[i][1] - 4) * noise(0.5 * frameCount)));
            }
            pg.endShape();
            pg.beginShape();
            for (int i = mockeye.length - 1; i >= 0; i--) {
                pg.vertex(-mockeye[i][0] * 0.15, -mockeye[i][1] * 0.15);
            }
            pg.endShape();
            pg.popMatrix();

            pg.pushMatrix();
            pg.translate(-1.1, -2.2, 0);
            for (int i = 0; i < 2; i++) {
                drawelec(i);
            }
            pg.popMatrix();

            for (int i = 0; i < 3; i++) {
                pg.pushMatrix();
                pg.translate(-1.1, -2 + 8 * noise(i * 10, 0.1 * frameCount), 0);
                drawelec(i);
                pg.popMatrix();
            }

        }
        pg.popMatrix();
    }


}



Prim p = new Prim(16, 16);

float px = dw * 8.5;
float py = dw * 8.5;
PVector forward;
PVector left;
PVector thwart;
PVector movement;
Bullet[] bullets = new Bullet[1024];
int bl = 0;
float[] farr = new float[256];
Enemy[] enemies = new Enemy[256];
int el = 0;
PGraphics pg;
float health = 100;
int score = 0;
PFont tfont;
PFont dfont;
void setup() {
    health = 100;
    score = 0;
    tfont = createFont("OCR A Std", 18);
    dfont = createFont("Lucida Sans", 12);
    size(720, 576, P3D);
    pg = createGraphics(720, 576, P3D);
    frameRate(30);
    oscP5 = new OscP5(this, 8338);
    oscP5.plug(this, "found", "/found");
    oscP5.plug(this, "rawData", "/raw");
    oscP5.plug(this, "orientation", "/pose/orientation");
    oscP5.plug(this, "mouthWidth", "/gesture/mouth/width");
    oscP5.plug(this, "mouthHeight", "/gesture/mouth/height");
    oscP5.plug(this, "poseScale", "/pose/scale");
    oscP5.plug(this, "posePos", "/pose/position");
    p.gen();
    //mat = makeMaze(mat[0].length,mat.length);
    forward = new PVector(0, 1, 0);
    left = new PVector(0, 1, 0);
    for (int i = 0; i < 100; i++) {

        enemies[el] = new Enemy((floor(random(20)) + 0.5) * dw, (floor(random(20) + 0) + 0.5) * dw);
        while (dist(enemies[el].x, enemies[el].y, px, py) < 50) {

            enemies[el] = new Enemy((floor(random(20)) + 0.5) * dw, (floor(random(20) + 0) + 0.5) * dw);
        }


        el++;
    }

}

void draw() {
    if (found != 0 && health > 0) {
        left.x = forward.x;
        left.y = forward.y;
        left.z = forward.z;
        left.rotate(PI / 2);
        thwart = new PVector(1, 1, 1);

        pg.beginDraw();
        pg.pushMatrix();
        pg.background(240);
        //beginpg.camera();

        pg.camera(px, 0, py, px + forward.x, 0, py + forward.y, 0, 1, 0);
        if (keyPressed) {
            if (key == 'm') {
                pg.camera(px, -100, py, px + forward.x, 0, py + forward.y, 0, 1, 0);
            }
        }
        pg.frustum(-0.1, 0.1, -0.1, 0.1, 0.1, 200);
        pg.scale(1, 0.72 / 0.576, 1);
        pg.pushMatrix();
        pg.fill(255, 0, 0);
        pg.translate(px, 0, py);
        //pg.sphere(2);
        pg.popMatrix();

        pg.pushMatrix();
        pg.fill(255, 0, 0);
        pg.translate(px + forward.x * 3, 0, py + forward.y * 3);
        //pg.sphere(1);
        pg.popMatrix();

        pg.stroke(100);
        pg.strokeWeight(2);
        //pg.noStroke();
        //pg.translate(100,100);
        //pg.translate(-px,0,-py);
        //rotateY(frameCount*0.1);
        for (int i = 0; i < p.walls.length; i++) {
            pg.pushMatrix();
            if (p.walls[i][2] != -1) {
                float wallx = p.walls[i][0] * dw;
                float wally = p.walls[i][1] * dw;
                pg.translate(wallx, 0, wally);
                pg.fill(constrain(map(dist(wallx, wally, px, py), 0, 100, 255, 240), 240, 255));
                if (p.walls[i][2] == 1) {


                    if (px >= wallx && px <= wallx + dw && py >= wally - 2 && py <= wally + 2) {
                        //thwart.x = 0;
                        //thwart.y = 0;
                        px -= movement.x;
                        py -= movement.y;
                        //pg.fill(255,0,0);
                    }
                    pg.translate(dw / 2, 0, 0);
                    pg.box(dw, 16, 2);

                } else {

                    if (py >= wally && py <= wally + dw && px >= wallx - 2 && px <= wallx + 2) {
                        //thwart.x = 0;
                        //thwart.y = 0;
                        px -= movement.x;
                        py -= movement.y;
                        //pg.fill(255,0,255);
                    }
                    pg.translate(0, 0, dw / 2);
                    pg.box(2, 16, dw);
                }
            }
            pg.popMatrix();
        }
        for (int i = 0; i < bl; i++) {
            pg.pushMatrix();
            pg.translate(bullets[i].x, bullets[i].y, bullets[i].z);
            pg.fill(100);
            pg.noStroke();
            pg.sphere(bullets[i].size / 2 + bullets[i].size / 2 * noise(0.5 * i, 0.1 * frameCount));
            bullets[i].update();
            pg.popMatrix();
            if (bullets[i].typ == 1) {
                if (bullets[i].y > 8) {


                    for (int j = 0; j < 3; j++) {
                        bullets[bl] = new Bullet(bullets[i].x, bullets[i].y, bullets[i].z - 0.01, PVector.random3D());
                        bullets[bl].size = 0.8;
                        bullets[bl].spd = 0.4;
                        bullets[bl].typ = 2;
                        bullets[bl].dec = 0.8;
                        bl++;
                    }
                    bullets[i].typ = 0;
                }
                for (int k = 0; k < p.walls.length; k++) {

                    if (p.walls[k][2] != -1) {
                        float wallx = p.walls[k][0] * dw;
                        float wally = p.walls[k][1] * dw;
                        float bx = bullets[i].x;
                        float by = bullets[i].z;
                        if ((p.walls[k][2] == 1 && bx >= wallx && bx <= wallx + dw && by >= wally - 1 && by <= wally + 1) || (p.walls[k][2] == 2 && by >= wally && by <= wally + dw && bx >= wallx - 1 && bx <= wallx + 1)) {
                            for (int j = 0; j < 3; j++) {
                                bullets[bl] = new Bullet(bullets[i].x, bullets[i].y, bullets[i].z - 0.01, PVector.random3D());
                                bullets[bl].size = 0.8;
                                bullets[bl].spd = 0.4;
                                bullets[bl].typ = 2;
                                bullets[bl].dec = 0.8;
                                bl++;
                            }
                            bullets[i].typ = 0;


                        }
                    }
                }
                if (bullets[i].mast == 2 && dist(bullets[i].x, bullets[i].z, px, py) < 2) {
                    health -= 2;
                    bullets[i].typ = 0;
                }


            }


            if (bullets[i].typ == 0) {
                for (int j = i; j < bl; j++) {
                    bullets[j] = bullets[j + 1];
                }
                bl--;
            }
        }
        for (int i = 0; i < el; i++) {
            enemies[i].drawenem();
            enemies[i].nav(p);
        }

        pg.popMatrix();
        pg.endDraw();
        image(pg, 0, 0);
        noFill();

        for (int i = 0; i < rawarr.length; i++) {
            farr[i] = rawarr[i];
            if (i % 2 == 0) {
                farr[i] = (640 - farr[i]) * 720 / 640;
            }
            if (i % 2 == 1) {
                farr[i] = farr[i] * 576 / 480;
            }

        }

        stroke(150);
        line(width / 2 - 200, height / 2 - 200, width / 2 - 200, height);
        line(width / 2 + 200, height / 2 - 200, width / 2 + 200, height);
        pushMatrix();
        translate(width / 2 - 280, height / 2 - 200);

        beginShape();
        for (int i = 0; i < 57; i++) {
            //vertex(i*10,50*noise(0.1*i,0.5*frameCount));
        }
        endShape();

        popMatrix();
        beginShape();
        for (int i = 0; i < 34; i += 2) {
            vertex(farr[i], farr[i + 1]);
        }
        for (int i = 52; i > 32; i -= 2) {
            vertex(farr[i], farr[i + 1]);
        }
        endShape(CLOSE);
        beginShape();
        for (int i = 72; i < 84; i += 2) {
            vertex(farr[i], farr[i + 1]);
        }
        endShape(CLOSE);
        beginShape();
        for (int i = 84; i < 96; i += 2) {
            vertex(farr[i], farr[i + 1]);
        }
        endShape(CLOSE);


        beginShape();
        for (int i = 96; i < 110; i += 2) {
            vertex(farr[i], farr[i + 1]);
        }

        for (int i = 124; i > 118; i -= 2) {
            vertex(farr[i], farr[i + 1]);
        }
        endShape(CLOSE);
        beginShape();
        for (int i = 108; i < 118; i += 2) {
            vertex(farr[i], farr[i + 1]);
        }
        vertex(farr[96], farr[97]);
        for (int i = 130; i > 124; i -= 2) {
            vertex(farr[i], farr[i + 1]);
        }
        endShape(CLOSE);

        //println(mouthHeight);
        if (mouthHeight > 1.8) {
            bullets[bl] = new Bullet(px + forward.x, 0.8, py + forward.y, new PVector(forward.x, 0, forward.y));
            bullets[bl].size = 0.6;
            bl++;

            for (int i = 0; i < 5; i++) {
                bullets[bl] = new Bullet(px + forward.x * 0.2, 0.3, py + forward.y * 0.2, PVector.random3D());
                bullets[bl].size = 0.2;
                bullets[bl].spd = 0.08;
                bullets[bl].typ = 2;
                bl++;
            }
        }
        if (poseScale > 5.3) {
            px += 0.3 * forward.x * thwart.x;
            py += 0.3 * forward.y * thwart.y;
            movement = forward.copy();
        }
        if (poseScale < 4.7) {
            px -= 0.3 * forward.x * thwart.x;
            py -= 0.3 * forward.y * thwart.x;
            movement = forward.copy();
            movement.rotate(PI);
        }
        float roty = degrees(orientation.y);
        if (roty > 4) {
            forward.rotate(0.04 + 0.01 * (roty - 5));
        }
        if (roty < -4) {
            forward.rotate(-0.04 + 0.01 * (roty + 5));
        }

        if (posePos.x > 340) {

            px -= 0.3 * left.x;
            py -= 0.3 * left.y;
            movement = left.copy();
            movement.rotate(PI);
        }
        if (posePos.x < 300) {
            px += 0.3 * left.x;
            py += 0.3 * left.y;
            movement = left.copy();

        }
        println(health);
    }
    fill(150);
    noStroke();
    //textFont(tfont);
    textAlign(CENTER);
    textSize(16);

    textFont(dfont);
    //text("["+multStr(".",floor(100-health)/4)+multStr("|",floor(health/2))+multStr(".",floor(100-health)/4)+"]",width/2,100);
    text("[" + multStr("|", floor(health / 2)) + "]", width / 2, 550);
    textFont(tfont);
    text(score, width / 2, 50);
    //rect(0,0,health*7.2,4);
    if (health <= 0) {
        textAlign(CENTER);
        textFont(tfont);
        text("GAME OVER", width / 2, height / 2);
    }

}

ngdon-lookingoutwards04

Rafael Loazno’s “Please Empty Your Pockets”

link to the project
This installation consists of “a conveyor belt with a computerized scanner that records and accumulates everything that passes under it”.
I think it is a really clever use of a combination of scanner and screen, and the moment people remove their object and the image of their object is still there feels really magical, and interactive art is wonderful often just because of these magical moments.
Moreover the interaction looks really great. People not only interact with the piece, but also in some way interact with those who interacted with the piece before them, since they can see all the things that has ever been placed on the belt. And then they add their own contribution to this collection of things. It’s like leaving one’s own mark on a monument.
Visually the piece has a very special style that reminds me of pop art, since it consists of many small, colorful every day objects.
It also make me contemplate what does it mean for an object to be in a pocket. It needs to be small to fit in a pocket, and it is necessary for it to be quickly accessible. Seeing the all these contents of people’s pockets almost make me believe objects kept in pockets should belong to a category. Like dogs, cats and pocket objects.

ngdon-Clock-Feedback

In the feedback many people mentioned that they’re confused by the fifth digit in my clock. Suggestions such as separating the digits with semicolons are made, which I think is an option that is doable, but compromises simplicity. Another way I can think of is to remove the last digit altogether. However then people wouldn’t notice that this is a “special” clock since most of them don’t have the patience to wait a minute for it to change. It’s a struggle.

Tega said that the sticks should bounce on landing, and I think that’s a great idea. I can try it by simply tweaking some parameters.

I feel happy that people like my clock.

ngdon – Plot

Table of Contents

plot4-pic_hd

(This post is very long and boring. Please, just jump to the pictures.)

INSPIRATION

Idea

My project is partly inspired by ideas hidden in the traditional Chinese landscape paintings that have always been fascinating to me. In these paintings, the artist tries to create a spiritual world consisting of mountains and waters into which he can escape from the worldly values. Thus the painter never tries to copy a mountain from life, instead he paints the mountain as he envisions in his mind, which often resembles an amalgamation of many mountains he has in his memory. That way the rocks and trees in the painting don’t represent actual rocks and trees, and instead they speak a language of their own and tell of the inner state of the painter. These paintings are often immersed in a sense of stillness and solitude.

And this is my inspiration. The plotter, a machine, shall draw a landscape conceived in its mind, using the elements not to depict an actual scene, but only to tell about the deep sense of distantness and desolation in its heart, to show us the world to which it desperately wants to escape.

 

A painting by Ni Zan (1301–1374)

Visuals

The fact that the plotter draws only uniform width lines reminds me of the hatching technique in drawing. I am fascinated by the way how lines closely drawn to each other seem like grayscale when viewed at a distance and at the same time feel very delicate when looked at closely. More specifically, I was thinking of Piranesi, the 18th century Italian printmaker’s etchings of Rome in ruins.

The Roman Antiquities, T. 1, Plate XIV by Giovanni Battista Piranesi

 

I also want my plotter drawing to look as if it is done by a person, albeit an incredibly patient one. I want it to have the imperfections, the whims and variations that are usually present in drawings done by a human artist. Moreover, I want it to resemble the style of my own drawings, so that when people see the plot done, they would say: “That’s Ngdon’s plot.”

 

ec-pic_hd
One of my sketches, drawn with the very same pen that was to plot my plot.

 

These considerations led me to decide that line hatching and dots will be the major elements in my plotter drawing.

TECHNIQUE

The drawing is rendered with two major steps. In the first step, the program procedurally generates a photo-ish picture of the landscape, and in the second, translates it into a half-tone drawing to be plotted.

Shape of Terrain

snip20160929_11

The shape of the mountains is generated with 3 perlin noises added on top of each other. As you can see in the image above, the red line represents a perlin noise with very small steps between seeds yet multiplied by a huge coefficient. This determines the general shape of the whole mountain. The green noise has a larger steps between seeds and a smaller coefficient, thus gives the mountain a more detailed outline. The blue noise, with large steps between seeds and a tiny coefficient, adds the finest details to the shape.

snip20160929_12

Such process is repeated multiple times to generate many mountains. To create the illusion of depth and distance, the y coordinate offset from bottom of the screen is incremented with every mountain. The field in the foreground is just basically very low mountains.

 

Texture of Terrain

The texture of the terrain is also generated with a controlled Perlin noise to imitate a rocky and grainy surface. Near the peak of a mountain it would be “noisier” and at the foot of the mountain less so. Then, a second perlin noise, closely related to the noises that generated the shape of the mountain, is used to divide the terrain into highlights and shadows.

snip20160929_13

 

Generative Ruins

I generated some ancient ruins at the foot of the mountains with an algorithm similar to the Markov Chain. Each piece of the ruin, a column, a wall, etc. would guess who its neighbor shall be to its right and above. For example, a piece of floor would probably guess that next to it is also a piece of floor, and above it, a column. And that column also tries to guess who its neighbor shall be: may be another column, or may be a wall.

snip20160929_15

 

Edge Finding

The pixels in the generated scene are categorized based on their greyscale values. Each pixel is rated in a scale of 1 (very dark) to 20 (very bright), and the score is stored in an array. Then, a loop iterate through this array, and checks if a pixel has a different score than the one next to it, and if so, that pixel is an point on edge.

I originally wanted to use a more sophisticated algorithm such as Sobel, but tried this first as a test. Then I discovered that I quite like the result, so I clang on to the method.

snip20160929_17

Half Tone

I used horizontal lines to hatch my drawing. Hatching generally works as follows: the darker an area is, the more hatches it’s going to get. Therefore, a very dark area might get a horizontal hatch every other row of pixels, while a very bright area might only get one every five rows of pixels.
Therefore, for each edge point, the program checks if the brightness of that pixel dictates that a hatch shall be drawn on that row, and if so, a horizontal line will start from that edge point and extend to the right, until it meets another edge point.

RESULT

PDF Exports

90761360

358874848

831764864

The Final Plot

plot4-pic_hd

1afw1-pic

1fw3232-pic

THOUGHTS

Plotting my design was a complicated experience. Five minutes into plotting and the tip of my micron pen is already bent. An hour later the plotter started rocking back and fro as its weight shifted. Another hour later it refuse to plot on certain areas due to uneven height. While I was having dinner for some reason it decided to make a black smear on the paper.

However I found that it was these unpredictable events/accidents that made the final plot so enchanting, so more interesting than something printed out of a printer. The errors in small places and the variation in line quality look almost human. The dots exhibits a wide range of variations due to the bent in shape of the pen tip. All the struggles I had been having with the plotter while it was plotting, now turns out to the most exciting parts of the result.

As I watched the plotter draw the plot almost the whole time, it felt as if I myself did the drawing. Looking at it afterwards, I can recall all the details about it such as “When drawing that tree, I so thought that it’s going to screw up the whole piece, but it turns out it didn’t” or “It felt so nice drawing this outline of mountain”. This kind of recollection I had been only able to do with my own drawings.

So it becomes a very strange feeling. I instinctively believe that I had done the piece myself, but then I know I didn’t. It’s like a dream, a deja-vu.

drawing1-pic_hd

CODE

public class Piece {
  Piece right;
  Piece up;
  Piece down;
  Piece left;
  int w;
  int h;
  float l;
  int typ;
  int[] dat;
  
  int row;
  int col;
  public Piece(int typ){
    
    this.typ = typ;    
    this.l = random(1.0);
    if (typ == 0){
      this.w = 50; this.h = 20;
      
    }else if (typ == 1){
      this.w = 15; this.h = 120;

      this.l = ceil(random(2.0));
      if (this.l == 1){
        this.l = 1;
      }else{
        this.l = random(1.0);
      }
    }else if (typ == 2){
      this.w = 50; this.h = 30;
    }else if (typ == 3){
      this.w = 60; this.h = 30;
    }else if (typ == 4){
      this.w = 50; this.h = 120;
    }
    //grow();
  }
  
  public int[] locPiece(){
    if (left == null && down == null){
      return new int[] {0,0};
    }else if (left != null){
      return new int[] {left.locPiece()[0]+left.w, left.locPiece()[1]};
    }else{
      return new int[] {down.locPiece()[0], down.locPiece()[1]-down.h};
    }
    
  }
  public void gr(int n){
    //println(col);
    if (col < random(1.0)*20){
      right = new Piece(n);
      right.left = this;
      right.col = col + 1;
      right.row = row;
      right.grow();
    }
  }
  public void gu(int n){
    if (row < random(1.0)*4){ up = new Piece(n); up.down = this; up.col = col; up.row = row+1; up.grow(); } } public void grow(){ if (typ == 0){ gr(0); if (random(1.0)>0.5){
        gu(1);
      }else{
        gu(4);
      }
    }
    if (typ == 1){
      if (l == 1){
        if (random(1.0)>0.1){
          gu(2);
        }else{
          gu(3);
        }
      }
    }
    if (typ == 2){
      if (random(1.0)>0.2){
        gu(1);
      }else{
        gu(4);
      }
    }    

    
  }
  
  
  public void drawPiece(){
    int[] loc = locPiece();
    //println(loc);
    if (typ == 0){
      fill(200,210);//floor(random(2.0))*180+20);
      stroke(0);
      rect(loc[0]-5,loc[1]-h,w+10,h);

    } else if (typ == 1){
      int mseg = 5;
      
      int seg = ceil(l*mseg);
      pushMatrix();
      translate(10,0);
      for (int i = 0; i< seg; i++){
        noStroke();
        fill(220);
        rect(loc[0],loc[1]-(i+1)*h/mseg,w,h/mseg);
        for (int j = 0; j Lines=new ArrayList();

int[] Epts;

PImage img;


int[][][] Vmap;




void tree(float x,float y,float a,float o, float l, int depth){
  if (depth > 0){
    float x1 = x + l*cos(a-o);
    float y1 = y + l*sin(a-o);
    float x2 = x + l*cos(a+o);
    float y2 = y + l*sin(a+o);
    strokeWeight(1);
    stroke(random(1.0)*255);
    line(x,y,x1,y1);
    line(x,y,x2,y2);
    tree(x1,y1,a-o,o,l*0.6,depth-1);
    tree(x2,y2,a+o,o,l*0.6,depth-1);
  }
  
}



void tree2(float x, float y, float l, int depth){
  if (depth > 0){
    float x1 = x;
    float y1 = y-l;
    strokeWeight(1);
    stroke(0,0,0);
    tree(x1,y1,-PI,PI/16,l,2);
    tree(x1,y1,0,PI/16,l,2);
    line(x,y,x1,y1);
    tree2(x1,y1,l*0.8,depth-1);
  }
}



void noisefill(int[][][] vm){
  for (int i = 0; i < vm.length; i++){
    for (int j = 0; j < vm[i].length; j++){
      vm[i][j][0] = parseInt(noise(0.01*i,0.004*j)*255);
    }
  } 
}

void scanimg(int[][][] vm){
  //image(img,0,0);
  for (int i = 0; i < vm.length; i++){
    for (int j = 0; j < vm[i].length; j++){
      
      vm[i][j][0] = parseInt((red(get(j+dx,i+dy))+green(get(j+dx,i+dy))+blue(get(j+dx,i+dy)))/3);
    }
  }  
  
  
}


void genterr(int[][][] vm){
  for (int i = 450; i < vm.length*2; i+=50){
    float hl = 0;
    for (int j = 0; j < vm[0].length; j++){
      float nz0 = noise(0.005*i,0.001*j);
      float nz1 = noise(0.1*i,0.005*j);
      float nz2 = noise(0.1*i,0.05*j,200);
      //stroke(map(i,0,vm.length,0,255));
      //line(j,i,j,i-nz*100);
      float h;
      if (i < vm.length*0.9){
        h = nz0*500+nz1*200+nz2*20;
      }else if (i < vm.length*0.95){
        h = 0;
      }else{
        h = 200+nz0*200+nz1*40+nz2*4;
      }
      for (int k = 0; k < h; k++){ float nz3 = noise(0.1*i,0.1*j,0.1*k); if (k > 2.0*h*noise(0.005*i,0.001*(j+1000))){
          if (i < vm.length*0.9){
            fill(constrain(parseInt(map(k,h/2,h,0,300)*(0.9+0.1*((1-k/h)+(k/h)*nz3))*i/vm.length),0,255));
          }else{
            fill(constrain(parseInt(map(k,h/2,h,0,250)*(0.8+0.2*((1-k/h)+(k/h)*nz3))*i/vm.length),0,255));
            //fill(0,0);
          }
        }else{
          if (i < vm.length*0.9){ fill(constrain(parseInt(map(k,h/2,h,0,80)*(0.1+0.9*((1-k/h)+(k/h)*nz3))*i/vm.length),0,255)); }else{ fill(constrain(parseInt(map(k,h/2,h,0,120)*(0.1+0.9*((1-k/h)+(k/h)*nz3))*i/vm.length),0,255)); //fill(0,0); } } noStroke(); rect(j,i-k,1,1); if (random(1) > 0.99 && k < h*0.7 && k > h*0.5 && i < vm.length*0.8){ tree2(j,i-k,5+5*random(1.0),5); } } fill(200); if (random(1) > 0.5){
        rect(j,i-h,1,1);
        
      }
      if (random(1) > 0.9){
        //tree2(j,i-h*0.9,5+5*random(1.0),5);
      }
      
      
      hl = h;
    }
    for (int j = 0;  j < vm[0].length; j++){ float h = 400; if (random(1) > 0.9995 && i > vm.length*1.0 && i < vm.length*1.2){
        Piece p = new Piece(0);
        p.grow();
        pushMatrix();
        translate(j,i-h*random(0.7,0.9));
        scale(0.0+3*(i-vm.length*0.9)/(vm.length*1.1));

        p.drawPiece();
        popMatrix();
      }
    }
    
  }
  
}






void levelfygrc(int[][][] vm){
  for (int i = 0; i < vm.length; i++){
    for (int j = 0; j < vm[i].length; j++){
      vm[i][j][1] = max(parseInt(map(vm[i][j][0],0,255,1,20))*2,1);
      //println(vm[i][j][1]);
      //vm[i][j][1] = 2*parseInt(255/max(1,vm[i][j][0]));
      
    }
  }    
}


int[] getedgepts(int[][][] vm){
  int[] epts = new int[width*height*2];
  epts[0] = 1;
  for (int i = 0; i < vm.length; i++){
    for (int j = 0; j < vm[i].length; j++){
      if (j==0 || vm[i][j-1][1] != vm[i][j][1]){
        epts[epts[0]] = i;
        epts[epts[0]+1] = j;
        epts[0] += 2;
      }
    }
  }  
  return epts;
}


void shade(int[][][] vm, int[] epts){
  
  for (int i = 1; i < epts[0]; i+=2){
    if (epts[i]%vm[epts[i]][epts[i+1]][1]==0){
      int[] l = new int[4];
      l[1] = epts[i];
      l[0] = epts[i+1];
      for (int j = 0; j < width; j++){
        if (epts[i+1]+j==vm[0].length-1
            //||epts[i]+j==vm.length-1
            ||vm[epts[i]][epts[i+1]+j][1] != vm[epts[i]][epts[i+1]][1]
           ){
          l[3] = epts[i];
          l[2] = epts[i+1]+j;
          //stroke(0,255,0,200);
          //line(l[0],l[1],l[2],l[3]);
          float d = dist(l[0],l[1],l[2],l[3]);
          int[] w = wiggle(l,max(parseInt(0.1*d),1),parseInt(vm[epts[i]][epts[i+1]][1]/2));
          for (int k = 2; k < w.length-1; k+=2){ int[] nl = new int[4]; nl[0] = w[k-2]; nl[1] = w[k-1]; nl[2] = w[k]; nl[3] = w[k+1]; if (random(10) > 0.0001*pow(vm[epts[i]][epts[i+1]][1],3)){
              //Lines.add(nl);
            }
          }
          
          Lines.add(l);
          break;
        }
      }
    }
    
  }
  
}

int[] wiggle(int[] l, int p1, int p2){
  //int d = parseInt(dist(l[0],l[1],l[2],l[3])/p1);
  if (p2 == width){p2 = 0;}
  int[] ls = new int[p1*2+2];
  for (int i = 0; i < p1+1; i++){
    ls[i*2] = l[0] + i*(l[2]-l[0])/p1;
    ls[i*2+1] = l[1] + i*(l[3]-l[1])/p1;
    
    ls[i*2] += parseInt(noise(0.1*ls[i*2]/p1,ls[i*2+1],10)*p2-p2/2);
    ls[i*2+1] += parseInt(noise(0.1*ls[i*2]/p1,ls[i*2+1],100)*p2-p2/2);
  }
  
  return ls;
  
}

int dx = 50;
int dy = 40;

import processing.pdf.*;
boolean bRecordingPDF;
int pdfOutputCount = 0; 

void setup(){
  size(1060,820);
  background(0);
  

  Vmap = new int[height-100][width-100][2];
  //noisefill(Vmap);
  translate(dx,dy);
  genterr(Vmap);

  scanimg(Vmap);

  levelfygrc(Vmap);
  Epts = getedgepts(Vmap);

  shade( Vmap,Epts);
  noLoop();
  
}

void draw(){
  beginRecord(PDF, ""+parseInt(floor(random(1000000000)))+".pdf");
  
  background(255);

  translate(dx,dy);

  
  for (int i = 1; i < Epts[0]; i+=2){
   stroke(0);
   strokeWeight(1);
   noFill();
   line(Epts[i+1],Epts[i],Epts[i+1],Epts[i]);
    
  }


  println(Lines.size());
  for (int i = 0; i < Lines.size(); i++){
    
    stroke(0);
    strokeWeight(1);
    line(Lines.get(i)[0],Lines.get(i)[1],Lines.get(i)[2],Lines.get(i)[3]);
  }

  endRecord();
  
}

SKETCHES

afweaf7-pic

P.S. Sincere apologies to those who couldn't use the plotter on Wednesday night because my plot was taking so long.

ngdon-LookingOutwards03

Work: Tom Beddard’s Aurullia

links: http://sub.blue/aurulliahttp://sub.blue/fractal-lab

I admire the stunning beauty of the visuals and the incredible amount of details in Tom Beddard’s Aurullia. It cleverly make use of maths and fractals, yet it feels fine tuned and distinguish itself from many other fractal art that looks like math demos. This way of concealing the technology in the piece is what I always liked. It also make me wonder about the details of its implementation.

The algorithm is based on fractals, using a type of Mandelbox called Mandalay. The use of subtle colors and how the parameters are tweaked to have the effect reveal the artist’s aesthetics.

The work has effective complexity on multiple levels: the terrain is random, yet the architecture on it seems to be constructed by intelligent lifeforms. Much of the architecture is orderly, yet there are small variations between them. The artist can control the general look, but much is generative.

ngdon-Reading03

Question 1A

I think a bottle of water and a sack of bricks are two perfect examples of effective complexity. The bottle is mass manufactured, looking the same as any other bottle of the same brand, yet the water in it is flows around in chaotic behavior; And the reverse is true with a sack of bricks.

Question 1B
I think the problem of authorship is a very interesting conflict. I’m always thinking about whether I or my computer is the artist, and even worse, what if I write a program that writes programs to make generative art, then which of us is the artist? I think one way of arguing is that the artist not the one who made the piece, but the one who found it, the first person/machine/thing to say “This is art.”

ngdon-animatedloop

trippytorus

https://github.com/LingDong-/60-212/tree/master/animatedloop

ngdon-animatedloop

The vision come to me suddenly; I didn’t know why a torus, why flying tapes, why in these psychedelic colors, but I just knew I want to make it.

As a 3D animation with thousands of moving parts is quite complicated, I tried to first break down the problem into small parts before solving it. A torus is a circle of circles, and a tape is mainly defined by a series of coordinates. So first I made a series of points orbit around a common center, which is easy. Than I generated a bunch of these orbiting points around a circle, and render tapes based on the coordinates of all the points.

The result looks very similar to what I envisioned. I believe I used the transformation matrix effectively so that I achieved the effect in relatively concise code. However some parts of the image has anti-alias while other parts don’t, which is a bit weird if looked at carefully.

I thought about how I can go further: Perhaps I can create a series of animation, in each of them colorful flying tapes form into a different shape: a sphere, a cylinder, a person, etc and form a style.

photo-on-9-22-16-at-6-06-pm

ngdon-interruptions

Interactive Interruptions
Click and drag to create interruptions

First I tried to figure out how the piece was possibly made. There are strokes distributed quite evenly across the canvas yet they each have a random direction. At several places, there are holes or blank spaces were there are less or no strokes.

I thought it would be fun to make the interruptions piece interactive where the user can click and drag to make the “holes” at places they want. I did this by giving physical properties to all the strokes and manipulate the forces exerted on them.

I tweaked the relationship between the distance a stroke is from the mouse and the force exerted on it so that the holes look more like those in the original piece.

I’m satisfied with the result.

https://github.com/LingDong-/60-212/tree/master/interruptions

ngdon-clock

ngdonclock

Here’s the fullscreen version

After staring at a digital clock for an extended period of time, I began to feel that the clock was dwelled by stick-like creatures who were enslaved to work endlessly in the confined rectangle of the display. And that was my inspiration.

I considered my goals. I want it to be playful and interesting, to be something that people will stare at for time longer than they should have at a clock, yet still utilitarian like a traditional digital clock, with which people can know the time immediately after a single glance. My clock can be placed at an airport or a train station, where both a large and clear clock and something to make waiting less boring is needed.

And then I started building everything from scratch. I wrote physics for the sticks, created a system for managing and animating them, etc.

ngdon_clocksketch

Afterwards I thought about the future of my project: These stick-like creatures will start to goof off when they assume nobody’s watching them. They will play with each other. And when the user comes back, the creatures will rush back into position.

I wrote my own physics engine for the sticks so that they behave like real sticks. In the future I might refine that engine into a separate project, perhaps called Stick2D, as an alternative to Box2D.

https://github.com/LingDong-/60-212/tree/master/clock

ngdon-lookingoutwards02

My huge interest in generative things, especially generative landscapes started years ago when I first saw Minecraft on somebody else’s computer. What amazed me was not only that the landscape is realistic and beautiful, but also that the maps were infinite, and every time a world is generated it was different from any other.
I spend hours traveling in these worlds pondering how they are generated. The style of my own work also started changing. I was beginning to use less and less pre-rendered images until one day I decided to have everything in a project procedurally generated.
The project was initially developed by a single person named Markus “Notch” Persson in java, and later by his team. This fact also convinced me how much a single person with a computer can do.
It is said that Notch was inspired by other generative games such as “Dwarf Fortress”.
I believe this project, among many others, may point to a very interesting future. What will we be able to generate? Will we be able to tell if something is real or generated?


Official site

Ngdon-FirstWordLastWord

The technology is ever advancing, and the tons of artistic possibilities are being opened by new medias. But I believe that whether it is for first or last word art, the creation process always essentially a remix of old things. For we are limited by the fact that we’re human beings, and what we care about are fundamentally the same things: life, death, emotions, etc.. We rearranges them, wrap them in a new form, give them some new colors, throw them in a new context, etc. but its always the same bits that make them up.

I am interested in both first and last word art. First word art excites me and inspires me, while last word art makes me truly enjoy and admire. I sometimes try to create things that are completely new, yet find myself resonating something ancient. Sometimes I try to work with an old medium, but some random new things popup in my work.

Ngdon-LookingOutwards-01

– Gene Kogan. He is a programmer and artist interested in generative systems, artificial intelligence, and software enabling self-expression and creativity. The talk is mainly about machine learning and neural networks. He teaches a course called ml4a(machine learning for artists) that helps artist utilize machine learning in their artwork.

– I most admire his work on teaching people about machine learning. It really helps bringing more people into this wonderful realm, and opens up possibilities for non scientists. Also his algorithm of creating handwritten characters is very interesting to me.

– One sentence that struck me is when he said “If you’re ever in a hurry to learn something, sign up to teach it”. I found it insightful and makes me think about how learning really works.

– He presents by showcasing and explaining projects he made. The images and videos he shows are really impressive, but I still do not fully understand the technology behind them after listening to his explanation. Maybe he does this deliberately to lure listeners to his full-length courses.

– Link: http://www.genekogan.com/about.html