Schlagwort-Archive: Vektor

Flocking


Processing 2.0

Mit Flocking Algorithmen lassen sich sehr imposante Graphiken und Animationen erstellen. Flocking Algorithmen versuchen das Verhalten von Tieren in Schwärmen nachzubilden.

Dafür erstellt man eine Klasse, die ein Individuum repräsentiert, das sich in seinen Bewegungen an den, in der Nähe befindlichen Individuen orientiert. Dabei folgt jedes Individuum einigen einfachen Regeln.

In einer einfachen Implementierung von Daniel Shiffman auf http://processing.org/learning/topics/flocking.html sind das folgende:

  1. Separation (Aufteilung); wähle eine Richtung, die einer Häufung von Individuen entgegenwirkt.
    2 Individuen dürfen sich nicht am selben Ort befinden. Deshalb müssen sie einander ausweichen, wenn sie einen bestimmten Abstand unterschreiten.
  2. Alignment (Angleichung): wähle eine Richtung, die der mittleren Richtung der benachbarten Individuen entspricht.
    Jedes Individuum richtet seine Bewegungsrichtung nach den Nachbarn aus. D.h. es wird eine durchschnittl. Bewegungsrichtung der, innerhalb eines bestimmten Abstands befindlichen Individuen errechnet. Die eigenen Bewegungsrichtung passt sich dieser an.
  3. Cohesion (Zusammenhalt): wähle eine Richtung, die der mittleren Position der benachbarten Individuen entspricht.
    Alle Individuen immerhalb eines bestimmten Bereichs bewegen sich Richtung dessen Zentrum.

Diese 3 Regeln führen zu einem Verhalten, wie im untenstehenden Sketch von Daniel Shiffman. Es können bei Bedarf natürlich noch weitere Regeln definiert werden.

Starte Applet

Flock flock;

void setup() {
  size(640, 360);
  flock = new Flock();
  // Add an initial set of boids into the system
  for (int i = 0; i < 150; i++) {
    flock.addBoid(new Boid(width/2,height/2));
  }
}

void draw() {
  background(50);
  flock.run();
}

// Add a new boid into the System
void mousePressed() {
  flock.addBoid(new Boid(mouseX,mouseY));
}

// The Boid class

class Boid {

  PVector location;
  PVector velocity;
  PVector acceleration;
  float r;
  float maxforce;    // Maximum steering force
  float maxspeed;    // Maximum speed

  Boid(float x, float y) {
    acceleration = new PVector(0,0);
    velocity = new PVector(random(-1,1),random(-1,1));
    location = new PVector(x,y);
    r = 2.0;
    maxspeed = 2;
    maxforce = 0.03;
  }

  void run(ArrayList<Boid> boids) {
    flock(boids);
    update();
    borders();
    render();
  }

  void applyForce(PVector force) {
    // We could add mass here if we want A = F / M
    acceleration.add(force);
  }

  // We accumulate a new acceleration each time based on three rules
  void flock(ArrayList<Boid> boids) {
    PVector sep = separate(boids);   // Separation
    PVector ali = align(boids);      // Alignment
    PVector coh = cohesion(boids);   // Cohesion
    // Arbitrarily weight these forces
    sep.mult(1.5);
    ali.mult(1.0);
    coh.mult(1.0);
    // Add the force vectors to acceleration
    applyForce(sep);
    applyForce(ali);
    applyForce(coh);
  }

  // Method to update location
  void update() {
    // Update velocity
    velocity.add(acceleration);
    // Limit speed
    velocity.limit(maxspeed);
    location.add(velocity);
    // Reset accelertion to 0 each cycle
    acceleration.mult(0);
  }

  // A method that calculates and applies a steering force towards a target
  // STEER = DESIRED MINUS VELOCITY
  PVector seek(PVector target) {
    PVector desired = PVector.sub(target,location);  // A vector pointing from the location to the target
    // Normalize desired and scale to maximum speed
    desired.normalize();
    desired.mult(maxspeed);
    // Steering = Desired minus Velocity
    PVector steer = PVector.sub(desired,velocity);
    steer.limit(maxforce);  // Limit to maximum steering force
    return steer;
  }

  void render() {
    // Draw a triangle rotated in the direction of velocity
    float theta = velocity.heading2D() + radians(90);
    fill(200,100);
    stroke(255);
    pushMatrix();
    translate(location.x,location.y);
    rotate(theta);
    beginShape(TRIANGLES);
    vertex(0, -r*2);
    vertex(-r, r*2);
    vertex(r, r*2);
    endShape();
    popMatrix();
  }

  // Wraparound
  void borders() {
    if (location.x < -r) location.x = width+r;
    if (location.y < -r) location.y = height+r;
    if (location.x > width+r) location.x = -r;
    if (location.y > height+r) location.y = -r;
  }

  // Separation
  // Method checks for nearby boids and steers away
  PVector separate (ArrayList<Boid> boids) {
    float desiredseparation = 25.0f;
    PVector steer = new PVector(0,0,0);
    int count = 0;
    // For every boid in the system, check if it's too close
    for (Boid other : boids) {
      float d = PVector.dist(location,other.location);
      // If the distance is greater than 0 and less than an arbitrary amount (0 when you are yourself)
      if ((d > 0) && (d < desiredseparation)) {
        // Calculate vector pointing away from neighbor
        PVector diff = PVector.sub(location,other.location);
        diff.normalize();
        diff.div(d);        // Weight by distance
        steer.add(diff);
        count++;            // Keep track of how many
      }
    }
    // Average -- divide by how many
    if (count > 0) {
      steer.div((float)count);
    }

    // As long as the vector is greater than 0
    if (steer.mag() > 0) {
      // Implement Reynolds: Steering = Desired - Velocity
      steer.normalize();
      steer.mult(maxspeed);
      steer.sub(velocity);
      steer.limit(maxforce);
    }
    return steer;
  }

  // Alignment
  // For every nearby boid in the system, calculate the average velocity
  PVector align (ArrayList<Boid> boids) {
    float neighbordist = 50;
    PVector sum = new PVector(0,0);
    int count = 0;
    for (Boid other : boids) {
      float d = PVector.dist(location,other.location);
      if ((d > 0) && (d < neighbordist)) {
        sum.add(other.velocity);
        count++;
      }
    }
    if (count > 0) {
      sum.div((float)count);
      sum.normalize();
      sum.mult(maxspeed);
      PVector steer = PVector.sub(sum,velocity);
      steer.limit(maxforce);
      return steer;
    } else {
      return new PVector(0,0);
    }
  }

