Processing‎ > ‎CreativeCoding‎ > ‎

Animation

http://sites.prenninger.com/arduino-uno-r3/processing/creativecoding/animation

http://www.linksammlung.info/

http://www.schaltungen.at/

                                                                                            Wels, am 2015-01-15

BITTE nützen Sie doch rechts OBEN das Suchfeld  [                                                              ] [ Diese Site durchsuchen]

DIN A3 oder DIN A4 quer ausdrucken
**********************************************************************************
DIN A4  ausdrucken
*********************************************************



1 Pin uC Interface for HD44780 (and compatible) LC Displays  elektor 130397-11

Aus 1 mach 8

Zwar war man sich eben noch sicher, dass man noch genug Pins übrig haben würde, doch während man entwickelt scheinen diese zu verschwinden. Dann muss man einen „größeren“ Mikrocontroller einsetzen. Oder aber einen Port-Expander bemühen. Letzteres ermöglicht dann auch den Anschluss eines ressourcenfressenden HD44780 (oder kompatiblen Chips) mit einem einzigen Pin. Man braucht dazu lediglich ein Shift-Register des Typs 74HC595 und ein paar Bauteile. Nicht ganz so schnell wie eine „echte“ parallele 8-bit-Schnittstelle, aber gut genug für viele Anwendungen.

http://www.elektor-labs.com/project/1-pin-uc-interface-for-hd44780-and-compatible-lc-displays-130397-i.13598.html?mc_cid=0ab5c85607&mc_eid=c135eea841
http://www.romanblack.com/shift1.htm



11. Animation

Programmierte Bewegung


Kern dieser Lesson ist die Vermittlung der Grundprinzipien der Animation mit Processing.
Ausgehend vom einfachsten Prinzip der Bildfolge, welches oft in der Spielprogrammierung angewendet wird, folgen Erklärungen zu linearer und non-linearer Animation.
Dabei stehen eindimensionale Bewegungen ebenso im Mittelpunkt wie die Bewegung auf Kreisen, Ellipsen und Kreissegmenten mit Hilfe von trigonometrischen Funktionen (Sinus und Kosinus),

1. Bildanimation

Dieser erste Abschnitt steht für die digitale Form eines Daumenkinos.
Durch die schnelle Abfolge von ähnlichen, aber nicht gleichen, Bildern in ausreichend zügiger Geschwindigkeit kommt es zur Wahrnehmung einer Bewegung.
Alle Bilder werden dafür im Teil des setup() geladen und im draw() je nach Bedarf abgebildet.
Die Entscheidung welches der Einzelbilder auf der Zeichenfläche erscheint wird beim automatisierten Ablauf anhand einer Variable gefällt.
Global instanziert bildet sie das Gedächtnis und gibt Aufschluss über die Abspielposition.
Mögliche weitere Einflüsse können aber auch Eingaben durch Tastatur und Maus sein.

Stop Trick Animation in Processing

Vier unterschiedliche Motive einer Szene in der Bart Skateboard fährt werden mit einer Geschwindigkeit von sechs Bildern pro Sekunde angezeigt.
Jedes Bild wird im setup() in eine der vier Bildvariablen geladen, eine weitere globale Variable namens "frame" speichert welches Bild angezeigt werden soll.
Am Ende eines jeden draw() -Durchlaufs wird der Wert von "frame" um eins erhöht.
Wenn dieser größer als fünf ist, kommt es zu einem Zurücksetzen auf eins - die Animation beginnt erneut.


// Processing-Sketch: Bildanimation_Daumenkino_1a.pde
// 4 Bilder bart01..04.jpg MENU > Sketch > AddFile.. > Desktop > bart01.jpg  hochladen

PImage img1;        // globale Variable zum Ablegen der Einzelbilder für die Animation
PImage img2;
PImage img3;
PImage img4; 
int frame;          // globale Variable zum Ablegen der Abspielposition

void setup () {
  size (90, 110);   //  Größe an Bild-Format anpassen
  frameRate (5);    // Setze dir Bildanzahl pro Sekunde auf 6
  frame = 1;        // Beginne mit Bild 1   
  // Lade alle vier bilder
  img1 = loadImage ("bart01.jpg");  // img1 = loadImage ("bart01.png");
  img2 = loadImage ("bart02.jpg");
  img3 = loadImage ("bart03.jpg");
  img4 = loadImage ("bart04.jpg");
}

void draw () {
  if (frame > 4) {   // setzt die Abspielposition auf das erste Bild wenn Programm das fünfte spielen würde (größer 4)
    frame = 1;
  }
// Spielt durch bedingte Abfragen immer das Einzelbild für die aktuelle Position in der Animation ('frame')
  if (frame == 1) { image (img1, 0, 0); }
  if (frame == 2) { image (img2, 0, 0); }
  if (frame == 3) { image (img3, 0, 0); }
  if (frame == 4) { image (img4, 0, 0); }  
  frame = frame + 1;                        // erhöht die Abspielposition um eins
}

4 Einzelbilder gleiche Größe einscannen und als *.jpg speichern.
2mm unten und oben  UND mittig

1.1 Lineare Animation

Da uns die Mittel gegeben sind durch diverse Zeichenfunktionen den Bildaufbau zu bestimmen, können wir grafische Element durch Modifikation der Position bewegen.
Im vorherigen Kapitel, zum Thema:
Globale Variablen, haben wir bereits eine Linie vom oberen Bildschirmrand nach unten fahren lassen.
Diesen Fall der Animation nehmen wir nun genauer unter die Lupe.
Die Linie bewegte sich mit einer konstanten Geschwindigkeit auf der y-Achse.
Zwischen allen Bildern wurde die selbe Distanz zurückgelegt - die Beschleunigung können wir deshalb mit 0 angeben.
Dieser Fakt macht die Animation zu einer linearen Animation:
 »Konstante Zustandsänderung zwischen allen Einzelbildern.
« Erhöhen wir den Weg der zwischen den Einzelbildern beschritten wird, erhalten wir eine schnellere Animation, sie bleibt aber linear.
Alle drei folgenden Beispielen wiederholen die angesprochene Animation aus Kapitel Variablen II.
Beispiel zwei und drei bedienen sich weiterhin der if-Bedingung um Kollisionen mit den Außenseiten des Sketchfensters zu simulieren.


1.1.1 lineare Bewegung, eine Richtung

Gezeichnet wird die gleichförmige Ellipse am Wert der globalen Variable xpos und der Hälfte der Höhe.
In jedem Durchlauf des draw()-Blocks erhöhen wir den Wert von xpos um 1, erhalten damit eine Bewegung des Kreises auf der x-Achse vom linken in den rechten Fensterabschnitt.
Diese Vorgang findet bis zum Schließen des Fensters statt.
Die Position des Kreises würde demnach innerhalb weniger Momente außerhalb des für uns sichtbaren Bereiches sein.
Um dies zu lösen integrieren wir eine Bedingung nach dem Aktualisieren der Position (xpos).
Wir fragen nach dem aktuellen Wert von xpos und schreiten ein, wenn sich der Kreis nicht mehr im Sketchfenster befindet.
Die Intervention schlägt sich im Zurücksetzen des Wertes von xpos auf 0 nieder - die Animation beginnt wieder von vorn.
Um den Kreis auf beiden Seiten (links und rechts) komplett verschwinden zu lassen, testen wir auf xpos < width + durchmesser und setzen der Wert im if-Block auf -durchmesser.

Kreis bewegt sich von links nach rechts


// Processing-Sketch: 1.1.1 lineare Bewegung, eine Richtung_1a.pde

float xpos = 0; // gloable Variable für die Ablage der Position und den Kreisdurchmesser
float durchmesser = 30;  // globale Variable Kreis-Durchmesser
 
void setup () {
  size(500, 100);        // Größe des Grafik-Fensters definieren
  smooth ();             // aktiviere Kantenglättung
}
 
void draw () {
  background (200);      // Hintergrund-Farbe grau
  xpos = xpos + 1;       // Position modifiziere  
// zurücksetzen der Kreisposition auf die linke Bildschirmseite wenn die Position größer als Bildschrimbreite + Durchmesser ist
  if (xpos > width + durchmesser) {
    xpos = -durchmesser;
  }
  ellipse (xpos, height/2, durchmesser, durchmesser);  // Zeichnen des Kreises an die aktuelle Position
}




1.1.2 lineare Bewegung, loop


Als zweiten Schritt geben wir uns die Auflage den Kreis auf der Horizontalen pendeln zu lassen.
Zur Realisierung bedarf es der Information in welcher Richtung wir ihn momentan bewegen sollen.
Dafür legen wir die Variable geschwindigkeit an, die ebenfalls den Weg beinhaltet, um welchen der Kreis zwischen den Einzelbilden verschoben wird.
Im vorherigen Beispiel haben wir dies zu Beginn des draw() mit xpos + 1 definiert.
Neben dieser änderung kommt eine zweite if-Bedingung hinzu.
Wir müsse bei Beiden, der linken und rechten Fensterseite, intervenieren, wenn sich der Kreis aus unserer Fläche bewegen sollte.
Diese Eingriffe bewirken eine Umkehrung der Richtung, festgelegt durch geschwindigkeit.
Wenn der Wert von geschwindigkeit positiv ist, bewegt sich der der Kreis von links nach rechts. Ist er negativ, von rechts nach links.
Das Invertieren geschieht durch die Multiplikation mit -1.

Kreis bewegt sich Ping-Pong


Die Änderung der Richtung wird mit zwei if-Bedingungen kontrolliert. 
Innerhalb dieser Bedingungen tauschen wir das Vorzeichen unsers Wertes.
// Processing-Sketch: 1.1.2 lineare Bewegung loop_1a.pde

float xpos;                // gloable Variable für die Ablage der Position
float geschwindigkeit = 2; // Positionsänderung pro Einzelbild
float durchmesser = 30;    // Kreisdurchmesser

void setup () {
  size(500, 100);          // Größe des Sketches definieren
  smooth ();               // aktiviere Kantenglättung
  xpos = width / 2;        // Startposition
}

void draw () {
  background (200);        // Hintergrund leeren

  if (xpos > width - durchmesser / 2) {   // Umkehr der Richtung wenn die Position des Kreises sich am rechten Fensterrand ist
    geschwindigkeit = geschwindigkeit * -1;
  }
  if (xpos <  durchmesser / 2) {   // Umkehr der Richtung wenn die Position des Kreises sich am linken Fensterrand ist
    geschwindigkeit = geschwindigkeit * -1;
  }
  xpos = xpos + geschwindigkeit;   // Position modifizieren, 'geschwindigkeit' kann positiv oder negativ sein (siehe: * -1)
  ellipse (xpos, height / 2, durchmesser, durchmesser);   // Zeichnen des Kreises an die aktuelle Position
}



Eine kompaktere Version ist die Kombination beider Bedingungen innerhalb einer if-Abfrage mit dem logischen || (ODER) Ausdruck.
Im Block multiplizieren wir geschwindigkeit mit -1 und kehren dabei das Vorzeichen um - ohne den absoluten Wert zu modifizieren.
// Processing-Sketch: 1.1.2 lineare Bewegung loop_1a.pde

float xpos;                // gloable Variable für die Ablage der Position
float geschwindigkeit = 2; // Positionsänderung pro Einzelbild
float durchmesser = 30;    // Kreisdurchmesser

void setup () {
  size(500, 100);          // Größe des Sketches definieren
  smooth ();               // aktiviere Kantenglättung
  xpos = width / 2;        // Startposition
}

void draw () {
  background (200);        // Hintergrund leeren
if (xpos > width - durchmesser || xpos < durchmesser / 2) {  // mit ODER 
  geschwindigkeit = geschwindigkeit * -1;
}   
  xpos = xpos + geschwindigkeit;   // Position modifizieren, 'geschwindigkeit' kann positiv oder negativ sein (siehe: * -1)
  ellipse (xpos, height / 2, durchmesser, durchmesser);   // Zeichnen des Kreises an die aktuelle Position
}


1.1.3 lineare Bewegung,

Im Beispiel drei werden alle Elemente, welche die zur Animation der Kugel auf der x-Achse geführt haben, ebenfalls auf die y-Achse angewendet.
D.h. wir benötigen eine weitere Variable yPos zur Ablage der Position und eine Variable yGeschwindigkeit um die Positionsänderung zu beschreiben.
Weiterhin testen wir auf die Ober- und Unterseite des Sketchfensters.
Das Resultat ist ein sich frei bewegender Kreis der von allen Seiten reflektiert wird.


// Processing-Sketch: 1.1.3 lineare Bewegung Billard_1a.pde

float xpos;  // gloable Variable für die Ablage der Position
float ypos;
float xGeschwindigkeit = 2; // PositionsÄnderung pro Einzelbild
float yGeschwindigkeit = 2;
float durchmesser = 20;     // Kreisdurchmesser

void setup () {
  size(405, 240);     // Größe des Grafik-Fensters definieren
  smooth ();          // aktiviere Kantenglättung
  xpos = width / 2;   // X-Startposition
  ypos = height / 2;  // Y-Startposition
}

void draw () {
  background (200);   // Hintergrund hell-grau
  if (xpos > width - durchmesser / 2) {    // rechter Fensterrand
    xGeschwindigkeit = xGeschwindigkeit * -1;
  }
  if (xpos < durchmesser / 2) {             // linker Fensterrand
    xGeschwindigkeit = xGeschwindigkeit * -1;
  }
  if (ypos > height - durchmesser / 2) {    // unterer Fensterrand
    yGeschwindigkeit = yGeschwindigkeit * -1;
  }
  if (ypos < durchmesser / 2) {             // oberer Fensterrand
    yGeschwindigkeit = yGeschwindigkeit * -1;
  }  
  // Position modifizieren, 'geschwindigkeit' kann positiv oder negativ sein (siehe: * -1)
  xpos = xpos + xGeschwindigkeit;
  ypos = ypos + yGeschwindigkeit;
  ellipse (xpos, ypos, durchmesser, durchmesser);   // Zeichnen des Kreises an die aktuelle Position
}



1.2 Non-lineare Animation
Im alltäglichen Leben treffen wir selten auf Bewegungsabläufe linearer Art.
Im Normalfall haben wir es permanent mit beschleunigten bzw. gebremsten Abläufen zu tun.
In diesem Gebiet findet eine positive oder negative Steigerung der Zustandänderungen statt - man bezeichnet dies auch als Beschleunigung.
Jegliches mobiles Gefährt steigert seine Geschwindigkeit aus der Ruhephase und fällt wiederum nicht sofort wieder in den Stillstand zurück.
In dem folgenden Abschnitt beschäftigen wir uns mit der Simulierung dieser Prozesse und versuchen Lösungen für diese Art von profanen Vorgängen zu finden.

1.2.1 non-lineare Animation, zufällig


Um die Schrittweite innerhalb der Animation zu variieren bedienen wir uns im folgenden Beispiel des bekannten random()-Befehls.
Das Resultat summiert auf die aktuelle Position verschiebt den Kreis von Bild zu Bild um unterschiedliche Distanzen.
Ausgeführt erhalten wir eine stockende Animation.
Dies liegt nicht an der Geschwindigkeit unseres Computers, sondern an dem durch random() generierten Chaos in der Berechnung von xpos.
Es ist kann dazu kommen, dass die Position in Bild 14 sieben Pixel erhöht wurde, und in Bild 15 um zwei vermindert.
Unsere Wahrnehmug erkennt darin keine wiederkehrende Merkmale (homogene Tendenz) - außer das sich der Kreis scheinbar nach rechts bewegt.
(Kommentare wurden zu übersichtlichkeit entfernt, siehe: lineare Bewegung - eine Richtung).


// Processing-Sketch: 1.2.1 non-lineare Animation, zufaellig_1a.pde

float xpos = 0;
float durchmesser = 40; // Balldurchmesser

void setup () {
  size(500, 100);       // Größe des Grafik-Fensters definieren
  smooth ();            // aktiviere Kantenglättung
}

void draw () {
  background (255, 180, 0);           // Hintergrund
  xpos = xpos + random (-3, 7); // addiere zufällige Schrittweite auf die aktuelle Position, vorwiegend in positiv
  if (xpos > width + durchmesser) {   // Zurücksetzen der Kreisposition
    xpos = -durchmesser;
  }
  ellipse (xpos, height / 2, durchmesser, durchmesser);   // Zeichnen des Kreises an die aktuelle Position
}




1.2.2 non-lineare Animation, gleichförmig


Für den nächsten Schritt bedarf es einer weiteren Variable.
In ihr legen wir die Zielposition unseres Kreises ab und berechnen in jedem draw()-Durchlauf die momentane Distanz zwischen beiden Punkten.
Im Schritt der Positionsaktualisierung addieren wir keinen fixen oder zufälligen Wert, sondern immer ein 60tel des verbleibenden Weges.
Demnach beginnt die Animation mit einer großen Schrittweite und schwacht mit dem Näherkommen ab.
Da wir jedoch nie unsere Zielposition erreichen werden, muß das Ziel weiter entfernt sein als in der if-Bedingung zum Neustarten abgefragt.


