Kategorie-Archiv: Processing

Rund um’s Thema Processing

Verschwindende Objekte – Processing und Arrays

Hi,

heute hab’ ich mich mal an eine Sache rangemacht die ich schon längst mal selbst in Processing realisieren wollte. Irgendwo hatte ich mal gesehen wie in einem Sketch eine Ellipse der Maus folgte, und diese dann Spuren hinterherzog die langsam verschwanden. Das fand ich ziemlich gut gemacht, und heute hatte ich spontan den Einfall, das selbst zu versuchen. Vorab möchte ich dir anhand eines Screenshots zeigen, wie es am Ende aussehen wird:

Verschwindende Formen

Verschwindende Formen

Ich schlage vor, wir fangen einfach mal an. Dafür muss aber erst einmal geklärt sein, was wir wollen und wie es funktioniert. Also was wollen wir, bzw. was will ich … ich will, dass Ellipsen meiner Maus folgen, diese aber mit der Zeit auf der Strecke kleiner werden bis sie weg sind. Aussehen wird es am Ende so wie in meinem Sketch auf OpenProcessing, hier der Sketch zum ausprobieren:

Bewege einfach mal die Maus um zu sehen was passiert. Drücke die Tasten 1, 2 oder 3 um den Modus für die Anzeige zu wechseln (klick’ vorher am besten einmal in den Sketch, damit dieser den Fokus hat).
Cool und wie funktioniert das?! …… also ich hab’ mir bei meinen Überlegungen gedacht wir brauchen auf jeden Fall schonmal temporäre Variablen, die nur dazu genutzt werden um die “alten” Ellipsen zu zeichnen, also die, die ich mit der Maus bereits abgefahren habe. Denn die werden ja dann mit der Zeit immer kleiner. Eine Variable wird dafür natürlich nicht reichen, wir brauchen eine für X und Y, damit wir die Ellipsen im Raum platzieren können. Und Variablen reichen dafür eh nicht, was wir brauchen sind Arrays. Würden wir nur eine Variable nehmen, dann könnten wir es höchstens noch gerade so machen, dass jede nachziehende Ellipse erst gezeichnet wird, wenn die vorherige komplett verschwunden ist, wir würden also im Endeffekt immer nur eine sehen. Und das wollen wir nicht, was wir wollen ist ja, dass überall dort wo wir mit der Maus sind/waren, Ellipsen auftauchen die dann wieder verschwinden (kleiner werden bis sie weg sind). Also ein Array in dem wir die X- und eines in dem wir die Y-Koordinaten ablegen.
Und du brauchst noch eine Variable “count”, die am Ende von void draw() immer um +1 hochgezählt wird. Die aktuellste, neueste Ellipse hat als Koordinaten im Array dann immer den größten Index, also “count”.

Bevor es daran geht, dafür zu sorgen dass die Ellipsen kleiner werden, musst du dir erst einmal vorstellen können, was bis dahin passiert. Wir bewegen quasi die Maus und zeichnen an jede Position, an dem die Maus ist, eine Ellipse. Klar kannst du’s auch ohne Array machen und lediglich ellipse(mouseX, mouseY, 50, 50); schreiben, allerdings würde dir das wenig bringen, am Anfang von void draw() wird immer background(0); ausgeführt, man würde davon also nichts sehen. Deswegen die Arrays, damit die Koordinaten aus dem Array in einer Schleife abgefragt, und die Ellipsen dann jedes mal neu gezeichnet werden können.
Lassen wir die Maus einmal folgende Koordinaten fahren:

Position 1: X=6 Y=193
Position 2: X=6 Y=192
Position 3: X=6 Y=191
Position 4: X=6 Y=190
Position 5: X=6 Y=189
Position 6: X=7 Y=189
Position 7: X=7 Y=188
Position 8: X=8 Y=188
Position 9: X=8 Y=187
Position 10: X=8 Y=186
Position 11: X=9 Y=186
Position 12: X=9 Y=185
Position 13: X=10 Y=184
Position 14: X=11 Y=182
Position 15: X=11 Y=181
Position 16: X=12 Y=180
Position 17: X=13 Y=179
Position 18: X=14 Y=178
Position 19: X=15 Y=176
Position 20: X=16 Y=175
Position 21: X=17 Y=174
Position 22: X=18 Y=173
Position 23: X=19 Y=173
Position 24: X=19 Y=172
Position 25: X=20 Y=172
Position 26: X=21 Y=171
Position 27: X=22 Y=171
Position 28: X=23 Y=170
Position 29: X=24 Y=169
Position 30: X=26 Y=169
Position 31: X=28 Y=168
Position 32: X=29 Y=168
Position 33: X=31 Y=167
Position 34: X=32 Y=167
Position 35: X=34 Y=167
Position 36: X=36 Y=166
Position 37: X=38 Y=166
Position 38: X=40 Y=166

Für dieses Beispiel hab ich die Maus einfach in paar Sekunden bewegt und die Koordinaten aufgezeichnet. Das sind also die einzelnen “Stationen” die die Maus abgefahren hat, und nun stell dir vor, dass diese Koordinaten jedes mal in ein Array geschrieben werden. Array für X-Koordinaten hätte an Index 0 also den Wert 6, das Array für Y-Koordinaten den Wert 193. Durch eine extra Variable (-> count) bestimmen wir noch den Index, der um 1 hochgezählt wird. Zu Anfangs lege ich für count den Wert 0 fest.

Schaun wir mal was void draw() macht:
count hat den Wert 0, Array (ich nenne die Arrays einfach mal X und Y) X hat an Index “count” (also Null) den Wert 6, Array Y hat an Index “count” (also Null) den Wert 193. Daraus folgt:
X[count] = 6
Y[count] = 193

Count wird um +1 hochgezählt:
count++;
void draw() ist am Ende angelangt, alles also wieder von vorn..
Nächster Durchlauf in void draw(), count hat mittlerweile den Wert 1, Array X bekommt an Index 1 (->count ist ja 1) den Wert 6, Array Y bekommt an Index 1 den Wert 192. count wird hochgezählt und hat jetz den Wert 2, void draw() ist am Ende, alles wieder von vorne …. und so weiter. Unser Array wird mit der Zeit ziemlich voll.

Aber zum wesentlichen: In einer Schleife wird das Array durchlaufen, logischerweise nur soweit wie das Array Werte hat, bzw. die Anzahl des Wertes der Variable “count”. In der Schleife dann werden die Ellipsen gezeichnet. Im Code sieht es dann in etwa so aus:

/**
 * Verschwindende Formen
 *
 * Dies soll anhand eines coolen Beispiels zeigen, was
 * man mit Arrays alles anstellen kann.
 *
 */

void setup()
{
  //Das Übliche, schwarzer Hintergrund, Größe und smooth
  size(300, 300);
  background(0);
  smooth();
}

//Variablen für temporäre (!) Koordinaten und Größe der Formen
int tpx[] = new int[100];
int tpy[] = new int[100];
//Count wird in void draw() kontinuierlich inkrementiert
int count = 0;

