Form V: 3D

3D, 3D-Transformationen, Navigation: Rotieren und Skalieren, Licht!, Komplexe Formen generieren

3D

Processing funktioniert grundsätzlich auch in 3D und ermöglicht dabei ungefähr das, was OpenGL ermöglicht. Dass in 3D statt 2D gerendert werden soll, legt size(...) fest:

size(600,400,P3D);

Viele bekannte Befehle bekommen jetzt einfach eine zusätzliche Koordinate: z

size(600,400,P3D);
background(255);

point(300,100,-50);

line(50,50,0,width-50,50,0);

fill(128);
beginShape();
vertex(100,100,0);
vertex(100,200,0);
vertex(200,200,0);
vertex(200,100,0);
endShape(CLOSE);

fill(128);
beginShape();
vertex(300,100,0);
vertex(300,200,100);
vertex(400,200,0);
vertex(400,100,-100);
endShape(CLOSE);

Dazu kommen neue Funktionen: box(...)...

box(100); // Würfel

box(100, 200, 50);	// Quader

...und sphere(...), inkl. sphereDetail(...):

sphereDetail(5);
sphere(100);

sphereDetail(100);
sphere(100);

3D-Transformationen

Kugel und Würfel können keine Position zugewiesen werden. Stattdessen erfolgt die Positionierung mit den bekannten Transformationen.

size(600,400,P3D);
background(255);

translate(150,200,0);
box(100);

translate(250,0,0);
sphere(75);

Rotationen können um einzelne Achsen erfolgen:

size(600,400,P3D);
background(0);

pushMatrix();
translate(100,200,0);
rotateX(radians(45));
box(100);
popMatrix();

pushMatrix();
translate(300,200,0);
rotateY(radians(45));
box(100);
popMatrix();

pushMatrix();
translate(500,200,0);
rotateZ(radians(45));
box(100);
popMatrix();

Navigation: Rotieren und Skalieren

Variablen für die Navigation: Zoomstufe und Rotation (z.B. um die Vertikale/y-Achse)

float zoom = 1;
float winkel = 0;

In draw() wird zunächst das Bild in die Bildschirmmitte geschoben...

translate(width/2,height/2,0);

...und dann um den Winkel gedreht und um den Zoom skaliert:

rotateY(radians(winkel));
scale(zoom);

Zusätzlich werden Winkel und Zoom von Frame zu Frame verändert:

float zoom = 1;
float winkel = 0;

void setup() {
  size(600, 400, P3D);
}

void draw() {
  background(0);
  
  translate(width/2,height/2,0);
  rotateY(radians(winkel));
  scale(zoom);
  
  pushMatrix();
  translate(-100, 0, 0);
  rotateX(radians(45));
  box(100);
  popMatrix();

  pushMatrix();
  translate(100, 0, 0);
  rotateY(radians(45));
  box(100);
  popMatrix();
  
  winkel = winkel+1;
  zoom = map(mouseX,0,width,0,2);
}

Licht!

lights() mach die Standardbeleuchtung an:

float zoom = 1;
float winkel = 0;

void setup() {
  size(600, 400, P3D);
}

void draw() {
  lights();
  noStroke();
  background(0);
  
  translate(width/2,height/2,0);
  rotateY(radians(winkel));
  scale(zoom);
  
  pushMatrix();
  translate(-100, 0, 0);
  rotateX(radians(45));
  box(100);
  popMatrix();

  pushMatrix();
  translate(100, 0, 0);
  rotateY(radians(45));
  box(100);
  popMatrix();
  
  winkel = winkel+1;
  zoom = map(mouseX,0,width,0,2);
}

ambientLight() sorgt für gleichmäßiges Licht von allen Seiten (mit Lichtfarbe: drei Farbwerte):

ambientLight(255,0,0);

directionalLight() ist farbiges Licht (drei Farbwerte) aus einer festgelegten Richtung (drei Koordinaten).

directionalLight(255,0,0,0,1,-1);

Dazu: pointLight() (von einem Punkt aus in alle Richtungen) und spotLight() (von einem Punkt aus in eine Richtung).

Komplexe Formen generieren

Wir erzeugen eine Form, die aus vielen Schichten besteht. Jede Schicht ist eine Liste von Punkten und all diese Schichten, stehen ebenfalls in einer Liste (von Listen!).

ArrayList<ArrayList> schichten;

void setup() {
  size(800,600,P3D);
  schichten = new ArrayList();
}

Das Zeichnen wird zunächst in die Bildschirmmitte verlegt, rotiert und gezoomt:

void draw() {
  background(0);
  lights();
  noStroke();
  fill(255);
  translate(width/2,height/2,0);
  scale(zoom);
  rotateY(radians(frameCount));
  // ...

Eine Schleife durchläuft alle Schichten und entnimmt die aktuelle Schicht als Liste von Puntken:

for (int i=0; i<schichten.size(); i++) {
  ArrayList<PVector> schicht = schichten.get(i);
  // ...

Bei der ersten und letzten Schicht (i==0 oder i==schichten.size()-1) werden alle Punkte zu einer Shape verbuden. Es entsteht ein Polygon für den Deckel und Boden der Form.

if (i==0 || i==schichten.size()-1) {
  beginShape();
  for (int j=0; j<schicht.size(); j++) {
	PVector pos3d = schicht.get(j);
	vertex(pos3d.x, pos3d.y, pos3d.z);
  }
  endShape(CLOSE);
}

Für die Seitenwände werden die Punkte der aktuellen Schicht (i) mit den Punkten aus der letzten Schicht (i-1) verbunden (daher nur wenn i>0). Die Fläche wird als QUAD_STRIP geschlossen.

if (i>0) {
  beginShape(QUAD_STRIP); 
  ArrayList<PVector> lezteSchicht = schichten.get(i-1);
  for (int j=0; j<schicht.size(); j++) {
    PVector pos3d1 = schicht.get(j);
    PVector pos3d2 = lezteSchicht.get(j);
    vertex(pos3d1.x, pos3d1.y, pos3d1.z);
    vertex(pos3d2.x, pos3d2.y, pos3d2.z+50);
  }
  PVector pos3d1 = schicht.get(0);
  PVector pos3d2 = lezteSchicht.get(0);
  vertex(pos3d1.x, pos3d1.y, pos3d1.z);
  vertex(pos3d2.x, pos3d2.y, pos3d2.z+50);
  endShape(CLOSE);
}

Am Ende der Schleife durch alle Schichten wird das Zeichenblatt nach hinten (z) verschoben, damit die Schichten einen Abstand voneinander haben.

translate(0,0,-50);

Hinzufügen neuer Schichten per Mausklick. Jede Schicht ist eine Menge von 10 Punkten, die auf einem Kreis liegen, dessen Radius r für jeden Punkt zufällig bestimmt wird, während alle Punkte ebenfalls zufällig in x-, y- und z-Richtung verschoben werden.

void mousePressed() {
  ArrayList neueSchicht = new ArrayList();
  for (int i=0; i<10; i++) {
    float r = random(50,200);
    neueSchicht.add(new PVector(r*cos(radians(i*36))+random(-10,10),r*sin(radians(i*36))+random(-10,10),random(-15,15)));
  }
  schichten.add(neueSchicht);
}

Das gesamte Programm:

ArrayList<ArrayList> schichten;
float zoom = 1;

void setup() {
  size(800,600,P3D);
  schichten = new ArrayList();
}

void draw() {
  background(0);
  lights();
  noStroke();
  fill(255);
  translate(width/2,height/2,0);
  scale(zoom);
  rotateY(radians(frameCount));
  
  for (int i=0; i<schichten.size(); i++) {
    ArrayList<PVector> schicht = schichten.get(i);
    if (i==0 || i==schichten.size()-1) {
      beginShape();
      for (int j=0; j<schicht.size(); j++) {
        PVector pos3d = schicht.get(j);
        vertex(pos3d.x, pos3d.y, pos3d.z);
      }
      endShape(CLOSE);
    }
    if (i>0) {
      beginShape(QUAD_STRIP); 
      ArrayList<PVector> lezteSchicht = schichten.get(i-1);
      for (int j=0; j<schicht.size(); j++) {
        PVector pos3d1 = schicht.get(j);
        PVector pos3d2 = lezteSchicht.get(j);
        vertex(pos3d1.x, pos3d1.y, pos3d1.z);
        vertex(pos3d2.x, pos3d2.y, pos3d2.z+50);
      }
      PVector pos3d1 = schicht.get(0);
      PVector pos3d2 = lezteSchicht.get(0);
      vertex(pos3d1.x, pos3d1.y, pos3d1.z);
      vertex(pos3d2.x, pos3d2.y, pos3d2.z+50);
      endShape(CLOSE);
    }
    translate(0,0,-50);
  }
  
  zoom = map(mouseX,0,width,0.1,4);
}

void mousePressed() {
  ArrayList<PVector> neueSchicht = new ArrayList();
  for (int i=0; i<10; i++) {
    float r = random(50,200);
    neueSchicht.add(new PVector(r*cos(radians(i*36))+random(-10,10),r*sin(radians(i*36))+random(-10,10),random(-15,15)));
  }
  schichten.add(neueSchicht);
}