// Processing-Sketch: 1.2.2 non-lineare Animation gleichfoermig_1a.pde

float xpos;
float ziel;
float durchmesser = 30;  // Balldurchmesser

void setup () {
  size(500, 100);        // Größe des Grafik-Fensters definieren
  smooth ();             // aktiviere Kantenglättung

  xpos = 0;
// Zielposition muss größer sein als der in der 'if'-Abfrage getestete Wert, sonst Programmstopp
  ziel = width + durchmesser * 2;
}

void draw () {
  background (150, 255, 0);         // Hintergrund
  float distanz = ziel - xpos;      // momentaner Abstand zwischen aktueller- und Zielposition
  xpos = xpos + distanz / 60;       // addiere ein 60'tel der Distanz auf die aktuelle Position
  if (xpos > width + durchmesser) { // Zurücksetzen der Kreisposition
    xpos = -durchmesser;
  }
  ellipse (xpos, height / 2, durchmesser, durchmesser); // Zeichnen des Kreises an die aktuelle Position
}



1.2.3 non-lineare, gleichförmige Animation + Mausklick


In diesem Beispiel entfallen alle Bedingungen zur Positionskontrolle des Kreises.
Er kann sich nicht aus dem Sketchfensterbewegen - die Position wird immer mit dem Klicken durch die Maus definiert.
Der Kreis folgt also unseren Anweisungen.


// Processing-Sketch: 1.2.3 non-lineare, gleichfoermige Animation+Mausklick_1a.pde

float xpos;   // aktuelle X-Position
float ypos;   // aktuelle Y-Position
float xZiel;  // aktuelles X-Ziel
float yZiel;  // aktuelles Y-Ziel
float durchmesser = 30;  // Ball-Durchmesser

void setup () {
  size(650, 240);      // Größe des Grafik-Fensters definieren
  smooth ();           // aktiviere Kantenglättung
  xpos = 0;            // X-Startposition
  ypos = 0;            // Y-Startposition
  xZiel = width / 2;   // erstes X-Ziel
  yZiel = height / 2;  // erstes Y-Ziel
}

void draw () {
  background (100,200,0);
  float xd = xZiel - xpos;   // Abstände zwischen aktueller- und Zielposition
  float yd = yZiel - ypos;
  xpos = xpos + xd / 20;     // addiere ein 20'tel der Distanz auf die aktuelle Position
  ypos = ypos + yd / 20;
  ellipse (xpos, ypos, durchmesser, durchmesser);   // Zeichnen des Kreises an die aktuelle Position
}

void mousePressed () {  // Wenn die Maus gedrückt wurde
  xZiel = mouseX;       // setze neue X-Zielposition
  yZiel = mouseY;       // setze neue Y-Zielposition
}



1.2.4 non-lineare Animation, trigonometrische Basis


Es existieren bereits einige mathematische Grundlagen um gleichförmige Animationsabläufe zu simulieren.
Zwei pragmatische sind die aus dem Matheunterricht bekannten Sinus & Kosinus, zur Winkel- und Kreisberechnung.
  • cos() Kosinuswert für einen gegeben Winkel cos()
  • sin() Sinuswert für einen gegebenen Winkel sin()
Im Bereich von zwei PI (Kreiszahl) bewegt sich die Kurve einmal durch den kompletten Bereich von 0 zu 1 und wieder zurück.
Diesen Wert sehen wir als relative Position auf unserer x-Achse - multiplizieren ihn demnach mit der Breite unserer Sketches.
Der Kurvenverlauf der Sinuskurve auf der y-Achse spiegelt folglich die Position des Kreises wieder.
Führen wir die folgenden Zeilen aus, ähnelt das Resultat der Draufsicht eines Pendels.
An der linken und rechten Fensterseite kommt es zu einem Abbremsen - in der Mitte besitzt der Kreis seine maximale Geschwindigkeit.
Die Variable pos wird zur Steuerung der Bewegung genutzt.
Sie beinhaltet einen Wert zwische 0 und zwei PI, ein vollständiger Kurvendurchlauf. Momentan addieren wir in jedem draw()-Durchlauf 0.1 auf pos.
Je größer der Summand, so schneller der Ablauf der Animation.


// Processing-Sketch: 1.2.4 non-lineare Animation trigonometrische Basis_1a.pde

float pos = 0;  // dient zur Ablages des Animationsfortgangs (beinhaltet aber nicht die exakte Position)
 
void setup () {
  size(800, 100);    // Größe des Grafik-Fensters definieren
  smooth ();         // aktiviere Kantenglättung
  fill(255,0,0);     // Kreis Füll-Farbe rot
}
 
void draw () {
  background (200);  // Hintergrund Farbe
  if (pos > PI * 2) { // setze 'pos' auf 0 zurück wenn die Sinuskurve einmal durchlaufen ist
    pos = 0;
  }   
  pos = pos + 0.1;    // erhöhe 'pos' pro Bild um 0.1 'pos' bewegt sich immer zwischen 0 und 2*PI
  float xZentrum    = width / 2;   // Pendelzentrum, zwischen positivem und negativem Kurvenabschnitt
  float xAmplitude  = width / 2 * sin (pos);   // momentaner Ausschlag des Pendels
  float xpos        = xZentrum + xAmplitude;   // Summe ist die momentane Position   
  ellipse (xpos, height / 2, 30, 30);          // Zeichnen des Kreises an die aktuelle Position
}



1.3 Kreisbewegung

Diese Art der Bewegung findet auf einem imaginären Kreis statt.
Auf der Grundlage der Position, des Radius und der Winkelangabe kann die Koordinate auf der Kreisbahn bestimmt werden.
Processing bietet die trigonometrischen Funktionen sin() & cos() an, welche für gegebenen Winkel und Radius den Abstand zum Zentrum des Kreises berechnen lassen.
  • cos() gibt den Kosinuswert zwischen -1 und 1 für ein gegebenes Bogenmaß zurück. cos()
    println (cos (PI / 3));  // 0.49999997
    println (cos (PI));  // -1.0
  • sin() gibt den Sinuswert zwischen -1 und 1 für ein gegebenes Bogenmaß zurück. sin()
    println (sin (PI / 3));  // 0.86602545

1.3.1 Gleichförmige Kreisbewegung


Im folgenden Beispiel hält die globale Variable angle die Position der Ellipse auf der Kreisbahn.
Der Wert 0.004 entspricht der Rotationsgeschwindigkeit um das Zentrum xcenter und ycenter
und wird jedes Einzelbild auf angle aufgeschlagen, es kommt zur Kreisbewegung.
Die genauen Koordinaten werden in jedem Bild berechnet und in x und y abgelegt.




// Processing-Sketch: 1.3.1 Gleichfoermige Kreisbewegung_1a.pde

float xcenter;   // Mittelpunkt auf der x-Achse
float ycenter;   // Mittelpunkt auf der y-Achse
float rad = 75;  // Radius der Kreisbahn
float angle;     // aktueller Rotationswinkel

void setup () {
  size(320,240);       // Größe des Grafik-Fensters definieren
  smooth ();           // aktiviere Kantenglättung
  noStroke();          // Linienfarbe deaktivieren
  background (255);    // Hintergrund Farbe weiß
  xcenter = width / 2; // Rotationsmittelpunkt-X
  ycenter = height/2;  // Rotationsmittelpunkt-Y
}

void draw () {
  fill (255, 10);                         // Löschfarbe weiß, transparent
  rect (75,35, width-150, height-70);     // Löschfenster   rect (0, 0, width, height);
  angle += 0.04;                          // Verschieben des Rotationswinkels
  float x = xcenter + cos (angle) * rad;  // Berechnung der aktuellen X-Position
  float y=ycenter+sin(angle)*rad;         // Berechnung der aktuellen Y-Position
  fill (0);                               // Strich-Farbe schwarz
  ellipse (x,y, 30,30);                   // Zeichnen des Kreises
}


1.3.2 Bewegung auf Kreisabschnitt in Processing
Um nur einen bestimmten Abschnitt der Kreisbahn abzufahren muss der Wertebereich des Winkels eingeschränkt werden.
Der Startwert für angle ist in diesem Sketch mit dem Wert von angleStart angegeben.
Wie im vorherigen Beispiel wird der Winkel von Bild zu Bild um einen festen Betrag erhöht.
Direkt nach dem erhöhen des Winkels überprüft eine if-Abfrage ob das Limit überschritten wurde. Ist dies der Fall, wird der Winkel wieder auf den Startwert angleStart zurückgesetzt.
Der Wertebereich setzt sich demnach wie folgt zusammen:
  • Startwinkel der Betrag der Variable angleStart
  • Endwinkel der abgefrage Winkel in der if-Bedingung


// Processing-Sketch: 1.3.2 Bewegung auf Kreisabschnitt_1a.pde

float xcenter;           // Mittelpunkt auf der x-Achse
float ycenter;           // Mittelpunkt auf der y-Achse
float rad = 75;          // Radius der Kreisbahn
float angle = 0;         // aktueller Rotationswinkel
float angleStart = 0.4;  // Startwinkel

void setup () {
  size(320, 240);    // Größe des Grafik-Fensters definieren
  smooth ();         // aktiviere Kantenglättung
  noStroke ();       // Linienfarbe deaktivieren
  background (255);  // Hintergrund Farbe weiß
  xcenter = width/2;     // Rotationsmittelpunkt-X
  ycenter = height / 2;  // Rotationsmittelpunkt-Y
  angle = angleStart;  // Verschieben des Rotationswinkels
}

void draw () {
  fill (255, 10);             // Löschfarbe weiß, transparent
  rect (0, 0, width, height); // Löschfenster
  angle += 0.04;   // Verschieben des Rotationswinkels
  if (angle > PI + angleStart) { // Zurücksetzen des Winkels wenn Endwinkel von einem PI erreicht ist
    angle = angleStart;
  }

  float x = xcenter + cos (angle) * rad;  // Berechnung der aktuellen X-Position
  float y = ycenter + sin (angle) * rad;  // Berechnung der aktuellen Y-Position
  fill (0);                 // Strich-Farbe schwarz
  ellipse (x, y, 30, 30);   // Zeichnen des Kreises
}



1.3.3.Bewegung auf einer Ellipse in Processing

Der Unterschied zwischen einem gleichförmigen Kreis und einer Ellipse besteht in der Varianz des Radius.
Die Punkte auf einem Kreis besitzen immer den selben Abstand zum Zentrum des Kreises
- bei einer Ellipse verändert sich dieser Abstand (Radius) von Punkt zu Punkt.
In diesem Beispiel wurden deshalb zusätzlich die beiden Variablen xRad und yRad eingeführt.
Für jedes Bild wird jeweils der Radius der Ellipse für die x und y-Achse berechnet.
Dabei kann durch das Verschieben der Maus deren Betrag geändert werden.



/ Processing-Sketch: 1.3.3.Bewegung auf einer Ellipse je nach Mausposition_1a.pde

float xcenter;    // Mittelpunkt auf der x-Achse
float ycenter;    // Mittelpunkt auf der y-Achse
float rad=75;     // Radius der Kreisbahn
float angle;      // aktueller Rotationswinkel

void setup () {
  size(200,200);        // Größe des Grafik-Fensters definieren
  smooth ();            // aktiviere Kantenglättung
  noStroke();           // Linienfarbe deaktivieren
  background(0);        // Hintergrund Farbe schwarz
  xcenter = width / 2;  // Rotationsmittelpunkt-X
  ycenter=height/2;     // Rotationsmittelpunkt-Y
}

void draw () {
  fill (235, 40);                              // Löschfarbe hell-weiß, transparent
  rect (0, 0, width, height);                  // Löschfenster
  angle += 0.04;                               // Verschieben des Rotationswinkels
  float xRad = ((float) mouseX / width) * rad; // Berechnen des Radiuses für X-Achse
  float yRad = ((float) mouseY/height)*rad;    // Berechnen des Radiuses für Y-Achse
  float x = xcenter + cos (angle) * xRad;      // Berechnung der aktuellen X-Position
  float y = ycenter+sin(angle)*yRad;           // Berechnung der aktuellen Y-Position
  fill (0);                                    // Strich-Farbe schwarz
  ellipse (x,y, 30,30);                        // Zeichnen des Kreises
}

1.3.4 Bewegung auf einer Spirale in Processing

Ãhnlich wie bei der Ellipse finden wir bei der Spirale einen 'dynamischen' Faktor welche die Form unserer Kreisbewegung beeinflusst.
Für jeden Schritt um welchen der Winkel verschoben wird, erhöhen wir ebenfalls den Radius rad um den Wert 0.2.
Dieser Wert beschreibt wie dicht oder offen unsere Spirale wächst.
Verglichen mit dem Beispiel der gleichförmigen Kreisbewegung ist dies der einzige Unterschied, auch wenn das Resultat komplexer erscheint.




// Processing-Sketch: 1.3.4 Bewegung auf einer Spirale_1a.pde

float xcenter;   // Mittelpunkt auf der x-Achse
float ycenter;   // Mittelpunkt auf der y-Achse
float rad = 0;   // Radius der Kreisbahn
float angle=0;   // aktueller Rotationswinkel

void setup () {
  size(320, 240);       // Größe des Grafik-Fensters definieren
  smooth ();            // aktiviere Kantenglättung
  noStroke ();          // Linienfarbe deaktivieren
  background (0);       // Hintergrund Farbe schwarz
  xcenter = width / 2;  // Rotationsmittelpunkt-X
  ycenter = height / 2; // Rotationsmittelpunkt-Y
}

void draw () {
  fill (150, 10);                        // Löschfarbe hell-grau, transparent
  rect (0, 0, width, height);            // Löschfenster
  angle += 0.04;                         // Verschieben des Rotationswinkels
  rad += 0.2;                            // Vergrößern des Radius pro Bild
  float x = xcenter + cos(angle)*rad;    // Berechnung der aktuellen X-Position
  float y = ycenter + sin (angle) * rad; // Berechnung der aktuellen Y-Position
  fill (200, 250, 0);                    // Strich-Farbe
  ellipse (x, y, 15, 15);                // Zeichnen des Kreises
}


1.3.5 Oszillierende Bewegung in Processing


Um realistisch bzw. organisch anmutende Bewegungen zu formulieren lohnt sich ein Blick auf sin() und cos().
Als trigonometrische Funktionen ermöglichen sie im Zusammenhang mit der Kreiszahl PI sauber erscheinende Schwingungen.
Der Bewegungsverlauf verhält sich dabei wie der Graf einer Sinus-, bzw. Kosinusfunktion.





// Processing-Sketch: 1.3.5 Oszillierende Bewegung Herzpumpen_1a.pde

float radBase = 60;     // Basisradius
float radDiff = 50;     // Differenzradius
float oscillation = 0;  // aktueller Zustand

void setup () {
  fill (255,0,255);     // Strichfarbe magenta
  smooth ();            // aktiviere Kantenglättung
}

void draw () {
  background (255);     // Hintergrund Farbe weiß  
  oscillation += 0.1;   // Zähler der oszillierenden Schwingung im Bereich zwischen 0 und zwei PI
  if (oscillation > TWO_PI) {
    oscillation = 0;
  }
  float rad = radBase + radDiff * sin (oscillation);   // Berechne den Kreisradius
  ellipse (width/2, height/2, rad, rad);               // Zeichne den Kreis PosX-Y Dm Breite-Höhe
}


*********************************************************

12. Transformationen

Verschieben, Drehen, Skalieren


Transformationen beschreiben die Änderung des Zustands (visueller) Elemente.
Diese Lesson führt die drei Grundeigenschaften Translation, Rotation und Skalierung sowie deren Manipulation ein.
Dabei steht die Ausrichtung visueller Elemente zueinander und zu ihrer Umgebung mit Hilfe der Befehle pushMatrix() und popMatrix() im Vordergrund.

Bisher haben wir in Processing lediglich die Möglichkeit kennengelernt, einfache geometrisch Grundformen in der Sketch anhand der aufgerufenen Methoden zu positionieren.
Auch bei Bildern und Texten funktionierte das nur nach diesem Prinzip:

// zeichne ein Rechteck an der Position x, y
rect(x, y, breite, höhe);

// zeichne ein Bild an der Position x, y
image(img, x, y);

// zeichne Text an der Position x, y
text("Creative Coding", x, y);


Die Möglichkeiten bei der Positionierung visueller Elemente sind jedoch schnell erschöpft.
So gibt es bisher z.B. keine Funktion mehrere, gezeichnete Elemente gleichzeitig zu verschieben, zu drehen oder gar zu skalieren.
Im folgenden Kapitel dreht sich deshalb alles um die Funktionen translate(), rotate() und scale().
Diese Funktionen werden unter dem Begriff "Transformation" zusammengefasst.
Eine Transformation eines graphischen Elements beschreibt demzufolge seine Verschiebung, Drehung und Skalierung im Raum.

1. Verschieben