void draw()
{
  //Hintergrund neu zeichnen und aussehen der Objekte angeben
  background(0);
  noStroke();
  fill(#ffffff, 127);
  //Die Koordinaten des aktuellen (neuesten) Objekts
  //werden festgelegt
  tpx[count] = mouseX;
  tpy[count] = mouseY;

  //In der Schleife werden nun alle Objekte bis zum aktuellen
  //Höchstwert durchlaufen (count wird je um 1 erhöht, siehe
  //letzte Zeile in void draw()
  for (int i = 0; i < count; i++)
  {
    noStroke();
    //Die Ellipsen werden gezeichnet
    ellipse(tpx[i], tpy[i], 50, 50);
  }


  //Zum Schluss wird diese Variable noch um +1 inkrementiert
  count++;
}

Du siehst die zwei temporären Arrays für X- und Y-Koordinaten tpx und tpy und die Variable count. In der Schleife in draw() geht’s dann los, die Werte werden aus dem Array geholt, mit diesen Werten werden die Ellipsen gezeichnet. Mit der Zeit (also nach jedem Durchlauf von draw()) kommt in das Array immer mehr rein. Tja, jetzt führe den Sketch doch mal aus und schau was passiert…
Dir wird aufgefallen sein, dass mir, wenn ich für die Größe des Arrays eine feste Angabe machen, Processing irgendwann eine “ArrayIndexOutOfBoundsException” werfen wird. In diesem Beispiel habe ich 100 genommen. Ich könnte jetzt auch 1000 nehmen oder 100000, dann hätten wir zwar mehr Zeit uns unser Ergebnis anzusehen, trotzdem wird es irgendwann abbrechen weil “count” die maximal angegebene Größe des Arrays (hier 100) überschreitet.
Es gibt aber natürlich Möglichkeiten, dem entgegenzuwirken, beispielsweise indem wir einfach das Array jedes mal erweitern. Das heißt ich setzte in meinem Beispiel die maximale Größe des Arrays immer auf -sagen wir mal zwei- Werte mehr als count – der Sketch würde also unendlich lange laufen (naja, fast..) wenn wir so wollen.
In dem Zuge lernst du also auch gleich den Befehl expand() kennen. Mit dem kannst du Arrays erweitern. Folgender Code bringt uns zu in dieser Richtung zum Ziel:

/**
 * Verschwindende Formen
 *
 * Dies soll anhand eines coolen Beispiels zeigen, was
 * man mit Arrays alles anstellen kann.
 *
 */

void setup()
{
  //Das Übliche, schwarzer Hintergrund, Größe und smooth
  size(300, 300);
  background(0);
  smooth();
}

//Variablen für temporäre (!) Koordinaten und Größe der Formen
int tpx[] = new int[1];
int tpy[] = new int[1];
//Count wird in void draw() kontinuierlich inkrementiert
int count = 0;

void draw()
{
  //Hintergrund neu zeichnen und aussehen der Objekte angeben
  background(0);
  noStroke();
  fill(#ffffff, 127);
  //Die Koordinaten des aktuellen (neuesten) Objekts
  //werden festgelegt
  tpx[count] = mouseX;
  tpy[count] = mouseY;

  //In der Schleife werden nun alle Objekte bis zum aktuellen
  //Höchstwert durchlaufen (count wird je um 1 erhöht, siehe
  //letzte Zeile in void draw()
  for (int i = 0; i < count; i++)
  {
    noStroke();
    //Die Ellipsen werden gezeichnet
    ellipse(tpx[i], tpy[i], 50, 50);
  }
  //Array wird immer um 2 mehr als count erweitert
  tpx = expand(tpx, count+2);
  tpy = expand(tpy, count+2);

  //Zum Schluss wird diese Variable noch um +1 inkrementiert
  count++;
}

Und schon klappt es. So, jetzt haben wir es geschafft, dass an allen Positionen an denen wir mit der Maus waren, Ellipsen gezeichnet werden. Du wirst dich vielleicht noch fragen, wieso wir vor der Schleife noch sagen, dass tpx[count] und tpy[count] jeweils mouseX und mouseY als Wert haben. Diese Werte werden ja nämlich nur ein mal zugewiesen, deswegen bleiben die Ellipsen auch auf ihren Positionen. Die neueste Ellipse (am höchsten Index, und der Index ist immer der Wert von count) bekommt als Werte mouseX und mouseY, am Ende von draw() wird count wieder um +1 erhöht. Sagen wir count hat den Wert 27, dann würden wir im Code sinngemäß folgende Zuweisung machen:
tpx[27] = mouseX;
tpy[27] = mouseY;
Jetzt wird count am Ende hochgezählt, ist nun also bei 28. Der Wert im Array an Index 27 wird also nun nie wieder angerührt und bekommt keine Wertzuweisung mehr. Durch die for-Schleife wird die Ellipse zwar jedes Mal neu gezeichnet, allerdings wird sich die Position nicht mehr verändern. Und jetzt geht’s nämlich darum, dass die Teile kleiner werden.

Für diesen Effekt brauchst du wieder die Arrays, in welche wir die Größe der Ellipse speichern. Anders als bei den Arrays für die Koordinaten brauchst du hiear aber nur eines, denn ich möchte für Breite und Höhe der Ellipsen immer dieselben Werte haben, quasi also runde Kreise ;-)
Den Anfangswert (wie für X und Y ja mouseX und mouseY festgelegt sind) weise ich dem Array für den Index wieder über die count-Variable zu. Ich hab’ spontan einfach einmal den Wert 50 für die Größe unserer Ellipse gewählt.
Aber wie geht’s jetzt weiter? Also erreichen möchten wir ja, dass die Ellipsen kleiner werden. Da alle Ellipsen ja immer dort neugezeichnet werden, wo die Maus schonmal langgefahren ist, könnten wir eigentlich genauso einfach in der for-Schleife angeben, dass für Größe[i] immer kleiner wird… machen wir das mal, in meinem Sketch heißt die Variable für die Größe der Ellipsen “ts”:

/**
 * Verschwindende Formen
 *
 * Dies soll anhand eines coolen Beispiels zeigen, was
 * man mit Arrays alles anstellen kann.
 *
 */

void setup()
{
  //Das Übliche, schwarzer Hintergrund, Größe und smooth
  size(300, 300);
  background(0);
  smooth();
}

//Variablen für temporäre (!) Koordinaten und Größe der Formen
int tpx[] = new int[1];
int tpy[] = new int[1];
float ts[] = new float[1];
//Count wird in void draw() kontinuierlich inkrementiert
int count = 0;
//Eine Variable um die Ansicht zwischen Ellipse und Rechteck zu wechseln
int form = 1;

void draw()
{
  //Hintergrund neu zeichnen und aussehen der Objekte angeben
  background(0);
  noStroke();
  fill(#ffffff, 127);
  //Die Koordinaten des aktuellen (neuesten) Objekts
  //werden festgelegt
  tpx[count] = mouseX;
  tpy[count] = mouseY;
  //Ausgangsgröße der Form (Ellipse, etc..)
  ts[count] = 50;

  //In der Schleife werden nun alle Objekte bis zum aktuellen
  //Höchstwert durchlaufen (count wird je um 1 erhöht, siehe
  //letzte Zeile in void draw()
  for (int i = 0; i < count; i++)
  {
    noStroke();
    //Die Ellipsen werden gezeichnet
    ellipse(tpx[i], tpy[i], ts[i], ts[i]);
    //Faktor, um den das Objekt kleiner wird - bestimmt
    //auch Geschwindigkeit. Hier wird immer um 1 verkleinert
    ts[i] -= 1;
  }

  //Hier werden die Arrays erweitert, und zwar immer um 
  //jeweils 2 mehr als count
  tpx = expand(tpx, count+2);
  tpy = expand(tpy, count+2);
  ts = expand(ts, count+2);

  //Zum Schluss wird diese Variable noch um +1 inkrementiert
  count++;
}

So, jetzt lass den Sketch mal laufen …. ich weiß was passiert, du wirst denken du hättest es endlich geschafft, doch nach wenigen Sekunden wirst du bemerken, dass da etwas noch gehörig schief läuft! Klar, die Ellipsen werden kleiner, allerdings hab ich an keiner Stelle im Code festgelegt was passieren soll wenn die Größe unter Null liegt, dann bekommen wir nämlich negative Werte. Processing nimmt das Vorzeichen (Minus) einfach weg und die Kreise werden wieder größer, und nach etwa 7 Sekunden wird der Sketch saumäßig lahm. Wenigstens ist dir noch aufgefallen, was der Fehler ist. Und mit einer einfachen If-Bedingung fragst du noch ab, ob die Größe (ts) größer oder kleiner als Null ist. Oder anders herum -so wie ich es gemacht habe- soll Variable ts nur heruntergezählt werden, wenn ts größer als Null ist. Ergänze deinen Code nun also noch um diese Abfrage und der Sketch ist so wie er ist fertig:

/**
 * Verschwindende Formen
 *
 * Dies soll anhand eines coolen Beispiels zeigen, was
 * man mit Arrays alles anstellen kann.
 *
 */

void setup()
{
  //Das Übliche, schwarzer Hintergrund, Größe und smooth
  size(300, 300);
  background(0);
  smooth();
}

//Variablen für temporäre (!) Koordinaten und Größe der Formen
int tpx[] = new int[1];
int tpy[] = new int[1];
float ts[] = new float[1];
//Count wird in void draw() kontinuierlich inkrementiert
int count = 0;
//Eine Variable um die Ansicht zwischen Ellipse und Rechteck zu wechseln
int form = 1;

void draw()
{
  //Hintergrund neu zeichnen und aussehen der Objekte angeben
  background(0);
  noStroke();
  fill(#ffffff, 127);
  //Die Koordinaten des aktuellen (neuesten) Objekts
  //werden festgelegt
  tpx[count] = mouseX;
  tpy[count] = mouseY;
  //Ausgangsgröße der Form (Ellipse, etc..)
  ts[count] = 50;

  //In der Schleife werden nun alle Objekte bis zum aktuellen
  //Höchstwert durchlaufen (count wird je um 1 erhöht, siehe
  //letzte Zeile in void draw()
  for (int i = 0; i < count; i++)
  {
    noStroke();
    //Die Ellipsen werden gezeichnet
    ellipse(tpx[i], tpy[i], ts[i], ts[i]);

    //Solange die Größe des Objekts größer als Null
    //ist, kann das Objekt verkleinert werden, sobald es
    //kleiner ist als Null, passiert einfach nichts mehr
    if (ts[i] > 0)
    {
      //Faktor, um den das Objekt kleiner wird - bestimmt
      //auch Geschwindigkeit. Hier wird immer um 1 verkleinert
      ts[i] -= 1;
    }
  }

  //Hier werden die Arrays erweitert, und zwar immer um 
  //jeweils 2 mehr als count
  tpx = expand(tpx, count+2);
  tpy = expand(tpy, count+2);
  ts = expand(ts, count+2);

  //Zum Schluss wird diese Variable noch um +1 inkrementiert
  count++;
}

Und -du hast es bestimmt gesehen- nicht vergessen, das Array “ti” wieder zu erweitern ;-) So sieht es doch ziemlich geil aus, finde ich.

Nicht relevant, aber gut zu wissen:
Wenn du aufmerksam bist und gut rechnen kannst ist dir vielleicht noch etwas aufgefallen… Du kannst angeben, um wieviel die Größe jedes mal verringert wird. Im eben gezeigten Sketch habe ich den Wert 1. Wenn du aber zum Beispiel 0.75 wählst, ziehst du Punkte hinter dir her, die nicht mehr verschwinden. Wenn du für background weiß wählst (background(255);) und für Füllfarbe Schwarz (fill(0);), kannst du es genauer beobachten. Versuche dabei, die Maus nur langsam zu bewegen. Das passiert, weil du Ellipsen mit dem Anfangswert 50 zeichnest. Wenn du nun ständig 0.75 abziehst, hast du irgendwann am Ende zu den Wert 1.25, davon dann nochmal 0.75 weg ergibt 0.5, davon nochmal 0.75 weg ergibt -0.25 -> HA, eine negative Zahl, und wie du weißt macht Processing daraus einfach eine positive. Du hast zwei Möglichkeiten. Und zwar musst du als Anfangswert eine Zahl wählen, die -wenn von ihr ja ständig 0.75 abgezogen werden- irgendwann dann genau 0.00 ergibt. Der Wert 50.25 würde beispielsweise funktionieren, wenn von diesem immer wieder 0.75 abgezogen werden, würde am Ende genau 0.00 herauskommen.
Die andere Möglichkeit ist, mit else abzufragen ob ts[i] kleiner(!) als 0 ist, also eine negative Zahl, und dann zu ts[i] einfach die Differenz hinzuzuaddieren. Dann ist es auch egal, was du als Anfangswert übergibst.

Ja, das wär’s für heute mal wieder, hoffe ich konnte dir einige interessante Dinge zeigen. Abschließend hier noch der komplette Code. Bei dem hier kannst du nun die Tasten 1, 2 oder 3 drücken, um zwischen Ellipse, Rechteck und Linie zu wechseln. Übrigens habe ich hier auch die else-Abfrage noch hinzugefügt, damit ts[i] am Ende immer 0.00 ist ;-)

/**
 * Verschwindende Formen
 *
 * Dies soll anhand eines coolen Beispiels zeigen, was
 * man mit Arrays alles anstellen kann.
 *
 */

void setup()
{
  //Das Übliche, schwarzer Hintergrund, Größe und smooth
  size(300, 300);
  background(255);
  smooth();
}

//Variablen für temporäre (!) Koordinaten und Größe der Formen
int tpx[] = new int[1];
int tpy[] = new int[1];
float ts[] = new float[1];
//Count wird in void draw() kontinuierlich inkrementiert
int count = 0;
//Eine Variable um die Ansicht zwischen Ellipse und Rechteck zu wechseln
int form = 1;

void draw()
{
  //Hintergrund neu zeichnen und aussehen der Objekte angeben
  background(255);
  noStroke();
  fill(0, 127);
  //Die Koordinaten des aktuellen (neuesten) Objekts
  //werden festgelegt
  tpx[count] = mouseX;
  tpy[count] = mouseY;
  //Ausgangsgröße der Form (Ellipse, etc..)
  ts[count] = 50;

  //In der Schleife werden nun alle Objekte bis zum aktuellen
  //Höchstwert durchlaufen (count wird je um 1 erhöht, siehe
  //letzte Zeile in void draw()
  for (int i = 0; i < count; i++)
  {
    noStroke();
    //Je nachdem welchen Wert "form" hat zeichnen wir ...
    switch(form)
    {
    case 1:
      //..eine Ellipse oder..
      ellipse(tpx[i], tpy[i], ts[i], ts[i]);
      break;
    case 2:
      //..ein Rechteck oder..
      rectMode(CENTER);
      rect(tpx[i], tpy[i], ts[i], ts[i]);
      break;
    case 3:
      //..eine Linie. Und eine Linie nur, wenn Variable "ts" mit diesem
      //Index noch größer ist als Null. Ansonsten würde die Linie nicht
      //verschwinden
      if (i > 1 && ts[i] > 0)
      {
        stroke(0);
        line(tpx[i], tpy[i], tpx[i-1], tpy[i-1]);
      }
      break;
    }
    //Solange die Größe des Objekts größer als Null
    //ist, kann das Objekt verkleinert werden, sobald es
    //kleiner ist als Null, passiert einfach nichts mehr
    if (ts[i] > 0)
    {
      //Faktor, um den das Objekt kleiner wird - bestimmt
      //auch Geschwindigkeit. Hier wird immer um 1 verkleinert
      ts[i] -= 0.75;
    }
    else
    {
      float tmp = 0 - ts[i];
      ts[i] += tmp;
    }
  }

  //Hier werden die Arrays erweitert, und zwar immer um 
  //jeweils 2 mehr als count
  tpx = expand(tpx, count+2);
  tpy = expand(tpy, count+2);
  ts = expand(ts, count+2);

  //Zum Schluss wird diese Variable noch um +1 inkrementiert
  count++;
}

void keyPressed()
{
  //Wir verändern die Variable "form", um zwischen Ellipse, Rechteck
  //und Linie zu wechseln. In void draw() wird abgefragt dass wenn
  //form == 1 eine Ellipse, wenn form == 2 ein Rechteck und wenn 
  //form == 3 eine Linie gezeichnet wird.
  switch(key)
  {
  case '1':
    form = 1;
    break;
  case '2':
    form = 2;
    break;
  case '3':
    form = 3;
    break;
  }
  //Arrayindex wird zurückgesetzt
  count = 0;
}

Du kannst als Anfangswerte auch mal Zufallszahlen übergeben, sieht auch nicht schlecht aus, ändere die Zeile einfach in:

  ts[count] = random(100);

Oder du änderst den Alphawert, oder machst zufällige Farben oder machst die Formen mit Rand …. experimentier’ einfach, wie gesagt, irgendwas wird schon dabei herauskommen ;-)

