http://sites.schaltungen.at/arduino-uno-r3/processing/creativecoding/animation
Wels, am 2015-01-15BITTE nützen Sie doch rechts OBEN das Suchfeld [ ] [ Diese Site durchsuchen]DIN A3 oder DIN A4 quer ausdrucken ********************************************************************************** DIN A4 ausdrucken
*********************************************************
|
|
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.pdefloat xpos; // gloable Variable für die Ablage der Positionfloat geschwindigkeit = 2; // Positionsänderung pro Einzelbildfloat durchmesser = 30; // Kreisdurchmesservoid 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.pdefloat xpos; // gloable Variable für die Ablage der Positionfloat geschwindigkeit = 2; // Positionsänderung pro Einzelbildfloat durchmesser = 30; // Kreisdurchmesservoid setup () { size(500, 100); // Größe des Sketches definieren smooth (); // aktiviere Kantenglättung xpos = width / 2; // Startposition}void draw () { background (200); // Hintergrund leerenif (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.pdefloat xpos; // gloable Variable für die Ablage der Positionfloat ypos;float xGeschwindigkeit = 2; // PositionsÄnderung pro Einzelbildfloat yGeschwindigkeit = 2;float durchmesser = 20; // Kreisdurchmesservoid 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 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.pdefloat xpos = 0;float durchmesser = 40; // Balldurchmesservoid 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.pdefloat xpos;float ziel;float durchmesser = 30; // Balldurchmesservoid 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.pdefloat xpos; // aktuelle X-Positionfloat ypos; // aktuelle Y-Positionfloat xZiel; // aktuelles X-Zielfloat yZiel; // aktuelles Y-Zielfloat durchmesser = 30; // Ball-Durchmesservoid 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. 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.pdefloat 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.
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.pdefloat xcenter; // Mittelpunkt auf der x-Achsefloat ycenter; // Mittelpunkt auf der y-Achsefloat rad = 75; // Radius der Kreisbahnfloat angle; // aktueller Rotationswinkelvoid 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
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.pdefloat xcenter; // Mittelpunkt auf der x-Achsefloat ycenter; // Mittelpunkt auf der y-Achsefloat rad = 75; // Radius der Kreisbahnfloat angle = 0; // aktueller Rotationswinkelfloat angleStart = 0.4; // Startwinkelvoid 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
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.pdefloat xcenter; // Mittelpunkt auf der x-Achsefloat ycenter; // Mittelpunkt auf der y-Achsefloat rad=75; // Radius der Kreisbahnfloat angle; // aktueller Rotationswinkelvoid 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
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.pdefloat xcenter; // Mittelpunkt auf der x-Achsefloat ycenter; // Mittelpunkt auf der y-Achsefloat rad = 0; // Radius der Kreisbahnfloat angle=0; // aktueller Rotationswinkelvoid 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.pdefloat radBase = 60; // Basisradiusfloat radDiff = 50; // Differenzradiusfloat oscillation = 0; // aktueller Zustandvoid 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.
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);
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.pdevoid 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.pdeint anzahl = 10; // Anzahl der Quadratpaarefloat 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.pdefloat winkel=0; // Winkel-Variablevoid 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()]]
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 hochladenPImage boid; // globales Bildobjekt Variablefloat xpos = 0; // globale Variablen zum Ablegen der aktuellen Bildpositionfloat 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.-
scale() scale()
scale
(3.7);
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
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
|
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.pdevoid 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.pdeint anzahl = 80; // Anzahl der Quadratefloat 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.pdefloat boxWidth = 80; // Ausschnittbreitefloat boxHeight = 60; // Ausschnitthöhefloat boxX = 100; // Ausschnitt x-Positionfloat boxY = 45; // Ausschnitt y-Positionvoid 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);
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:
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.pdesize(430, 200); // Grafik-Ausgabe-Fenster-Größe //fill(0); // Füllfarbe schwarzstroke(0); // Strichfarbe schwarzstrokeWeight(2); // Strichbreite// Erstellen und Befüllen der Variablenfloat rSize = 40.0; // Quadratgröße 20.0float pos1 = 0.0; // Position Quadrat 1float pos2 = 20.0;float pos3 = 40.0;float pos4 = 60.0;float pos5 = 80.0;// Zeichnen der Rechtecke nach den Werten der Variablenrect(pos1, pos1, rSize, rSize);rect(pos2, pos2, rSize, rSize);rect(pos3, pos3, rSize, rSize);rect(pos4, pos4, rSize, rSize);rect(pos5, pos5, rSize, rSize);
|
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;
|
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];
|
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önnenfloat[] 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 = 4pos[0] = 1.255; // speichere den Wert an der ersten Stellepos[1] = -2.5; // speichere den Wert an die zweite Stellepos[2] = 120.555; // speichere den Wert an die dritte Stellepos[3] = -259999999; // speichere den Wert an die zweite Stellepos[4] = 12000000; // speichere den Wert an die dritte Stelleprintln (pos[0]); // auslesen des 1. Wertes, Ausgabe in der Konsoleprintln (pos[1]); // auslesen des 2. Wertes, Ausgabe in der Konsoleprintln (pos[2]); // auslesen des 3. Wertes, Ausgabe in der Konsoleprintln (pos[3]); // auslesen des 4. Wertes, Ausgabe in der Konsoleprintln (pos[4]); // auslesen des 5. Wertes, Ausgabe in der Konsole
|
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 Wertenfloat[] pos = {1.2, -2.5, 12.5, 100000, -309999}; // gibt die Werte in der Konsole ausprintln (pos[0]); println (pos[1]); println (pos[2]); println (pos[3]); println (pos[4]);
|
// Processing-Sketch: 13.2.2 Zeicheneinstellungen_1a.pdefill (255, 0, 0); // Füllfarbe Rotstroke (0); // Strich-Farbe schwarzfloat rSize = 20.0; // Varaibel für die Quadratgrößefloat[] 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
|
(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.pdefloat[] 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' neupos[1] = pos[2] / 4; // definiert den Wert des 2 Feldes mit einem Viertel des 3 Feldes neuprintln (pos[0]); // gibt die Werte in der Konsole ausprintln (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:
- Da der Wert immer im null'ten Feld abgelegt wird, startet unsere Zählvariable i bei 0
- 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.
- Die Schleife läuft deshalb solange i kleiner als die Anzahl ist.
- Unseren letzten Wert erreichen wir mit Arraylänge - 1.
- Die Zählvariable muss nach jedem Schleifendurchlauf um 1 erhöht werden um kein Feld des Arrays auszulassen.
int [] array = { "Alles" , "aus" , "der" , "Liste" };
for ( int i=0; i < array. length ; i = i + 1) {
print (array[i] + " " );
}
|
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.pdefill(0); // Füllfarbe schwarzstroke(0); // Strich-Farbe schwarzfloat 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}
|
Lineare Bewegung mit Arrays (1D) in Processing
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];
|
for ( int i=0; i < xpos. length ; i++) {
xpos[i] = random ( width );
ypos[i] = random (durchmesser / 2, height - durchmesser);
}
|
// Processing-Sketch: 13.2.6 Array Beispiele_1a.pdefloat xpos[] = new float[150]; // globale Arrays für die Ablage der Positionfloat 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
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.pdefloat[] xpos = new float[100]; // gloable Variable für die Ablage aller Positionenfloat[] ypos = new float[100]; float[] xGeschwindigkeit = new float[100]; // Positionsänderung pro Einzelbildfloat[] yGeschwindigkeit = new float[100];color[] farbe = new color[100]; // Farbe der Kreisefloat[] durchmesser = new float[100]; // Kreisdurchmesser der Kreisevoid 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.
void setup () {
}
void draw () {
}
|
void mousePressed () {
}
void keyPressed () {
}
|
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)
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:
- Unser Code wird kürzer und übersichtlicher.
- Wir brauchen Codeteile nicht immer und immer wieder zu schreiben, was die Fehlerquote senkt.
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) {
}
|
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.pdevoid 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 Aufrufz.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.pdevoid 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.pdevoid 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 Anwendungfloat entfernungZumMittelpunkt(float x, float y) { float d = dist(x, y, width/2, height/2); return d; // gebe das ergbenis zurück}
|
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.pdevoid 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.pdevoid 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.pdevoid 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
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.pdevoid 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
testEin 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.
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.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);
|
Butterfly bfW = new Butterfly ( "Zitronenfalter" , "weiblich" );
Butterfly bfM = new Butterfly ( "Zitronenfalter" , "männlich" );
|
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);
|
bfW.fly ();
bfW.land ();
|
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.pdeBall 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 BallBall 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 AufrufBall 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 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ändenint 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();}
|
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-ArraysBall 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
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.pdeGizmo 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
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.pdeGizmo 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
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.pdeGizmo 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
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.pdeGizmo 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; } }}
Verweise
- ^ Funktionen - Modularisierung, Parametrisierung und Wiederverwendbarkeit von Programm-Code, Creative Coding.
- ^ Arrays - Datenreihen, Creative Coding.
- ^ Valentino Braitenberg, Vehicles: Experiments in Synthetic Psychology, Cambridge, MA: The MIT Press, 2011.
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)
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.pdeKreis scheibe; // das Objekt soll scheibe heißenvoid setup() { size(300, 300); // Grafik-Fläche scheibe = new Kreis(20); // Kreis Durchmesser} void 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.pdeKreis[] scheiben = new Kreis[50]; // 100 Kreisscheibenvoid 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 205int 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.pdeWir 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 Kreisesimport controlP5.*; // Bibliothek der GUI-WidgetsControlP5 steuerung; // Zugriff auf die Steuerelemente float rMax; // maximale Groessefloat x0, y0; // Bildschirm Mitteint num; // Anzahl berechnete Punktefloat r2; // Radius und Frequenz des zweiten Kreisesint 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 zeichnenvoid 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, Zeichenstilimport controlP5.*; // Bibliothek der GUI-WidgetsControlP5 steuerung; // Zugriff auf die Steuerelemente float rMax; // maximale Groessefloat x0, y0; // Bildschirm Mitteint num; // Anzahl berechnete Punkteint style;float r2; // Radius und Frequenz des zweiten Kreisesint f2; int prev; // wievielter Vorgaengerfloat phase=0;float tempo=0.02; // Geschwindigkeit der Drehungvoid 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 vorbereitenfloat[] 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:[email protected]
ENDE