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:

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:

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“:

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:

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 😉

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

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

Kommentar hinterlassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Ich stimme zu.

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.