Der wichtigste Bestandteil einer Transformation ist das Versetzen des Ursprungs von dem aus das Element gezeichnet wird.
Das kann über den Befehl translate erreicht werden:
  • translate() verschiebt das gesamte Koordinatensystem der Zeichenfläche um die Parameterangaben von x und y. translate()
    float x = 41;
    float y = 95;
    translate (x, y);
translate() verschiebt dabei nicht nur den Ursprung des nachfolgenden Objekts, sondern den Ursprung aller Objekte, die nachfolgend gezeichnet werden.
Diese Funktionsweise ist u.a. einer der Vorteile von translate(), da gezielt Gruppen von graphischen Elementen verschoben werden können.
Nach dem Aufruf von translate() verschiebt sich also das gesamte zu Grunde liegende Koordinatensystem.
Durch die Parameter von translate() wird die zukünftige x- und y-Position des Koordinatenursprungs angegeben.

2. Rotieren


Über rotate() können alle gezeichneten Element gedreht werden.
Dabei werden Elemente nicht um den Punkt gedreht an dem sie gezeichnet werden, sondern um den Ursprung von dem aus sie gezeichnet werden.
Das heisst, dass der einfache Aufruf von rotate() das gesamte Koordinatensystem der Sketch rotiert.
Der einzige Parameter von rotate() gibt den Grad der Drehung im Bogenmaß an, wobei 2 PI einer vollen Drehung entsprechen.
Um zwischen Bogenmaß und Gradmaß umzurechnen stellt Processing die Funktionen radians() und degrees() zu Verfügung.
  • rotate() dreht das gesamte Koordinatensystem um seinen Ursprung (x:0, y:0) nach dem im Bogenmaß gegebenen Winkel.
  • Eine Drehung von 360 Grad entsprechen dabei 2 PI (6.2831855...). Processing bietet neben dem Ausdruck PI weiterhin HALF_PI und TWO_PI. rotate()
    rotate (1.349);
  • radians() gibt das Bogenmaß für einen Winkel in Grad zurück. radians()
    // 'rad' = Radiant für 45 Grad
    float rad = radians (45);
  • degrees() stellt das Gegenteil von radians() dar - wandelt eine Winkelangabe vom Bogenmaß in Grad. degrees()
    // 'deg' = Grad für PIviertel
    float deg = degrees (PI / 4);

12.2.1.0 Rotieren von Elementen


Es werden drei Dreiecke vom relativ nach unten-links verschobenen Sketchursprung (x:0, y:0) aus gezeichnet.
Zwischen allen Dreiecken drehen wir die gesamte Zeichenfläche um den Wert von Winkel (genau zwei mal).
Da der Winkel bei rotate() mit einem Minus übergeben wird, rotieren wir gegen den Uhrzeigersinn.


// Processing-Sketch: 12.2.1.0 Rotieren von Elementen_1a.pde

void setup () {
  size(380, 380); // Ausgabe-Fenster-Größe
  smooth ();      // Kantenglättung aktivieren
  noStroke ();    // Linienfarbe deaktivieren
}

void draw () {
  background (255);       // Hintergrund weiß
  float winkel = PI*0.09; // Winkeländerung zwischen allen Dreiecken 

  // Koordinatenursprung (0,0) in den Mittelpunkt der Zeichenfläche verschieben
  translate (width * 0.15, height*0.85);

  fill (255,125,0);     // orangenes Dreieck
  triangle (0,40, 300,0, 0,-40);  // Dreieck

  rotate (-winkel);        // Zeichenfläche rotieren
  fill (125,255,0);        // grünes Dreieck
  triangle (0,40, 300,0, 0,-40);  // Dreieck

  rotate (-winkel);        // Zeichenfläche rotieren
  fill (0,125,255, 150);   // blaues Dreieck, transparent
  triangle (0,40, 300,0, 0,-40);  // Dreieck

  rotate (-winkel);        // Zeichenfläche rotieren
  fill (255,255,0);        // gelbes Dreieck
  triangle (0,40, 300,0, 0,-40);  // Dreieck 

  rotate (-winkel);        // Zeichenfläche rotieren
  fill (200);              // graues Dreieck
  triangle (0,40, 300,0, 0,-40);  // Dreieck   

  rotate (-winkel);         // Zeichenfläche rotieren
  fill (255,0,0, 100);      // rotes Dreieck, transparen
  triangle (0,40, 300,0, 0,-40);  // Dreieck  

  rotate (-winkel);         // Zeichenfläche rotieren
  noFill ();                      // Füllfläche deaktivieren
  stroke(0);                      // Linienfarbe schwarz
  triangle (0,40, 300,0, 0,-40);  // Dreieck
}

2.1 Rotieren um den relativen Mittelpunkt

Dadurch dass alle Elemente um den Koordinatenursprung gedreht werden,
muss man vor dem Rotieren mittels translate() den Koordinatenursprung
auf den gewünschten Mittelpunkt verschieben, um ein Objekt um die eigene Achse zu drehen.

12.2.1.1 Rotation um das Zentrum der Zeichenfläche


Schritt eins besteht aus dem ändern des Zeichenmodus für alle mit rect() gezeichneten Elemente.
Wir setzen diesen auf CENTER - nun wir vom Zentrum aus gezeichnet, nicht von der oberen linken Ecke.
Im zweiten Schritt setzen wir den Ursprung unseres Koordinatensystems mit translate() auf die Mitte der Zeichenfläche.
Die sichtbare Fläche der x-Achse geht nun von -250 bis +250; vorher wurde uns der Bereich von 0 bis +500 dargestellt (selbiges wird für die y-Achse durchgeführt).
Wenn wir nun ein Quadrat an der Position x:0, y:0 zeichnen liegt dieses genau in der Mitte des Sketchfensters.
Mit diesen Voraussetzungen können wir beginnen den Teil der Abbildung zu verfassen.
In einer Schleife positionieren wir jeweils zwei Quadrate (unterschiedlicher Größe) an x:0, y:0 und generieren jeweils einen Farbwert.
Am Ende des draw() Blocks erhöhen wird den Winkel um im nächsten Bild eine weitere Verschiebung zu erhalten.
Da eine ganze Umdrehung durch zwei PI (TWO_PI) repräsentiert wird, setzen wir winkel auf 0 zurück.


// Processing-Sketch: 12.2.1.1 Rotation um das Zentrum_1a.pde

int anzahl = 10;      // Anzahl der Quadratpaare
float winkel = 0;     // aktueller Winkel
 
void setup () {
  size(420, 300);     // Ausgabe-Fenster-Größe
  smooth ();          // Kantenglättung aktivieren
  rectMode (CENTER);  // Rechtecke am Mittelpunkt positionieren
  noStroke ();        // Linienfarbe deaktivieren
}
 
void draw () {
  background (200);   // Hintergrund grau
  translate (width / 2, height / 2); // Koordinatenursprung (0,0) in den Mittelpunkt der Zeichenfläche verschieben  
  
  if (winkel > TWO_PI) {             // Winkel auf 0 Grad setzen wenn 360 Grad Überschritten
    winkel = 0;
  }
   
  for (int i=0; i < anzahl; i = i + 1) {      // für jeden Quadratpaar
    rotate (winkel);                          // Koordinatensystem um den Winkel drehen    
    fill (i*20, i*30, 80);                    // großes Quadrat dunkelblau
    rect (0, 0, 180 - i * 16, 180 - i * 16);  // großes Quadrat zeichnen 
    fill (i*23, i*38, 140);                   // kleines Quadrat hell
    rect (0, 0, 180-i*18, 180-i*18);          // kleines Quadrat zeichnen
  }
  
  winkel = winkel + 0.004;                    // Dreh-Winkel erhöhen
}



12.2.1.2 Rotieren um die X, Y & Z-Achse


Bisher haben wir Elemente im kartesischen Koordinatensystem immer auf zwei Achsen positioniert: der horizontalen x-Achse und der vertikalen y-Achse.
In der dritten Dimension kommt die Z-Achse hinzu, auf der wir Objekte quasi zu uns hin- und wegbewegen können.
Im folgenden Beispiel wird eine Linie um die z-Achse rotiert, wodurch man einen räumlichen Effekt erhält.
Sobald man die z-Achse mit einbezieht, muss dem size(weite, höhe) Befehl neben der Weite und Höhe ein dritter Parameter namens OPENGL oder P3D hinzugefügt werden werden.
Dieser bezeichnet einen extra Rendermodus den Processing für die 3D Darstellung benötigt. OPENGL muss über die Zeile import processing.opengl.*; importiert werden, bei P3D ist dies nicht nötig.



// Processing-Sketch: 12.2.1.2 Rotieren um die X, Y & Z-Achse_1a.pde

float winkel=0;          // Winkel-Variable

void setup() {
  size(600, 600, P3D);   // hinzufügen des P3D Rendermodus
  rectMode(CENTER);      // zentriert, mittig
  background(200);       // Hintergrund hell-grau
}

void draw() {
  scale(2);              // alle Elemente um 200% vergrössern 
  if (mousePressed) {    // mit Maus klick Grafik zeichnen
    winkel=winkel+0.007;
  } else {
    winkel=winkel;
  }
  rotateX(120);           // um die X-Achse rotieren
  translate(146,175);     // Startpunkt verschieben

  for (int i=0; i<350; i=i+5) {
    stroke(255,0,0, 18);  //Strich-Farbe rot, transparent
    rotateZ(winkel);      // um die Z-Achse rotieren
    rotateY(i+winkel);    // um die Y-Achse rotieren 
    line(0, 22+winkel*3, -2, 22+winkel*3);
  }
}

12.2.2 Winkelberechnung zwischen zwei Punkten

Um den Winkel zwischen zwei Punkten zu berechnen, stellt Processing die Funktion zur Verfügung:
  • atan2() [[processing-reference:atan2()]]
Diese Funktion berechnet den Winkel eines Punktes (x, y) ausgehend vom Koordinatenursprung im Bereich von -PI bis PI.
Um also den Winkel zwischen zwei Punkten zu berechnen, muss vor der Verwendung von atan2() der Koordinatenursprung mit translate() verschoben werden.


// Processing-Sketch: 12.2.2 Winkelberechnung zwischen zwei Punkten mit Maus_1a.pde
// Bild bart00.jpg MENU > Sketch > AddFile.. > Desktop > bart00.jpg  hochladen

PImage boid;    // globales Bildobjekt Variable
float xpos = 0; // globale Variablen zum Ablegen der aktuellen Bildposition
float ypos = 0;

void setup () {
  size (600, 266);                       // Ausgabe-Fenster-Größe
  smooth ();                             // Kantenglättung aktivieren
  background (255);                      // Hintergrund-Farbe weiß
  imageMode (CENTER);                    // Bilder zentriert positionieren
  boid = loadImage ("bart00.jpg");       // Bild in 'boid' ablegen
}

void draw () {
  background (255);
  xpos = xpos + (mouseX - xpos) / 10.0;   // nonlineare Bewegung des Bildes zur X-Maus
  ypos = ypos + (mouseY - ypos) / 10.0;   // nonlineare Bewegung des Bildes zur Y-Maus
  translate (xpos, ypos);                 // Koordinatensystem verschieben
  float angle = atan2 (mouseY - ypos, mouseX - xpos) + PI/2;    // Winkel zwischen Maus und Bild berechnen
  rotate (angle);                         // Koordinatensystem rotieren
  image (boid, 0, 0);                     // Bild darstellen
}


3. Skalieren

Genau wie translate() und rotate() wirkt sich scale() auf das gesamte Koordinatensystem aus. Wie der Name vermuten lässt, wird dabei das Koordinatensystem skaliert, also vergrößert oder verkleinert.

3.1 Lösen der Zeichenfläche

Da sich alle Funktionen für die Transformation immer auf das gesamte Koordinatensystem auswirken, gibt es in Processing die Möglichkeit mehrere Korrdinatensysteme von einander abzugrenzen, und so einzelne Transformation unabhängig voneinander anzuwenden.
Die beiden Funktionen die dazu nötig heissen:
  • pushMatrix() erzeugt eine neue Ebene in der Zeichfläche.
  • Diese übernimmt alle aktuellen Eigenschaften (wie Position und Rotation) vom Hintergrund. pushMatrix()

  • pushMatrix(); // erzeugt eine neue Ebene
    translate (90, 0); // verschiebt das Koordinatensystem um 90 Pixel nach rechts
    rect (0, 0, 40, 40); // zeichnet ein Rechteck an x:90, y:0
* popMatrix() verbindet die mit pushMatrix() generierte Ebene (bzw. deren Inhalt) mit dem Hintergrund.
Nach dem Aufruf von popMatrix() gelten die gleichen Bedingungen für Position und Rotation der Zeichenfläche wie vor dem Aufruf von pushMatrix(). popMatrix()

rect (0, 0, 40, 40);  // zeichnet ein Quadrat an x:0, y:0
pushMatrix ();  // erzeugt eine neue Ebene
translate (60, 0);  // verschiebt das Koordinatensystem um 60 Pixel nach rechts
rect (0, 0, 40, 40);  // zeichnet ein Quadrat an x:60, y:0
popMatrix ();  // fügt Ebene und Hintergrund zusammen
rect (0, 70, 40, 40);  // zeichnet ein Quadrat an x:0, y:70

Mithilfe dieser beiden Funktionen können Koordinatensysteme (Matrizen) der Reihe nach gespeichert (push) und wieder abgerufen werden (pop).
Dieses Konzept wird auch als Stack (Stapel) bezeichnet.
Die Wirkungsweise von Stacks ist in ungefähr mit Ebenen in Bildbearbeitungsprogrammen
(z.B. Adobe Photoshop) vergleichbar. pushMatrix() und popMatrix() grenzen also in Processing "Ebenen" ab, von denen jede ihren eigenen Ursprung, eine Ursprungsrotation und eine Skalierung hat, die mit den in dieser Lesson vorgestellten Funktionen definiert werden können (aber nicht müssen!)

12.3.1.1 Ebenen 1
  translate, rotate
In diesem Beispiel erzeugen wir nacheinander vier Ebenen.
Gleichmäßig auf der Zeichenfläche verteilt und einheitlich rotiert werden sie jeweils, nach dem Zeichprozess, mit dem Hintergrund verbunden.
Dabei verwenden wir wieder rectMode(CENTER); um die Quadrate um ihren Mittelpunkt (nicht die obere-linke Ecke) zu drehen.
Da wir mit pushMatrix() und popMatrix() arbeiten, werden die Befehle zum Verschieben und Drehen immer auf den ursprünglichen Zustand des Koordinatensystems angewendet.
Soll bedeuten - nach dem Aufruf von popMatrix() befindet sich der Ursprung der Sketchfläche wieder bei x:0, y:0 und ist nicht gedreht.


// Processing-Sketch: 12.3.1.1 Ebenen 1_1a.pde

void setup() {
  size(500,140);      // Ausgabe-Fenster-Größe
  smooth();           // Kantenglättung aktivieren
  rectMode (CENTER);  // Rechteck-Zeichenursprung auf den Mittelpunkt setzen
}

void draw() {

  noLoop();                  // führe den 'draw' nur 1mal aus
  background (255);          // Hintergrund-Farbe weiß
  float winkel = 0.26;       // Winkeländerung zwischen allen Quadraten

  pushMatrix();              // QUADRAT 1 neue Ebene erstellen
  translate (90, height/2);  // Ebenen-Ursprung nach x:90, y: 70 verschieben
  rotate (winkel*1);         // Ebene um '0.26' rotieren
  rect (0,0, 60,60);         // Quadrat in Ebene zeichnen
  popMatrix();               // Ebene auf Hintergrund reduzieren

  pushMatrix ();             // QUADRAT 2  neue Ebene erstellen
  translate (190, height/2);
  rotate (winkel*2);
  rect (0,0, 60,60);
  popMatrix();

  pushMatrix ();              // QUADRAT 3  neue Ebene erstellen
  translate (290, height/2);
  rotate (winkel*3);
  rect (0,0, 60,60);
  popMatrix();

  pushMatrix ();              // QUADRAT 4  neue Ebene erstellen
  translate (390, height/2);
  rotate (winkel*4);
  rect (0,0, 60,60);
  popMatrix();
}



12.3.1.2 Ebenen 2

Eine Erweiterung von Beispiel 1 stellt dieses Script dar.
Zwei globale Variable (anzahl und rand) dienen zur Kontrolle des produzierten Ergebnisses und können nach Belieben modifiziert werden.
Im draw() Block werden für die Zustandsänderung zwischen den Quadraten die Schritte bei der x-Verschiebung und Winkeländerung berechnet und in xstep und astep abgelegt. Mittels einer for-Schleife wird der schon in Beispiel 1 formulierte Zeichenablauf für jedes Quadrat aufgerufen.
Die Zählvariable i steuert dabei den Umfang der Verschiebung auf der x-Achse und der Rotation um das Zentrum der Elemente.
Weiterhin wird die Füllfarbe in jedem Durchlauf definiert - wir erhalten einen groben Farbverlauf.
Dieses Beispiel demonstriert auf anschauliche Weise die "Macht" von Schleifen.
Mit deutlich weniger Aufwand erhalten wir ein komplexere Komposition.