Bis dahin
Marius

Immer noch Angst vor Processing?

Hey,

wir sind inzwischen weit gekommen, behandelt wurden einfache Formen, Klassen, die translate()-Funktion, Farben, das Koordinatensystem, Rotationen und vieles, vieles mehr. Der ein oder andere hat trotzdem immer noch Angst davor, sich die Programmiersprache Processing anzueignen. Vielleicht weil es “zu kompliziert” ist oder du denkst “das bekomme ich nie hin”? Oder etwa weil das was du dir vornimmst einfach nicht klappen will? Dann solltest du jetzt weiterlesen, dieser Artikel ist für alle die immer noch so ihre Zweifel mit Processing haben – völlig zu unrecht, wie sich im weiteren Verlauf herausstellen wird…

Wieso gerade Processing, was bringt es mir?
Zuerst stellt sich (dir?) natürlich die Frage, wieso du gerade Processing lernen solltest. Nun, da gibt es -abgesehen davon dass man mit Processing richtig geile visuelle Effekte zaubern kann- schon ein paar Gründe. Processing ist in erster Linie kostenlos, und was nichts kostet kann man immer zumindest mal ausprobieren. Weiterhin ist Processing keine exotische Programmiersprache sondern lediglich eine vereinfachte Java-Version, welche sich per Definition dieser Sprache auf das wesentliche beschränkt. Daraus folgt, dass du zwar eine überschaubare Menge an Befehlen, allerdings dadurch aber auch weniger zu schreiben hast. Die Kunst, das besondere an dieser Programmiersprache ist nicht, mit unmengen an Befehlen und tausenden von Möglichkeiten irgendetwas zu realisieren.. nein, das besondere an Processing ist die enorme Vielfalt an Möglichkeiten die sich gerade mit diesen paar Hand voll Funktionen umsetzen lassen. Außerdem kann man mit Processing nichts falsch machen. Egal wie du an welcher Stelle im Code was schreibst, es kommt immer was dabei raus. Und um ein anderes Ergebnis zu erreichen musst du meist nicht einmal den passenden Befehl suchen sondern die Stellen im Code einfach nur etwas umstellen. Desweiteren läuft Processing unter Windows, Linux und MAC, und die Sketche die du mit Processing erstellst lassen sich in Webbrowsern als Applet einbinden.

Aber ist das alles? Was gehört denn dazu, Processing überhaupt erst zu erlernen? Außerdem könnte Ich ja was falsch machen…
Processing ist eine Sprache die man nur durch Selbst-ausprobieren erlernt. Und Processing ist auch nicht nur irgendeine Programmiersprache, Processing zu beherrschen ist eine Kunst. Gut, Ich sage ja generell, dass Programmierung nicht nur eine Fähigkeit, sondern auch eine Kunst ist. Andererseits geht es ja hier aber hauptsächlich um Visualisierung, unter dem Aspekt kann so der ein oder andere mit Processing erzeugte Sketch doch als Kunstwerk angesehen werden, schaue dir nur mal an was die Leute (und auch ich ;-) ) so auf OpenProcessing.org reinstellen.
Wo ich gerade auf OpenProcessing zu sprechen komme … falls du auf der Seite mal ein wenig gestöbert hast, wirst du du dir vielleicht doch hier und da mal gedacht haben “das bekomm ich so nie hin”. Die meisten Leute scheuen sich davor, weil sie Angst haben, etwas falsch zu machen. Aber mal realistisch betrachtet, was willst du falsch machen? Nimm dir doch von der Seite einfach mal einen Code der dir gefällt, und mach ihn kaputt. Scheißegal, einfach nur um zu sehen was passiert wenn man hier was weglässt, dort was hinzufügt oder umstellt oder verändert …. dabei findest du raus, wie der Code funktioniert, welche Stelle was tut, und das ganz nebenbei, ohne großartig zu lernen. Lernen kannst du eh vergessen, wie gesagt, Processing lernst du nur durch machen. Du willst dir gar nicht vorstellen wieviele Processing-Projekte auf meinem PC abgespeichert sind, und davon sind die meisten beinahe dieselben, die ich nur mehrmals abgespeichert hab’, weil Ich an irgendeiner Stelle im Code was klitzekleines verändert hab und das Ergebnis einfach so geil aussah dass Ich es als eigenständiges Projekt abspeichern wollte. Viele Processing-Nutzer wissen wovon ich rede ;-) Aber genau das ist es ja, was diese Programmiersprache ausmacht, was sie so besonders macht.
Vielleicht hast du den Code von einem Sketch (von OpenProcessing oder von den Beispielen die das Programm von sich aus mit dabei hat) und hast keine Ahnung was dort drin steht, aber das beste was man in diesem Fall machen kann, ist einfach mal hier und dort eine Zeile zu entfernen um zu sehen, wie sich das im Sketch bemerkbar macht. So hast du eigentlich sofort im Kopf drin “ah, die Zeile raus und dann haben die Formen keinen grünen Rand mehr” und merkst es dir dadurch quasi automatisch und ganz nebenbei.

Aber wie fang’ ich an, ich kann doch nicht einfach irgendwas da reinschreiben
Und genau das unterscheidet Processing von anderen Programmiersprachen – es ist scheißegal was du dort in das Codefenster reinhackst, irgendwas kommt immer dabei raus. Zu Anfangs wirst du ohnehin keine großartigen Pläne machen, was genau du mit Processing umsetzen möchtest. Zum einen fehlt dir -logischerweise- jedes Wissen. Zum anderen: bei Processing musst du dich nicht auf die Befehle selbst konzentrieren, es sind schon einige, aber doch noch gerade so wenig, dass du sie mit der Zeit locker alle drauf hast. Dauert auch nicht lange, und manche Befehle wirst du möglicherweise nie brauchen weil du schon mit ein paar wenigen so viele Möglichkeiten hast, die verschiedensten Sketche zu programmieren. Weniger auf die Befehle selbst kommt es bei Processing nämlich vielmehr darauf an, wie du die Befehle einsetzt. Am Anfang bekommst du durch deine Codes sicherlich das ein oder andere mal ein anderes Ergebnis als du eigentlich erwartet hast. Manchmal “überrascht” dich Processing einfach mit dem Ergebnis. Wenn du bevor du den Sketch ausführst schon sagen kannst, wie das Ergebnis aussehen wird, dann bist du auf dem besten Weg. Und das ist manchmal gar nicht so einfach, deswegen braucht dich das am Anfang auch nicht interessieren, das kommt mit der Zeit.

