Processing 3D

You are already familiar with Processing. The discussion here present advanced features of Processing, explaining how the 3D modeling is currently done in Processing and outline the different approach with the library ANAR+ in the second part.

Examples

Basics

Yet Another very simple sketch

We will start with a very simple sketch.

void setup(){
 size(800,400); 
}
 
void draw(){
  background(255);
  fill(255,0,0);
  for(int i=0; i<30; i++)
    rect(i*15,i*10,10,40);
}

Which give something like this:

anar.ch_img_introduction_intro02.jpg

  1. size(width,height); initialize a new window
  2. fill(255,0,0); tells processing to paint the shapes in red
  3. for loop repeat 30 times the next instruction
  4. rect(xPosition,yPosition,with,height); draw a rectangle shape

Processing Renderer

Processing have different types of windows. The default window is a 2D window. Processing have different ways to display elements depending on the type of window you choose. You could choose a different type of window by giving an extra value when you initialize a window.

size(800,400);
size(800,400,JAVA2D);
size(800,400,P2D);
size(800,400,P3D);
size(800,400,OPENGL);
size(800,400,PDF);

For 3D, we will use mainly OPENGL, but P3D will works fine as well. Both are different and display things slightly differently (the shadows an colors are not the same, lines thickness). The difference between both is that OPENGL is using accelerated graphics to display the shapes on the screen. Since OPENGL is implemented differently on each systems, the results may change on every systems. P3D,P2D,Java2D doesn't require any external libraries, but OPENGL does (PDF as well).

So we could modify our sketch for this:

import processing.opengl.*;
 
void setup(){
 size(800,400,OPENGL); 
}
 
void draw(){
  background(255);
  fill(255,0,0);
  for(int i=0; i<30; i++)
    rect(i*mouseX/10f,i*10,10,40);
}

anar.ch_img_introduction_intro03.jpg

I've added also mouseX for interactivity, otherwise, it's too boring. The result looks very similar to the previous and everything looks planar.

Camera

The next example introduce the camera. The method is asking for 9 parameters: camera(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ) where eyeXYZ is the camera position and centerXYZ is the target position (where you lookAt). upXYZ could be used for different kind of projections, but there's two methods that make it easier:

void draw(){
  background(255);
  camera(mouseX, mouseY, 100, mouseX/10f, mouseY/10f, 0.0, 0.0, 1.0, 0.0);
  fill(255,0,0);
  for(int i=0; i<30; i++)
    rect(i*15,i*10,10,40);
}

anar.ch_img_introduction_intro04.jpg

Alternative Cameras