// Processing-Sketch: 12.3.1.2 Ebenen 2_1a.pde

int anzahl = 80;      // Anzahl der Quadrate
float rand = 40;      // Randbreite auf der x-Achse
 
void setup () { 
  size(600, 100);     // Grafik-Ausgabe-Fenster-Größe  
  smooth ();          // Kantenglättung aktivieren 
  rectMode (CENTER);  // Rechteck-Zeichenursprung auf den Mittelpunkt setzen
}
 
void draw() {
  noLoop();                                  // führe den 'draw' nur 1x aus
  background (255);  
  float xstep = (width - rand*2) / anzahl;   // x-Verschiebung pro Quadrat  
  float astep = TWO_PI / (anzahl - 1);       // Winkel-Verschiebung pro Quadrat   
  for (int i=0; i < anzahl; i = i + 1) {    
    pushMatrix ();                           // neue Ebene erstellen   
    translate (rand + i*xstep, height / 2);  // Ebeneursprung verschieben   
    rotate (i*astep);                        // Ebene um '0.26' rotieren   
    fill (i*13, i*3, i*4);                   // Füllfarbe mittels definieren    
    rect (0,0, 50,50);                       // Quadrat in Ebene zeichnen    
    popMatrix ();                            // Ebene auf Hintergrund reduzieren
  }
}

4. Relative Positionierung

Größenverhältnisse und Positionen können auf Basis eines Faktors (Zoomstufe) in Verhältnisse gesetzt werden. Das Resultat ist eine Verkleiner- bzw. Vergrößerung eines existierenden Systems.
Im Beispiel zeichnen wir ein Rechteck an die Position x:80, y:60 mit der Größe w:100, h:45.
Die Position der Maus im Sketch wird auf die Lage eines Kreises im von uns gezeichneten Rechteck übertragen.
Wenn sich der Cursor im Fenster oben-links befindet, ist der Kreis ebenfalls in der oberen linken Ecke des Vierecks positioniert.
In allen Fällen wir die Kreisposition relativ zur Mausposition im Programmfenster berechnet.
Die Größenverhältnisse beider Flächen müssen nicht übereinstimmen.

12.4.1 Relative Positionierung



// Processing-Sketch: 12.4.1 Relative Positionierung mit Maus_1a.pde

float boxWidth  =  80;  // Ausschnittbreite
float boxHeight =  60;  // Ausschnitthöhe
float boxX      = 100;  // Ausschnitt x-Position
float boxY      =  45;  // Ausschnitt y-Position

void setup () {      
  size(480, 240);      // Ausgabe-Fenster-Größe
  smooth();            // Kantenglättung aktivieren
}

void draw () {
  background (235);                                       // Hintergrund-Farbe grau                                              
  noFill ();                                              // Füllfläche deaktivieren
  stroke (100);                                           // Linien-Farbe dunkel-grau
  rect (boxX,boxY, boxWidth,boxHeight);                   // Zeichne Rechteck-Ausschnitt
  float x = boxX + boxWidth * ((float) mouseX / width);   // X-Kreisposition im Ausschnitt
  float y = boxY + boxHeight*((float) mouseY / height);   // Y-Kreisposition im Ausschnitt
  fill(0);                                                // Füll-Farbe schwarz
  noStroke();                                             // Linien-Farbe deaktivieren
  ellipse (x,y, 14,14);                                   // Zeichne Kreis
  println("mouseX = " + mouseX + "  mouseY = " + mouseY); // Nachrichten-Fenster-Ausgabe 
}



*********************************************************

13. Arrays

Datenreihen

Thema der Lesson ist die Einführung in Arrays - Datenreihen.

Nach einer kurzen Wiederholung zu Variablen und Datentypen werden der Aufbau und Umgang mit Arrays erläutert, sowie praktische Beispiele aus der Animation fortgeführt die die Verwendung von Arrays verdeutlichen.


1. Wiederholung: Datentypen

Durch die Einführunf von Variablen (Variablen I & Variablen II) haben wir die Möglichkeit kennen gelernt unterschiedlich Formen von Daten zu speichern, auszulesen und zu verändern. Folgende Datentypen kennen wir bereits:
  • Interger - simple Ganzzahl, beispielsweise 0, 1, 8, -25, etc. int
    int number = 10;
  • Float - gebrochene Zahl (Fließkommazahl).
  • Die Trennung erfolgt hierbei durch einen Punkt, nicht wie im Deutschen gewohnt durch ein Komma. float
    float number = 12.5819;
  • Character - speichert ein einzelnes Zeichen in der Unicode - Codierung, z.B.: 'a', '?', 'ä'.
  • Beachte die einzelnen Anführungszeichen um das Zeichen auf der rechten Seite des Istgleich! char
    char a = 'a';
  • Boolean - Bool'scher Wert kann nur einer "wahr" - true oder "nicht wahr" - false beinhalten.
  • Wir treffen diesen Datentyp meist bei if-Bedingungen an. boolean
    boolean nice = true;
  • Color - beinhaltet die Rot-, Grün- und Blauanteiligkeit (optional den Alphakanal) einer Farbe (color(43, 31, 22), color(44, 199, 199), color(235, 223, 167), etc.) color_datatype
    color pink = color(255, 0, 255);
Diese Datentypen werden auch primitiv genannt, da ihr Vorkommen, ihre Struktur und ihre Größe in der verwendeten Programmiersprache fest verankert sind.
Beispielsweise kann eine Variable vom Typ int immer nur einen ganzzahligen Wert beinhalten.
Dieser Fakt hat in unseren Programmen bisher kein Hindernis dargestellt - erlaubte uns aber, speziell im Bereich der Animation, nur eine stark begrenzte Anzahl von Elementen zu bearbeiten.
Neben den primitiven Datentypen gibt es noch die sogenannten zusammengesetzten Datentypen. Ihre Größe und ihr Umfang ist nicht vorgegeben.
Ihre Struktur kann in einigen Fällen verändert werden, da sie im Grunde aus primitiven Datentypen zusammengesetzt sind.
Einen zusammengesetzten Datentypen haben wir bereits bei der Arbeit mit Text kennengelernt:
  • String - Kette aus Zeichen in Unicode - Codierung ("Creative", "Coding", "Creative Coding", etc.
  • Umschlossen von doppelten Anführungszeichen!) String
    String text = "too much!";

2. Arrays

Variablen sind das Gedächtnis unserer Programme.
Wir legen dort Informationen (Texte, Zahlen, Ja/Nein) ab um sie im weiteren Ablauf wiederverwenden zu können bzw. zu modifizieren.
Die primitiven Variablen haben uns erlaubt eine Information pro deklarierte Variable zu speichern - dies ist gut, aber sehr wenig.
Um Programme flexibel zu gestalten, beschäftigen wir uns nun mit dem Erstellen und Verarbeiten von sogenannten Arrays.
Arrays sind Listen, Reihen von Daten (Array = engl. Reihe).
Ein Array besteht aus einer von uns bestimmten Anzahl von Feldern, welche alle einen Wert des gleichen Datentyps speichern können.

13.2.0 Beispielsweise die Positionen von fünf Quadraten:


// Sketchsettings
fill(0);
stroke(0);
 
// Erstellen und Befüllen der Variablen
float rSize = 20.0;
float pos1 = 0.0;
float pos2 = 20.0;
float pos3 = 40.0;
float pos4 = 60.0;
float pos5 = 80.0;
 
// Zeichnen der Rechtecke nach den
// Werten der Variablen
rect(pos1, pos1, rSize, rSize);
rect(pos2, pos2, rSize, rSize);
rect(pos3, pos3, rSize, rSize);
rect(pos4, pos4, rSize, rSize);
rect(pos5, pos5, rSize, rSize);


// Processing-Sketch: 13.2.0 Positionen von 5 Quadraten_1a.pde

size(430, 200);   // Grafik-Ausgabe-Fenster-Größe  
//fill(0);        // Füllfarbe schwarz
stroke(0);        // Strichfarbe schwarz
strokeWeight(2);  // Strichbreite

// Erstellen und Befüllen der Variablen
float rSize = 40.0;  // Quadratgröße 20.0
float pos1 = 0.0;     // Position Quadrat 1
float pos2 = 20.0;
float pos3 = 40.0;
float pos4 = 60.0;
float pos5 = 80.0;

// Zeichnen der Rechtecke nach den Werten der Variablen
rect(pos1, pos1, rSize, rSize);
rect(pos2, pos2, rSize, rSize);
rect(pos3, pos3, rSize, rSize);
rect(pos4, pos4, rSize, rSize);
rect(pos5, pos5, rSize, rSize);


Diese Schreibweise mag für fünf Elemente noch erträglich sein, ist aber beispielsweise bei 100 Elementen unbrauchbar (erst recht wenn wir die Positionen über die Zeit hinweg verändern wollen!).
Hier kommen Arrays ins Spiel und verkürzen den Code beachtlich.

13.2.1 Deklarieren und Initialisieren

Arrays werden in folgender Form notiert:

// deklariere ein float Array
float[] pos;

Die beiden eckigen Klammern hinter der Datentypbezeichnung markieren, dass es sich hierbei um ein Array von float-Werten handelt.
Als nächstes muss das Array initialisiert werden, d.h. wir bestimmen wie viele Werte das Array umfassen soll.
Dies geschieht mithilfe eines neuen Begriffs in Processing, dem new.
Mithilfe von new erzeugen wir ein 'neues' Array an float-Werten, in das wir ab sofort Werte speichern können.
Als letztes geben wir noch die Anzahl an Werten an, die in dem Array gespeichert werden sollen.
Die gesamte Zeile sieht dann wiefolgt aus:

// deklariere und erzeuge ein float-Array mit 5 Werten
float[] pos = new float[5];

Die '5' innerhalb der zweiten eckigen Klammern bezieht sich also auf die 5 float-Werte die ab sofort in dem Array gespeichert werden können.

13.2.2 Schreiben und Lesen

In einem so initialisierten Array können also ab sofort Werte gespeichert und ausgelesen werden.
Dabei hat jeder Wert einen eigenen Index - beginnend bei 0 - über den auf den Wert an der jeweiligen Stelle zugegriffen werden kann.
Beim Zugriff auf das Array werden wieder die eckigen Klammern benutzt:

// Processing-Sketch: 13.2.2 Schreiben und Lesen_1a.pde
// Deklarieren und Erzeugen eines float-Array mit der Möglichkeit 5 Werte ablegen zu können

float[] pos = new float[5];
// Befüllen des Arrays mit Werten. Beginnend bei 0.
// Das letzte Feld im Array sprechen wir deswegen mit Länge-1 an. 
// Array mit 5 Feldern --> 5-1 = 4

pos[0] = 1.255;      // speichere den Wert  an der ersten Stelle
pos[1] = -2.5;       // speichere den Wert  an die zweite Stelle
pos[2] = 120.555;    // speichere den Wert  an die dritte Stelle
pos[3] = -259999999; // speichere den Wert  an die zweite Stelle
pos[4] = 12000000;   // speichere den Wert  an die dritte Stelle

println (pos[0]);    // auslesen des 1. Wertes, Ausgabe in der Konsole
println (pos[1]);    // auslesen des 2. Wertes, Ausgabe in der Konsole
println (pos[2]);    // auslesen des 3. Wertes, Ausgabe in der Konsole
println (pos[3]);    // auslesen des 4. Wertes, Ausgabe in der Konsole
println (pos[4]);    // auslesen des 5. Wertes, Ausgabe in der Konsole


Ausserdem gibt es die Mölichkeit Arrays mit festen Werten zu erzeugen.
Dazu werden sie nach der Deklaration in geschweiften Klammern, getrennt durch Kommata, geschrieben.
Damit ersparen wir uns die Zeilen für das Befüllen des Arrays, können unseren Quelltext aber schlechter lesen:

// Processing-Sketch: 13.2.2 Schreiben und Lesen_1a.pde
// Erstelle ein float-Array mit 5 Feldern und befülle diese sofort mit den Werten

float[] pos = {1.2, -2.5, 12.5, 100000, -309999};
 
// gibt die Werte in der Konsole aus
println (pos[0]);  
println (pos[1]);  
println (pos[2]);   
println (pos[3]);   
println (pos[4]);


Unter Verwendung dieser Schreibweise sieht das obere Beispiel nun wiefolgt aus:


// Processing-Sketch: 13.2.2 Zeicheneinstellungen_1a.pde

fill (255, 0, 0);    // Füllfarbe Rot
stroke (0);          // Strich-Farbe schwarz

float rSize = 20.0;  // Varaibel für die Quadratgröße
float[] pos = { 0.0, 20.0, 40.0, 60.0, 80.0 }; // float-Array mit Position der Rechtecke

// Zeichnen der Rechtecke, x- und y-Position wird dabei dem Array 'pos' entnommen und jeweils für beide Achsen eingesetzt.
rect(pos[0], pos[0], rSize, rSize);
rect(pos[1], pos[1], rSize, rSize);
rect(pos[2], pos[2], rSize, rSize);
rect(pos[3], pos[3], rSize, rSize);
rect(pos[4], pos[4], rSize, rSize);

13.2.3 Größe

Jedes Array bietet die Möglichkeit über die Eigenschaft length seine Größe abzufragen.
Die Schreibweise dazu sieht wiefolgt aus:

float[] pos = {0.0, 20.0, 40.0, 60.0, 80.0};
println (pos.length); // gibt '5' in der Konsole aus

Die Länge eines Arrays ist demnach eine Eigenschaft auf die mit der Punkt-Schreibweise zugegriffen werden kann
(wie z.B. width oder height von Bildern, als img.width).

13.2.4 Rechenoperationen

Modifizieren und Neudefinieren von Werten erfolgt bei Werten innerhalb eines Arrays wie bei den uns bekannten einfachen Variablen.
Links des Istgleich (=) befindet sich das Feld, wessen Wert wir ändern wollen.
Bei einer Variable war dies die Nennung dieser durch den Variablennamen.
Um ein Feld in einem Array anzusprechen, bedienen wir uns, wie oberhalb bereits erwähnt, der eckigen Klammern.

// Processing-Sketch: 13.2.4 Rechenoperationen_1a.pde

float[] pos = {0.0, 20.0, 40.0};
 
pos[0] = pos[1] + pos[2]; // legt die Summe aus Feld 2 und 3 in Feld 1 ab.
pos[2] = 91.41;           // definiert den Wert des dritten Feldes mit dem Wert '91.41' neu
pos[1] = pos[2] / 4;      // definiert den Wert des 2 Feldes mit einem Viertel des 3 Feldes neu

println (pos[0]);         // gibt die Werte in der Konsole aus
println (pos[1]);  
println (pos[2]);



13.2.5 Arrays und for-Schleifen

Die häufigste Verwendung dieser Eigenschaft findet sich bei der Bearbeitung von Arrays mit Hilfe von for-Schleifen.
Um ein Array, unabhänig vom Datentyp, einmal vollständig von Beginn bis Ende durchzulaufen sieht diese Schleife vom Aufbau immer gleich aus:
  1. Da der Wert immer im null'ten Feld abgelegt wird, startet unsere Zählvariable i bei 0
  2. Wir wollen bis zum letzten Wert vordringen, benötigen deswegen die Anzahl von Schleifendurchläufen wie unser Array Felder hat. Informatiker zählen, damit es nicht langweilg wird von 0 an - dem müssen wir uns beugen.
  3. Die Schleife läuft deshalb solange i kleiner als die Anzahl ist.
  4. Unseren letzten Wert erreichen wir mit Arraylänge - 1.
  5. Die Zählvariable muss nach jedem Schleifendurchlauf um 1 erhöht werden um kein Feld des Arrays auszulassen.
Alle drei Regeln kombiniert ergeben folgende Vorlage:

int[] array = {"Alles", "aus", "der", "Liste"};
 
for (int i=0; i < array.length; i = i + 1) {
    print (array[i] + " ");
}

Im folgenden Beispiel wenden wir das Muster in zwei Formen an.
Zuerst füllen wir jedes Feld des Arrays mit einem zufälligen Wert zwischen 0 und der Breite unseres Sketchfensters.
Danach durchlaufen wir es ein zweites Mal und Positionieren unsere Quadrate an den durch random() festgelegten Positionen.


// Processing-Sketch: 13.2.5 Arrays und for-Schleifen_1a.pde

fill(0);    // Füllfarbe schwarz
stroke(0);  // Strich-Farbe schwarz

float rSize = 20.0;
float[] pos = new float[5]; // deklariere und initialisiere das Array

// Gehe mit Hilfe einer for-Schleife durch jede einzelne Position des Arrays.
// Das Ende der for-Schleife wird über die Länge-Eigenschaft des Arrays bestimmt.
for (int i=0; i < pos.length; i++) {
  pos[i] = random(width);
}

for (int i=0; i < pos.length; i++) {
  rect(pos[i], pos[i], rSize, rSize); // zeichne die Rechtecke nach dem selben Prinzip
}


13.2.6 Array Beispiele