Ich bin noch nicht so ganz überzeugt. Du hast leicht reden, du kannst Processing ja bereits…
Ja, ich kann Processing bereits. Allerdings hab auch ich einmal klein angefangen, und von einem Profi bin ich auch noch ein gutes Stück weit entfernt. Bei mir war es so, dass ich in einer Zeitschrift (soweit ich mich erinnere war es die “c`t Programmieren”) einen Artikel über Processing gelesen habe, darin war unter anderem folgender Beispielcode abgedruckt:

// Stern interaktiv
// Den Mittelpunkt der Strahlen bestimmt der Mauszeiger,
// ein Mausklick speichert eine Momentaufnahme als Bitmap.

float winkel; 
float laenge; 
float dx; 
float dy;
int breite;

// Die Funktion setup() ruft Processing beim Programmstart einmal auf
void setup(){
  size(600, 600);
  background(15, 15, 20);
  // Mauscursor wird als Hand dargestellt
  cursor(HAND);
  smooth();
  // Bei Programmstart sollen die Strahlen vom Mittelpunkt des Fensters ausgehen
  mouseX = width/2;
  mouseY = height/2;
}

// Die Funktion draw() ruft Processing nach setup() immer wieder neu auf
void draw (){
  // zufaellige Linienbreite
  breite = int(random(1,7));
  strokeWeight(breite);
  // zufaellige Farbe (innerhalb gewisser Grenzen)
  stroke (random (25, 150), random (5, 255), random(100));  
  // zufaelliger Winkel
  winkel = random(0, 2*PI); 
  // je breiter sie sind, umso kuerzer sollen die Linien sein
  laenge = random((min(width/2, height/2))*(7-breite)/6);
  dx = cos(winkel)*laenge;
  dy = sin(winkel)* laenge;
  line (mouseX, mouseY,  mouseX+dx, mouseY-dy); 
}

// Die Funktion mousePressed() ruft Processing automatisch auf, 
// sobald eine Maustaste gedrueckt wird
void mousePressed(){
  // aktueller Inhalt des Programmfensters wird als Pixelbild gespeichert
  save("Stern.png");
}

Ich fand das ziemlich cool, also hab’ ich mir Processing kurzerhand runtergeladen und das Teil abgetippt. Hinterher hab Ich den Code auseinandergenommen um zu sehen was passiert wenn hier und da was fehlt oder ich Codezeilen einfach vertausche. Und von da an war ich ein Fan dieser Programmiersprache – bis heute. Und ich sage jeder kann sich Processing aneignen. Du tippst etwas, führst es aus und siehst was du gemacht hast. Sofort und auf der Stelle. Natürlich gibt es gewisse Regeln die zu beachten sind wie beispielsweise die Notation, hinter jede Zeile ein Semikolon etc.. aber Processing unterstützt dich dabei. Wenn du im Code einen Fehler hast, weist dich Processing darauf hin, hebt die betreffende Zeile farblich hervor und gibt unten an, was du falsch gemacht hast. Zum Beispiel ein Argument zuviel an eine Funktion übergeben oder versucht, eine Kommazahl einer int-Variable zuzuweisen. Processing führt dich zu der Zeile und schreibt dazu, was der Fehler ist, du korrigierst es und schon klappt es wieder. Processing lässt gar nicht erst zu, dass dein Programm ausgeführt wird und dann erst im Nachhinein mit einem Fehler wieder abbricht. Ausnahmen sind sogenannte Laufzeitfehler, zum Beispiel wenn du mit mouseX oder mouseY herumspielst und versehentlich versucht wird, durch Null zu dividieren oder wenn Processing feststellt, dass eine Schleife unendlich läuft.

Okay, ich will mir Processing zumindest mal anschauen. Aber was, wenn ich nicht weiterkomme? Wer hilft mir, gibt es gute Anlaufstellen?
Klar gibt es Anlaufstellen. Es gibt die Learning-Section, ein Wiki und das wichtigste, die Referenz (alles auf Englisch, aber das stellt sicher kein Problem dar). Zudem sind in der Processing IDE (also dem Programm selbst) über das Menü “File->Examples” noch etliche Beispielsketche zu finden. Es gibt ein Forum (auf Englisch) und im Internet auch noch einige Tutorials, jedoch nicht ganz so viele auf Deutsch. Und zu guter Letzt gibt’s ja immer noch meine Seite hier, auf der du Schritt für Schritt von Anfang an an Processing herangeführt wirst :-) Ganz hilflos bist du also nicht. Mit meinen Tutorials bist du jedoch an meinen Wissensstand gebunden, du wächst sozusagen mit mir.

Okay, hast du zum Schluss noch einen Tipp für mich, wie ich jetzt am besten loslege?
Sicher =) Zuerst musst du dir Processing natürlich runterladen, das kannst du hier machen. Wie du Processing installierst habe Ich in diesem Artikel beschrieben. Danach geht’s dann los mit der Programmierung, auch dazu gibt’s hier einen Artikel von mir, in dem du alles nachlesen kannst.
Und wenn du das hast, geht es daran dir die wichtigsten Befehle in der Processing Referenz zusammenzusuchen. Begleitend dazu findest du in meinen Artikeln jeweils Links und Verweise für bestimmte Befehle, mit denen du direkt zur Referenz für die jeweilige Funktion gelangst. Zuerst -und das ist das wichtigste- solltest du das Koordinatensystem verstehen, danach dann die einfachen Standard-Befehle für Fenstergröße, Anti-Aliasing (smooth()), Strichfarbe, Füllfarbe, Hintergrundfarbe, Formen und vor allem wie void setup() und void draw() arbeiten. Direkt im Anschluss lernst du Variablen kennen, und spätestens dann bist du ohnehin schon mittendrin in Processing und wirst bis dahin bereits mehr als überzeugt, ja ich denke geradezu begeistert sein von dieser Programmiersprache.

Aber jetzt lad’ dir Processing erstmal runter, wühl’ dich durch meine Artikel und lass dich davon faszinieren wie überraschend schnell du zu tollen Ergebnissen kommst :-)

Bis dahin
Marius

Rotation mit Processing – Teil 1 :: Der Radiant (radians())

Hi,

heute zu einem sehr spannenden Thema, der Rotation, wie bereits kurz angesprochen ist dieses Thema die Basis für solche Spielereien. Bisher konnten wir beliebige Formen (ellipse(), line(), point(), rect(), etc…) von links nach rechts, rechts nach links, oben nach unten usw.. bewegen, linear eben, immer in eine Richtung. Viel cooler ist es aber, Formen im Kreis zu bewegen. Hier haben wir die Möglichkeit, die Objekte kontinuierlich zu rotieren, beispielsweise im Kreis immer nach links oder immer nach rechts. Mit Sinus ( sin() ) und Kosinus ( cos() ) gibt es darüber hinaus die Möglichkeit, Objekte pendeln zu lassen, beispielsweise ein Pendel wie bei älteren Standuhren, die in einem ca. 45° Winkel von links nach rechts pendeln. Rotation eben, und das ist das Thema, mit dem wir uns heute und in den kommenden Artikeln beschäftigen werden – ein sehr umfangreiches Thema also.
Ich gehe davon aus dass deine Mathematikkenntnisse -sagen wir mal- recht passabel sind und du das nötige Wissen für Kreisberechnungen, Sinus, Kosinus, PI etc.. bereits mitbringst. Im Zusammenhang mit Processing sehen wir uns jetzt zuerst einmal die Funktion rotate() an. In der Referenz ist recht schön beschrieben, was diese Funktion tut:

Rotates a shape the amount specified by the angle parameter. Angles should be specified in radians (values from 0 to TWO_PI) or converted to radians with the radians() function.

Übersetzen wir das einmal ins Deutsche:

Rotiert eine Form zu dem Wert eines Winkels. Winkel werden über radians (Radiant) angegeben (gültig sind Werte von 0 is TWO_PI, also 2xPI) bzw. über die radians() Funktion in einen Radianten konvertiert.

radians() konvertiert eine Zahl die in Grad (°) angegeben ist in eben diesen sogenannten Radiant. 2xPI entspricht 360°, aus der Schule wissen wir, 360° bedeutet immer genau einen vollständigen Kreis. PI entspricht somit 360° / 2, also 180°. Stell dir eine Uhr vor mit Sekunden-Zeiger. Der Zeiger startet bei 0 (oder bei 12, wie auch immer) und fängt an zu wandern, wenn der Zeiger auf 6 Uhr steht, haben wir 180° geschafft. 180° sind 3,14159…, also PI. Der Zeiger wandert weiter, wenn er wieder auf 12 Uhr steht hat er genau eine Runde gemacht, 360° also, und somit 2xPI (6.2831855…). Du wirst feststellen, dass 0° und 360° sich überschneiden, der Zeiger steht in beiden Fällen an derselben Stelle. Soweit eigentlich klar. Und was hat das nun mit dem Radianten zu tun? radians() konvertiert einen in Grad angegebenen Wert in eine natürliche Zahl, und diese Zahl ist der Radiant.

Im Zusammenhang mit der rotate()-Funktion kann so eine Grad-Angabe (mit der wir Menschen in der Regel besser zurecht kommen) übergeben werden, um eine Rotation darzustellen. Wir brauchen nicht überlegen um zu wissen 180° ist ein halber Kreis, 90° ist ein Viertel und 360° ist ein Ganzes. Somit bleibt es uns erspart, diese ganzen Zahlen als Radiant zu berechnen, das macht die radians() Funktion für uns.

Ich möchte, dass ein Objekt (eine Form, z.B. ein Rechteck) um 90° rotiert wird. Kein Problem, radians() macht das für uns. Durch folgende Angabe übergeben wir einfach unsere Anzahl in Grad (90°) an die radians()-Funktion, die rotate()-Funktion wiederum arbeitet dann mit dem an die radians()-Funktion übergebenen Wert, intern ist es für die Rotate-Funktion ja dann kein Grad-Wert mehr sondern der Radiant. Die Schreibweise sieht wie folgt aus:

  rotate(radians(90));

BAM! Ganz simpel und saumäßig praktisch. Wir übergeben 90 und radians macht daraus 1.5707964, das ist der Wert mit dem die rotate()-Funktion dann arbeitet, aber das braucht uns dann nicht mehr interessieren, wir bleiben entspannt bei unserer Grad-Angabe. Jetzt wo du das weißt, sehen wir uns an wie die rotate()-Funktion arbeitet. Die Rotate-Funktion kann an beliebiger Stelle im Code eingefügt werden. Alle nachfolgenden Formen (alles was nach dem rotate()-Befehl geschrieben wird), wird rotiert. Du musst dann für z.B. eine Ellipse keinen Code hinterlegen, der diese Bewegt, denn das macht die rotate()-Funktion. So könntest du beispielsweise ein Rechteck nach links rotieren lassen, und ein darüberliegendes nach rechts. Aber Ich würde sagen genug von dem ganzen theoretischen Kram, schaun wir uns lieber an wie das mit der Rotation in der Praxis funktioniert. Darstellen möchte Ich ein Rechteck, welches sich kontinuierlich dreht.

void setup()
{
  size(300, 300);
  smooth();
  background(0);
  frameRate(60);
}

int grad = 0;

void draw()
{
  background(0);
  rotate(radians(grad));
  grad++;
  println(grad + "°");
  fill(255);
  noStroke();
  rect(0, 0, 50, 50);
  if (grad >360) {
    grad = 0;
  }
}

So, schaut doch schon ganz gut aus. Nur das Rechteck, dass das in der linken oberen Ecke ist, schaut hässlich aus. Verlegen wir den Nullpunkt in die Mitte, dann macht das schon mehr her:


void setup()
{
  size(300, 300);
  smooth();
  background(0);
  frameRate(60);
}

int grad = 0;

void draw()
{
  background(0);
  translate(width / 2, height / 2);
  rotate(radians(grad));
  grad++;
  println(grad + "°");
  fill(255);
  noStroke();
  rect(0, 0, 50, 50);
  if (grad >360) {
    grad = 0;
  }
}

Ich deklariere eine Variable “grad” mit dem Wert 0. Dieser Wert wird je um 1 erhöht. Diese Variable übergebe ich an radians, diese macht aus dem Grad-Wert den Radiant, die rotate()-Funktion arbeitet mit diesem konvertierten Wert und rotiert entsprechend. Wie du siehst ist es ganz einfach. Interessant wäre nun auch, wenn sich das Rechteck anstatt von der oberen linken Ecke aus um den Mittelpunkt des Rechtecks rotiert. Hierfür stehen dir zwei Möglichkeiten zur Verfügung: Entweder du passt den rectMode() entsprechend an mit rectMode(CENTER); (dafür musst du bevor das Rechteck gezeichnet wird eine Zeile mit eben genanntem Code einfügen) oder aber du zeichnest das Rechteck in der Positionsangabe jeweils minus die Hälfte der Breite / Höhe.

Beide Möglichkeiten einmal zum testen:

  rectMode(CENTER);
  rect(0, 0, 50, 50);

oder so:

  rect(-25, -25, 50, 50);

Versuche, die Stelle mit dem Rechteck im Code jeweils mit der einen und der anderen Möglichkeit zu zeichnen, das Ergebnis ist dasselbe. Falls Ich in meinen Artikeln noch gar nicht erklärt habe, was der rectMode() überhaupt ist, will Ich es dir noch ganz kurz erklären: Der rectMode() gibt an, wo der quasi-Ausgangspunkt des Rechtecks ist. Die ersten beiden Parameter für rect() geben ja an, ab welcher X- / Y-Position angefangen soll zu zeichnen, die letzten beiden Parameter bestimmen Breite und Höhe des Rechtecks. Der Punkt von dem ab das Rechteck dann gezeichnet wird, ist immer der obere linke (das wäre dann standardmäßig rectMode(CORNER);). Wenn du nun aber rectMode(CENTER) angibst bevor du das Rechteck zeichnest, ist dieser Punkt nicht mehr die obere linke Ecke der Form sondern genau die Mitte. Es gibt noch weitere Modi, insgesamt stehen CORNER, CORNERS, CENTER und RADIUS zur Verfügung, dasselbe gibt es noch für ellipseMode().
Bevor ich aber jetzt zu weit abschweife, komme Ich lieber auf das wesentliche zurück. Rotation.
Wie gesagt, du siehst es ist ziemlich einfach. Die Abfrage ob grad > 360 ist könntest du auch weglassen, das ganze würde trotzdem ganz normal weiterlaufen, allerdings wollte Ich das mit den Grad im Ausgabefenster nochmal verdeutlichen.

Transparente Rechtecke mit Rotation

Transparente Rechtecke mit Rotation

Ich denke es ist an der Zeit, mit dieser tollen Funktion endlich etwas sinnvolles anzufangen. Ich schlage vor wir fügen in einer Schleife einfach mehrere Rechtecke (natürlich transparant ;-) ) mit unterschiedlicher Rotation hinzu. Den Wert der Rotation müssen wir nicht selbst anpassen, da jede rotate()-Anweisung von der zuvor geschriebenen abhängig ist. Außerdem bleibt so nichts komplett dem Zufall überlassen, das heißt unsere Bewegungen lassen im Laufe der Zeit ein Muster wiedererkennen. Wo war ich … achja genau, und rectMode() stellen wir wieder auf CENTER. Du kannst übrigens in deinen Tests das rectMode(CENTER); einmal rausnehmen und gucken wie es dann aussieht.
Ich zeig’ dir mal was Ich mir da ausgedacht hab’, auf der linken Seite ein Screenshot, unten der Code:

void setup()
{
  size(300, 300);
  background(0);
  smooth();
  frameRate(60);
}

float angle;

void draw()
{
  background(0);
  translate(width/2, height/2);
  rectMode(CENTER);
  fill(255);
  for (int i = 255; i >10; i-=10)
  {
    rotate(radians(angle)/PI);
    strokeWeight(0.1);
    stroke(0);
    fill(i, 25);
    rect(0, 0, i, i);
  }
  angle += 1;
}

Ergebnis:

Ja, ich würde sagen ich bin recht zufrieden mit dem Ergebnis, es schaut ziemlich gut aus. Auf OpenProcessing kannst du dir den Sketch auch nochmal live ansehen oder hier die rotierenden Rechtecke als Download direkt herunterladen. Ich denke das Prinzip hast du verstanden, und wie Ich dir schon vorausprophezeit habe, ist es nicht wirklich schwierig. Fassen wir dennoch die wichtigsten Punkte erneut zusammen:

- Jede rotate()-Anweisung ist abhängig von der zuvor geschriebenen, wenn wir zwei Rechtecke rotieren wird das zweite (darüberliegende) sich in jedem Fall schneller bewegen.
- Tipp: Um eine Form in die andere Richtung zu rotieren kannst du vor die Variable (ich nenne sie meist “angle”) ein Minuszeichen setzen, den Wert also negativ machen. Beispiel: rotate(radians(-angle));. Doch aufgepasst, wenn du zuvor bereits eine rotate-Anweisung hast ohne das Minuszeichen aber mit demselben Wert (“angle” eben dann) hört die Rotation auf, damit hebst du für alle weiteren Objekte oder Formen die du hinzufügst die Rotation auf. Ich weiß, etwas schwierig vorzustellen, unten folgt noch ein Beispiel.
- Für 90° kannst du statt rotate(radians(90)); auch rotate(HALF_PI); schreiben

Ich erkläre dir noch was mit Punkt 2 und dem Aufheben der Rotation gemeint ist. Wenn du eine rotation() von sagen wir 45° hast, ein Rechteck zeichnest, dann noch eine rotation() hinzufügst wieder mit 45° und erneut ein Rechteck darüberlegst, dreht sich das erste Rechteck um 45°, das zweite um 90°, weil es durch die erste rotate()-Anweisung 45° rotiert und durch die zweite Anweisung nochmal. Ich zeige es dir anhand eines Screenshots, zuerst wurde das weiße, dann das schwarze Rechteck hinzugefügt:

Zwei um 45° rotierende Rechtecke

Zwei um 45° rotierende Rechtecke

Um also eine Rotation für alle nachfolgenden Objekte aufzuheben, schreibst du denselben Code wie den in der vorangehenden Rotate-Anweisung, nur mit Minus-Zeichen vor der Winkel-Variable:

Rotation aufheben

Rotation aufheben

Mit dieser rotate()-Funktion lässt sich, wie du nun siehst, so allerhand anstellen. Nun möchte Ich einmal etwas neues versuchen, und zwar einen Punkt (oder eine Ellipse) die sich zwar im Kreis dreht, dessen Radius dabei jedoch immer größer wird. Dafür musst du den X-/Y-Koordinaten der Form lediglich einen dynamischen, sich inkrementierenden Wert zuweisen, also eine Variable deren Wert immer größer wird. In der einfachsten Form könnte dies so aussehen:

void setup()
{
  size(300, 300);
  background(0);
  smooth();
}

float angle;
float speed = 5;
float ball_radius = 0;
float ball_size = 5;

void draw()
{
  translate(width / 2, height / 2);
  rotate(radians(angle));
  noStroke();
  fill(255);
  ellipse(ball_radius, ball_radius, ball_size, ball_size);
   ball_radius += 0.25;
  angle += speed;
}

Und wenn man nun noch mit Füllfarbe, Strichfarbe und -dicke, Schnelligkeit, Größe und so weiter rumspielt und hier und da anpasst, könnte das nach wenigen Änderungen so aussehen:

void setup()
{
  size(300, 300);
  background(0);
  smooth();
}

float angle;
float speed = 77;
float ball_radius = 1;
float ball_size = 15;

void draw()
{
  translate(width / 2, height / 2);
  rotate(radians(angle));
  fill(255, 10);
  stroke(0);
  strokeWeight(0.1);
  ellipse(ball_radius, ball_radius, ball_size, ball_size);
  ball_radius += 0.25;
  ball_size += 0.1;
  angle += speed;
}

Rotierende Linien

Rotierende Linien


Damit kannst du vielerlei Zeugs anstellen, es gibt beinahe endlos viele Möglichkeiten, die du einbauen könntest. Strichdicke, Strichfarbe, Füllfarbe, Transparenz (Alpha), Größe, Geschwindigkeit, Rechtecke, Linien, Ellipsen und dann noch Zufallswerte für eine der genannten Funktionen, Radius ändern blablablablabla … So einiges also =) In dem Screenshot den du links siehst habe Ich denselben Code wie eben, nur statt einer ellipse Linien, eine andere Strichfarbe, andere Geschwindigkeit und … ja, völlig anderes Ergebnis:

void setup()
{
  size(300, 300);
  background(0);
  smooth();
}

float angle;
float speed = 20;
float ball_radius = 0;
float ball_size = 5;

void draw()
{
  translate(width / 2, height / 2);
  rotate(radians(angle));
  fill(255, 10);
  strokeWeight(0.1);
  stroke(0, random(200), random(255), random(255));
  line(random(ball_radius), random(ball_radius), random(ball_size), random(ball_size));
  ball_radius += 0.25;
  ball_size += 0.1;
  angle += speed;
}

Damit bist du nun eine Weile beschäftigt, es gibt viel auszuprobieren ;-) Auch in den kommenden Artikeln wird die Rotation immer wieder mal das Thema sein, spätestens wenn wir dann sin() und cos() hinzufügen. Außerdem gibt’s ja noch rotateX, rotateY und rotateZ für 3-Dimensionale Spielereien … wir haben viel vor :-)

Bis dahin
Marius

Processing: Translate – Koordinatensystem verschieben

Hi,

in diesem Artikel möchte Ich dir die translate()-Funktion zeigen.

Wie du weißt, ist der Null-Punkt im Koordinatensystem von Processing immer in der oberen linken Ecke. Veranschaulichend dazu folgende Grafik:

Koordinatensystem

Koordinatensystem

Die Translate-Funktion nun ist dazu da, um diesen Nullpunkt zu verschieben. Stell dir vor du verschiebst den Nullpunkt an Position X = 20 und Y = 20. Wenn du nun ein Rechteck an Position 0, 0 zeichnen würdest mit einer Größe von 80×40, sieht es im fertigen Bild aus als wäre das Rechteck an 20, 20. Folgende Grafik soll dir zeigen, wie translate() arbeitet (Auch hier wieder eine Größe für das Rechteck von 80×40 an Position X=50 und Y=90):

Koordinatensystem

Koordinatensystem mit verschobenem Nullpunkt

Dasselbe Gebilde, nur dass Ich, bevor Ich den ganzen Kram dort draufgezeichnet hab, den Nullpunkt verschoben habe auf 20, 20. Hier bisschen schlecht zu sehen, da das komplette Raster mitverschoben wurde.

Sehen wir uns das nochmals an. Ein simples Rechteck an 0, 0. Einmal ganz normal und einmal mit verschobenem Nullpunkt genau in der Mitte des Fensters (width / 2, height / 2) – auf Bild klicken um es in Originalgröße anzusehen:

Ohne Nullpunkt-Verschiebung (translate())

Ohne Nullpunkt-Verschiebung (translate())

Mit Nullpunkt-Verschiebung (translate())

Mit Nullpunkt-Verschiebung (translate())

Du siehst, derselbe Code, nur dass beim zweiten Bild der NullPunkt verschoben wurde. Hier auch gleicht ersichtlich, wie translate funktioniert. Du übergibst als Parameter einfach nur die X- / Y-Koordinaten für den neuen Nullpunkt.
Spannend wird die translate() Funktion im Zusammenhang mit Rotationen, die ich im nächsten und in einigen weiteren Artikeln behandeln möchte.
Zum herumexperimentieren mit translate() hab’ Ich dir auf OpenProcessing etwas schönes zusammengebastelt, einfach mal ausprobieren ;-) Lies’ die Beschreibung unter dem Sketch um zu wissen was du anstellen kannst.

Ein wichtiger Hinweis sei noch zu beachten: Es ist möglich, translate zu verschachteln. Schreibe translate(50, 50), um den Nullpunkt nach X=50 und Y=50 zu verschieben. Wenn du danach erneut translate aufrufst, beispielsweise mit Wert 20, wird der Nullpunkt nicht erneut von 0, 0 verschoben sondern von 50, 50. Sobald draw() beendet ist und wieder an den Anfang springt, hast du wieder 0, 0. Alles andere wäre auch schwachsinnig, da sich der Nullpunkt ansonsten ja mit der Zeit ins unendliche verschiebt. Schau mal:

void draw() und translate()

void draw() und translate()

Klick das Bild an, damit du’s in Originalgröße anschauen kannst.

So, Ich denke die Translate-Funktion können wir damit abschließen, viel muss dazu auch nicht gesagt werden. Im nächsten Artikel wird es um Rotationen gehen, eine echt prickelnde Sache die dir Spaß machen wird =)

Bis dahin
Marius

Lösung Aufgabe 1

Hi,

im heutigen Artikel möchte ich die Auflösung zu Aufgabe 1 posten. Gegeben war, sofern du dich erinnerst, nur ein Bild

Ein Screenshot des fertigen Sketches

Ein Screenshot des fertigen Sketches

und der Code für void setup(). Falls du es nicht lösen konntest, fragt sich nun wie das denn jetzt genau funktioniert. Wichtig zu wissen ist, dass hier in einer Schleife nur eine einzige Linie gezeichnet wird, dabei verändern sich die Parameter für line() nur bei y1 und x2. Bei einer Linie ist es ja nun so, dass wir zuerst den ersten Punkt x1 und Punkt y1 angeben, an dem die Linie beginnt, und dann Punkt x2 und y2 an dem diese endet. Beachten wir dabei, dass der Anfang und das Ende der Linie in diesem Screenshot nie direkt in den Raum, sondern immer nur am linken oder unteren Rand des Fensters gezeichnet werden. Das heißt wir setzen die Linie jeweils am linken Rand an, und zeichnen sie bis irgendwohin an den unteren Rand. Dabei wirst du feststellen, dass x1 (der erste X-Punkt der Linie) immer bei 0 ist, und y2, also der End-Punkt der Linie, immer den Wert für die Höhe des Fensters (height) hat, somit also am unteren Rand. Was ändert sich also? Eigentlich nur y1, also jeweils die Höhe des Anfangspunktes (vertikal) und x2, das wäre dann der Endpunkt der sich nur horizontal verschiebt.
Nochmal kurz die Parameter für line() veranschaulicht: line(x1, y1, x2, y2);
Ich zeige dir das anhand eines Bildes, bei der die Schrittweise der Linien nicht bei 10 (so ist der Wert wenn man das Ergebnis wie im ersten Bild erreichen möchte) sondern bei 50 angesetzt sind:

Beispiel mit weniger Linien

Beispiel mit weniger Linien

Du siehst, hier sind viel weniger Linien, oder anders gesagt der Abstand zwischen den Linien ist hier einfach größer.
Erkennst du nun die Funktionalität die dahintersteckt? In einer Schleife wird sooft durchlaufen wie die Breite / Höhe des Fensters ist. Dabei wird je Schleifendurchlauf eine Linie gezeichnet, die Schrittweise ist hier immer +10. Da nur y1 und x2 sich verändern, übernehmen wir hier die Schleifenvariable als Wert. Sehen wir uns einmal an, was in dieser Schleife passiert und wie sich das auf die Parameter für line() und damit auf unsere gezeichneten Linien auswirkt:

Schleifendurchlauf: 0 - i hat den Wert 0 -> line(0, 0, 0, height);
Schleifendurchlauf: 10 - i hat den Wert 10 -> line(0, 10, 10, height);
Schleifendurchlauf: 20 - i hat den Wert 20 -> line(0, 20, 20, height);
Schleifendurchlauf: 30 - i hat den Wert 30 -> line(0, 30, 30, height);
Schleifendurchlauf: 40 - i hat den Wert 40 -> line(0, 40, 40, height);
Schleifendurchlauf: 50 - i hat den Wert 50 -> line(0, 50, 50, height);
Schleifendurchlauf: 60 - i hat den Wert 60 -> line(0, 60, 60, height);
Schleifendurchlauf: 70 - i hat den Wert 70 -> line(0, 70, 70, height);
Schleifendurchlauf: 80 - i hat den Wert 80 -> line(0, 80, 80, height);
Schleifendurchlauf: 90 - i hat den Wert 90 -> line(0, 90, 90, height);
Schleifendurchlauf: 100 - i hat den Wert 100 -> line(0, 100, 100, height);
Schleifendurchlauf: 110 - i hat den Wert 110 -> line(0, 110, 110, height);
Schleifendurchlauf: 120 - i hat den Wert 120 -> line(0, 120, 120, height);
Schleifendurchlauf: 130 - i hat den Wert 130 -> line(0, 130, 130, height);
Schleifendurchlauf: 140 - i hat den Wert 140 -> line(0, 140, 140, height);
Schleifendurchlauf: 150 - i hat den Wert 150 -> line(0, 150, 150, height);
Schleifendurchlauf: 160 - i hat den Wert 160 -> line(0, 160, 160, height);
Schleifendurchlauf: 170 - i hat den Wert 170 -> line(0, 170, 170, height);
Schleifendurchlauf: 180 - i hat den Wert 180 -> line(0, 180, 180, height);
Schleifendurchlauf: 190 - i hat den Wert 190 -> line(0, 190, 190, height);
Schleifendurchlauf: 200 - i hat den Wert 200 -> line(0, 200, 200, height);
Schleifendurchlauf: 210 - i hat den Wert 210 -> line(0, 210, 210, height);
Schleifendurchlauf: 220 - i hat den Wert 220 -> line(0, 220, 220, height);
Schleifendurchlauf: 230 - i hat den Wert 230 -> line(0, 230, 230, height);
Schleifendurchlauf: 240 - i hat den Wert 240 -> line(0, 240, 240, height);
Schleifendurchlauf: 250 - i hat den Wert 250 -> line(0, 250, 250, height);
Schleifendurchlauf: 260 - i hat den Wert 260 -> line(0, 260, 260, height);
Schleifendurchlauf: 270 - i hat den Wert 270 -> line(0, 270, 270, height);
Schleifendurchlauf: 280 - i hat den Wert 280 -> line(0, 280, 280, height);
Schleifendurchlauf: 290 - i hat den Wert 290 -> line(0, 290, 290, height);
Schleifendurchlauf: 300 - i hat den Wert 300 -> line(0, 300, 300, height);
Schleifendurchlauf: 310 - i hat den Wert 310 -> line(0, 310, 310, height);
Schleifendurchlauf: 320 - i hat den Wert 320 -> line(0, 320, 320, height);
Schleifendurchlauf: 330 - i hat den Wert 330 -> line(0, 330, 330, height);
Schleifendurchlauf: 340 - i hat den Wert 340 -> line(0, 340, 340, height);
Schleifendurchlauf: 350 - i hat den Wert 350 -> line(0, 350, 350, height);
Schleifendurchlauf: 360 - i hat den Wert 360 -> line(0, 360, 360, height);
Schleifendurchlauf: 370 - i hat den Wert 370 -> line(0, 370, 370, height);
Schleifendurchlauf: 380 - i hat den Wert 380 -> line(0, 380, 380, height);
Schleifendurchlauf: 390 - i hat den Wert 390 -> line(0, 390, 390, height);

Du siehst den aktuellen Schleifendurchlauf. Daneben, welchen Wert i in dem Moment hat und am Schluss noch wie der Code für die zu zeichnende Linie wäre. Würde man das ohne Schleife machen und dafür aber alle Befehle für Line (die ganze Liste die Ich eben geschrieben hab) in den Code setzen, würden wir dasselbe Ergebnis bekommen, aber davon abgesehen, wer will sich die Mühe machen ;-)

Tja und das war es eigentlich schon. Am einfachsten ist es übrigens, wenn du für Breite und Höhe des Fensters dieselben Werte nimmst. Es geht natürlich auch wenn diese verschieden sind, andernfalls müsstest du halt aber wiederum besondere Berechnungen anstellen um das dann in rechteckigen Fenstern genauso schön zustande zu bringen.

Der Code für das ganze Teil hier sieht wie folgt aus:

void setup()
{
  size(400, 400);
  background(0);
  smooth();
}

void draw()
{
  background(0);
  strokeWeight(0.5);
  stroke(255);
  for (int i = 0; i < width; i += 10)
  {
    line(0, i, i, height);
  }
  noLoop();
}

Ich hoffe du konntest wieder was lernen ;-)

Bis dahin
Marius

Processing – Screenshots

Hi,

heut zeige Ich dir ein paar Screenshots die Ich von einem Sketch den Ich geschrieben habe aufgenommen hab. Das mag nicht sonderlich interessant klingen, ja, aber das spannende ist, dass sie alle aus ein und demselben Programm heraus entstanden sind welches Ich jeweils nur minimal ändern musste um völlig unterschiedliche Ergebnisse zu bekommen. Statt einem Rechteck eine Linie oder eine Ellipse oder Punkte, dann noch hier und da die Füllfarbe und den Alphawert variieren und noch ein wenig die Geschwindigkeit und Größe der zu zeichnenden Formen anpassen, die Funktionalität dahinter bleibt ständig dieselbe. Sieh’s dir einfach mal an:

In einem der nächsten Artikel werde Ich darauf näher eingehen und dann erneut hierauf verlinken. Und was für einen Nutzen kannst du aus dem heutigen Artikel ziehen? Vermutlich keinen, aber zumindest zeigt es doch wieder einmal, dass in Processing die kleinste Kleinigkeit das Ergebnis vollkommen verändern kann. Und das ist genau das was ich an Processing so toll finde. In diesem Sinne viel Spaß beim selber ausprobieren ;-)

PS: Du wartest bestimmt noch auf die Auflösung für die Aufgabe im letzten Artikel. Den Code dafür gibt’s dann im kommenden Artikel, also sei nicht so ungeduldig :-P

Marius

Processing – Aufgabe (1)

Hi,

wie im letzten Artikel angekündigt stelle Ich dir ab jetzt immer wieder mal eine oder mehrere Aufgaben. Fangen wir heute mit der ersten an.

Gegeben ist ein Screenshot vom Ergebnis des fertigen Sketches:

Ein Screenshot des fertigen Sketches

Ein Screenshot des fertigen Sketches

Versuche nun, dies mit deinem eigenem Code nachzustellen. Der Code für void setup() lautet:

void setup()
{
  size(400, 400);
  background(0);
  smooth();
}

Jetzt ist es an dir, den Code zu schreiben der in void draw() gehört.

Falls du wirklich hängen solltest habe Ich hier zwei Tipps, klicke auf einen Button um einen Tipp zu bekommen, klicke auf den anderen Button für den zweiten Tipp. Natürlich wäre es das beste wenn du es ganz allein schaffst ;-)


Ich bin sicher du schaffst das ;-)

Marius

Processing Aufgaben

Hi,

in Kürze gibt’s hier auch Artikel zu Processing in denen ich dir Aufgaben stelle, die zu lösen sind. Dabei lernst du, wie Codes zu verändern sind, was für Befehle notwendig sind um zu einem bestimmten Ziel zu gelangen. Allgemein wird es dir dazu dienen dich erstens mehr mit Processing auseinanderzusetzen und zweitens Aufgaben selbstständig zu lösen =)

In einem Artikel stehen die Aufgaben, im darauffolgenden Artikel findest du jeweils den Link mit der Auflösung zu diesen Aufgaben. Die Lösung gibt’s also immer erst im nächsten Artikel.

Bis dahin noch viel Spaß mit Processing
Marius

processing_logo

Processing – Nützliches, Tipps und Tricks (Teil 1)

Hi,

heute möcht Ich dir ein paar nützliche Dinge mit Processing zeigen. Das betrifft nicht nur die IDE und spezielle Codes, sondern auch mathematische Tipps. Zugegeben, die IDE (also das Programm in welches du deinen Code reinhackst und über STRG+R ausführst) ist wirklich mehr als klein gehalten, und es gibt auch keine Autovervollständigungen oder sowas wie IntelliSense und eine direkte Projektverwaltung ist auch nicht mit drin, aber abgesehen von dem was Processing nicht kann sehen wir uns lieber an was es kann, denn das ist mehr als auf den ersten Blick vielleicht zu erwarten ist.
Du hast dich vielleicht schonmal gefragt wie Ich den Processing-Code in meinen Beispielen farblich so korrekt in meine Artikel einbringe. Tja, das tolle ist, dass sich der

Processing-Code als HTML kopieren

lässt. Für Leute die selbst Tutorials zu Processing schreiben ist diese Funktion ein wahrer Segen und auch für solche die Ihren Code einfach anderen zeigen möchten ist es praktisch. Markiere mit der Maus einfach die gewünschte Textpassage oder mit STRG+A den kompletten Code und wähle dann im Menü [Edit]->[Copy as HTML], alternativ drücke die Tastenkombi [STRG+Shift+C]. Ein kleiner Bug ist, dass diese Funktion manchmal den Geist aufgibst, das siehst du dann wenn unten im schwarzen Fenster irgendwas in roter Schrift steht, kommt aber in den seltensten Fällen vor und nach einem Neustart von Processing funktioniert es wie gehabt.

Negative in Positive Zahlen umkehren, oder Positive in Negative

Eine weitere coole Sache ist, wie Ich finde, die Funktion :abs:, mit dieser kann jede negative Zahl in eine positive gekehrt werden. So wird aus -17 dann 17, aus -7823 wird 7823 und so weiter. Umgekehrt funktioniert das mit der abs()-Funktion nicht, positive Zahlen lassen sich nicht in negative umkehren. Dafür gibt’s aber einen anderen Trick, kennst du vielleicht noch aus der Schule. Man rechnet einfach die gewünschte Zahl mal minus 1. Beide Möglichkeiten demonstriere Ich dir anhand eines kleinen Beispiels:

void setup()
{
  int a, b, c, d;
  a = -8;
  b = 12;
  c = -884;
  d = 9;
  println("a: " + a);
  println("b: " + b);
  println("c: " + c);
  println("d: " + d);
  a = abs(a);
  b = b * -1;
  //Dasselbe wie eine Zeile darüber nur mit kürzerer Schreibweise:
  c *= -1;
  d = abs(d);
  println("-----------");
  println("a: " + a);
  println("b: " + b);
  println("c: " + c);
  println("d: " + d);
}

Schau dir die Ausgabe im schwarzen Fenster unten gut an -du wirst scrollen müssen-, dann wird dir einiges klar werden, schwer ist es eigentlich nicht. Wichtig ist soetwas zum Beispiel wenn du willst dass ein Objekt -beispielsweise ein Rechteck oder eine Ellipse- sobald es an eine Wand oder ein anderes gewünschtes Objekt stößt die Richtung umkehrt und “abprallt”, in einem der letzten Artikel habe Ich das schon eingesetzt.

Was wirklich ätzend ist, wenn du deinen Code schreibst und dich nach einiger Zeit selbst nicht mehr auskennst, weil das ein unstrukturiertes Durcheinander ist und die Zeilen nicht alle sauber eingerückt sind. Zum Glück stellt uns Processing auch dafür eine Möglichkeit zur Verfügung, die

Auto-Formatierung

Mit dieser Funktion wird dein Code automatisch korrekt eingerückt und formatiert. Dafür drücke einfach zwischendurch mal [STRG+T], schon ist dein Code wieder sauber formatiert. Ganz einfache Sache aber ich find’s super =)

Sketch im Vollbildmodus laufen lassen

Schonmal versucht ein Projekt im Vollbildmodus laufen zu lassen? Klar, du kannst den Sketch öfter starten und die Breite und Höhe durch ausprobieren ermitteln und demnach anpassen. Viel einfacher geht das jedoch ohne lästiges ausprobieren, indem du einfach auf die vordefinierten Variablen screen.width und screen.height zurückgreifst und diese als Parameter an die Funktion :size: übergibst. Mehr kannst du in der Referenz unter :screenwh: nachlesen. screen.width und screen.height haben als Wert automatisch immer die Breite und Höhe des gesamten Bildschirms, also keine PI mal Daumen Angaben mehr =)

Apropos PI mal Daumen, wusstest du dass Processing auch eine

PI Konstante … Konstanten?!

mitbringt? PI kennst du sicherlich, dieses 3,14159…. Damit kannst du spezielle Kreis-Berechnungen und einiges mehr anstellen. Aber nicht nur das, Processing hat noch mehr auf Lager, denn außer :pi: gibt es noch :quarter_pi:, :half_pi: und :two_pi:. QUARTER_PI ist ein Viertel von PI, HALF_PI die Hälfte von PI und TWO_PI das doppelte von PI. Damit kannst du dir wieder eine Zeile Code sparen in welcher du PI erstmal zur Hälfte, einem Viertel oder einem Doppelten umrechnen musst. Wieso PI-Konstante und nicht PI-Funktion? Ganz einfach, PI ist ein konstanter Wert und keine Funktion in der noch extra etwas berechnet werden müsste. Gehen wir näher auf die Bedeutung von Konstanten ein, eine Konstante ist wie eine Variablen deren Wert nicht geändert werden kann. Wenn du in eigenen Programmen Konstanten verwenden willst geht das auch, du kannst diesen zu Anfangs einen Wert zuweisen der aber im weiteren Programmablauf an keiner Stelle im Code mehr geändert werden kann. Natürlich kann diese Variable von jeder Stelle aus im Code verwendet werden, das schon, aber ändern kannst du sie nicht mehr, und wenn du es versuchst wird das Programm nicht starten weil eine kluge Entwicklungsumgebung bei solchen Sachen sehr pingelig ist ;-) Variablen sind dynamisch, also variabel und Konstanten sind feste, unveränderbare Werte, konstant eben. Auf die Bedeutung dieser und einiger weiterer Spezialbegriffe werde Ich in einem der kommenden Artikel noch zu sprechen kommen, Ich habe vor eine Art Glossar für Fachbegriffe im Zusammenhang mit Processing zu erstellen. Ich werde dabei auf die jeweiligen Stellen in der Processing-Referenz verweisen oder auf Wikipedia verlinken und auch mit eigenen Worten die Begriffe erläutern und hier und dort meinen Senf dazukommentieren. Wo wir gerade vom Kommentieren sprechen, du weißt sicherlich bereits dass du in Processing

Zeilen auskommentieren

kannst, aber Ich finde das dauert immer ewig. Man muss wenn man eine Zeile auskommentieren möchte an den Zeilenanfang springen und dann “//” eingeben, bei mehreren Zeilen dann davor ein “/*” und ans Ende ein “*/”. Einfacher geht das finde Ich, wenn man die zu auskommentierenden Codepassagen einfach mit der Maus markiert und dann im Menü [Edit]->[Comment/Uncomment] wählt. Normaler Code wird dann auskommentiert, umgekehrt, also wenn die Zeilen bereits auskommentiert sind, werden sie wieder zu normalen Codezeilen. Zumindest bei mehrzeiligen Codeteilen lohnt sich hier der Griff zur Maus den Ich als Programmierer zugegeben doch eher meide ;-)

Haufenweise Beispiel-Sketche

Ich habs vor kurzem schon angesprochen und du wirst es vielleicht auch schon entdeckt haben, Processing bringt eine beträchtliche Reihe an Beispiel Sketchen mit, an denen du dich orientieren kannst. Zu finden sind diese im Menü über [File]->[Examples...]. Einfach draufklicken, dauert eventuell ein paar Sekunden bis das Fenster aufgeht. Dort sind massig Beispielprojekte die du öffnen und ausführen kannst. Anfangs eine sehr spannende Sache um auch mal zu sehen was mit Processing alles möglich ist. Du kannst dir aus den geöffneten Sketchen Codepassagen und Algorithem ansehen und herauskopieren, besser ist es aber sicherlich -so hab Ich das gemacht- wenn du dir anschaust was der Sketch macht (einfach mit STRG+R ausführen) und dann versuchen das nach eigenem ermessen nachzuprogrammieren. Aus diesen Beispiel-Projekten kannst du dir viele Ideen für eigene Projekte holen. Es macht echt Spaß, wühl dich mal durch ;-)

Gespeichert … oder doch nicht?

Wenn du Processing startest bekommt der Sketch zunächst einen temporären Dateinamen bestehend aus dem Wort sketch, dem aktuellen Monat, den Tag (als Zahl) und einen Buchstaben, also wenn für den heutigen Tag bereits ein Sketch so abgespeichert wurde “a”, dann “b”, dann “c” und so weiter. Wenn Ich bei mir jetzt in diesem Moment Processing starte habe Ich zwischen dem weißen Codefenster und dem Menü einen Tab in dem folgendes drinsteht: sketch_may09b. Aber ganz egal was dort steht (wieso hab Ich jetzt so weit ausgeholt O.o), du wirst vielleicht bemerkt haben dass wenn du Code eintippst dahinter immer dieses Paragraphen-Zeichen (§) auftaucht. Hast du dich mal gefragt wofür das ist? Falls du nicht von selbst drauf kommst, dieses Zeichen gibt an, dass das Programm (also dein Sketch) so wie es jetzt ist noch nicht abgespeichert wurde. Wenn du also tippst und zwischendurch speicherst, verschwindet dieses §-Zeichen. Sobald du aber auch nur eine Taste drückst wird es wieder angezeigt, da der Sketch mit exakt diesem Inhalt wie er in dieser Sekunde ist noch nicht abgespeichert wurde. So hast du immer im Blick ob dein Sketch gepsichert ist oder nicht. Wenn das §-Zeichen angezeigt wird und du versuchst, Processing zu beenden, wirst du durch einen Dialog gefragt ob du noch abspeichern möchtest. Wird das §-Zeichen nicht angezeigt und du schließt Processing, taucht dieser Dialog nicht auf da ja scheinbar vor kurzem gespeichert und in der Zeit bis zur Schließung von Processing nichts mehr am Code hinzugefügt oder verändert wurde, dein Projekt ist also in jedem Fall vor Datenverlust geschützt (es sei denn du bekommst einen Bluescreen, der Strom ist auf einmal weg oder ein Meteor ist direkt mitten in deinen Rechner geknallt, Ich gehe aber davon aus dass deine Kiste läuft ;-) )

Zu Tipps und Tricks für Processing gibt’s demnächst noch einen Teil (oder mehrere, mal schauen), hoffe Ich konnte dir heute wieder was nützliches beibringen ;-)

Bis dahin
Marius

Easing Effekt mit Processing (Teil 2)

Hi,

wie schon im vorvorletzten Artikel angekündigt geht es heute darum, unsere Kugel(n) und deren Eigenschaften in eine separate Klasse auszulagern. Wenn das getan ist, können wir beliebig viele Kugeln erzeugen.
Erstellen wir also zuerst einen neuen Tab, vergeben einen Namen für die Datei und schreiben dann die Klasse. Ich nenne sie hier ball. Den Code vom letzen mal hast du hoffentlich noch, ansonsten zieh dir den Artikel einfach nochmal rein. Wenn du das Projekt (den Sketch also) in Processing offen vor dir hast, kannst du eben beschriebenes durchführen. Jetzt schneide schonmal die Codeteile für die Kugel aus und füge sie der Klasse hinzu. Ich habe das mit 1 Funktion gelöst, void display_ellipse(). Die Klasse sollte also so ausschauen bei dir:

class ball
{
  float x, y;
  float targetX, targetY;
  float easing = 0.05;
  float ball_size = 20;

  void display_ellipse()
  {
    targetX = mouseX;
    targetY = mouseY;
    float dx = targetX - x;
    float dy = targetY - y;
    if (abs(dx) > 1)
    {
      x += dx * easing;
    }
    if (abs(dy) > 1)
    {
      y += dy * easing;
    }
    fill(#FFFFFF);
    ellipse(x, y, ball_size, ball_size);
  }
}

In der IDE sollten auch bei dir 2 Tabs offen sein:

Die Klasse steht, was jetzt noch fehlt ist eine Instanz der Klasse in der easing.pde. Das mit den Arrays machen wir gleich, vorerst aber nur um zu sehen dass sich am Ergebnis selbst dadurch nichts geändert hat hier die easing.pde:

void setup()
{
  smooth();
  background(#000000);
  size(300, 300);
  noStroke();
  balls = new ball();
}

ball balls;;

void draw()
{
  background(0);
  balls.display_ellipse();
}

Drücke jetzt STRG+R um den Sketch auszuführen.

Machen wir jetzt ein Array daraus und erstellen wir direkt mehrere Instanzen. Dafür bedienen wir uns der Schreibweise wie auch beim letzen Artikel:

void setup()
{
  smooth();
  background(#000000);
  size(300, 300);
  noStroke();
  for (int i = 0; i < ball_amount; i++)
  {
    balls[i] = new ball();
  }
}

int ball_amount = 10;
ball[] balls = new ball[ball_amount];

void draw()
{
  background(#000000);
  //fill(#000000, 32);
  //rect(0, 0, width, height);
  for (int i = 0; i < balls.length; i++)
  {
    balls[i].display_ellipse();
  }
}

Du siehst, wir erstellen ein Array mit 11 möglichen Werten (wir haben trotzdem nur 10 Kugeln, da in der Schleife die letzte davon unterschlagen wird). Also 10 Kugeln die mit Verzögerung der Maus folgen … richtig? Doch halt … bevor du den Sketch nun ausführst, kommt dir an dem Code irgendetwas komisch vor? Hast du nicht auch das Gefühl dass da irgendwas nicht stimmen kann? Naja, Drück nochmal STRG+R und schau was passiert.

Gemacht? Ich bin sicher dir ist was aufgefallen. Wir haben zwar 10 Kugeln, allerdings ist für alle die selbe Verzögerung eingestellt, denn die easing Variable in class ball ist immer 0.05. Tjoa und was nun? Wir schreiben den Code einfach um und weisen der Variable direkt nach der Instanziierung einen dynamischen Wert zu. Hierfür bedienen wir uns der Schleife in void setup(). Ich hab dafür eine Variable “di” erstellt, die setze Ich auf 0.02. In der Schleife zähle Ich zu “di” immer +0.01 dazu. So haben wir für jede Kugel nicht nur unterschiedliche Werte, sondern sie werden auch inkrementiert. Dadurch erreichen wir, dass die Kugeln der Maus mit einer Art Schlangenbewegung folgen, jede Kugel hat immer ein bisschen mehr oder weniger (je nachdem wo für dich jetzt vorne oder hinten ist) Verzögerung als ihre Vorgängerkugel (oder die nachfolgende).
Wichtig ist, dass wir die Zuweisung von “di” zu “balls.easing” in void setup() machen, wenn wir es in draw() machen würden, würden die Kugeln bei jedem Durchlauf einen anderen Wert für easing haben (aber auch das kannst du probeweise ausprobieren :P ).

Schaun wir uns den Kram jetzt erst mal an:

void setup()
{
  smooth();
  background(#000000);
  size(300, 300);
  noStroke();
  float di = 0.02;
  for (int i = 0; i < ball_amount; i++)
  {
    balls[i] = new ball();
    balls[i].easing = di;
    di += 0.01;
  }
}

int ball_amount = 10;
ball[] balls = new ball[ball_amount];

void draw()
{
  background(#000000);
  //fill(#000000, 32);
  //rect(0, 0, width, height);
  for (int i = 0; i < balls.length; i++)
  {
    balls[i].display_ellipse();
  }
}

Nochmal kurz erklärt, Variable ball_amount gibt an, wieviele Kugeln wir wollen. Auch den Wert kannst du variieren. Bei vielen Kugeln (mehr als 50 oder so) solltest du den Anfangswert für “di” heruntersetzen auf etwa 0.005, und demensprechend auch den Inkrementierungswert (derzeit ja 0.01) auf etwa 0.001.

Im folgenden Codebeispiel habe Ich noch hinzugefügt dass die Kugeln größer werden, außerdem ist die Fenstergröße hier auf 500×500 gesetzt. Außerdem füge Ich Linien zwischen den Kugeln hinzu, und je näher die Kugel ihrer Vorgängerkugel ist, desto dünner wird diese Linie. Und nochwas kommt hinzu, nämlich eine Transparenz der Kugeln. Dafür muss in void draw_ellipse() Die Zeile fill(#FFFFFF); verändert werden in fill(#FFFFFF, 127);.

So nun der Code von easing.pde und ball.pde:

easing.pde:

void setup()
{
  smooth();
  background(#000000);
  size(500, 500);
  noStroke();
  float di = 0.02;
  float bs = 10;
  for (int i = 0; i < ball_amount; i++)
  {
    balls[i] = new ball();
    balls[i].easing = di;
    balls[i].ball_size = bs;
    bs += 1;
    di += 0.005;
  }
}

int ball_amount = 10;
ball[] balls = new ball[ball_amount];

void draw()
{
  background(#000000);
  //fill(#000000, 32);
  //rect(0, 0, width, height);
  for (int i = 0; i < balls.length; i++)
  {
    stroke(255);
    if(i >= 1)
    {
      strokeWeight(dist(balls[i].x, balls[i].y, balls[i-1].x, balls[i-1].y)/100);
      line(balls[i].x, balls[i].y, balls[i-1].x, balls[i-1].y);
    }
    noStroke();
    balls[i].display_ellipse();
  }
}

ball.pde

class ball
{
  float x, y;
  float targetX, targetY;
  float easing = 0.05;
  float ball_size = 20;

  void display_ellipse()
  {
    targetX = mouseX;
    targetY = mouseY;
    float dx = targetX - x;
    float dy = targetY - y;
    if (abs(dx) > 1)
    {
      x += dx * easing;
    }
    if (abs(dy) > 1)
    {
      y += dy * easing;
    }
    fill(#FFFFFF, 127);
    ellipse(x, y, ball_size, ball_size);
  }
}

Hier noch live anzuschauen:

Damit würde Ich dieses Projekt eigentlich als fertig ansehen. Auch wenn Ich noch einen Haufen Ideen hätte was man da noch alles einbauen könnte =) Mein Ergebnis kannst du dir auch noch hier anschauen.

Bis dahin
Marius