  // Cohesion
  // For the average location (i.e. center) of all nearby boids, calculate steering vector towards that location
  PVector cohesion (ArrayList<Boid> boids) {
    float neighbordist = 50;
    PVector sum = new PVector(0,0);   // Start with empty vector to accumulate all locations
    int count = 0;
    for (Boid other : boids) {
      float d = PVector.dist(location,other.location);
      if ((d > 0) && (d < neighbordist)) {
        sum.add(other.location); // Add location
        count++;
      }
    }
    if (count > 0) {
      sum.div(count);
      return seek(sum);  // Steer towards the location
    } else {
      return new PVector(0,0);
    }
  }
}

// The Flock (a list of Boid objects)

class Flock {
  ArrayList<Boid> boids; // An ArrayList for all the boids

  Flock() {
    boids = new ArrayList<Boid>(); // Initialize the ArrayList
  }

  void run() {
    for (Boid b : boids) {
      b.run(boids);  // Passing the entire list of boids to each boid individually
    }
  }

  void addBoid(Boid b) {
    boids.add(b);
  }
}

Erläuterungen zur Boid Klasse:

  • Die Vektoren location, velocity und acceleration kontrollieren Position, Geschwindigkeit und Beschleunigung.
  • In jedem draw() Durchlauf wird die acceleration (Beschleunigung) basierend auf der 3 Kräfte Separation, Alignment und Cohesion neu berechnet.
  • Separation: Es werden alle Boids durchlaufen und ein Distanzvektor errechnet. Weiters wird aus allen Distanzvektoren, die größer 0 und kleiner als ein desiredseparation genannter Wert sind, ein Durchsschnittdistanzsvektor namens steer errechnet. Von diesem wird dann noch der aktuelle Geschwindigkeitsvektor abgezogen.
  • Alignment: Auch hier werden wieder alle Boids durchlaufen. Dabei wird ein durchschnittlicher  Geschwindigkeitsvektor aller Boids errechnet, die sich innerhalb einer bestimmten Distanz (neighbordist) befinden. Auch von diesem wird dann wieder der Boid eigene Geschwindigkeitsvektor abgezogen.
  • Cohesion: Bei der Cohesion wird ein durchschnittlicher Positionsvektor aller, sich innerhalb einer best. Distanz befindenen Boids errechnet. In diese Richtung wird dann gesteuert.
  • In der flock-Methode der Boid Klasse werden dann alle Kräfte gewichtet und zur aktuellen Beschleunigung aufsummiert.
  • In update wird dann die aktuelle Beschleunigung zur aktuellen Geschwindigkeit hinzugerechnet. Dann wird noch die Geschwindigkeit auf die Maximalgeschwindigkeit begrenzt und die Beschleunigung auf 0 gesetzt.

Visualisierung von länderbezogenen Daten


Mich interessieren Daten. Oft kann man Daten in Tabellenform aber schwer fassen. Deshalb habe ich versucht Daten die österreichischen Bundesländer zu visualisieren.

D.h. konkret möchte ich die Arbeitslosenquote der Bundesländer farblich als Österreichkarte darstellen. Dafür brauchen wir als erstes eine Karte Österreichs als *.svg Datei. Die ist schnell auf Wikipedia gefunden: Map of Austria.

Vorbereiten der Vektorgrafik

Ich habe diese Karte zwecks einfacherer Analyse in Processing leicht modifiziert. Das ist sehr einfach, da es sich bei .svg Dateien eigentlich nur um XML Dateien handelt. Man kann sie einfach in jedem Texteditor öffnen. Unter Linux geschieht das dann auch noch gleich mit Code-Highlighting. Folgende Tags wurden von mir einfach aus der Datei entfernt.

<title>Austria</title>
 <desc>Political map of Austria</desc>
 <filter id="f1">
 <feGaussianBlur in="SourceGraphic" stdDeviation="10"/>
 </filter>
 <path id="Shadow" filter="url(#f1)" ..........................................>

Modifizierte Datei:

Data Mining

Das Aufspüren und Aufbereiten der Daten ist in diesem Fall sehr einfach. Die Statistik Austria bietet sie in verschiedenen Formen an. Ich beschränke mich vorerst auf die Quote der Bundesländer im Jahr 2010 und kopiere die ensprechende Tabelle.

Die erste Visualisierung

Die .svg Datei Österreichs wird geladen und dann in die einzelenen Pfade der Bundesländer zerlegt. Die Daten der Arbeitslosenzahlen werden in einem Array in einer Reihe, den Pfaden der Bundesländern entsprechend abgelegt. Dann wird jeder Pfad gezeichnet und entsprechend der ArbeitslosenQuote eingefärbt.

Starte Applet

// PShape Objekte erzeugen
PShape austria;
PShape[] bundesl;

//Titel
String title="Arbeitslosenqote Bundesländer 2010";

// Daten von Statistik Austria
float[] arblQuote = {
3.9, //Burgenland
3.9, //Carinthia
3.6, //Lower_Austria
3.7, //Upper_Austria
2.9, //Salzburg
4.2, //Styria
2.8, //Tyrol
3.9, //Vorarlberg
7.3  //Vienna
};

//Grundfarbe der Visualisierung
int col=210;

void setup() {
size(900, 500);

//Vektorgrafik laden
austria = loadShape("Map_of_Austria.svg");

//Pfade der einzelnen Bundesländer extrahieren
bundesl = austria.getChildren();

//Formatierung des Originals deaktivieren
austria.disableStyle();
noLoop();
colorMode(HSB, 360, 100, 100);
textAlign(CENTER, CENTER);
}

void draw() {
background(0, 0, 100);
stroke(col, 20, 99);

//Zeichnen der Bundesländer
for (int i=0; i<bundesl.length; i++) {
//Füllfarbe festlegen
fill(col, arblQuote[i]*13, 100);
//Zeichnen der Pfade
shape(bundesl[i], 0, 0);
println(bundesl[i].getName());
}
//Titel
textSize(20);
text(title, width/4,height*1/20);
}

Legende:

Eine Legende wäre jetzt natürlich noch nett. Dafür sollte man natürlich auch noch den Farbbereich automatisch an den Wertbereich der Daten anpassen.

starte Applet