Lineare Bewegung mit Arrays (1D) in Processing
Eines der ersten Beispiele zum Thema Animation war die Lineare, ohne jegliche Beschleunigung auf der x-Achse von links nach rechts.
Dieses kleine Projekt dient uns als Basis um 150 Kreise die selbe Aktion ausführen zu lassen.
Wir übernehmen also den kompletten Quelltext und modifizieren als Erstes die Deklaration unserer globalen Variable xpos zum Speichern der Kreisposition - float xpos wird zu float[] xpos.
Dieses Array ermöglicht uns mehrere Werte in einer Variable ablegen zu können - jeweils eine pro Kreis.
Gleiche Form, andere Name für die y-Achse, damit sich die Kreise nicht überlagern.


float xpos[] = new float[150];
float ypos[] = new float[150];

Im setup() -Block durchlaufen wir mit einer for-Schleife beide Arrays und legen mittels random() eine zufällige Startposition in xpos und ypos für jeden Kreis fest:

for (int i=0; i < xpos.length; i++) {
  xpos[i] = random (width);
  ypos[i] = random (durchmesser / 2, height - durchmesser);
}
Die Struktur des draw() bleibt bestehen. Eine weitere for-Schleife bewirkt die Positionsabfragen und Änderungen auf alle Kreise anzuwenden.


// Processing-Sketch: 13.2.6 Array Beispiele_1a.pde

float xpos[] = new float[150];  // globale Arrays für die Ablage der Position
float ypos[] = new float[150]; 
float durchmesser = 30;

void setup () {
  size(600, 200);
  smooth ();

  for (int i=0; i < xpos.length; i++) {
    xpos[i] = random (width);           // einmaliges Festlegen der x- und y-Position
    ypos[i] = random (durchmesser / 2, height - durchmesser);
  }
}

void draw () {
  
  background (255);   // Hintergrund auf weiß leeren
  // Führe die Schleife für jeden einzelnen Kreis aus, angegeben durch die Länge von 'xpos'
  for (int i=0; i < xpos.length; i++) {   
    xpos[i] = xpos[i] + 1;  // Position modifiziere
    if (xpos[i] > width) {  // Zurücksetzen der Kreisposition auf die linke Bildschirmseite 
      xpos[i] = 0;          // wenn die Position größer als Bildschrimbreite + Durchmesser ist
    }     
    fill (random (210, 255), random (25, 60), 0);
    ellipse (xpos[i], ypos[i], durchmesser, durchmesser);     // Zeichnen des Kreises an die aktuelle Position
  }
}

Lineare Bewegung mit Arrays (2D) in Processing
Array Animation

Beispiel Nummer zwei treibt das oben Durchgeführte auf die Spitze (siehe 1D Version).
Wir legen für alle Attribute: Position, Geschwindigkeit, Durchmesser und Farbe jeweils ein Array mit 100 Feldern an.
Jedem Kreis gehört demnach ein Wert aus jedem Array.
Die Startposition ist bei allen die Selbe (Sketchmitte).
Durch die unterschiedlichen Geschwindigkeiten (können auch negativ sein) springt die Gruppe sofort auseinander und beginnt sich auf der Zeichenfläche zu verteilen.
Wir setzen den Hintergrund nie mit background() zurück und erhalt deshalb diese komplex anmutende Collage.


// Processing-Sketch: 13.2.6 Array Beispiele s-w_1a.pde

float[] xpos = new float[100];             // gloable Variable für die Ablage aller Positionen
float[] ypos = new float[100]; 
float[] xGeschwindigkeit = new float[100]; // Positionsänderung pro Einzelbild
float[] yGeschwindigkeit = new float[100];
color[] farbe = new color[100];            // Farbe der Kreise
float[] durchmesser = new float[100];      // Kreisdurchmesser der Kreise

void setup () {
  size(500, 240);                          // Ausgabe-Fenster-Größe
  smooth ();                               // Kantenglättung aktivieren
  noStroke ();                             // keine Strichfarbe        
  // einmaliges Festlegen aller benötigten Werte zu Beginn des Programms
  for (int i=0; i < xpos.length; i++) {
    xpos[i] = width / 2;
    ypos[i] = height / 2;
    xGeschwindigkeit[i] = random (-1, 1);
    yGeschwindigkeit[i] = random (-1, 1);
    durchmesser[i] = random (10, 40);
    farbe[i] = color (random (100, 255), 20);
  }
}

void draw () {
  // Führe die Schleife für jeden einzelnen Kreis aus, angegeben durch die Länge von 'xpos'
  for (int i=0; i < xpos.length; i++) {
    if (xpos[i] > width - durchmesser[i] / 2) {  // rechter Fensterrand
      xGeschwindigkeit[i] *= -1;
    }
    if (xpos[i] < durchmesser[i] / 2) {          // linker Fensterrand
      xGeschwindigkeit[i] *= -1;
    }
    if (ypos[i] > height - durchmesser[i] / 2) {  // unterer Fensterrand
      yGeschwindigkeit[i] *= -1;
    }
    if (ypos[i] < durchmesser[i] / 2) {          // oberer Fensterrand
      yGeschwindigkeit[i] *= -1;
    }

    // Position modifizieren, 'geschwindigkeit' kann positiv oder negativ sein (siehe: * -1)
    xpos[i] = xpos[i] + xGeschwindigkeit[i];
    ypos[i] = ypos[i] + yGeschwindigkeit[i];

    fill (farbe[i]);                              // Kreisfarbe
    ellipse (xpos[i], ypos[i], durchmesser[i], durchmesser[i]);     // Zeichnen des Kreises an die aktuelle Position
  }
}


13.2.7 Mausverfolger mit Arrays in Processing



Ãhnlichen Effekt kennen wir schon aus vergangenen Kursstunden.
Bei dem Spielen mit der Mausposition hatten wir statt den Hintergrund komplett vollfarbig zu füllen ein semitransparentes Rechteck über die gesamte Zeichenfläche gelegt.
Nun, in Kombination mit background(), speichern wir die letzten 70 Koordinaten der Maus und positionieren an ihnen unsere Kreise.
Zwei Arrays vom Typ float beinhalten wieder die x- und y-Koordinaten.
Bei jedem draw()-Durchlauf verschieben wir alle gespeicherten Werte, in den Arrays xpos und ypos, um einen Index an den Anfang der Liste (Richtung 0).
Dadurch vergessen wir die Informationen über die älteste Position.
An das Ende der Listen kommen die aktuellen Koordinaten der Maus.


// Processing-Sketch: 13.2.7 Mausverfolger mit Arrays_1a.pde

// globale Variablen zum Ablegen der Mausposition(en)
int[] xpos = new int[70];
int[] ypos = new int[70];

void setup() {
  size(500, 500);    // Ausgabe-Fenster-Größe
  smooth();          // Kantenglättung aktivieren
  noStroke();        // keine Strichfarbe
}

void draw() {
  background (255);  // Hintergrund-Farbe grau  
  // wir verschieben in jedem Einzelbild alle bisher gespeicherten Mauspositionen um eins nach 
  // vorn in unserem Array. In das letzte Feld kommt später die aktuelle Position der Maus.
  for (int i=0; i< xpos.length - 1; i++) {
    xpos[i] = xpos[i + 1];
    ypos[i] = ypos[i + 1];
  }
  xpos[xpos.length - 1] = mouseX;   // aktuelle Mausposition in das letzte Feld speichern
  ypos[xpos.length - 1] = mouseY;
  for (int i=0; i < xpos.length; i++) {
    fill (255, 0, 0, 25);   // Füllfarbe rot, transparent
    ellipse (xpos[i], ypos[i], i / 0.75, i / 0.75);   // Zeichnen aller Kreise
  }
}



*********************************************************
14. Funktionen

Modularisierung, Parametrisierung und Wiederverwendbarkeit von Programm-Code


Diese Lesson führt Funktionen, sowie deren Verwendung mit Hilfe von Parametern und Rückgabewerten als grundlegenden Bestandteil komplexerer Programme ein und zeigt anhand mehrerer Beispiele, wie sie dazu beitragen können ein Programm verständlicher und effektiver zu gestalten.

Bei der Einführung von sich endlos wiederholenden Programmen haben wir zwei Bestandteile kennengelernt, die wir bisher nicht weiter hinterfragt hatten.

void setup() {
}
void draw() {
}

Auch die Events für Mouseclicks und Tastatureingaben funktionierten nach dem selben Prinzip.

void mousePressed() {
}
void keyPressed() {
}

Mithilfe dieser Statements können wir unser Programm bisher in "Funktionsbereiche" unterteilen.
Daher kommt auch der Name dieser Bestandteile: Funktionen. Jeder dieser Bereiche hat seine fest definierte Aufgabe, z.B.:
  • setup() - wird einmal zu Beginn des Programms ausgeführt
  • void draw() - wird, je nach frameRate() pro Sekunde ausgeführt (default 60 Frames pro Sekunde) - Hauptteil unseres Processing Programms.
  • mousePressed() - wird einmal ausgeführt, wenn die Maus gedrückt wird (Vergleiche dazu mousePressed)
  • keyPressed() - wird einmal ausgeführt, wenn eine Taste auf der Tastatur gedrückt wird (Vergleiche dazu keyPressed)
Neben den von Processing vordefinierten Funktionen (weitere können über die Reference gefunden werden) haben wir auch die Möglichkeit eigene Funktionen zu schreiben und somit Programme gemäß unseren Anforderungen zu strukturieren.
Dabei schreiben wir für jede Aufgabe die wir über unseren Code realisieren wollen eine Funktion.
Diese können wir dann, wann immer wir sie brauchen, aufrufen. Das hat zwei große Vorteile:
  1. Unser Code wird kürzer und übersichtlicher.
  2. Wir brauchen Codeteile nicht immer und immer wieder zu schreiben, was die Fehlerquote senkt.
Dieses Prinzip, komplexe Aufgaben in ihre Grundbestandteile zu gliedern, um diese später einfach wiederverwenden zu können, bezeichnet man als Modularität.

14.1. Aufbau von Funktionen

Funktionen sind aus folgenden Bestandteilen zusammengesetzt: Name, Parameter (auch mehrere), Rückgabewert. Diese werden im Code wiefolgt notiert:

// ohne Parameter
Rückgabewert Name () {
}
// mit einem Parameter
Rückgabewert Name (Parameter) {
}
// mit mehreren Parametern
Rückgabewert Name (Parameter1, Parameter2) {
}

An der Stelle des Rückgabewertes haben wir bisher nur void (engl. leer) kennen gelernt - kein Rückgabewert.

14.1.1 Funktionen ohne Rückgabewert

In ihrer einfachsten Form erledigen Funktionen ihre Aufgaben (z.B. Zeichnen) und geben kein Ergebnis zurück.
Bisher haben wir nur diese Art von Funktionen kennengelernt.
Bei ihnen steht an Stelle der Rückgabewertes das Schlüsselwort void.

// Processing-Sketch: 14.1.1 Funktionen ohne Rückgabewert_1a.pde
void setup() {
}
void draw() {
  zeichneEllipse();
}
void zeichneEllipse() {
  ellipse(50, 50, 50, 50);
}


14.1.2 Parameter

Um die auszuführende Aufgaben (hier das Zeichnen einer Ellipse) flexibler zu definieren, sodass beim Aufruf
z.B. angegeben werden kann wo, oder in welcher Größe die Ellipse gezeichnet werden soll, kann man sich beliebig vieler Parameter bedienen.
Jede bekannte Variable kann ein Parameter sein.
Die Parameter einer Funktion werden, durch Kommata getrennt, in den runden Klammern notiert.


// Processing-Sketch: 14.1.2 Parameter_1a.pde

void setup() {
}

void draw() {
  zeichneEllipse(25, 25);    // Funktionsaufruf mit zwei Parametern
  zeichneEllipse(75, 75);
}

void zeichneEllipse(float x, float y) { // Funktion mit zwei float Parametern
  ellipse(x, y, 50, 50);
}




14.1.3 Rückgabewerte

Abseits von Aufgaben, wie dem Zeichnen von zusammengesetzten Formen, die keine Ergebnisse für den weiteren Ablauf der Software erzeugen, gibt es oftmals Fälle in denen man ein Ergebnis von einer Funktion zurück erwartet (bspw. die kompliziertere Berechnung von Positionen oder Flächen uvm.).
Die Art des erwarteten Ergebnisses wird durch den Rückgabewert bei der Definition der Funktion definiert.



Statt void also z.B. int, float, String, ….
Innerhalb der Funktion wird dann das Ergebnis nach Abschluss aller nötigen Schritte mit dem Schlüsselwort return zurück zu geben.

Dm je nach Mausposition

// Processing-Sketch:  14.1.3 Rückgabewerte mit Maus_1a.pde

void setup() {
  background(255);
  fill(0);
}
 
void draw() {
  background(255);
  float d = entfernungZumMittelpunkt(mouseX, mouseY);
  ellipse(50, 50, d, d);
}
 
// berechnet die Entfernung von einem Punkt (x, y Parameter) zum Mittelpunkt der Anwendung
float entfernungZumMittelpunkt(float x, float y) {
  float d = dist(x, y, width/2, height/2);
  return d;    // gebe das ergbenis zurück
}



14.2. Anwendungsbeispiele


14.2.1 Kreuze machen

Exemplarisch dient uns als Ausgangspunkt eine Komposition bestehend aus drei auf der Zeichenfläche angeordneten X'en.
Sie unterscheiden sich durch Position, Farbgebung und Strichstärke.
Der Aufbau, die Proportion und Lage der beiden Linien zueinander, verhält sich bei allen X'en gleich.


// Processing-Sketch: 14.2.1 Kreuze machen_1a.pde

void setup () {
  size (350, 240);  // Ausgabe-Fenster-Größe
  background (0);   // Hintergrund-Farbe schwarz
  smooth ();        // Kantenglättung aktivieren
  noLoop ();        // führe den 'draw' nur 1x aus
}