With the camera() method, you could do almost evrything with it, but it is still not very intuitive for everyone. Hopefully, someone (thanks Kristian!) wrote an extended library for it. You could install the OCD camera library the say way you did it with ANAR+ (copy the library within your sketchFolder/libraries folder. You need to restart Processing after the installation. When correctly installed, the library should be in you MENU>SKETCH>IMPORT LIBRARY>OCD. You may also have a look at the extended set of libraries for processing

import damkjer.ocd.*;
import processing.opengl.*;
 
Camera camera;
 
void setup(){
 size(800,400,OPENGL);
 camera = new Camera(this, 100, -125, 150); //Initial position of the camera
}
 
void draw(){
  background(255);
  camera.pan((mouseX-width/2)/1000f);
  camera.roll((mouseY-height/2)/1000f);
  camera.feed();
  fill(255,0,0);
  for(int i=0; i<30; i++)
    rect(i*15,i*10,10,40);
}

anar.ch_img_introduction_intro05.jpg

One of our brilliant student last year took the time to explain more in detail how to use it with ANAR+:

Push & Pop Transformations

From now, we will keep the camera in a fixed position and we will see how to move objects in a scene. This is done through pushMatrix() and popMatrix(). We won't explain into too much details why they came with this technique while this is a long story computer graphics involving math and matrices. But you might retain one thing, this is the form commonly used in computer graphics, probably due to OPENGL which is also using a similar form.

The camera will be fixed with camera(width/2,height,400,width/2,height/2,0, 0.0, 1.0, 0.0);. It gives a kind of perspective on the scene. To better view the effect, we will draw a set of lines.

import processing.opengl.*;
 
void setup(){
 size(800,400,OPENGL);
}
 
void draw(){
  background(255);
  camera(width/2,height,400,width/2,height/2,0, 0.0, 1.0, 0.0);
 
  stroke(200);  
  for(int i=0; i<80; i++)
    line(i*10,0,i*10,height);  
 
  pushMatrix();
 
  //Your transformations here
  translate(0,0,mouseY);
  rotateY(mouseX/(float)width*2*PI); //In radians
 
  fill(255,0,0);
  for(int i=0; i<30; i++)
    rect(i*15,i*10,10,40);
 
  popMatrix();
 
}

anar.ch_img_introduction_intro06.jpg

Here, you could see push() pop() as a loop where everything drawn within this loop is transformed (here with translate and rotate). The transformation must be written before the object is drawn and the order of the transformation is important. You may try to modify the code to see the effects of the permutations. Note, angles in computer graphics are usually done in radians. You may write a function that convert every angles in degree into radians:

float degreeToRadians(float v){
  return v/360 * 2 * PI;
}

and then change

rotateY(degreeToRadians(30)); //In degree

Usually, in programming, people use mainly radians, but as you see, it could be easily converted.

3D Objects

At this point, you may wonder how to create 3D forms. We will describe a general way to generate forms. Let's say that we want to create a cube. In processing, it's easy, you may use

box(size);

When you have a closer look at the processing references, you may discover that you only have two 3D primitives box & sphere:

Processing provides very basic functions and it is expected that you build your own more complex 3D forms from those primitives types.

Let's say that we want a cube, but we won't use box(). We could create each faces and translate|rotate the faces to the right position.

void draw(){
  background(255);
  camera(width/2.1,height,400,width/2,height/2,0, 0.0, 1.0, 0.0);
 
  stroke(200);  
  for(int i=0; i<80; i++)
    line(i*10,0,i*10,height);  
 
  stroke(0); 
  fill(255,0,0,100);
 
  float boxSize = mouseX;
 
  //first face
  rect(0,0,boxSize,boxSize);
 
  //second face
  pushMatrix();
  rotateX(PI/2);
  rect(0,0,boxSize,boxSize);
  popMatrix();
 
  //third face
  pushMatrix();
  rotateY(-PI/2);
  rect(0,0,boxSize,boxSize);
  popMatrix();  
 
  //fourth face
  pushMatrix();
  translate(boxSize,0,0);
  rotateY(-PI/2);
  rect(0,0,boxSize,boxSize);
  popMatrix();
 
  //... I'm bored...
 
}

anar.ch_img_introduction_intro07.jpg

Writing your own methods

We could write our own function making a cube. This way, it will be easier to reuse our code. Let's say that we have this method called lazyBox()

import processing.opengl.*;
 
void setup(){
  size(800,400,OPENGL);
}
 
void draw(){
  background(255);
  camera(width/2.1,height,400,width/2,height/2,0, 0.0, 1.0, 0.0);
 
  grid();
  lazyBox();
 
  for(int i=0; i<10; i++)
  {
    pushMatrix();
    translate(i*mouseY,i*mouseY,0);
    rotateZ(-i/5f);
    lazyBox();
    popMatrix(); 
  }
 
}
 
void lazyBox(){
  stroke(0); 
  fill(255,0,0,100);
 
  float boxSize = mouseX;
 
  //first face
  rect(0,0,boxSize,boxSize);
 
  //second face
  pushMatrix();
  rotateX(PI/2);
  rect(0,0,boxSize,boxSize);
  popMatrix();
 
  //third face
  pushMatrix();
  rotateY(-PI/2);
  rect(0,0,boxSize,boxSize);
  popMatrix();  
 
  //fourth face
  pushMatrix();
  translate(boxSize,0,0);
  rotateY(-PI/2);
  rect(0,0,boxSize,boxSize);
  popMatrix();
 
  //... I'm bored...
}
 
void grid(){
  stroke(200);  
  for(int i=0; i<80; i++)
    line(i*10,0,i*10,height);   
}

anar.ch_img_introduction_intro07lazybox.jpg

I started to split my code into sections. We now have lazyBox() which could be called just like box(). Note that I could use a push() pop() nested. I could manage to get parts which are transformed generally (here all the faces are moved in one block and then rotated).

As I'm a good boy, I also wrote the method grid().

In fact, the method box() in processing is very similar to what we are doing here. Instead of using mouseX we could define a parameter to get different box sizes:

void draw(){
  background(255);
  camera(width/2,height,400,width/2,height/2,0, 0.0, 1.0, 0.0);
 
  grid();
 
  for(int i=0; i<10; i++)
  {
    pushMatrix();
    translate(i*mouseY,i*mouseY,0);
    rotateZ(-i/5f);
    lazyBox(i*10);
    popMatrix(); 
  }
 
}
 
void lazyBox(float boxSize){
  stroke(0); 
  fill(255,0,0,100);
 
  //first face
  rect(0,0,boxSize,boxSize);
 
  //second face
  pushMatrix();
  rotateX(PI/2);
  rect(0,0,boxSize,boxSize);
  popMatrix();
 
  //third face
  pushMatrix();
  rotateY(-PI/2);
  rect(0,0,boxSize,boxSize);
  popMatrix();  
 
  //fourth face
  pushMatrix();
  translate(boxSize,0,0);
  rotateY(-PI/2);
  rect(0,0,boxSize,boxSize);
  popMatrix();
 
  //... I'm bored...
}

anar.ch_img_introduction_intro08.jpg

Extreme 3D using vertex

There's a different way to draw a cube. This way is more general and is more similar to the way it's done in computer graphics. Let's say that we don't have the method rect() and we want to create a cube by defining all individual points. We could use beginShape().

import processing.opengl.*;
 
void setup(){
  size(800,400,OPENGL);
}
 
void draw(){
  background(255);
  camera(width/2,height,400,width/2,height/2,0, 0.0, 1.0, 0.0);
 
  grid();
 
  pushMatrix();
  translate(width/2f,height/2f,0);
  lazyBox(mouseX,mouseY,frameCount%500);
  popMatrix(); 
}
 
void lazyBox(float boxWidth,float boxLength, float boxHeight){
  stroke(0); 
  fill(255,0,0,100);
 
  //first face
  beginShape();
  vertex(0, 0, 0);
  vertex(boxWidth, 0, 0);
  vertex(boxWidth, boxLength, 0);
  vertex(0, boxLength, 0);
  endShape(CLOSE);
 
  //second face
  beginShape();
  vertex(0, 0, 0);
  vertex(boxWidth, 0, 0);
  vertex(boxWidth, 0, boxHeight);
  vertex(0, 0, boxHeight);
  endShape(CLOSE);  
 
  //third face
  beginShape();
  vertex(boxWidth, 0, 0);
  vertex(boxWidth, boxLength, 0);
  vertex(boxWidth, boxLength, boxHeight);
  vertex(boxWidth, 0, boxHeight);
  endShape(CLOSE);   
 
  //I'm even more bored... 
 
}
 
void grid(){
  stroke(200);  
  for(int i=0; i<80; i++)
    line(i*10,0,i*10,height);   
}

anar.ch_img_introduction_intro09.jpg

Here, we start to use vertex, which is a point expressed in X Y Z. There's many ways to define a point. We already saw vertex. Vertex is commonly used by OPENGL.

Points with Arrays

The points are defined with an multi dimensional array of floats.

float[][] pt = new float[10][3];
 
pt[0][0] = 1; //x of pt 0
pt[0][1] = 3; //y of pt 0
pt[0][2] = 5; //z of pt 0
 
pt[1][0] = 2; //x of pt 1
pt[1][1] = 4; //y of pt 1
pt[1][2] = 6; //z of pt 1
 
pt[2][0] = 2; //x of pt 2
pt[2][1] = 4; //y of pt 2
//... so on...

Points with class

You may create a class containning 3 fields (x y z). In this case you could name them or use them within an array. Note, class in JAVA is a large concept. This is the base of Object-Oriented approach.

class Pt{
 float x,y,z;
 
 Pt(float a, float b, float c){
  x = a;
  y = b;
  z = c; 
 }
}
 
void doThis(){
 Pt myPt = new Pt(0,1,2);
 println(myPt.x); //returns 0
 
 Pt[] myArray = new Pt[10];
 myArray[0] = new Pt(4,5,6);
 println(myArray[0].x); //returns 4
}

Points with PVector

At the time we started to work on ANAR+, PVector wasn't implemented yet. This is a new object.

PVector myPt1 = new PVector(0,1,2);
PVector myPt2 = new PVector(3,4,5);
 
println(myPt1.y); //returns 1.0
println(myPt2.z); //returns 5.0
 
myPt1.add(myPt2);
 
println(myPt1.z); //returns 7.0
println(myPt1); //returns [ 3.0, 5.0, 7.0 ]

Points with anar.Vertex

As said previously, PVector wasn't there when we started to write ANAR+. So you could find yet another implementation of a point.

import anar.*;
 
void setup(){
  size(800,400);
  Vertex v1 = new Vertex(0,1,2);
  Vertex v2 = new Vertex(3,4,5);
 
  println(v1);    //x(0.0) y(1.0) z(2.0)
  println(v2.z);  //5.0
 
  Vertex v3 = v1.multiply(v2);
  println(v1);    //x(0.0) y(1.0) z(2.0)
  println(v3);    //x(0.0) y(4.0) z(10.0)
 
  Vertex v4 = v3.rotateZ(10);
  println(v4);    //x(2.1760845) y(-3.356286) z(10.0)
}

You may notice the difference with the previous example (with PVector) where the operation on myPt1 change the result of the vector. In anar.Vertex, the result doesn't modify the result of v1. In ANAR+, operations doesn't have side effects on the object(vector).

Also, in ANAR+, you could call geometric transformations on Vertex objects such as translate(), scale(), rotateX().

Points with Pt.create()

To finish, in ANAR+, we have also a last form of points. This is the form that you are the most likely to use. The difference with the previous (anar.Vertex) is that anar.Pt (created with Pt.create()) is parametric (in the sense of parametric modeling). This is the basis of a behavior called a Transmigration Operation.

import anar.*;
 
void setup(){
  size(800,400);
  Pt a = Anar.Pt(0,1,2);
  Pt b = Anar.Pt(3,4,5);
 
  println(a.x());    //0.0
  println(b);        //x(03.000f) y(04.000f) z(05.000f)null
 
  a.rotateZ(3);
  println(a);        //x(-00.141f) y(-00.990f) z(02.000f)null
 
  a.tag("superName");
  println(a);        //x(-00.141f) y(-00.990f) z(02.000f)superName
}

Here, we just show a basic use of Pt. You could see also how you could give names to each points.

We will see more in detail how Transmigration Operations are done in ANAR+, scenegraph, and primitives. (see: parametric for a more detailed tutorial).

ANAR+

As you will see, in ANAR+, the things is are done very differently than in processing. We outline here some changes between both languages, this should help to give an overview of how to find an equivalent between processing and ANAR+.

Comparaison

Processing ANAR+
Point
PVector p1 = new PVector(0,1,2);
Pt p1 = Anar.Pt(0,1,2);
Lines
stroke(255,0,0);
line(0,1,2,4,5,6);
line(4,5,6,7,8,9);
line(7,8,9,10,11,12);
Pt a = Anar.Pt(0,1,2);
Pt b = Anar.Pt(4,5,6);
Pt c = Anar.Pt(7,8,9);
Pt c = Anar.Pt(10,11,12);
Pts line1 = new Pts(a,b,c,d).stroke(255,0,0);
Face
fill(255,0,0);
beginShape();
vertex(0, 0, 0);
vertex(100, 0, 0);
vertex(100, 100, 0);
vertex(0, 100, 0);
endShape(CLOSE); 
Anar.Pt(0, 0, 0);
Anar.Pt(100, 0, 0);
Anar.Pt(100, 100, 0);
Anar.Pt(0, 100, 0);
Face face1 = new Face(a,b,c,d).fill(255,0,0);
3D Forms
void myBox(float boxWidth,float boxLength, float boxHeight){
  stroke(0); 
  fill(255,0,0,100);
 
  //first face
  beginShape();
  vertex(0, 0, 0);
  vertex(boxWidth, 0, 0);
  vertex(boxWidth, boxLength, 0);
  vertex(0, boxLength, 0);
  endShape(CLOSE);
 
  //second face
  beginShape();
  vertex(0, 0, 0);
  vertex(boxWidth, 0, 0);
  vertex(boxWidth, 0, boxHeight);
  vertex(0, 0, boxHeight);
  endShape(CLOSE);  
 
  //... 
 
}
Anar.Pt(0, 0, 0);
Anar.Pt(boxWidth, 0, 0);
Anar.Pt(boxWidth, boxLength, 0);
Anar.Pt(0, boxLength, 0);
Face face1 = new Face(a,b,c,d);
Obj myBox = face1.extrude(boxHeight).fill(255,0,0,100).stroke(0);
Transform
pushMatrix();
translate(100,0,0);
rotateZ(1.5);
scale(1,1,3);
myBox(100,100,100);
popMatrix();
myBox.translateX(100).rotateZ(1.5).scaleZ(3);
Export
import processing.dxf.*;
 
void setup() {
  size(500, 500, P3D);
}
 
void draw() {
  beginRaw(DXF, "output.dxf");
 
  myBox(100,100,100);
 
  if (record) {
    endRaw();
    record = false;
  }
}
RhinoScript.export(myBox,"output");
Distance
pushMatrix();
translate(0,100,0);
rotateZ(1.3);
float x1 = modelX(0, 0, 0);
float y1 = modelY(0, 0, 0);
float z1 = modelZ(0, 0, 0);
popMatrix();
 
pushMatrix();
translate(100,0,0);
rotateY(2.1);
float x2 = modelX(0, 0, 0);
float y2 = modelY(0, 0, 0);
float z2 = modelZ(0, 0, 0);
popMatrix();
 
dist(x1, y1, z1, x2, y2, z2);
Pt a = new Anar.Pt().translateY(100).rotateZ(1.3);
Pt b = new Anar.Pt().translateX(100).rotateY(2.1);
a.length(b);

Scenegraph

One major difference between processing and ANAR+ is the scenegraph. In processing, where the most part of your code is in the draw() loop, in ANAR+, almost everything is done in setup(). All you will find in draw() is usually a Obj.draw() invocation. This has to do with the structure of the scenegraph where the most parts of your structure is predetermined and calculated only once. In processing, in fact, everything is recalculated for each frame, not in ANAR+. Also, you define colors (fill and stroke) per objects.

import processing.opengl.*;
import anar.*;
 
Obj myBox1,myBox2;
 
void setup(){
  size(800,400,OPENGL);
  Anar.init(this);
 
  myBox1 = new Extrude(new Rect(10,20),20).fill(255,0,0,100);
  myBox2 = new Extrude(new Rect(10,20),20).fill(0,255,0,100).translateX(-30);
 
  Anar.slidersAdd(myBox1);
}
 
 
void draw(){
  background(255); 
  myBox1.draw();
  myBox2.draw();
}

anar.ch_img_introduction_intro12.jpg

When we started to work on ANAR+, we found that push() and pop() was counter intuitive and maybe too far from what is commonly used in the CAD modelers architects are used to. Also, as architectural design require management of large set of objects, we found that structure to be more effective.