/* @pjs preload="Map_of_Austria.svg"; */

// PShape Objekte erzeugen
PShape austria;
PShape[] bundesl;

//Titel and Source
String title="Arbeitslosenqote Bundesländer 2010";
String source=" Quelle: Statistik Austria";

// Daten von Statistik Austria
float[] arblQuote = {
3.9, //Burgenland
3.9, //Carinthia
3.6, //Lower_Austria
3.7, //Upper_Austria
2.9, //Salzburg
4.2, //Styria
2.8, //Tyrol
3.9, //Vorarlberg
7.3  //Vienna
};

//Grundfarbe der Visualisierung
int col=210;

void setup() {
size(900, 500);

//Vektorgrafik laden
austria = loadShape("Map_of_Austria.svg");

//Pfade der einzelnen Bundesländer extrahieren
bundesl = austria.getChildren();

//Formatierung des Originals deaktivieren
austria.disableStyle();
noLoop();
colorMode(HSB, 360, 100, 100);
textAlign(CENTER, CENTER);
rectMode(CENTER);
}

void draw() {
background(0, 0, 100);
stroke(col, 20, 99);

// Koordinatensystem speichern
pushMatrix();
translate(0, 30);

//Zeichnen der Bundesländer
for (int i=0; i<bundesl.length; i++) {
//Füllfarbe festlegen
fill(col, map(arblQuote[i],getMinValue(arblQuote),getMaxValue(arblQuote),20,100), 100);
//Zeichnen der Pfade
shape(bundesl[i], 0, 0);
println(bundesl[i].getName());
}
//Koordinatensystem repositionieren
popMatrix();

//Legende zeichnen
legende(10, 50, 2.8, 7.3);

text(source, width/2, height*19/20);
//Titel
textSize(20);
text(title, width/4, height*1/20);
}

void legende(int x, int y, float valMin, float valMax) {
pushMatrix();
translate(x+10, y+10);
fill(0, 0, 0);
text("Legende:", 30, 0);
translate(0, 25);

//Min und Max Werte für die Beschriftung finden
int minVal=floor(valMin);
int maxVal=ceil(valMax);
float step= (float) (maxVal-minVal)/5;

//Farbfelder und Beschriftungen zeichnen
for (int i =0; i<6; i++) {
fill(col, map(minVal+step*i,getMinValue(arblQuote),getMaxValue(arblQuote),20,100), 100);
rect (0, i*20, 20, 10);
String legValue = " %";
fill(0, 0, 0);
text( (float)round(((float) minVal+step*i) * 100 )/100+" %", 30, i*20);
}
popMatrix();
}