void draw () {

  stroke (80);                // Strichfarbe dunkel-grau
  strokeWeight (20);          // Strichbreite dick
  line (50, 40, 110, 105);
  line (110, 40, 50, 105);

  stroke (210); // Strichfarbe hell-grau   strokeWeight (10); // Strichbreite mittel   line (150, 140, 210, 200);   line (210, 140, 150, 200);   stroke (255); // Strichfarbe weiß   strokeWeight (2); // Strichbreite dünn   line (50, 140, 110, 200);   line (110, 140, 50, 200); }


14.2.2 Modulare Version

Alle veränderbaren Eigenschaften müssen im Funktionskonstrukt variable formuliert werden.
Der Inhalt des Funktionsblocks (zwischen den geschweiften Klammern {.....}) dient zum Verfassen des Regelwerks.
Die wechselnden Charakteristika werden mit Parametern belegt.
Bis auf den Grauton der Strichstärke sind alle Angaben mit dem Datentyp float beschreibbar.
Die Namen der im Kopf der Funktion erzeugten Variablen (Parameter) beginnen alle mit dem Wort the, gefolgt von einem Großbuchstaben.
Dabei handelt es sich nicht um eine durch Processing vorgegebene Notwendigkeit.
Vielmehr soll dadurch die Zugehörigkeit der Variable ersichtlich sein.
Alle mit diesem Begriff beginnenden Variablen sind Parameter einer Funktion, wurden nicht in der Funktion erzeugt bzw. sind nicht global verfügbar.
Mittels dieser Variablen werden im Funktionskörper die notwendigen Zeichenprozesse formuliert.
Da der Inhalt der Variablen die unterschiedlichsten Werte enthalten kann, erzielt man durch variable Aufrufe der Funktion verschiedenartige Xe.


// Processing-Sketch: 14.2.2 Modulare Version_1a.pde

void setup () {
  size (355, 240);  // Ausgabe-Fenster-Größe
  background (0);   // Hintergrund-Farbe schwarz
  smooth ();        // Kantenglättung aktivieren
  noLoop ();        // führe den 'draw' nur 1x aus
}

void draw () {
  drawCross (50,40, 60,80, 20);       // Linie X-Y, Linienbreite
  drawCross (150,140, 60,210, 10);
  drawCross (50,140, 60,255, 2);
}

void drawCross (float theX, float theY, float theSize, int theGrey, float theWeight) {
  stroke (theGrey);                              // Strichfarbe 
  strokeWeight (theWeight);                      // Strichbreite 
  line (theX, theY, theX+theSize, theY+theSize);
  line (theX+theSize, theY, theX, theY+theSize);
}


14.2.3 for-Kombination 1

Nach dem 'manuellen' Aufruf folgt nun das Verpacken der Zeichenanweisung in einer for-Schleife.
Genau 20 mal aufgerufen werden die Xe übereinander und leicht nach rechts gezeichnet.
Neben der Position ändern sich alle weiteren Parameter durch das Einbeziehen der Zählvariable i.
Die Funktionsdefinition und deren Aufruf wurden aus Gründen der Lesbarkeit umgebrochen.
Bei einer großen Anzahl von Parametern bzw. bei Berechnungen innerhalb des Aufrufes kann dies von Vorteil sein.


// Processing-Sketch: 14.2.3 for-Kombination1_1a.pde

void setup () {
  size (320, 240);   // Ausgabe-Fenster-Größe
  background (200);  // Hintergrund hell-grau
  smooth ();         // Kantenglättung aktivieren
  noLoop ();         // führe den 'draw' nur 1x aus
}

void draw () {
  for (int i=0; i < 20; i++) {
    drawCross (90 + i, 
    70 - i / 2, 
    100 + i, 
    70 + i * 4, 
    20 - i);
  }
}

void drawCross (float theX, float theY, float theSize, int theGrey, float theWeight) {
  stroke (theGrey);                              // Strichfarbe 
  strokeWeight (theWeight);                      // Strichbreite 
  line (theX, theY, theX+theSize, theY+theSize);
  line (theX+theSize, theY, theX, theY+theSize);
}


14.2.4 for-Kombination 2

Funktionen und For-Schleife
Schleifenkombination zwei lässt alle möglichen Parameter der Funktion drawCross() zufällig variieren.
Jeder Aufruf erzeugt eine andere Anordnung von 70 Kreuzen, welche sich durch Position, Größe, Stärke und Farbgebung unterscheiden.
Da die Graustufe der Strichfarbe eine Ganzzahl (integer) ist, muss der durch random() generierte Wert mittels int() konvertiert werden.
Anderenfalls erhält man beim Ausführen einen Fehler für unlautere Reihenfolge der Datentypen beim Funktionsaufruf.

// Processing-Sketch: 14.2.4 for-Kombination2_1a.pde

void setup () {
  size (320, 240);  // Ausgabe-Fenster-Größe
  background (0);   // Hintergrund hell-grau
  smooth ();        // Kantenglättung aktivieren
  noLoop ();        // führe den 'draw' nur 1x aus
}

void draw () {
  for (int i=0; i < 70; i++) {    // erzeugt 70 Kreuze
    drawCross  (random (width),   // Zufalls-WERT
    random (height), 
    random (10, 100), 
    int (random (40, 255)), 
    random (1, 18));
  }
}

void drawCross (float theX, float theY, float theSize, int theGrey, float theWeight) {
  stroke (theGrey);                              // Strichfarbe 
  strokeWeight (theWeight);                      // Strichbreite 
  line (theX, theY, theX+theSize, theY+theSize);
  line (theX+theSize, theY, theX, theY+theSize);
}




*********************************************************

15. Objekt-Orientierte Programmierung


Processing 2.0 Objekte und Klassen

https://lernprocessing.wordpress.com/2010/01/06/objekte-und-klassen/

http://michaelkipp.de/processing/

http://www.hib-wien.at/leute/wurban/informatik/PROCESSING/klassen/klassen1.html

https://www.processing.org/reference/class.html



Klassen, Attribute, Methoden und Instanzen


Diese Lesson führt die letzten Elemente im Basiskurs zur Programmierung mit Processing ein - Objekte und deren Verwendung.

Nach einer Einführung zum Konzept der Objekt-Orientierten Programmierung werden die grundlegenden Bausteine von

zusammengesetzten Datentypen (Klassen) erklärt und beispielhaft ihr Einsatz bei der Programmierung erläutert.

15.1. Grundgedanke

test Ein modulares Programm besteht aus einzelnen Modulen, von den jedes eine bestimmte Aufgabe erfüllen soll.
Von der Lesson Kontrollstrukturen kennen wir den Ansatz der Wiederverwendbarkeit.
Sie ermöglichen es einem einzelnen Wert mehrfach in einem Programm aufzutauchen, so dass dieser einfach geändert werden kann.
Funktionen[1] abstrahieren eine bestimmte Aufgabe und ermöglichen ebenfalls eine Wiederverwendbarkeit dieser Aufgabe.
Dabei ist immer entscheidend, was eine Funktion tut und nicht wie sie im Detail funktioniert(Abstraktion).
Diese Herangehensweise ermöglicht es, sich auf die Ziele eines Programms zu kümmern anstelle sich in Details zu verlieren.
Der OOP-Ansatz erweitert diese Modularität, in dem Variablen und Funktionen zu Objekten gruppiert werden.
Das Ziel dabei ist das Gestalten von regelbasiertem Verhalten in Form von Objekten.
Die Verhaltensweisen eines Objektes sind formulierte Prozesse, die auf einen bestimmten Input einen bestimmten Output liefern.
Man spricht bei diesen speziellen Funktionen von Methoden.
Ein Objekt kann weiterhin erst definiert werden, wenn wir seine Eigenschaften kennen.
Durch diese Eigenschaften wird das Objekt unterscheid- und vergleichbar mit anderen Objekten.

Dabei ist es durchaus möglich Analogien zwischen Software-Objekten und realen Objekten zu bilden.
Butterfly

15.2. Begriffe

Im Texteditor wurde der Code des Sketches bis dato nur in einem Tab formuliert.
Dieser Tab trägt den Namen unter welchem der Sketch abgelegt wurde - bzw. einen temporären, kryptischen wenn dies noch nicht passiert ist.
Wie bereits angesprochen ist ein Vorteil von oop die Weiterführung des Modularisierungsansatzes von Programmen.
Jede sog. Klasse, die als Grundlage für die Objektgenerierung dient, wird in einem neuen Tab geschrieben.
Um einen neuen Tab anzulegen, Klickt man im rechten Teil der Tableiste auf den quadratischen Button (→) und wählt 'New Tab'.
Über der Konsole erscheint ein Eingabefeld in dem Processing der Name der Klasse mitgeteilt werden muss.
Nach dem Bestätigen der Eingabe mit 'Ok' erhält man ein neues Tab und kann mit der Ausformulierung der Klasse beginnen.

15.2.1 Klasse

Basis für eine OOP-Konstruktion ist die Klasse.
Ihr Inhalt dient als Bauplan für das spätere Erstellen von Objekten - auch Instanzen oder Klasseninstanzen genannt.
Der Begriff class leitet die Formulierung ein, worauf der Klassenname folgt.
Der Klassenname muss mit einem Großbuchstaben beginnen und taucht beim Arbeiten als Datentyp bzw. direkt hinter dem Wort new auf.
Innerhalb der geschweiften Klammern folgen die Attribute und Methoden der Klasse.
Alles was außerhalb des Klassenblocks steht, gehört nicht zur Klasse.

class Butterfly {
 
  // Der Klassenblock
 
}

15.2.2 Attribut

Attribute sind die Eigenschaften die eine Klasse aufweist.
Sie geben den Objekten die Möglichkeit sich voneinander zu unterscheiden, bzw. eine Vergleichbarkeit untereinander herzustellen.
In der Formulierung der Klasse tauchen die Attribute an erster Stelle auf.
Da es sich bei ihnen um Variablen handelt, folgt nach der Angabe des Datentyps (String, int, float, …) der bezeichnende Name. Neben dem Datentyp können weitere OOP-spezifische Kriterien vergeben werden.
Da es sich dabei aber um keine Notwendigkeit handelt, belassen wir es in diesem Abschnitt bei der kompakten Version.

class Butterfly {
 
  String species;
  String gender;
 
}

15.2.3 Methode

Die als Fähigkeiten aufgeführten Methoden werden ebenfalls im Klassenkörper platziert.
Direkt unter den Attributen formulieren sie Prozesse in von Funktionen bekannter Schreibweise.
Das Zurückgeben von Variablen als Result des Aufrufs (siehe Rückgabewerte in der Lesson zu Funktionen[1]) und die Angabe von Parametern ist wie bei normalen Funktionen möglich.
Durch Einsatz dieser Bausteine kann eine hohe Varianz in das Verhalten unterschiedlicher Objekte gelegt werden.

class Butterfly {
    
   void fly () {
     // Prozess 'fliegen'
   }
 
   void land () {
     // Prozess 'landen'
   }
}

15.2.4 Konstruktor

Beim Arbeiten mit Capture wurden Ausdrücke wie Capture c = new Capture (…); verwendet (siehe Kapitel über Video in Processing).
Da Informationen wie Bildgröße, Bilder pro Sekunde und Kameranamen für Processing notwendig sind, mussten dafür Werte zwischen den Klammern (…) angegeben werden. Beim Anlegen einer Klasse, in dem Fall Capture, können solche Daten mit der Festlegung eines sog. Konstruktors erzwungen werden.
Der Konstruktor ist eine spezielle Funktion einer Klasse und hat keinen Rückgabewert. Im Klassenaufbau findet er zwischen den Attributen und Methoden Platz.
Durch Parameter im Funktionskopf wird definiert, welche Informationen beim Ausdruck new KlassenName (…) zwischen den Klammern angegeben werden müssen. Innerhalb des Konstruktors erfolgt eine Zuweisung, wobei jeder Parameter seinem Attribut zugeordnet wird.

class Butterfly {
 
  String species;
  String gender;
 
  Butterfly (String theSpezies, String theGender) {
    species = theSpezies;
    gender = theGender;
  }
}

15.3. Arbeiten mit Klassen

15.3.1 Instanzen/Objekte erzeugen

Mit einer Klasse haben wir gleichzeitig einen Datentyp erstellt.
Beim Anlegen von Instanzen der Klasse taucht der Klassenname vor dem Instanznamen zum ersten Mal auf.
Diese Schreibweise ist bereits vom Erzeugen von Variablen bekannt.
Nach dem = folgte bei Variablen die Zuweisung des Wertes, z.B. "Text" oder Zahlen.
Da es sich bei Klassen automatisch um komplexe Datentypen handelt, muss eine Instanz der Klasse mit dem Wörtchen new erstellt werden.
Dadurch nimmt sich Processing den 'Objektbauplan' und strickt uns ein Abbild der Klasse - eine Instanz.
KlassenName InstanzName = new KlassenName (Parameter des Konstruktors);
Bezogen auf unser Schmetterlingsklasse sieht das Erstellen von Instanzen wie folgt aus:

Butterfly bfW = new Butterfly ("Zitronenfalter", "weiblich");
Butterfly bfM = new Butterfly ("Zitronenfalter", "männlich");

Die Instanzen tragen die Bezeichnungen bfW und bfM. Beide sind von der Gattung 'Zitronenfalter' - unterscheiden sich jedoch im Geschlecht.
Die Anzahl und Reihenfolge der übergebenen Parameter beim Erzeugen muss mit Definition im Konstruktor übereinstimmen.

15.3.2 Arbeiten mit Instanzen/Objekten

Der Zugriff und das Ansprechen von Instanzen funktioniert über die Punktnotation.
Instanz- und Attributs- bzw. Methodenname werden dabei durch einen Punkt voneinander getrennt.
15.3.2.1 Attribute
Wo bei der Wertzuweisung von Variablen nur der Variablenname links neben dem = stand, taucht bei Attributen eine Kombination aus Instanz- und Attributsname auf.
Das gleiche Prinzip gilt für das Auslesen von Attributen (siehe println() im Beispiel).

InstanzName.AttributName = "WERT";
println (InstanzName.AttributName);
15.3.2.2 Methoden
Genau wie Attribute spricht man die Fähigkeiten von Klasseninstanzen durch eine Kombination aus Instanz- und Methodenname an.
In der zweiten Zeile des Pseudocodes gibt die Methode der Instanz einen float Wert zurück, welcher in der Variable val abgelegt wird.
Der Aufruf gestaltet sich demnach wie bei Funktionen, nur das vor dem Methodennamen explizit eine Instanz angegeben werden muss, auf welche diese ausgeführt werden soll.

InstanzName.MethodenName (Parameter der Methode);
float val = InstanzName.MethodenName (Parameter der Methode);

Die Schmetterlingsinstanz btW kann also auf diese Weise fliegen und landen:

bfW.fly ();
bfW.land ();

Da die Methoden in der Klasse keinen Rückgabewert haben und keine Parameterangaben zulassen, kann der Aufruf nur in dieser Form erfolgen.


15.4. Beispiele

https://www.processing.org/reference/class.html

Die Klasse »Ball«

15.4.1 Anlegen der Klasse

Im ersten Beispiel legen wir die Klasse Ball in einem neuen Tab an.
Dieses hält neben dem Klassenkörper die Attribute x, y und diameter für Position und Durchmesser.
Im Sketch (L15_01_oop_ball1) legen wir die Instanz b global fest.
Nachdem wir im setup()-Block das Sketchfenster eingerichtet haben, Erzeugen wir die Ballinstanz mit b = new Ball(); und füllen die Attribute mit Werten.
Das weitere Programm macht nichts anderes, als uns eine Ellipse mit der Position und Größe des Balls abzubilden.


// Processing-Sketch: Ball-example-01_1a.pde

// Instanz 'b' der Klasse 'Ball'
Ball b;

void setup () {
  size (260, 240);    // Grafik-Fenster-Größe
  smooth ();          // Kantenglättung


  b = new Ball ();    // Erzeugen der Instanz
  b.x = 120;          // Füllen der Attribute
  b.y = 140;
  b.diameter = 90;
}

void draw () {
  background (200);
  // Zeichnen des Balls durch Auslesen der Instanzeigenschaften
  ellipse (b.x, b.y, b.diameter, b.diameter);
}



// Processing-Sketch: BallKlasse-01_1a.pde

// Klasse Ball in einem neuen Tab (Dreieck nach unten > New Tab > Name for new file: nameKlasse-00_1a)
class Ball {
  float x;         // Attribute der Klasse
  float y;
  float diameter;
}


15.4.2 Anlegen mittels Konstruktor

Da das 'manuelle' Befüllen der Instanz mit Werten eine relativ umständliche Angelegenheit ist, legen wir in der Klasse einen Konstruktor dafür an.
Dieser wird automatisch bei der Erzeugung der Instanz von uns abgefragt.
Visuell bestehen keine unterschiede zwischen diesem und dem ersten Beispiel.

// Processing-Sketch: Ball-example-02_1a.pde

Ball b; // Instanz 'b' der Klasse 'Ball'

void setup () {
  size (320, 240);            // Grafik-Fenster-Größe
  smooth ();                  // Kantenglättung
// Erzeugen der Instanz und gleichzeitiges Füllen der Attribute durch den Konstruktor
  b = new Ball (120, 140, 90);
}

void draw () {
  background (0);
  ellipse (b.x, b.y, b.diameter, b.diameter);
}



// Processing-Sketch: BallKlasse-02_1a.pde

// Klasse Ball in einem neuen Tab (Dreieck nach unten > New Tab > Name for new file: nameKlasse-00_1a)
class Ball {
  float x;    // Attribute der Klasse
  float y;
  float diameter;

  // Konstruktor der Klasse 'Ball'
  Ball (float theX, float theY, float theDiameter) {
    x = theX;
    y = theY;
    diameter = theDiameter;
  }
}

15.4.3 Bewegen durch die Methode »move«

Momentan besteht die Klasse nur aus Eigenschaftsdefinitionen.
Sie ist nur brauchbar um Werte/Charakteristika abzulegen bzw. auszulesen.
In diesem Schritt wird eine Methode (Fähigkeit) zum Bewegen des Balls festgelegt.
Platziert im Klassenkörper hat sie den Namen 'move' und besitzt keinen Rückgabewert und keine Parameter[1].
Innerhalb dieser Methode wird der Wert von x um 1 erhöht und wenn notwendig auf 0 zurückgesetzt.
Bei jedem draw() Durchlauf wird b.move(); aufgerufen, was eine Bewegung des Ball von links nach rechts zur Folge hat.


//Processing-Sketch: Ball-example-03_1a.pde
// Bewegter Ball

Ball b;  // Instanz 'b' der Klasse 'Ball'

void setup () {
  size (320, 240);             // Grafik-Fenster-Größe
  smooth ();                   // Kantenglättung
  b = new Ball (120, 140, 90);
}

void draw () {
  background (255, 255, 0);    // Hintergrund-Farbe gelb
  b.move ();
  ellipse (b.x, b.y, b.diameter, b.diameter); // Kreis X-Y und Breite-Höhe
}



// Processing-Sketch: BallKlasse-03_1a.pde

// Klasse Ball in einem neuen Tab (Dreieck nach unten > New Tab > Name for new file: nameKlasse-00_1a)
class Ball {
  float x;
  float y;
  float diameter;

  Ball (float theX, float theY, float theDiameter) {
    x = theX;
    y = theY;
    diameter = theDiameter;
  }

  void move () {
    x = x + 1;
    if (x > width) {
      x = 0;
    }
  }
}


************************
//Processing-Sketch: Ball-example-30_1a.pde 
// Wir wollen drei farbige Bälle (Kreisscheiben) darstellen. 
// Drei bunte Bälle bewegen sich bei jedem Aufruf

Ball ball1, ball2, ball3; 

void setup() { 
  size(200, 200); 
  ball1 = new Ball(#ff0000);  // Roter Ball
  ball2 = new Ball(#00ff00);  // Grüner Ball
  ball3 = new Ball(#0000ff);  // Blauer Ball
  frameRate(30); 
  smooth();
}

void draw() { 
  background(0); 
  ball1.move(); ball2.move(); ball3.move(); 
  ball1.paint(); ball2.paint(); ball3.paint();
}




// Processing-Sketch: BallKlasse-30_1a.pde 
// Klasse Ball in einem neuen Tab (Dreieck nach unten > New Tab > Name for new file: nameKlasse-00_1a)
 class Ball {   float x, y; // Ort   float vx, vy; // Geschwindigkeit   float r; // Radius   int col; // Farbe   Ball(int farbe) {     r = 20;      col = farbe;       x = random(2*r, width-2*r);     y = random(2*r, height-2*r);     vx = random(-4, 4);     vy = random(-4, 4);     paint();   }    // das Ballobjekt darstellen   void paint() {     fill(col);     noStroke();     ellipseMode(RADIUS);     ellipse(x, y, r, r);   }     // den Ball bewegen   void move() {     // jeder Aufruf aktualisiert die Koordinaten (x/y)     // sowie den Geschwindigkeitsvektor (vx/vy)   } }

704_d_fritz-x_Processing API KurzReferenz (31 Seiten)_1a.pdf
http://www.hib-wien.at/leute/wurban/informatik/PROCESSING/movingballs/index.html






//Processing-Sketch: Ball-example-31_1a.pde 
// Bunte Bälle bewegen sich
// Reflexion an den Wänden

int ANZAHL = 1;
Ball[]  balls; 

void setup() {
  size(200, 200);
  balls = new Ball[ANZAHL];
  for (int i=0; i<ANZAHL; i++) balls[i] = new Ball(i);
  frameRate(10);
  smooth();
  background(0);
}

void draw() {
  fill(0, 16);
  rect(0, 0, width, height);
  for (int i=0; i<ANZAHL; i++) balls[i].move();
  for (int i=0; i<ANZAHL; i++) balls[i].paint();
}



// Processing-Sketch: BallKlasse-31_1a.pde
// Klasse Ball in einem neuen Tab  (Dreieck nach unten > New Tab > Name for new file: nameKlasse-00_1a)
class Ball {   float x, y; // Ort   float vx, vy; // Geschwindigkeit   float r; // Radius   color col; // Farbe   Ball(int number) {     x = width/2.0;     y = height/2.0;     vx = 17;     vy = 11;     r = 20;     colorMode(HSB);     col = color(0, 255, 255);     paint();   }   // das Ballobjekt zeichnen   void paint() {     noStroke();     fill(col);     ellipseMode(RADIUS);     ellipse(x, y, r, r);   }   // den Ball bewegen   void move() {     // falls Wand überschritten, Richtung ändern     if (x+vx<r || x+vx>=width-r) vx = -vx;     if (y+vy<r || y+vy>=height-r) vy = -vy;     // neue Position     x = x+vx;      y = y+vy;   } }

http://www.hib-wien.at/leute/wurban/informatik/PROCESSING/movingballs/index2.html


15.4.4 Array von Bällen

Beispiel Nummer vier demonstriert die Klasse Ball unter Verwendung eines Arrays[2].
Zwei for-Schleifen dienen dabei das Array im setup() zu füllen und draw() auszulesen.
Innerhalb der ersten Schleife ändert die Zählvariable i die Startpositionen der einzelnen Bälle.

//Processing-Sketch: Ball-example-04_1a.pde
// Anlegen des Ball-Arrays

Ball b[] = new Ball[20];

void setup () {
  size (320, 240);
  smooth ();
  // Erzeugen aller Ballinstanzen
  for (int i=0; i < b.length; i++) {
    b[i] = new Ball (i * 15, 20 + i * 10, 10);
  }
}

void draw () {
  background (0);
  // für jede Ballinstanz
  for (int i=0; i < b.length; i++) {
    // Bewegen des Balls
    b[i].move ();
    // Darstellen im Sketchfenster
    ellipse(b[i].x, b[i].y, b[i].diameter, b[i].diameter);
  }
}



// Processing-Sketch: BallKlasse-04_1a.pde 
// Klasse Ball in einem neuen Tab  (Dreieck nach unten > New Tab > Name for new file: nameKlasse-00_1a)
class Ball {
  float x;
  float y;
  float diameter;

  Ball (float theX, float theY, float theDiameter) {
    x = theX;
    y = theY;
    diameter = theDiameter;
  }

  void move () {
    x = x + 1;
    if (x > width) {
      x = 0;
    }
  }
}



15.4.5 Gizmo Tierchen

Der folgende Beispielkomlex soll die schrittweise Entwicklung von simulierten «Lebewesen» verdeutlichen.
Generell gibt es mehrere Prinzipien für die Simulation selbstständig anmutender «Lebewesen».
Die beiden bekanntesten sind die Steering Behaviors for Autonomous Vehicles von Craig W. Reynolds und die Braitenberg Vehicles von Valentino Braitenberg.
Letztere gehen aber schnell über die reine Bewegung hinaus, in dem Braitenberg Konstruktionsprinzipien,
z.B. für die Simulation von Gedächtnisfunktionen beschreibt. Sein Buch[3] empfehlen wir jedem, der sich ein wenig für Biologie interessiert!.
Unter Hinzunahme der Vektor Klasse PVector werden Bewegungen im Sketchfenster simuliert, ohne das die Objekte dabei die Zeichenfläche verlassen.
Im ersten Schritt folgen die Gizmos einem selbstgesetzten Ziel.
Kurz vor dem Erreichen wird dieses verschoben - sie bleiben in ständiger Bewegung.
Da die erzielte geradlinige Positionsänderung unnatürlich wirkt, verwirft die zweite Version den Gedanken des festen Ziels.
Stattdessen werden die Gizmos mit jeweils einer Richtung ausgestattet.
Ebenfalls ein Vektor, variieren wir x und y Wert von Bild zu Bild, um ein konfuses Verhalten zu erzeugen.
Objektorientiertes Animieren #01 (zielstrebig) in Processing
Durch eine Animation streben 30 Gizmos über die Zeichenfläche.
Sie folgen einem selbstgestecktem Ziel. Kurz vor dem Zielpunkt wird dieser neu definiert.
Start- und Zielpositionen sind zufällig festgelegt.
//Processing-Sketch: Gizmo-example-01_1a.pde
Gizmo giz[] = new Gizmo[30];

void setup () {
  size (320, 240);
  background (0);
  stroke (255);

  for (int i=0; i < giz.length; i++) {
    float x = random (width);
    float y = random (height);
    giz[i] = new Gizmo (x, y);
  }
}

void draw () {
  for (int i=0; i < giz.length; i++) {
    giz[i].move ();
    point (giz[i].position.x, giz[i].position.y);
  }
}




// Processing-Sketch: GizmoKlasse-01_1a.pde
// Klasse Gizmo in einem neuen Tab(Dreieck nach unten > New Tab > Name for new file: nameKlasse-00_1a)  class Gizmo {   PVector position;   PVector target;   Gizmo (float theX, float theY) {     position = new PVector (theX, theY);     target   = new PVector ();     newRandomTarget ();   }   void move () {     PVector step = new PVector ();     step.set (position);     step.sub (target);     step.div (40);     position.sub (step);     if (position.dist (target) < 3) {       newRandomTarget ();     }   }   void newRandomTarget () {     target.x = random (width);     target.y = random (height);   } }


15.4.6 Objektorientiertes Animieren #02 (wandern) in Processing

Im zweiten Teil wird das feste Ziel gegen eine Richtung getauscht.
Diese Verändert sich von Bild zu Bild um einen Bereich von -0.15 bis 0.15 und wird auf die Position addiert.
Damit die Geschwindigkeit der Gizmos konstat ist, führen wir normalize() vor der Addition auf die Richtung aus (damit Länge von 1, siehe Normalenvektor).
Zum Schluss wird die aktuelle Position auf ein Verlassen der Zeichenfläche überprüft - bei Eintritt kehren wir die Richtung auf der entsprechenden Achse um.
//Processing-Sketch: Gizmo-example-02_1a.pde
Gizmo giz[] = new Gizmo[10];

void setup () {
  size (320, 240);
  background (0);
  stroke (255);

  for (int i=0; i < giz.length; i++) {
    float x = random (width);
    float y = random (height);
    giz[i] = new Gizmo (x, y);
  }
}

void draw () {
  noStroke ();
  fill (0, 2);
  rect (0, 0, width, height);

  stroke (255);
  for (int i=0; i < giz.length; i++) {
    giz[i].move ();
    point (giz[i].position.x, giz[i].position.y);
  }
}



// Processing-Sketch: GizmoKlasse-02_1a.pde
// Klasse Gizmo in einem neuen Tab (Dreieck nach unten > New Tab > Name for new file: nameKlasse-00_1a)  class Gizmo {   PVector position;   PVector direction;   float spin = 0.15;   Gizmo (float theX, float theY) {     position    = new PVector (theX, theY);     direction   = new PVector ();     direction.x = random (-1, 1);     direction.y = random (-1, 1);   }   void move () {     direction.x += random (-spin, spin);     direction.y += random (-spin, spin);     direction.normalize ();     position.add (direction);     if (position.x < 0 || position.x > width) {       direction.x *= -1;     }     if (position.y < 0 || position.y > height) {       direction.y *= -1;     }   } }

15.4.7 Objektorientiertes Animieren #03 (wandern-farbig) in Processing

Sketch drei färbt die Gizmos in vollem Spektrum ein.
Der HSB-Farbraum eignet sich für dieses Anliegen besonders (colorMode (HSB, 255);), da wir nur den Farbton Ändern und Sättigung und Helligkeit konstant beleiben.
Klasse 'Gizmo' wird für die Farbtonberechnung um die Methode getHue() erweitert.
In ihr berechnet sich die Farbe aus der Lage des Vektors direction.
Demnach erhält jede Richtung ihren eigenen Farbton.

//Processing-Sketch: Gizmo-example-03_1a.pde
Gizmo giz[] = new Gizmo[10];

void setup () {
  size (320, 240);
  colorMode (HSB, 255);
  background (0);
  stroke (255);

  for (int i=0; i < giz.length; i++) {
    float x = random (width);
    float y = random (height);
    giz[i] = new Gizmo (x, y);
  }
}

void draw () {
  noStroke ();
  fill (0, 2);
  rect (0, 0, width, height);

  for (int i=0; i < giz.length; i++) {
    giz[i].move ();
    stroke (giz[i].getHue (), 255, 255);
    point (giz[i].position.x, giz[i].position.y);
  }
}




// Processing-Sketch: GizmoKlasse-03_1a.pde 
// Klasse Gizmo in einem neuen Tab (Dreieck nach unten > New Tab > Name for new file: nameKlasse-00_1a)
class Gizmo {   PVector position;   PVector direction;   float spin = 0.15;   Gizmo (float theX, float theY) {     position    = new PVector (theX, theY);     direction   = new PVector ();     direction.x = random (-1, 1);     direction.y = random (-1, 1);   }   void move () {     direction.x += random (-spin, spin);     direction.y += random (-spin, spin);     direction.normalize ();     position.add (direction);     if (position.x < 0 || position.x > width) {       direction.x *= -1;     }     if (position.y < 0 || position.y > height) {       direction.y *= -1;     }   }   int getHue () {     PVector v = new PVector (0, 1);     float a = PVector.angleBetween (v, direction);     a /=  TWO_PI;     return int (255 * a);   } }


15.4.8 Objektorientiertes Animieren #04 (wandern-maskiert) in Processing

In diesem Sketch bestimmt ein zu Begin im setup() gelandenes [[de:p5:basics:lesson6#abbilden|Bild]], ob die 'Gizmos' im draw() abgebildet werden.
Mittig im Bild ist schwarz/weiß der Schriftzug 'gizmo' platziert.
Nach dem Auslesen des Farbwertes im Bild an der Gizmo-Position mit [[de:p5:basics:lesson6#auslesen|get()]], prüft eine if-Bedingung ob sich der Gizmo über einem Schriftzeichen befindet. Wenn 'ja' wird er gezeichnet - anderenfalls nicht.
Das Bild wird nicht abgebildet.

//Processing-Sketch: Gizmo-example-04_1a.pde
Gizmo giz[] = new Gizmo[700];
PImage typo;
 
void setup () {
  size (520, 540);
  stroke (0);
  background (255);
  typo = loadImage ("Brea Lynn.jpg"); // MENUE > Sketch > AddFile... > Foto
  for (int i=0; i < giz.length; i++) {
    float x = random (width);
    float y = random (height);
    giz[i] = new Gizmo (x, y);
  }
}
 
void draw () {
  noStroke ();
  fill (255, 30);
  rect (0, 0, width, height);
  stroke (0);
  for (int i=0; i < giz.length; i++) {
    giz[i].move ();
    int x = int (giz[i].position.x);
    int y = int (giz[i].position.y);
    color pixel = typo.get (x, y);
     
    if (brightness (pixel) < 40) {
      point (x, y);
    }
  }
}



// Processing-Sketch: GizmoKlasse-04_1a.pde
// Klasse Gizmo in einem neuen Tab(Dreieck nach unten > New Tab > Name for new file: nameKlasse-00_1a)
class
Gizmo {       PVector position;   PVector direction;       float spin = 0.15;       Gizmo (float theX, float theY) {     position    = new PVector (theX, theY);     direction   = new PVector ();     direction.x = random (-1, 1);     direction.y = random (-1, 1);   }       void move () {     direction.x += random (-spin, spin);     direction.y += random (-spin, spin);     direction.normalize ();     position.add (direction);           if (position.x < 0 || position.x > width) {       direction.x *= -1;     }     if (position.y < 0 || position.y > height) {       direction.y *= -1;     }   } }


http://www.creativecoding.org/lesson/basics/processing/objekte


*********************************************************

Processing     sketch.properties


Eine Java-Properties-Datei ist eine Textdatei, die in der Programmiersprache Java als einfacher Konfigurationsmechanismus verwendet wird.

Eine Property (deutsch „Eigenschaft“) ist in diesem Zusammenhang ein Text, der unter einem bestimmten Namen abgelegt ist.

Java-Properties-Dateien haben üblicherweise die Dateiendung*.properties“.

http://de.wikipedia.org/wiki/Java-Properties-Datei

https://processing.org/reference/environment/


Objektorientierte Programmiertechnik  Klasse

ORDNER > KreisProgramm > KreisProgramm1_1a.pde   (Hauptdatei)

                                             KreisKlasse1_1a.pde       (ev. mehrere Klassen-Dateien)

MENU > Sketch > Show Sketch Folder (Strg-K)

Die objektorientierte Programmiertechnik erlaubt es uns, noch einen wichtigen Schritt weiter zu gehen. Es ist möglich, die Funktionen, die mit dem Kreis arbeiten, und die Variablen, die mit dem Kreis zu tun haben in ein Paket zusammenzupacken.

Dazu denken wir so: Was ist unsere Kreisscheibe:
- sie hat EIGENSCHAFTEN : Mittelpunkt, Radius, Farbe, Tempo. Diese Variablen werden als ATTRIBUTE der Kreisscheibe bezeichnet.
- sie hat FÄHIGKEITEN : sie ändert ihren Ort, sie kann gezeichnet werden. Diese Funktionen bezeichnet man als METHODEN.

Damit hätten wir alles fixiert, was wichtig ist. Wir haben ein MODELL geschaffen, dieses wird als KLASSE bezeichnet. Um nun eine Kreisscheibe zu erhalten, muss sie anfangs ERZEUGT werden (mithilfe des KONSTRUKTORS aus der Klasse ABGELEITET), wodurch wir ein konkrete verwendbares Exemplar aus der Klasse erzeugen - ein OBJEKT.

Zur Schreibweise: während Variablennamen immer mit einem Kleinbuchstaben beginnen, pflegt man Klassennamen groß zu schreiben. Auf Attribute und Methoden greift man zu, indem man an den Objektnamen einen Punkt setzt und dann den Attribut- bzw. Methodennamen anhängt. 


Punkt geht auf Mausposition



//Processing-Sketch: KreisProgramm1_1a.pde
Kreis scheibe;                // das Objekt soll scheibe heißen

void setup() {
  size(300, 300);             // Grafik-Fläche
  scheibe = new Kreis(20);    // Kreis Durchmesservoid draw() {
  background(255,0,255);      // Hintergrund weiß
  scheibe.update();
  scheibe.paint();
} 


//Processing-Sketch: KreisKlasse1_1a.pde
// Klasse Kreis in einem neuen Tab (Dreieck nach unten > New Tab > Name for new file: nameKlasse-00_1a)
class Kreis {   float x, y; // Mittelpunkt des Kreises   int r; // sein Radius   color c; // seine Farbe   float tempo; // easing-Variable   Kreis(int radius) { // das ist der Konstruktor     ellipseMode(RADIUS);     colorMode(HSB, 360, 100, 100);     x = random(width);     y = random(height);     c = color(random(255), 100, 100);     tempo = random(0.01, 0.1);     r = radius;   }    void paint() {     noStroke();     fill(c);     ellipse(x, y, r, r);   }   void update() {     float dx = mouseX-x;     float dy = mouseY-y;     x += tempo*dx;     y += tempo*dy;   } }

Und noch ein positiver Nebeneffekt: die Klassenvariablen x,y,r,c und tempo waren ursprünglich Variable, die im Hauptprogramm definiert wurden, und damit sichtbar für alle weiteren Funktionen. Andere Programmteile mussten berücksichtigen, dass diese Variablennamen bereits vergeben sind.

Das ist nun anders - die Attribute sind innerhalb der Klasse GEKAPSELT und damit nach außen unsichtbar geworden.



*********************

2. Beispiel

Wenn wir eine Kreisscheibe erzeugen können, können wir auch viele herstellen. Die Klasse muss dazu nicht verändert werden. Wir bereiten einfach im Hauptprogramm eine ganze Liste von Kreisen vor und bearbeiten sie gemeinsam in for-Schleifen.


//Processing-Sketch: KreisProgramm2_3b.pde

Kreis[] scheiben = new Kreis[50];   // 100 Kreisscheiben

void setup() {
  size(400, 400);
  for (int i=0; i<scheiben.length; i++) {
    scheiben[i] = new Kreis(int(random(5, 25)));
  }
  frameRate(40);
} 

void draw() {
  background(255);          // Hintergrund weiß  wird aber hell-grau
  for (int i=0; i<scheiben.length; i++) {
    scheiben[i].update();
    scheiben[i].paint();
  }
}

//Processing-Sketch: KreisKlasse2_1a.pde

// Klasse Kreis in einem neuen Tab (Dreieck nach unten > New Tab > Name for new file: nameKlasse-00_1a)

class Kreis {

  float x, y;       // Mittelpunkt des Kreises
  int r;            // sein Radius
  color c;          // seine Farbe
  float tempo;      // easing-Variable


  Kreis(int radius) {					// das ist der Konstruktor
    ellipseMode(RADIUS);
    colorMode(HSB, 360, 100, 100);
    x = random(width);
    y = random(height);
    c = color(random(255), 100, 100);
    tempo = random(0.01, 0.1);
    r = radius;
  } 

  void paint() {
    noStroke();   
    fill(c);
    ellipse(x, y, r, r);
  }

  void update() {
    float dx = mouseX-x;
    float dy = mouseY-y;
    x += tempo*dx;
    y += tempo*dy; 

    if (mouseX<=10 || mouseY<=10 || 
      mouseX>=width-11 || mouseY>=height-11) 
    {
      x = random(width);
      y = random(height);
    }
  }
}

100 Kreisscheiben brauchen (fast) nicht mehr Schreibarbeit als eine einzige. Ohne Klassen hätten wir 100 Variable für die Radien, 100 für die x-Werte, 100 für die y-Werte, ... benötigt. Gut, dass es die objektorientierte Programmiertechnik gibt!

Du kannst Dir den Quelltext des Sketches runterladen. Ich hab noch ein paar kleine Änderungen angebracht, damit sich folgendes Verhalten für 50 Kreisscheiben ergibt:

http://www.hib-wien.at/leute/wurban/informatik/PROCESSING/klassen/klassen1.html



*********************************************************

                            Processing                                             Seite 209

Klassen in eine Datei auslagern
Sie haben vielleicht bemerkt, dass wenn Sie eine Klasse in einem Programm hinzufügst, dieses unter Umständen recht lang wird.
Gehören die Zeilen, die ich jetzt sehe, zur Klasse oder ist das Code aus meinem Hauptprogramm
Die ganze Logik in einer einzigen Datei (Sketch) zu speichern, wie wir das bisher gehandhabt haben, ist nicht immer vorteilhaft.
Deshalb bietet der Processing-Editor die Möglichkeit, Codepassagen in eine separate Datei auszulagern.
Das bietet sich immer dann an, wenn logische Programmeinheiten, wie eine oder mehrere Klassen, vorkommen.

Werfen wir einen Blick auf die Processing-IDE.
Das kleine nach unten gerichtete Dreieck neben dem Sketch-Namen, hat einen bestimmten Zweck.
Wenn Sie einmal mit der Maus darauf klicken, dann bekommen Sie ein Auswahlmenü angezeigt.

1. New Tab
2. Rename
Delete
Previous Tab
Next Tab
sketch_150118a


Hinzufügen eines neuen Tab-Reiters
Direkt der 1. Menüpunkt lautet New Tab, was übersetzt Neuer Tabulator bedeutet.
Wenn man darauf klickst, erscheint im Editorfenster der folgende Dialog, der es uns ermöglicht, einen Namen für die auszulagernde Datei zu vergeben.
Angenommen, ich tippe dort den Namen KreisKlasse001_1a ein und bestätige mit OK (CR / Enter-Taste), dann sieht meine IDE im Anschluss folgendermaßen aus:
Ich habe eine neue Registerkarte im Editor erzeugt, die mit dem Namen KreisKlasse001_1a bezeichnet wurde.
Dort können wir jetzt die Klassendefinition  //Processing-Sketch: KreisKlasse001_1a.pde  eintragen.
Wenn man zwischen den einzelnen Ansichten wechseln möchte, klickt man mit der Maus einfach auf die gewünschte Registerkarte mit dem entsprechenden
Namen.
Die Hauptdatei bleibt dabei immer am linken Rand positioniert,
während die zusätzlichen Dateien in alphabetischer Reihenfolge von links nach rechts gelistet werden.
Jetzt hat man den Code (Sketch) geteilt und der Sketch besteht aus einzelnen Passagen, die zusammen jeweils eine logische Einheit bilden.
Dies trägt maßgeblich zur Steigerung der Übersichtlichkeit bei und ist sowohl bei der Programmpflege als auch bei der Fehlersuche äußerst hilfreich.

Wenn man auf diese Art und Weise mehrere unterschiedliche Registerkarten erstellt hat, wird Processing sie beim Start des Programms so zusammenfügen, als gäbe es nur eine einzige Registerkarte, die den gesamten Code enthält.

FRAGE:
Ist denn jetzt wirklich eine neue Datei erstellt worden oder wird die Ansicht nur aufgesplittet?
Eine berechtigte Frage, die aber schnell zu beantworten ist.
Schauen wir einfach mal in unser Sketch-Verzeichnis, in dem unsere Projektdatei enthalten ist.
Dazu muss man nicht umständlich über den Browser den betreffenden Ordner suchen.
Die Entwicklungsumgebung besitzt zu diesem Zweck einen nützlichen Menüeintrag.
Wähle Sie MENU > Sketch > Show Sketch Folder (Strg-K) und es wird sofort der entsprechende Ordner geöffnet.

Und was sehen wir?
Genau, es ist eine weitere Datei mit dem Namen KreisKlasse001_1a hinzugekommen, die die Klasseninformationen enthält.
Natürlich lassen sich nicht nur Klassen auslagern, sondern z. B. auch Funktionen.

Wenn Sie regen Gebrauch von dieser Funktionalität machen, werden Sie sich an der hieraus resultierenden Übersichtlichkeit erfreuen.

Das macht natürlich nur wirklich Sinn, wenn der Code sehr umfangreich geworden ist. 



//Processing-Sketch: ObjekteUeberfahren001_1a.pde
// Seite 205

int ANZAHL = 10, radius = 80;
Kreis[] obj;

void setup() {

  obj = new Kreis[ANZAHL];
  size(400, 300); 
  smooth(); 
  noFill(); 
  stroke(255, 0, 0);
  for (int i = 0; i < ANZAHL; i++)
    obj[i] = new Kreis((int)random(width), (int)random(height));
}

void draw() {
  background(235);  // Hintergrund hell-grau
  for (int i = 0; i < ANZAHL; i++)
    obj[i].zeige();
}



//Processing-Sketch: KreisKlasse001_1a.pde
// Klasse Kreis in einem neuen Tab (Dreieck nach unten > New Tab > Name for new file: nameKlasse-00_1a)
 class Kreis {
  int xKoordinate, yKoordinate;   boolean istUeber = false;   // Konstruktor   Kreis(int x, int y) {     xKoordinate = x;      yKoordinate = y;   }   void zeige() {     ueber();     ellipse(xKoordinate, yKoordinate, radius, radius);   }   void ueber() {     if (dist(mouseX, mouseY, xKoordinate, yKoordinate) < radius/2) {     istUeber = true;     strokeWeight(4);   }    else {     istUeber = false;     strokeWeight(1);     }   } }






*********************************************************

                            Spirograph

Kreis_1.pde
Wir wollen Punkte zeichnen, die auf einem Kreis liegen. Im Mathematikunterricht haben wir gelernt, dass die Koordinaten auf einer Kreisline mit Radius R durch ( Rcos(w) / Rsin(w) ) gegeben sind, wobei w der Winkel im Bogenmaß ist.
Für 70 Punkte genügt uns folgendes Programm:

Kreis_2.pde

Wollten wir nun etwa 100 Punkte zeichnen und dem Kreis eine andere Größe geben, müssten wir die entsprechenden Zahlen im Programm suchen und ausbessern. Und hoffen, dass wir nichts übersehen. Eine andere Größe des Fensters würde zu einem veränderten Mittelpunkt führen, wodurch auch hier viele Folgeänderungen gemacht werden müssen. Viel besser wäre es, hätten wir Variable eingesetzt, die diese Werte enthalten. Bei kluger Wahl ihrer Namen wären die einzelnen Programmzeilen auch viel leichter verständlich (die wichtigsten Änderungen habe ich hervorgehoben):

Kreis_3.pde

Wir lagern die Arbeit des Zeichnens in eine eigene Funktion aus und übergeben ihr die (mathematischen) Koordinaten des Zielpunktes. Somit erledigt diese neue Funktion die Umwandlung der mathematischen Koordinaten in Bildschirmkoordinaten, sowie ihre Darstellung.

Kreis_4.pde
Statt eines einfachen Kreises zeichnen wir eine Kurve, die beim Abrollen eines kleinen Kreises auf einem großen entsteht. Falls Du das nette Spielzeug 'Spirograph' kennst - genau so etwas ist das. Wir addieren zu den Kreiskoordinaten einen zweiten Kreis dazu. Sein Radius soll r2 heißen, seine Frequenz (Umdrehungsgeschwindigkeit) f2.

Kreis_5.pde
  Eine GUI zur Steuerung
Eine sehr einfache und praktische Möglichkeit der interaktiven Steuerung von Parametern ist die Bibliothek controlP5, die im Internet (auch über die Processing-Homepage) erhältlich ist. Man bekommt eine kleine gepackte Datei, die im Processing-Verzeichnis ins Verzeichnis 'libraries' gehört. Dazu erstellen wir dort ein neues Verzeichnis namens 'controlP5' und entpacken den Inhalt der zip-Datei dort hinein.
Mit Hilfe dieser Bibliothek erzeugen wir einen Schieberegler für die Frequenz f2 des kleinen Kreises. Es genügt, dem Slider den Namen der Variable f2 anzugeben, schon ist die Variable ferngesteuert. Die Syntax lautet: addSlider("variable", minimalWert, maximalWert, posX, posY, groesseX, groesseY)

Kreis_6.pde
Kreis_7.pde
Kreis_8.pde
Kreis_9.pde

Nun kann man immer mehr nette Ideen verwirklichen. Lade Dir die Quelltexte aller Sketche herunter und vollziehe sie schrittweise nach - es ist nicht schwierig!
Die Lininen müssen nicht unbedingt vom Nachbarpunkt aus gezeichnet werden - die Variable 'prev' (previous) bestimmt, der wievielte Punkt als Nachbar angesehen werden soll. Wir erhalten damit interessante Grafiken, die an die Fadengrafiken des GZ-Unterrichts erinnern. Mit hübschen Schiebereglern lässt sich eine Unmenge von Mustern erzeugen:

Kreis_10.pde
Kreis_11.pde

Mit Animation!  Die letzte Stufe soll ein Programm sein, das sogar eine Rotation der Figur erlaubt, die nun aus Linienstücken zusammengesetzt ist.

Kreis_demo.pde
Was alles möglich ist, zeigt zu einem kleinen Teil das folgende Demo-Programm (Javascript und ein aktueller Browser sind notwendig...)




//Processing-Sketch: Kreis_5_1a.pde 
// viele Punkte berechnen und einen 
// Schieberegler fuer die Frequenz des zweiten Kreises

import controlP5.*;    // Bibliothek der GUI-Widgets
ControlP5 steuerung;   // Zugriff auf die Steuerelemente 

float rMax;   // maximale Groesse
float x0, y0;  // Bildschirm Mitte
int num;      // Anzahl berechnete Punkte

float r2;    // Radius und Frequenz des zweiten Kreises
int   f2; 

void setup() {
  size(400+100, 400);  // links Platz fuer den Regler schaffen
  rMax = 150;
  x0 = (width-100)/2+100;    // 100 Pixel Platz für Regler
  y0 = height/2;
  num = 400;

  r2 = 0.5;
  f2 = 3;
  frameRate(30);

  steuerung = new ControlP5(this);
  Slider s1 = steuerung.addSlider("f2", -20, 20, 20, 20, 10, 200);
}

void draw() {
  float w, x, y;    // Winkel und Koordinaten
  background(200);
  for (int n=0; n<num; n++) {
    w = TWO_PI*n/num;
    x = cos(w)+r2*cos(f2*w);
    y = sin(w)+r2*sin(f2*w);
    plot(x, y);
  }
}

// Punkt (x/y) auf Bildschirmkoordinaten umrechnen und zeichnen
void plot(float xx, float yy) {
  float rSize = rMax/(1+abs(r2));  // Vergroesserungsfaktor
  float x = x0+rSize*xx;
  float y = y0-rSize*yy;

  stroke(255);
  fill(0, 0, 160);
  ellipse(x, y, 20, 20);
}

http://www.hib-wien.at/leute/wurban/informatik/PROCESSING/spirograph/spirograph.html


***************************


// Processing-Sketch: Kreis_11_1a.pde
// Regler für Drehung, Zeichenstil

import controlP5.*;    // Bibliothek der GUI-Widgets
ControlP5 steuerung;   // Zugriff auf die Steuerelemente 

float rMax;            // maximale Groesse
float x0, y0;          // Bildschirm Mitte
int num;               // Anzahl berechnete Punkte
int style;
float r2;              // Radius und Frequenz des zweiten Kreises
int   f2; 
int prev;              // wievielter Vorgaenger
float phase=0;
float tempo=0.02;      // Geschwindigkeit der Drehung

void setup() {
  size(600, 400);
  rMax = 150;
  x0 = (width-200)/2+200;    // 100 Pixel Platz für Regler
  y0 = height/2;

  style = 1;
  num = 200;  
  prev = 1;
  r2 = 0.55;
  f2 = 4;
  menu();
  frameRate(30);
}

void menu() {
  steuerung = new ControlP5(this);
  Slider s1 = steuerung.addSlider("f2", -20, 20, 20, 20, 10, 200); 
  Slider s2 = steuerung.addSlider("r2", 0, 2, 50, 20, 10, 200);  
  Slider s3 = steuerung.addSlider("num", 1, 600, 80, 20, 10, 360);  
  Slider s4 = steuerung.addSlider("prev", 1, 300, 110, 20, 10, 200);  
  Slider s5 = steuerung.addSlider("tempo", -0.1, 0.1, 140, 20, 10, 200);  
  Slider s6 = steuerung.addSlider("style", 1, 3.9, 170, 20, 10, 200);
}  

float[] xx = new float[1000];    // Felder mit 1000 Elementen vorbereiten
float[] yy = new float[1000];   

// ---------------------------------------------------------- void draw() {   float w, x, y; // Winkel und Koordinaten   background(0);   for (int n=0; n<num; n++) {     w = TWO_PI*n/num;     // berechnete Werte in die Felder schreiben     xx[n] = cos(w+phase)+r2*cos(f2*w+phase);     yy[n] = sin(w+phase)+r2*sin(f2*w+phase);   }   for (int n=0; n<num; n++) { // alle     if (style==1) {       strokeWeight(1);       plot(n);     }     if (style==2) {       plot2(n);     }     if (style==3) {       strokeWeight(3);       plot(n);        plot2(n);     }   }   phase += tempo; }


// ---------------------------------------------------------- void plot(int n) { // zeichne eine Linie zum Vorgaenger   float rSize = rMax/(1+abs(r2));   float x = x0+rSize*xx[n];   float y = y0-rSize*yy[n];   int vorher = (n-prev)%num;   if (vorher<0) { // FALSCH   if (vorher..0) {     vorher = vorher+num;   }   float x2 = x0+rSize*xx[vorher];   float y2 = y0-rSize*yy[vorher];   stroke(255);   line(x2, y2, x, y); }
// zeichne einen 'Punkt' void plot2(int n) {   float rSize = rMax/(1+abs(r2));   float x = x0+rSize*xx[n];   float y = y0-rSize*yy[n];   noStroke();   fill(50, 250, 50, 150);   ellipse(x, y, 15, 15); }



http://www.hib-wien.at/leute/wurban/informatik/PROCESSING/spirograph/spirograph.html


DIN A4  ausdrucken
*********************************************************
Impressum: Fritz Prenninger, Haidestr. 11A, A-4600 Wels, Ober-Österreich, mailto:schaltungen@schaltungen.at
ENDE



Comments