float getMaxValue ( float[] vals) {
float [] values= new float [vals.length];
arrayCopy(vals,values);
Arrays.sort(values);
return values[values.length-1];

float getMinValue ( float[] vals) {
float [] values= new float [vals.length];
arrayCopy(vals,values);
Arrays.sort(values);
return values[0];
}

Beschriftung:

Damit das Ganze einen echten Mehrwert gegenüber der konventionellen Erstellung in einem Grafikprogramm bietet, sollte das Programm die Grafik automatisch beschriften. Dafür müssen wir die in der original-.svg Datei englischsprachigen Titel durch deutschsprachige ersetzen.

Die Quoten und Namen der Bundesländer sollen in der Mitte der jeweiligen Pfade platziert werden. Dafür müssen wir die minimalen und maximalen x und y Koordinaten aller Punkte eines Pfades ermitteln. Dann zeichnen wir genau in die Mitte.

Starte Applet

Änderungen zum Beispiel vorher:

  1. Hintergrundfarbe geändert, damit die Beschriftung besser lesbar wird.
    background(0, 0, 80);
    
  2. In draw() werden die Koordinaten der Beschriftungstexte ermittelt und dann gezeichnet.
    <pre>//Zeichnen der Quoten
    fill(360,0,100);
    float [] coords = new float [2];
    coords= getCoords(bundesl[i].getChild(0));
    textSize(18);
    text(  (float)round(((float) arblQuote[i]) * 100 )/100+" %", coords[0],coords[1]);
    
  3. Funktion zur Ermittlung der Koordinaten.
    float [] getCoords (PShape thisShape) {
    
    float xMax=0;
    float yMax=0;
    float xMin=100000;
    float yMin=100000;
    float [] coords = new float [2];
    
    //Ermitteln der maximalen und minimalen Pfad-Koordinaten
    for (int i =0; i<thisShape.getVertexCount(); i++) {
    if (thisShape.getVertexX(i)>xMax)  xMax=thisShape.getVertexX(i);
    if (thisShape.getVertexY(i)>yMax)  yMax=thisShape.getVertexY(i);
    if (thisShape.getVertexX(i)<xMin)  xMin=thisShape.getVertexX(i);
    if (thisShape.getVertexY(i)<yMin)  yMin=thisShape.getVertexY(i);
    }
    coords[0]=(xMin+xMax)/2;
    coords[1]=(yMin+yMax)/2;
    return coords;
    }
    

Interaktivität:

starte Applet

    1. Erweitern der Datenbasis
      Damit in unserer Anwendung nun auch Entwicklungen sichtbar werden, verwende ich nicht nur die Daten aus dem Jahr 2010, sondern eine ganze Tabelle mit den Daten der Jahre 1995-2010, wie sie von Statistik Austriaveröffentlicht werden. Damit diese von Processing auf einfache Weise automatisch eingelesen werden können, habe ich eine .csv Tabelle der folgender Struktur erstellt:

      • unnötige Spalten und Zeilen gelöscht
      • die , durch . ersetzt
      • die Bundesländer-Beschriftung von links nach rechts kopiert
      • im Sketch Ordner als list.csv gespeichert

      Download: list.csv

      1995;1996;1997;1998;1999;2000;2001;2002;2003;2004;2005;2006;2007;2008;2009;2010;
      3.8;4.2;4.3;3.4;3.6;3.2;4.1;4.3;4.2;5.6;6;5;3.7;3.6;4.6;3.9;Burgenland
      3;2.9;3.4;3.5;3.5;3.1;3.2;2.7;3.4;4.6;4.8;4.4;3.9;3.4;4.2;3.9;Kärnten
      3.4;3.8;3.8;4.4;3.3;3;3.2;3.6;3.5;4.2;4.3;4;3.6;3.4;4.3;3.6;Niederösterreich
      3.2;3.7;3.5;3.5;3.5;3.1;2.9;3.1;3.3;3.7;4;3.2;3.2;2.6;4;3.7;Oberösterreich
      2.7;3.2;3.2;3.4;2.7;2.3;1.9;2.8;2.2;3.7;3.2;3.1;3;2.5;3.2;2.9;Salzburg
      3.5;4.1;4;3.8;3.2;3.2;3.7;3.8;4;3.7;4.1;3.9;3.7;3.4;4.6;4.2;Steiermark
      2.9;3.2;3.4;2.9;2.5;2.5;2.3;2;2.6;3.3;3.5;2.9;2.8;2.4;2.9;2.8;Tirol
      3.3;3.9;3.9;3.6;3.5;2.4;2.4;2.5;4.1;4.1;5.3;4.4;3.6;3.9;4.9;3.9;Vorarlberg
      5.3;5.9;6.3;6.3;5.7;5.7;5.8;7.3;7.8;8.9;9.1;8.8;8.3;6.7;7.5;7.3;Wien

    2. Automatisches Einlesen der Daten aus einer .csv -Datei
      Hier habe ich als Ausgansbasis ein Skript von che-wei wang verwendet: http://cwwang.com/2008/02/23/csv-import-for-processing/. Mit ein paar kleinen  Modifikationen liest es auch in unserem Programm die .csv-Datei ein. Und speichert die Daten in einem 2-dimenstionalen Array.
    3. Ermitteln der minimalen und maximalen Werte
      Das ist jetzt nicht mehr ganz so einfach, wie vorher, aber kein allzu großes Problem, das in setup() erledigt wird:

      //Ermitteln der minimalen und maximalen Werte für die Farbwahl
      for (int j=0; j<csv[0].length;j++) {
      for (int i=0; i<arblQuote.length;i++) {
      arblQuote[i]=parseFloat(csv[i+1][j]);
      }
      if (getMinValue(arblQuote)<minVal) minVal=getMinValue(arblQuote);
      if (getMaxValue(arblQuote)>maxVal) maxVal=getMaxValue(arblQuote);
      }
      

      Damit werden dann in weiterer Folge unsere Werte mit konstanten Farbwerten über alle Jahre hinweg beschriftet.

    4. Übertragen der Jahreszahlen und des aktuell gewählten Jahres in entsprechende Variablen
      //Auslesen der Jahre
      years=new int[csv[0].length-1];
      for (int i=0; i< csv[0].length-1;i++) {
      years[i]=parseInt(csv[0][i]);
      }
      selYear=0;
      
    5. In draw(): Daten des aktuellen Jahres übertragen.
      //Daten des aktiven Jahres laden
      for (int i=0; i<arblQuote.length;i++) {
      arblQuote[i]=parseFloat(csv[i+1][selYear]);
      }
      
    6. Für jedes Bundesland den Namen anzeigen, wenn sich die Maus darüber befindet. (Funktioniert beim Bundesland Tirol nur bedingt!)
      //Namen anzeigen
      if (bundesl[i].getChild(0).contains(mouseX, mouseY)) {
      fill(0, 0, 40);
      textSize(14);
      text(bundesl[i].getName(), coords[0], coords[1]+20);
      }
      
    7. Interface, für die Auswahl der Jahre
      int yearSelector (int [] years) {
      
      noFill();
      pushMatrix();
      strokeWeight(1.5);
      
      //Linie zeichnen
      stroke(0, 70, 90);
      line(width-5, 0, width-5, height);
      strokeWeight(1);
      
      //Beschriftung
      translate(width-50, -10);
      for (int i=0; i<years.length; i++) {
      translate(0, height/years.length);
      
      //Hervorheben des gewählten Jahres
      if (years[i]==years[selYear]) {
      fill(0, 70, 90);
      triangle(35, 0, 45, -10, 45, 10);
      textSize(24);
      fill(360);
      }
      else {
      textSize(12);
      fill(0, 0, 60);
      }
      text(years[i], 0, 0);
      }
      popMatrix();
      return selYear ;
      }
      
    8. Mausaktivität abrufen um damit das gewünschte Jahr zu wählen
      //Mausposition auswerten
      void mouseMoved () {
      if (mouseX > width-60 && mouseX<width) {
      selYear=(int) map(mouseY, 0, height, 0, years.length);
      }
      }
      

Und diverse kleinere Modifikationen, z.B.: die Anpassung der Textpositionen an die Länge des Textinhalts usw. Den gesamten Quellcode findest du im Applet.


Objekte und Klassen


Processing 2.0

Das Programmieren mit Objekten und Klassen nennt man objektorientiertes Programmieren. Es bietet den Vorteil einer viel besseren Übersicht bei längeren Programmen und ist hier auch dringend anzuraten.

Grundlegendes:

Eine Klasse ist eine allgemeine Vorlage für ein Objekt. Nehmen wir als Beispiel ein Auto. Die Vorstellung von einem Auto wäre dann die Klasse. Die Eigenschaften eines Autos sind in der Klasse definiert (Bsp.: Auto hat 4 Räder, 4 Lichter, ein Lenkrad usw.). Manche Eigenschaften können aber auch variieren, so dass z.B. manche Autos unterschiedlich viele Sitze oder Türen aufweisen (oder auch eine aktuelle Geschwindigkeit und Richtung). Diese Eigenschaften übermittelt man der Klasse, wenn nach ihrer Vorlage ein Objekt erstellt werden soll. Ein Auto hat aber nicht nur Eigenschaften, die in Form von Variablen gespeichert werden, sondern auch Funktionen. Man nennt diese Methoden. Mit Hilfe dieser Methoden kann man ein Objekt manipulieren. Das Auto könnte beispielsweise beschleunigen und somit ändert sich die aktuelle Geschwindigkeit. Es könnte auch nach links lenken und somit die Fahrtrichtung ändern.

Beispiel: Auto starte Applet

Wir schreiben eine Klasse Auto. Diese Autos sollen beschleunigen, bremsen und lenken können.

Klasse: Auto

class Auto {
}

Die Richtung der Autos wird über einen Winkel gesteuert, der dann in einen Richtungsvektor umgerechnet wird. Er kann durch die Tasten LINKS, RECHTS verändert werden. Der Richtungsvektor wird dann jeden draw() Durchlauf zum aktuellen Positionsvektor addiert. Und schon bewegt sich das Auto. Durch die Tasten AUF, AB kann das Auto dann noch beschleunigen und bremsen.

Eigenschaften: Farbe, aktuelle Richtung,  aktuelle Position usw.

class Auto {
// 2 Vektoren für die Position und Richtung des Fahrzeugs
PVector position;
PVector direction;
// Richtung und Geschwindigkeit
float angle, speed;
//Farbe
color clr;
}

Methoden: Lenken, Beschleunigen, Bremsen, Auto zeichnen,  usw.

class Auto {
  //Methode des Objekts Auto
void render() {
fill(clr);
//der Code ab hier zeichnet des Autos
PVector normVector = new PVector(1, 0);
float angle = direction.heading2D();
pushMatrix();
translate(position.x, position.y);
rotate(angle-PI);
strokeWeight(2);
rect(0, 0, 100, 50);
fill(0);
rect(-35, -30, 15, 3);
rect(-35, 30, 15, 3);
rect(30, -30, 15, 3);
rect(30, 30, 15, 3);
popMatrix();
}

//Methode des Objekts Auto
void move() {
// aufgrund des Winkels wird die Bewegungsrichtung angepasst
direction.x = sin(angle)*speed;
direction.y = cos(angle)*speed;
position.add (direction);
}

void steerLeft() {
angle+=0.1;
}

void steerRight() {
angle-=0.1;
}

void accelerate() {
speed+=0.1;
}

void slowDown() {
speed-=0.1;
}
}

Der Konstruktor

Damit das Ganze dann auch wirklich funktioniert, brauchen wir noch einen sog. Konstruktor. Das ist eine Methode, die bei der Erzeugung des Objekts ein mal aufgerufen wird und die initialen Werte für das Objekt festlegt. Diese können beim Aufruf als Parameter mitgeliefert, oder einfach in der Klasse selbst festgelegt werden.

class Auto {
// der sog. Konstruktor wird einmal ausgeführt, wenn das Objekt erstellt wird
Auto (float aangle, color aclr) {
position    = new PVector (width/2, height/2);
angle = aangle;
speed = 0.1;
direction   = new PVector (0.1, 0);
clr    = aclr;
}
}

Die gesamte Klasse Auto sieht dann so aus:

class Auto {
// 2 Vektoren für die Position und Richtung des Fahrzeugs
PVector position;
PVector direction;
// Richtung und Geschwindigkeit
float angle, speed;
//Farbe
color clr;

// der sog. Konstruktor wird einmal ausgeführt, wenn das Objekt erstellt wird
Auto (float aangle, color aclr) {
position    = new PVector (width/2, height/2);
angle = aangle;
speed = 0.1;
direction   = new PVector (0.1, 0);
clr    = aclr;
}

//Methode des Objekts Auto
void render() {
fill(clr);
//der Code ab hier zeichnet des Autos
PVector normVector = new PVector(1, 0);
float angle = direction.heading2D();
pushMatrix();
translate(position.x, position.y);
rotate(angle-PI);
strokeWeight(2);
rect(0, 0, 100, 50);
fill(0);
rect(-35, -30, 15, 3);
rect(-35, 30, 15, 3);
rect(30, -30, 15, 3);
rect(30, 30, 15, 3);
popMatrix();
}

//Methode des Objekts Auto
void move() {
// aufgrund des Winkels wird die Bewegungsrichtung angepasst
direction.x = sin(angle)*speed;
direction.y = cos(angle)*speed;
position.add (direction);
}

void steerLeft() {
angle+=0.1;
}

void steerRight() {
angle-=0.1;
}

void accelerate() {
speed+=0.1;
}

void slowDown() {
speed-=0.1;
}
}

So, jetzt stellt sich noch die Frage, wie man Objekte erzeugt und manipuliert (also auf die Eigenschaften und Methoden zugreifen kann). Objekte verhalten sich diesbezüglich wie Variablen. Diese müssen, wie wir in Variablen gelernt haben, initialisiert und deklariert werden. Das gilt auch für Objekte.

Hier das Hauptprogramm des fertigen Beispiels:

/** Copyright 2012 Thomas Koberger
*/

// https://lernprocessing.wordpress.com/
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* KEYS
* UP               : Beschleunigen
* DOWN             : Bremsen
* LINKS            : Lenke nach links
* RECHTS           : Lenke nach rechts
*/

// Erzeugt ein Objekt der Klasse Auto
Auto Kaefer;

void setup() {
size(800, 800);
// Aufruf des Konstruktors
Kaefer = new Auto(0, color(100, 50, 255));
rectMode(CENTER);
}

void draw() {
background(200);
//Aufruf der Methoden des Objekts
Kaefer.move();
Kaefer.render();
}

// Zum Steuern des Autos werden
void keyPressed() {
if (key == CODED) {
if (keyCode == UP) {
Kaefer.accelerate();
}
else if (keyCode == DOWN) {
Kaefer.slowDown();
}
else if (keyCode == LEFT) {
Kaefer.steerLeft();
}
else if (keyCode == RIGHT) {
Kaefer.steerRight();
}
}
}

Und noch ein Beispiel: Wir wollen ein Programm schreiben, das Ameisen erzeugt, die sich auf zufälligen Bahnen bewegen und Futter sammeln. Dabei sollten ganz einfache Regeln gelten: Findet eine Ameise Futter und trägt gerade kein Futter, soll sie das gefunden Futter aufnehmen. Das trägt sie dann, bis sie wieder auf Futter stößt. Hier soll sie das Futter, das sie gerade trägt fallen lassen.

Wir werden also diesmal objektorientiert vorgehen und brauchen für unser Projekt 2 Objekte: das Futter und die Ameisen. Wie müssen diese Objekte nun beschrieben werden? Fangen wir der Einfachheit halber mit der Beschreibung des Futters an. Dieses hat in unserem Beispiel eine sehr passive Rolle und sollte somit leicht zu beschreiben sein.

Wie wir wissen nennt man die Beschreibung eines Objektes Klasse (class). Die sieht nun bei unserem Futter so aus:

class Food {
 float xPos;
 float yPos;
 float radius;
 int traeger;
 int frame=0;
 color clr;

 Food (float axPos, float ayPos, float aradius, int atraeger, color aclr) {
 xPos   = axPos;
 yPos   = ayPos;
 radius = aradius;
 traeger = atraeger;
 clr    = aclr;
 }

Dabei werden im oberen Teil die benötigten Variablen definiert. Unten haben wir den sog. Konstruktor. Dieser weist den Variablen xPos, yPos usw. von uns definierte Werte zu. Der Konstruktor gibt im Gegensatz zu einer Funktion nichts zurück, nicht einmal void und wird immer, wenn ein neues Objekt erstellt wird automatisch aufgerufen. Somit ist die Definition der Klasse abgeschlossen.

Um ein Objekt der Klasse Food zu generieren müssen wir noch folgendes machen:

1. Wir erzeugen eine Variable der Klasse Food mit dem Namen Futter.

Food Futter; </em>

2. Wir rufen eine Funktion mit dem Namen der Klasse auf und befüllen die Variable Futter mit einem Objekt der Klasse Food.

Futter=new Food();

3. Jetzt enthält unsere Variable Futter lauter Nullen für alle int, float und color – Variablen, die in ihr enthalten sind. Indem wir jetzt unseren Konstruktor aufrufen, befüllen wir unser Objekt mit den von uns gewünschten Werten. Dafür schreiben wir der besseren Übersichtlichkeit halber eine eigene Funktion.

void gibFutter() {
 Futter = new Food[50];
 int border = 50;
 for (int f=0; f<Futter.length; f++) {
 float xPos = random(border, width-border);
 float yPos = random(border, height-border);
 float radius = random(5, 5);
 int traeger = 500;
 color clr = color(50,200,0,120);
 Futter[f] = new Food(xPos, yPos, radius, traeger, clr);
 }
}

Diese Funktion nennen wir gibFutter. Sie erstellt in diesem Fall 20 Futterobjekte, die mit zufälligen Koordinaten versehen werden (und 50px Abstand zum Rand, damit sie auch von den Ameisen aufgenommen werden können).

Das wärs dann eigentlich. Fast das gleiche, aber mit ein wenig mehr Eigenschaften machen wir mit den Ameisen. Die Klasse inkl. dem Konstruktor sieht bei den Ameisen dann so aus:

class Animals {

 PVector position;
 PVector direction;
 float spin = 0.10;
 float radius;
 boolean loaded;
 int blocked;
 int traegt;
 color clr;

 Animals (float theX, float theY,
 float aradius, boolean aloaded, int ablocked, int atraegt, color aclr) {
 position    = new PVector (theX, theY);
 direction   = new PVector (10,10);
 direction.x = random (-1, 1);
 direction.y = random (-1, 1);
 radius = aradius;
 loaded = aloaded;
 blocked = ablocked;
 traegt = atraegt;
 clr    = aclr;
 }
}

Methoden:

Um unseren Objekten jetzt noch sagen zu können, was sie machen sollen, müssen wir in den Klassendefinitionen noch etwas ergänzen. Und zwar sog. Methoden. Das sind Funktionen in der Klassendefinition, die  Eigenschaften der Objekte ändern können. Z.B. sollten sich die Ameisen fortbewegen und am Bildschirm dargestellt werden können. Dafür haben wir 2 Methoden, nämlich die Methode render und die Methode move.

//Methode des Objekts Ameise
 void render() {
 fill(clr);
 //der Code ab hier dient dem Zeichnen der Ameise
 PVector normVector = new PVector(1,0);
 float angle = direction.heading2D();
 pushMatrix();
 translate(position.x,position.y);
 rotate(angle-PI);
 strokeWeight(2);
 line(0-radius/2,0-radius, 0+radius/2, 0+radius);
 line(0+radius/2,0-radius, 0-radius/2, 0+radius);
 line(0,0-radius, 0, 0+radius);
 ellipse(0,0,radius*0.5,radius*0.5);
 ellipse(0+radius-1,0,radius*1.5,radius);
 ellipse(0-radius*0.7,0,radius,radius);
 //ellipse(position.position.x, position.position.y, radius*2, radius*2);
 popMatrix();
 }

 //Methode des Objekts Ameise
 void move() {
 direction.x += random (-spin, spin);
 direction.y += random (-spin, spin);
 direction.normalize ();
 position.add (direction);

 if (position.x < radius || position.x > (width-radius)) {
 direction.x *= -1;
 }
 if (position.y < radius || position.y > (height-radius)) {
 direction.y *= -1;
 }
 }

Der Aufruf einer solchen Methode sieht dann so aus:

Ameise[i].move();
Ameise[i].render();

Außerdem können zu jedem Zeitpunkt alle Variablen eines Objekts abgerufen werden. Ein Beispiel für eine Abfrage kommt im gesamten Quellcode unten vor.

Unser Beispiel ist bewusst eher einfach gehalten und könnte noch um viele nette Features erweitert werden.

Beispiel Ameisen: starte Applet


Animals [] Ameise;
Food [] Futter;
//Der Aufruf einer solchen
//Methode sieht dann so aus:
void setup() {
 size(400, 400,P2D);
 gibFutter();
 buildAmeise();
 frameRate(120);
}

void draw() {
 background(180);
 //die erste Schleife geht in jedem Frame alle Ameisen durch
 for (int i=0; i<Ameise.length; i++) {
 //damit weichen die Ameisen einader aus
 for (int u=0; u<Ameise.length; u++) {
 if ((Ameise[i].position.x < Ameise[u].position.x+20 &&  Ameise[i].position.x > Ameise[u].position.x-20
 && Ameise[i].position.y < Ameise[u].position.y+20 && Ameise[i].position.y > Ameise[u].position.y-20) && u!=i) {
 Ameise[i].direction.x *= -1;
 Ameise[i].direction.y *= -1;
 }
 }

 Ameise[i].move();
 Ameise[i].render();
 Ameise[i].blocked-=1;

 //die zweite Schleife alle Futterpakete
 for (int f=0; f<Futter.length; f++) {
 if (Ameise[i].position.x < Futter[f].xPos+6 &&  Ameise[i].position.x > Futter[f].xPos-6
 && Ameise[i].position.y < Futter[f].yPos+6 && Ameise[i].position.y > Futter[f].yPos-6) {
 //hierhin kommt man nur, wenn eine Ameise auf Futter gestoßen ist
 if(Ameise[i].loaded && Ameise[i].traegt!=f) {
 //hier kommt hin, was passiert, wenn eine schon beladene Ameise auf Futter trifft
 Ameise[i].blocked=30; //Anzahl der Frames, bevor die Ameise wieder Futter aufnehmen kann
 Ameise[i].loaded = false;
 Futter[f].traeger=500;
 }
 else {
 //das passiert, wenn die Ameise auf Futter gestoßen ist, nichts trägt und nicht gesperrt ist.
 if (Ameise[i].blocked<1) {
 Ameise[i].traegt= f;
 Futter[f].traeger= i;

 Futter[f].dragged();
 Ameise[i].loaded = true;
 }
 }
 }
 Futter[f].render();
 println(Futter[f].traeger);
 }
 }
}

//diese Funktion erzeugt die Ameisen
void buildAmeise() {
 Ameise = new Animals[5];
 int border = 50;
 for( int i =0;i<Ameise.length; i++) {
 float X = random(border, width-border);
 float Y = random(border, height-border);

 float theX = random(border, width-border);
 float theY = random(border, height-border);

 float radius = random(15, 15);
 boolean loaded = false;
 int blocked = 0;
 int traegt = 50;
 color clr = color(120,100);
 Ameise[i] = new Animals(theX, theY, radius, loaded, blocked, traegt, clr);
 }
}

//hier beginnt die Definition des Objekts Ameise

void gibFutter() {
 Futter = new Food[50];
 int border = 50;
 for (int f=0; f<Futter.length; f++) {
 float xPos = random(border, width-border);
 float yPos = random(border, height-border);
 float radius = random(5, 5);
 int traeger = 500;
 color clr = color(50,200,0,120);
 Futter[f] = new Food(xPos, yPos, radius, traeger, clr);
 }
}

class Animals {

 PVector position;
 PVector direction;
 float spin = 0.10;
 float radius;
 boolean loaded;
 int blocked;
 int traegt;
 color clr;
 Animals (float theX, float theY,
 float aradius, boolean aloaded, int ablocked, int atraegt, color aclr) {
 position    = new PVector (theX, theY);
 direction   = new PVector (10,10);
 direction.x = random (-1, 1);
 direction.y = random (-1, 1);
 radius = aradius;
 loaded = aloaded;
 blocked = ablocked;
 traegt = atraegt;
 clr    = aclr;
 }

 //Methode des Objekts Ameise
 void render() {
 fill(clr);
 //der Code ab hier dient dem Zeichnen der Ameise
 PVector normVector = new PVector(1,0);
 float angle = direction.heading2D();
 pushMatrix();
 translate(position.x,position.y);
 rotate(angle-PI);
 strokeWeight(2);
 line(0-radius/2,0-radius, 0+radius/2, 0+radius);
 line(0+radius/2,0-radius, 0-radius/2, 0+radius);
 line(0,0-radius, 0, 0+radius);
 ellipse(0,0,radius*0.5,radius*0.5);
 ellipse(0+radius-1,0,radius*1.5,radius);
 ellipse(0-radius*0.7,0,radius,radius);
 //ellipse(position.position.x, position.position.y, radius*2, radius*2);
 popMatrix();
 }

 //Methode des Objekts Ameise
 void move() {
 direction.x += random (-spin, spin);
 direction.y += random (-spin, spin);
 direction.normalize ();
 position.add (direction);

 if (position.x < radius || position.x > (width-radius)) {
 direction.x *= -1;
 }
 if (position.y < radius || position.y > (height-radius)) {
 direction.y *= -1;
 }
 }
}
//hier beginnt die Definition des Objekts Futter
class Food {
 float xPos;
 float yPos;
 float radius;
 int traeger;
 int frame=0;
 color clr;

 Food (float axPos, float ayPos, float aradius, int atraeger, color aclr) {
 xPos   = axPos;
 yPos   = ayPos;
 radius = aradius;
 traeger = atraeger;
 clr    = aclr;
 }
 //Methode des Objekts Futter
 void render() {
 fill(clr);
 ellipse(xPos, yPos, radius*2, radius*2);
 }

//Methode des Objekts Futter
void dragged() {
if (Ameise[traeger].blocked<1) {

xPos=Ameise[traeger].position.x;
yPos=Ameise[traeger].position.y;
}

}
}

Aufgabe: Programmiere basierend auf dem Ameisen-Beispiel ein kleines Autorennspiel.

Vektoren


 

Processing 2.0

Ein PVector ist ein Objekt, das 2- oder 3-dimensionale Vektoren beschreibt. Ein Vektor hat eine bestimmte Richtung und eine bestimmte Länge. Man kann auch sagen, er erstreckt sich vom Punkt A zum Punkt B. In der Programmierung verwenden wir Vektoren, um Bewegung zu generieren.

Der Vorteil bei der Verwendung von Vektoren liegt darin, dass man schon mit 2 Vektor- Variablen die aktuelle Geschwindigkeit und Richtung eines Objektes beschreiben kann und mit einem zweiten die Änderung derselben. Diese beiden müssen dann nur noch in jedem Frame addiert werden.

Beispiel: starte Applet

/** Copyright 2012 Thomas Koberger
*/

// https://lernprocessing.wordpress.com/
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//hier werden die Vektoren erstellt
PVector direction = new PVector(1,1);
PVector position = new PVector(20,20);
int radius = 15;

void setup() {
size(300,300);
}

void  draw() {
//für den Schweif
fill(255,20);
rect(0,0,width,height);
//damit sich überhaupt was bewegt
position.add (direction);
//die nächsten Zeile lassen die Ellipse zurückprallen
if (position.x < radius || position.x > (width-radius)) {
direction.x *= -1;
}
if (position.y < radius || position.y > (height-radius)) {
direction.y *= -1;
}
//zeichnet die Ellipse
fill(50);
ellipse(position.x,position.y,radius*2,radius*2);
}

Das Ganze kann dann noch gut mit Zufallszahlen kombiniert werden.

Methoden:

  • vector.set(x,y,z) … legt die x-, y- und z-Werte des Vektors fest
  • vector.get() … gibt die x-, y- und z-Werte des Vektors zurück
  • vector.mag() … gibt die Länge des Vektors zurück
  • vector1.add(vector2) … Addiert 2 Vektoren
  • vector1.sub(vector2) … Subtrahiert 2 Vektoren
  • vector1.mult(float) … Multipliziert  einen Vektor mit einer Zahl
  • vector1.div(float) … Dividiert  einen Vektor durch eine Zahl
  • vector.nomalize() … Ändert die Länge auf 1
  • vector1.anglebetween(vector2) … gibt den Winkel zwischen 2 Vektoren zurück
  • vector.heading2D() … gibt die Richtung des Vektors zurück

Aufgabe: Verändere das Programm so, dass der Kreis zufällig seine Richtung leicht variiert.

Vektoren im 3 dimensonalen Raum

Richtig interessant wird die Verwendung von Vektoren in 3D. Auch hier können alle oben genannten Methoden verwendet werden. D.h., um beispielsweise ein Objekt an die Position eines Vektors zu verschieben geht man wie folgt vor:

PVector loc=new PVector(x,y,z);

translate(loc.x, loc.y, loc.z);
Dann wird das Objekt auf die Position (0,0,0) gezeichnet. Wir haben also das Koordinatensystem verschoben. Um das Koordinatensystem wieder an zurückzusetzten verwendet man pushMatrix() und popMatrix().

Da Objekte im Raum, aber nicht nur eine Position, sondern auch eine bestimmte Orientierung aufweisen, stellt sich schnell die Frage, wie man die Orientierung im Raum definiert.

In einer Fläche (2 Dimensionen) ist das raltiv einfach. Man braucht dafür nur einen Winkel. Dieser steht normal zur Fläche (siehe Bsp.).

Beispiel: Rotate 2D starte Applet

/** Copyright 2012 Thomas Koberger
*/

// https://lernprocessing.wordpress.com/
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

PVector zero, actual;

void setup() {
size(300,300, P2D);
zero= new PVector(100,0);
rectMode(CENTER);
}

void draw() {
background(200);
stroke(0);

// Verschiebe das Koordinatensystem in den Mittelpunkt
translate(width/2, height/2);
line(0,0,zero.x,zero.y);

// Berechne die Koordinaten des Vektors actual aus der Mausposition
actual=new PVector(cos(map(mouseX,0,width,0,2*PI))*100,sin(map(mouseX,0,width,0,2*PI))*100);

stroke(200,0,50);
line(0,0,actual.x,actual.y);
text("Winkel:"+degrees(actual.heading2D()),height/2-130,width/2-30);

//Verschiebe das Koordinatensystem an die Position von actual
translate(actual.x,actual.y);

//Rotiere das Koordinatensystem
rotate(actual.heading2D());
rect(0,0,20,20);
}

Die Axis-Angle Methode

Im 3 dimensionalen Raum muss neben dem Winkel noch eine Achse angegeben werden, um welche gedreht wird. Wenn ich  sage, meine beiden Unterarme liegen  im Winkel von 90 zueinander, so würde sofort gefragt, in welcher Richtung. Ich könnte nämlich meine Arme in viele verschiedene Positionen drehen, und trotzdem könnten sie immer 90 zueinander liegen. Was wir brauchen ist die Definition einer Achse, um die gedreht wird.

Eine Methode, Axis-Angle Methode genannt, bietet die Möglichkeit beides, nämlich die Achse und den Rotationswinkel zu berechnen. Dabei kommen zwei mathematische Methoden zum Einsatz. Nämlich das sog. Punktprodukt od. Skalarprodukt (gibt den Rotationswinkel an) und das Kreuzprodukt, welches über einen neuen Vektor die Rotationsachse repräsentiert.

Beispiel: Axis-Angle Methode für Processing 2.0

Achtung: läuft mit Processing 2.0b6 nicht im Android Mode!!!

// von Thomas Koberger
/**
* MOUSE               : Rotation um die Achse zwischen Objekten
*
* KEYS
* Space               : neue Zufallswerte für Vektoren
*/
// https://lernprocessing.wordpress.com/
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Damit die Szene rotiert und gezoomt werden kann
// Im Javascript auszukommentieren
import peasy.*;

// Im Javascript Mode auszukommentieren
PeasyCam cam;

PVector zero, left, right;

void setup() {
size(800, 800, P3D);

// Im Javascript auszukommentieren
cam = new PeasyCam(this, 0, 0, 0, 1000);

// Vektoren werden definiert
zero= new PVector(0, 0, 0);
left=new PVector(-width/4+random(-100, 100), random(-100, 100), random(-100, 100));
right=new PVector(width/4+random(-100, 100), random(-100, 100), random(-100, 100));
}

void draw() {
background(50);

// Im Javascript und Android Mode einkommentieren
//translate (width/2,height/2,0);
drawAxes(300);
stroke(255);
fill(200);

// Zeichne Würfel und Linien
line(left.x, left.y, left.z, right.x, right.y, right.z);
pushMatrix();
translate(left.x, left.y, left.z);
box(20);
popMatrix();
pushMatrix();
translate(right.x, right.y, right.z);
box(20);
popMatrix();
pushMatrix();
stroke(200, 0, 50);

// Verschiebe das Koordinatensystem an die Position right
translate(right.x, right.y, right.z);

// Kopie von left erstellen
PVector diff=new PVector(left.x, left.y, left.z);

// Differenzvektor berechnen
diff.sub(right);

// Koordinatensystem verschieben
translate(diff.x/2, diff.y/2, diff.z/2);
diff.normalize();

// Orientierung des mittleren Quaders festlegen
zero=new PVector(0, 0, 1);

// Achse und Winkel berechnen
float angle = acos(zero.dot(diff));
PVector axis = zero.cross(diff);

//Rotiere das Koordinatensystem
rotate(angle, axis.x, axis.y, axis.z);

// Rotiere um die Achse
rotateZ(map(mouseX, 0, width, -PI, PI));
drawAxes(50);
fill(50, 0, 200);
box(20);
popMatrix();
}

// Koordinatensystem zeichnen
void drawAxes(int scl) {
stroke(255, 0, 0);
line(-scl, 0, 0, scl, 0, 0);
text("+x", scl, 0, 0);
text("-x", -scl-30, 0, 0);
stroke(0, 255, 0);
line(0, -scl, 0, 0, scl, 0);
text("+y", 0, scl+30, 0);
text("-y", 0, -scl, 0);
stroke(0, 0, 255);
line(0, 0, -scl, 0, 0, scl);
text("+z", 0, 0, scl+30);
text("-z", 0, 0, -scl+30);
}

// Achse neu ausrichten
// für Javascript Mode auskommentieren
void keyPressed() {
if (key == ' ') {
left=new PVector(-width/4+random(-100, 100), random(-100, 100), random(-100, 100));
right=new PVector(width/4+random(-100, 100), random(-100, 100), random(-100, 100));
}
}

PVector – Einführung von Daniel Shiffman